电子说
前面我们有聊过乐观锁和悲观锁的实现,均是对于单体架构的场景下的实现。那么现在我们来总结看下分布式情况下如何实现锁机制。
常见场景
我们来看下一个场景,假设我现在在分布式系统下要做一个业务逻辑的消费动作,我如何保证我的消费动作只被消费一次不重复消费?有的同学第一时间就想到了MQ,诸如Zookeeper。我们今天暂不谈MQ,那其实核心还是代码执行的锁机制问题。
我们再来看一个场景,我们有个接口需要经常查数据库DB数据,如果场景允许我们经常会对其加一层缓存,并设定过期时间。假设在某一瞬间,缓存过期,但此时并发量又很大,会有大量的请求穿透去数据库请求数据,造成缓存雪崩效应。于是,我们就可以考虑加锁机制,只让一个请求去执行查询DB更新缓存的操作。
基本原理
回顾下我们之前聊到锁的原理,分布式锁也是一样的,要实现它必须满足:
互斥:任何时刻只能有一个客户端对其加锁;
避免死锁:要充分考虑某客户端在持有锁的期间崩溃,也不能导致后续其他客户端不能加锁;
谁加锁谁解锁:加锁和解锁必须是同一个客户端,否则容易出现A客户端把B客户端的锁给解了,导致锁机制失效。
示例实践
我们仅以Redis实现分布式锁为例来说明分布式锁的实现。以单机单机部署Redis的情况为例,如果有分布式Redis集群部署的情况,可以参考Redlock算法的实现。下面我们进入Redis+Lua实现分布式锁的实践。
我们来看示例代码。
加锁
注意到代码的每个细节了么?都是至关重要的。上面的set是封装过的,那我们来简单说明一下这个方法吧,该方法分别对应了上面的锁需要满足的条件。比如,NX操作保证了锁的互斥,设置过期时间避免了死锁,唯一请求ID用来标注客户端,在解锁的时候可以用来校验是不是同一个客户端自己的锁。
解锁
解锁这个动作就有趣了,看似简单却暗藏玄机,也是很重要的环节。因为解锁存在一个判断是都本客户端的锁的操作,之后才执行解锁。而这个if判断在高并发的情况下我们不得不考虑操作的原子性,这其实和PHP等其他语言代码考虑高并发的原理是大相径庭(有兴趣的看官也可以思考下,为什么有判断就要保证原子性呢,有哪些可能出现问题的场景)。那我们如果保证操作的原子性呢?第一反应是想到事务?我们这里借助Lua脚本来保证原子性,Redis的eval命令执行Lua脚本保证原子性。
我们来看下示例代码
我们同样来说明下面的解锁代码。其实很简单,就是执行了一个Lua脚本,这个脚本实现了或者当前锁的值,即唯一请求ID值,判断是否同一个客户端的请求ID,如果是,则执行Redis的del操作。
好了,关于Redis实现分布式的锁例子就到这里了,这里只是简单的示例便于理解,实际生产将需要考虑更多的场景和因素,比如集群,Zookeeper方式实现,时间和能力有限,这里就不展开赘述。
全部0条评论
快来发表一下你的评论吧 !