基于 Etcd实现分布式锁
Ectd 简介
Ectd 是一个高可用的键值存储系统,具体以下特点:
- 简单:使用 Go 语言编写,部署简单;
- 安全:可选 SSL 证书认证;
- 快速:在保证强一致性的同时,读写性能优秀;
- 可靠:采用 Raft 算法实现分布式系统数据的高可用性和强一致性。
重要的是,etcd 支持以下功能,正是依赖这些功能来实现分布式锁的:
- Lease 机制:即租约机制(TTL,Time To Live),Etcd 可以为存储的 KV 对设置租约,当租约到期,KV 将失效删除;同时也支持续约,即 KeepAlive。
- Revision 机制:每个 key 带有一个 Revision 属性值,etcd 每进行一次事务对应的全局 Revision 值都会加一,因此每个 key 对应的 Revision 属性值都是全局唯一的。通过比较 Revision 的大小就可以知道进行写操作的顺序。
在实现分布式锁时,多个程序同时抢锁,根据 Revision 值大小依次获得锁,可以避免 “羊群效应” (也称 “惊群效应”),实现公平锁。 - Prefix 机制:即前缀机制,也称目录机制。可以根据前缀(目录)获取该目录下所有的 key 及对应的属性(包括 key, value 以及 revision 等)。
- Watch 机制:即监听机制,Watch 机制支持 Watch 某个固定的 key,也支持 Watch 一个目录(前缀机制),当被 Watch 的 key 或目录发生变化,客户端将收到通知。
实现过程
就实现过程来说,跟“买房摇号”很相似。
1、定义一个 key 目录(如:/xxx/lock/
)用于存放客户端(进程)的操作 ID。类似申请买房的号码牌;
2、客户端先 put
key /xxx/lock/id
,id 是全局唯一的,可以使用 UUID,并设置过期时间 TTL
,防止死锁。记下返回的 Revision
值 R
。类似你拿到一个选房序号,并规定了进去选房时间,超时还没有选中,就失效了;
3、get
目录 /xxx/lock/
下所有的 key 及对应的 Revision
值,与上一步返回的 Revision
值进行比较:
- 如果当前返回的
Revision
值 R 小于或等于目录下所有的 key 对应的Revision
,则当前客户端获取到了锁。类似你是排在第一个选房的,不用等了,直接选房就是; - 否则,记下所有比 R 小的 Revision 对应的 key,Watch
/xxx/lock/
。盯紧大屏幕,等待排你前面的人选房;
4、当所有靠前的 key 都被删除之后,则意味着的客户端获取到了锁。类似前面的人都选好房或者弃权了,终于轮到你选房了!
但是,这里有两个问题,也是分布式锁实现方案之间的重要区别:
- 客户端拿到锁后,在合法时间内(过期时间前)没有释放锁(工作没有做完),会导致不同客户端同时拿到或释放同一个锁的情况;
- 当锁依赖的中间件服务是多节点集群部署时,怎么保证新节点与故障节点的数据一致性?