CAP模型与分布式锁选型
分布式架构的基础原理之一:
CAP原理:
C:Consistency 强一致性
A:Availability 可用性(高可用)
P:Partition tolerance 分区容忍性
分布式系统三者之中只能满足其二者,一般追求CP或AP, CA模型是不追求的,没有必要
实例:
数据库更新一条记录:
满足C:数据库主库更新, 从库也必须要更新
满足A:必须保证两个节点(主库与从库)都是可用的
满足P:当主库与从库之间出现网络分区,保证整个系统对外是可用的
在这个实例上,
追求AP模型也即追求:服务对外的高可用性,但是允许数据之间的不一致
追求CP模型也即追求:网络分区后数据的一致性,允许暂时服务的不可用
在此基础上
若用哨兵redis做分布式锁, 则追求的是AP模型 , 解决不了数据的高一致性 , 对数据一致性有高度要求不能使用
相对来说用zookeeper做分布式锁, 追求的是CP模型, 可利用zookeeper实现数据一致性
具体实现:
redis: setNX 语句
锁的实现主要基于redis的SETNX命令(SETNX详细解释参考这里),我们来看SETNX的解释
SETNX key value 将 key 的值设为 value ,当且仅当 key 不存在。 若给定的 key 已经存在,则 SETNX 不做任何动作。 SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。 返回值: 设置成功,返回 1 。 设置失败,返回 0 。
使用SETNX完成同步锁的流程及事项如下:
使用SETNX命令获取锁,若返回0(key已存在,锁已存在)则获取失败,反之获取成功.
为了防止获取锁后程序出现异常,导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态,需要为该key设置一个“合理”的过期时间
释放锁,使用DEL命令将锁数据删除
zookeeper :
实现原理
相关概念 有序节点:顾名思义就是有顺序的节点。zk会在生成节点时根据现有的节点数量添加整数序号。比如已经存在节点/lock/node-0000000000,下一个节点就是/lock/node-0000000001。
临时节点:临时节点只在zk会话期间存在,会话结束或超时时会被zk自动删除。
事件监听:通过zk的事件监听机制可以让客户端收到节点状态变化。主要的事件类型有节点数据变化、节点的删除和创建。 实现步骤
了解完上面的三个概念,下面介绍具体实现。 算法流程如下: 1、每个客户端创建临时有序节点
2、客户端获取节点列表,判断自己是否列表中的第一个节点,如果是就获得锁,如果不是就监听自己前面的节点,等待前面节点被删除。
3、如果获取锁就进行正常的业务流程,执行完释放锁。
上述步骤2中,有人可能担心如果节点发现自己不是序列最小的节点,准备添加监听器,但是这个时候前面节点正好被删除,这时候添加监听器是永远不起作用的,其实zk的API可以保证读取和添加监听器是一个原子操作。
为什么要监听前一个节点而不是所有的节点呢?这是因为如果监听所有的子节点,那么任意一个子节点状态改变,其它所有子节点都会收到通知(羊群效应),而我们只希望它的后一个子节点收到通知。
代码实现
根据Zookeeper的开源客户端Curator实现分布式锁。采用zk的原生API实现会比较复杂,所以这里就直接用Curator这个轮子,采用Curator的acquire和release两个方法就能实现分布式锁。
总结
redis的分布式锁和zk的分布式锁各有利弊,
我们目前项目也基本是用redis实现分布式锁,redis相对比较简单,但不能保证数据的强一致性
采用zk实现分布式锁在实际应用中应该比较少见,需要一套zk集群,而且频繁监听对zk集群来说也是有压力,Springcloud项目也一般也不再部署zookeeper。
听说现在有更好的选择: etcd实现分布式锁 , 提供锁的续租,竞锁,探锁,监控等功能, 需单独部署, 待未来了解