工作中用到了分布式锁,特意研究了下各种场景和实现方案。
为什么用分布式锁?
其实提到锁这个东西,我理解它有点类似现实生活中的锁。
举个例子:比如门锁,现实生活中门上锁,不是所有的人都可以进去,只有拿着钥匙的人才能进去。
那结合到我们编程中来道理也是一样的,在一些特定场景,一些特定的资源是有限的。(比如库存等)这个时候我们加上锁,只有拿着锁钥匙的人进去后才能走下面的流程
应用场景:
1.最常见扣减库存
2.缓存击穿/缓存雪崩(也可以采用分布式锁)
3.在高并发的场景下,阻止流量打到后边等等
实现方案:
1.利用mysql实现分布式锁
1.创建锁表,利用唯一性约束,获取锁时插入数据库。
2.释放锁时,删除数据
优点:容易理解,实现简单
缺点:性能比较差,适合并发不高的场景
2.基于redis setnx实现分布式锁
1.主要设置锁的超时时间,避免死锁
2.如果锁过期了事情没干完-使用多线程(守护线程)延长过期
3.为了保证原子性操作,实现的时候采用Redisson(API实现比较简单/源码中加锁/释放锁操作都是用 Lua 脚本完成的,封装的非常完善,开箱即用)
优点:redis的性能很高,可以支撑高并发的获取、释放锁操作。
缺点:集群环境下,主节点宕机时,两个线程会拿到锁
其实需要自己不断去尝试获取锁,比较消耗性能。
3.基于redLock实现分布式锁
1.RedLock 算法虽然是需要多个实例,但是这些实例都是独自部署的,没有主从关系。
RedLock 作者指出,之所以要用独立的,是避免了 Redis 异步复制造成的锁丢失,
比如:主节点没来的及把刚刚 Set 进来这条数据给从节点,就挂了。如上面第二点说的
问题
2.红锁算法认为,只要 2N+1 个节点加锁成功,那么就认为获取了锁。
3.集群有5台节点三个节点加锁成功并且花费时间小于锁的有效期
4.认定加锁成功了
缺点:有一台机器重启,有两个线程拿到锁(概率很低)
4.基于zookeeper实现分布式锁
加锁流程
首先在zookeeper创建根节点,也就是持久节点(rootNode),作为锁节点
在根节点(rootNode)下创建临时节点(node_x):
查询根节点的子节点列表,检查如果当前创建的节点的序号是最小话,就认定该客户端已经获得锁
如果不是最小的节点,说明获取锁失败,此时客户端就要找比自己小1的节点(node_x-1),
对其注册事件监听器(等待node_x-1的节点被删除的时候,通知当前节点获取锁)
获取锁的客户端在执行完业务就删除当前最小节点。删除最小节点后客户端会通过监听器收到通知,
然后再次判断是否自己的节点最小,是的话进行加锁,不是的话重复上述操作
释放锁流程
等待获取锁的客户端执行完业务流程,直接删除当前的最小的临时顺序节点(L)即可
这样监听L的删除事件的客户端,就会被通知到,尝试进行锁的获取
优点:zookeeper天生设计定位就是分布式协调,强一致性。锁的模型健壮、简单易用、适合做分布式锁。
如果获取不到锁,只需要添加一个监听器就可以了,不用一直轮询,性能消耗较小。
缺点:代码通过Watcher机制实现,实现相对复杂
上面介绍了目前主流做分布式锁的方案,做技术选项和对比的时候.根据实际应用场景选择合适的方案。用的较多是直接单独用一台redis来做分布式锁,已满足大部分场景。
每一种实现方案都是有它的优缺点,我们在知道它的优缺点以后再进行选择。