etcd
etcd获取分布式锁
cli, err := clientv3.New(clientv3.Config{Endpoints: endpoints})
if err != nil {
log.Fatal(err)
}
defer cli.Close()
// create two separate sessions for lock competition
s1, err := concurrency.NewSession(cli, concurrency.WithTTL(10))
if err != nil {
log.Fatal(err)
}
defer s1.Close()
m1 := concurrency.NewMutex(s1, "/my-lock/")
// acquire lock for s1
if err := m1.Lock(context.TODO()); err != nil {
log.Fatal(err)
}
fmt.Println("acquired lock for s1")
if err := m1.Unlock(context.TODO()); err != nil {
log.Fatal(err)
}
fmt.Println("released lock for s1")
1、首先通过 concurrency.NewSession 方法创建 Session,本质是创建了一个 TTL 为 10 的 Lease。
2、其次得到 session 对象后,通过 concurrency.NewMutex 创建了一个 mutex 对象,包含 Lease、key prefix 等信息。
3、然后通过 mutex 对象的 Lock 方法尝试获取锁。
当 CreateRevision 为 0 时,它会创建一个 prefix 为 /my-lock 的 key( /my-lock + LeaseID),并获取到 /my-lock prefix 下面最早创建的一个 key(revision 最小),分布式锁最终是由写入此 key 的 client 获得,其他 client 则进入等待模式。
4、最后使用结束,可通过 mutex 对象的 Unlock 方法释放锁。
未获得锁的 client 是如何等待的呢?
// wait for deletion revisions prior to myKey
hdr, werr := waitDeletes(ctx, client, m.pfx, m.myRev-1)
// release lock key if wait failed
if werr != nil {
m.Unlock(client.Ctx())
} else {
m.hdr = hdr
}
通过 Watch 机制各自监听 prefix 相同,revision 比自己小的 key,因为只有 revision 比自己小的 key 释放锁,我才能有机会,获得锁,如下代码所示,其中 waitDelete 会使用我们上面的介绍的 Watch 去监听比自己小的 key
为什么使用etcd分布式锁比redis分布式锁更好?
相比 Redis 基于主备异步复制导致锁的安全性问题,etcd 是基于 Raft 共识算法实现的,一个写请求需要经过集群多数节点确认。因此一旦分布式锁申请返回给 client 成功后,它一定是持久化到了集群多数节点上,不会出现 Redis 主备异步复制可能导致丢数据的问题,具备更高的安全性。
分布式锁的三个主要核心要素
安全性、互斥性。在同一时间内,不允许多个 client 同时获得锁。
活性。无论 client 出现 crash 还是遭遇网络分区,你都需要确保任意故障场景下,都不会出现死锁,常用的解决方案是超时和自动过期机制。
高可用、高性能。加锁、释放锁的过程性能开销要尽量低,同时要保证高可用,避免单点故障。