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 以了解更多信息。
如果您有任何意见,我们乐于倾听,我们也欢迎任何改进。
祝您分布式锁定愉快!