minio/dsync:一个用于 Go 的分布式锁和同步包

minio/dsync 是一个用于在n个节点网络上进行分布式锁的软件包。它设计简洁,可扩展性有限(n <= 16)。每个节点都连接到所有其他节点,来自任何节点的锁请求都将广播到所有连接的节点。如果n/2 + 1个节点响应为肯定,则节点将成功获取锁。如果获取了锁,则客户端可以根据需要保持锁定的状态,并且之后需要释放锁。这将导致解锁消息广播到所有节点,之后锁将再次可用。

动机

此软件包是为 Minio 对象存储 的分布式服务器版本开发的。为此,我们需要一个简单可靠的分布式锁定机制,用于最多 16 台服务器,每台服务器都将运行minio server。锁定机制本身应该是读写互斥锁,这意味着它可以由单个写入器或任意数量的读取器持有。

对于 minio,分布式版本启动方式如下(例如,对于 6 台服务器的系统)

$ minio server server1/disk server2/disk server3/disk server4/disk server5/disk server6/disk

(请注意,应在服务器 server1 到 server6 上运行相同的命令)

设计目标

  • 简单设计:通过保持设计的简单性,可以避免许多棘手的边缘情况。
  • 无主节点:没有主节点的概念,如果使用主节点并且主节点出现故障,则锁定将完全停止。(除非您有一个带有从节点的设计,但这会增加更多复杂性。)
  • 弹性:如果一个或多个节点出现故障,其他节点不应该受到影响,并且可以继续获取锁(前提是不超过n/2–1个节点出现故障)。
  • sync.RWMutex 的直接替换,并支持sync.Locker接口。
  • 自动重新连接到(重新启动的)节点。

使用示例

下面是一个简单的示例,展示了如何使用 dsync 保护单个资源


import (
    "github.com/minio/dsync"
)

func lockSameResource() {

    // Create distributed mutex to protect resource 'test'
    dm := dsync.NewDRWMutex("test")

    dm.Lock()
    log.Println("first lock granted")

    // Release 1st lock after 5 seconds
    go func() {
        time.Sleep(5 * time.Second)
        log.Println("first lock unlocked")
        dm.Unlock()
    }()

    // Acquire lock again, will block until initial lock is released
    log.Println("about to lock same resource again...")
    dm.Lock()
    log.Println("second lock granted")

    time.Sleep(2 * time.Second)
    dm.Unlock()
}

运行时将输出以下内容

2016/09/02 14:50:00 first lock granted
2016/09/02 14:50:00 about to lock same resource again...
2016/09/02 14:50:05 first lock unlocked
2016/09/02 14:50:05 second lock granted

(请注意,在多台机器上分布式运行此程序会更有意思)。

除了写锁之外,dsync 还支持多个读锁。有关示例,请参见 此处

性能

对于同步软件包,性能当然至关重要,因为它通常是一个非常频繁的操作。由于 dsync 自然涉及网络通信,因此性能将受每秒可以交换的消息数量(或所谓的远程过程调用RPC)的限制。

根据参与分布式锁定过程的节点数量,需要发送更多消息。例如,在 8 台服务器的系统上,每次锁定和后续解锁操作总共交换 16 条消息,而在 16 台服务器的系统上,则总共交换 32 条消息。

此外,由于同步机制是对(分布式)系统实际功能的补充操作,因此它不应消耗过多的 CPU 能力。

minio/dsync 支持高达

  • 在性能中等强大的服务器硬件上,16 个节点每秒 7500 次锁定(每个服务器 CPU 使用率 10%)

更多性能数据可以在 此处找到。

陈旧锁和已知缺陷

在分布式系统中,陈旧锁是指实际上不再活动的节点上的锁。这可能是由于例如服务器崩溃或网络暂时不可用(部分网络中断)导致的,例如无法再传递解锁消息。

陈旧锁通常不容易检测到,并且它们可能导致问题,因为它们会阻止对资源的新锁定。minio/dsync 具有一个陈旧锁检测机制,可以在特定条件下自动删除陈旧锁(有关更多详细信息,请参见 此处)。

另一个潜在问题是允许对资源进行多个独占(写)锁定(因为多个并发写入可能导致数据损坏)。默认情况下,minio/dsync 需要至少有n/2+1个底层锁才能授予锁(并且通常在正常情况下,启动并运行的服务器数量远大于此或所有服务器)。

但是,即使锁仅由最少数量的n/2+1个节点支持,也需要两个节点出现故障才能允许对同一资源授予另一个锁(前提是所有出现故障的节点都重新启动)。根据节点数量,发生这种情况的可能性越来越小,因此虽然并非不可能,但发生的可能性非常小。

这是一个更详细的 示例,其中还包括一个表格,列出了为了产生这种不良影响需要出现故障或崩溃的节点总数。

更多内容

当然,关于实现细节、扩展和其他潜在用例、与其他技术和解决方案的比较、限制等,还有更多内容需要说明。访问 github 上的 minio/dsync 以了解更多信息。

如果您有任何意见,我们乐于倾听,我们也欢迎任何改进。

祝您分布式锁定愉快!