寻找分布式锁定模式


10

我需要为C#中的分布式系统提出一个自定义的递归对象锁定机制\模式。本质上,我有一个多节点系统。每个节点对n个状态段具有独占写入权限。至少一个其他节点上的只读也可以使用相同的状态。一些写入/更新必须在所有节点上都是原子的,而其他更新最终将通过后台复制过程,队列等变得一致。

对于原子更新,我正在寻找一种模式或示例,这些模式或示例可以有效地使我将对象标记为锁定,然后可以分发,提交,回滚等。由于系统具有高级别的并发性,因此我假设我需要能够堆叠锁,这些锁要么超时,要么在释放锁之后展开。

事务或消息传递不是这个问题的重点,但是我已经为一些额外的上下文提供了它们。话虽如此,请随意阐明您认为需要的消息。

尽管我对实现全新产品不愿接受任何新想法,但是这是我所设想的模糊示例

thing.AquireLock(LockLevel.Write);

//Do work

thing.ReleaseLock();

我在考虑使用扩展方法,可能看起来像这样

public static void AquireLock(this IThing instance, TupleLockLevel lockLevel)
{ 
    //TODO: Add aquisition wait, retry, recursion count, timeout support, etc...  
    //TODO: Disallow read lock requests if the 'thing' is already write locked
    //TODO: Throw exception when aquisition fails
    instance.Lock = lockLevel;
}

public static void ReleaseLock(this IThing instance)
{
    instance.Lock = TupleLockLevel.None;
}

为了澄清一些细节...

  • 所有通信都是使用二进制请求/响应协议的TCP / IP
  • 没有中介技术,例如队列或数据库
  • 没有中央主节点。在这种情况下,锁定安排由锁的发起者和伙伴定义,伙伴将以某种形式的超时响应请求以控制其行为

有人有什么建议吗?


锁通常是大多数系统中的标准功能。我想它也适用于C#。(Google搜索结果:albahari.com/threading/part2.aspx)您是否在尝试实现基本Mutex或信号量以外的功能?
Dipan Mehta

2
@DipanMehta对不起,我应该更清楚地解决这个问题。我提到的节点是网络上的计算机。我对Mutex和Semaphores的理解是,它们是机器范围的锁(例如,跨进程),而不是可以在网络上的机器之间扩展的锁。
JoeGeeky 2011年

@JoeGeeky您的问题在这里很普遍,对于Stack Overflow来说可能太理论化了。如果您想在那里重新询问,可以,但您需要更多以代码为中心的措词。
亚当·李尔

Answers:


4

感谢您的澄清。

在这种情况下,我建议您使用发布/订阅模型。Google的Chubby分布式锁定协议Paxos的实现)

我从来没有使用过的Paxos(或胖),但似乎是一个开源实现在这里

如果那不起作用,则可以使用例如消息传递库中常见的可疑对象之一来实现自己的Paxos版本:零消息队列库RabbitMQActiveMQ


上一个答案:

关于SO([A][B])的大多数建议都使用消息队列来实现跨机器锁定。

您的AcquireLock方法会将识别锁对象的内容推入队列,在成功之前检查锁的先前实例。您的ReleaseLock方法将从队列中删除锁对象。

SO用户亚特兰蒂斯表明,在这篇文章中杰夫关键的职位对一些细节的。


谢谢,但是由于我没有中央主服务器,数据库或队列,因此这些解决方案不适合。我用其他一些详细信息更新了这个问题,以澄清其中的一些细节。
JoeGeeky 2011年

我将无法直接使用这些产品,因为已经有一个明确定义的协议,必须用于节点之间的所有通信,但是Chubby和Paxos可能具有明确定义的模式,我可以从中学习。我会看一看。
JoeGeeky 2011年

@JoeGeeky是的,Paxos链接具有序列图,可以让您使用首选的通信链接来实现它。
彼得·K

尽管不是直接的答案,但通读所有Chubby和Paxos的内容有助于我定义自己的解决方案。我没有使用那些工具,但是能够根据它们的一些概念定义一个合理的模式。谢谢。
JoeGeeky 2012年

@JoeGeeky:很高兴听到这至少有所帮助。谢谢你的打勾。
Peter K.

4

在我看来,您这里有几种混合技术:

  • 通讯(您实质上依赖于100%的可靠性...这可能是致命的)

  • 锁定/互斥

  • 超时(出于什么目的)?

一个警告:分布式系统中的超时可能充满危险和困难。如果使用它们,则必须非常仔细地设置和使用它们,因为不加选择地使用超时并不能解决问题,而只是缓解了灾难。(如果要查看如何使用超时,请阅读并理解HDLC通信协议文档。这是适当而巧妙地使用的一个很好的示例,结合了巧妙的位编码系统可以检测诸如IDLE线路之类的东西) 。

有一段时间,我在使用通信链接(不是TCP,其他)连接的多处理器分布式系统中工作。我了解到的一件事是,作为一个粗略的概括,有一些危险的多编程地方可以去:

  • 对队列的依赖通常会以眼泪结束(如果队列已满,则很麻烦。除非您可以计算出永远不会满的队列大小,否则就可以使用无队列解决方案)

  • 依赖锁定是很痛苦的,请尝试一下是否还有另一种方法(如果必须使用锁定,请参考文献,在过去的2-3年中,多处理器分布式锁定一直是许多防伪论文的主题)

我您必须继续使用锁定,然后:

我要保证,您将超时仅用作最后手段的恢复-即检测基础通信系统的故障。我将进一步假设您的TCP / IP通信系统具有高带宽,并且可以将其视为低延迟(理想情况下为零,但这永远不会发生)。

我的建议是,每个节点都有它可以连接的其他节点的连接性列表。(节点不在乎连接来自何处。)节点可以连接到的节点表的数量留待分开整理,您无需说这是静态设置还是其他方式。同样方便地忽略的是诸如连接将进入节点的IP端口号的分配之类的事情-有充分的理由仅在单个端口或多个端口上接受请求。这需要仔细考虑。这些因素将包括隐式排队,排序,资源使用,操作系统类型和功能。

一旦节点知道与谁连接,他们就可以向该节点发送锁定请求,并且必须从该远程节点的锁定答复中接收回信。您可以将这两个操作打包到包装器中,以使其看起来很原子。这样的结果是希望获得锁的节点将进行如下调用:

if (get_lock(remote_node) == timeout) then
  {
    take some failure action - the comms network is down
  }

/* Lock is now acquired - do work here */

if (release_lock(remote_node) == timeout) then
  {
    take some failure action - the comms network is down
  }

调用get_lock和release_lock应该类似于(原则上):

send_to_remote_node(lock_request)
get_from_remote_node_or_timeout(lock_reply, time)
if (result was timeout) then
  return timeout
else
  return ok

您将必须非常小心地使用分布式锁定系统,因为持有锁定的同时执行的工作单元很小且速度很快,因为您将拥有许多可能等待获得锁定的远程节点。这实际上是一个停停等待的多处理器/通信系统,该系统功能强大,但性能却不高。

建议采取完全不同的方法。您是否可以使用远程过程调用,其中每个RPC调用都携带一整套信息,这些信息包可以由收件人处理,从而消除了对锁的需求?


重新阅读问题后,您似乎并不想真正关心通讯方面,而只是想解决锁定问题。

因此,我的回答似乎有些偏离主题,但是,我相信,如果没有正确解决其锁定问题,就无法解决锁定问题。打个比方:在不好的基础上盖房子会导致房屋倒塌……最终。


1
超时语义主要用于处理从网络中消失的节点,或者用于处理锁定堆栈中的大量积压订单……这将限制在等待获取锁定时花费的时间,并将为那些请求锁定的用户提供机会在意外的延迟,故障等之中启动其他进程。此外,这可以防止万一发生故障而将其永远锁定。非常感谢您的关注,尽管在这一点上,鉴于最终会失败,我看不到任何替代方案
JoeGeeky

要说出您的其他意见,尽管我希望使用FIFO模式来堆叠和释放锁,但我本身并没有使用队列(就异步通信的意义而言)。除了需要以某种方式阻止并成为更大的握手的一部分之外,我还没有完全按照所需的请求/响应模式协调工作。目前,我正在研究单个节点中的堆叠锁定机制,然后研究它如何在分布式方案中工作。根据您的建议,我会做更多的阅读。谢谢
JoeGeeky

@JoeGeeky-FIFO是一个队列。当心队列。仔细考虑这一面。听起来很像您不只是要“下架”,还必须仔细考虑问题和解决方案。
quick_now 2011年

我理解...我试图澄清btwn异步进程中使用的FIFO队列的区别(例如,一个进程入队,然后另一个出队)。在这种情况下,将需要按顺序管理事物,但是进入队列的进程要等到(a)他们获得锁,(b)被拒绝锁,或者(c)他们超时并离开线路,才会离开。更像是站在自动柜员机上排队。在成功的情况下,它的行为类似于FIFO模式,但是过程可能在到达生产线的最前面之前就混乱了。至于现成的?不,但这不是一个新问题
JoeGeeky 2011年

0

使用分布式缓存(例如NCache)可以轻松实现您的问题。您需要的是悲观锁定机制,您可以在其中使用对象获取锁定。然后执行您的任务和操作,并释放该锁,以供其他应用程序以后使用。

看下面的代码;

在这里,您将获得特定键的锁,然后执行任务(范围是一个或多个操作),然后在完成后最终释放该锁。

// Instance of the object used to lock and unlock cache items in NCache
LockHandle lockHandle = new LockHandle();

// Specify time span of 10 sec for which the item remains locked
// NCache will auto release the lock after 10 seconds.
TimeSpan lockSpan = new TimeSpan(0, 0, 10); 

try
{
    // If item fetch is successful, lockHandle object will be populated
    // The lockHandle object will be used to unlock the cache item
    // acquireLock should be true if you want to acquire to the lock.
    // If item does not exists, account will be null
    BankAccount account = cache.Get(key, lockSpan, 
    ref lockHandle, acquireLock) as BankAccount;
    // Lock acquired otherwise it will throw LockingException exception

    if(account != null && account.IsActive)
    {
        // Withdraw money or Deposit
        account.Balance += withdrawAmount;
        // account.Balance -= depositAmount;

        // Insert the data in the cache and release the lock simultaneously 
        // LockHandle initially used to lock the item must be provided
        // releaseLock should be true to release the lock, otherwise false
        cache.Insert("Key", account, lockHandle, releaseLock); 
        //For your case you should use cache.Unlock("Key", lockHandle);
    }
    else
    {
        // Either does not exist or unable to cast
        // Explicitly release the lock in case of errors
        cache.Unlock("Key", lockHandle);
    } 
}
catch(LockingException lockException)
{
    // Lock couldn't be acquired
    // Wait and try again
}

摘自链接:http : //blogs.alachisoft.com/ncache/distributed-locking/

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.