分布式锁的基本原理和案例实现

电子说

1.3w人已加入

描述

前面我们有聊过乐观锁和悲观锁的实现,均是对于单体架构的场景下的实现。那么现在我们来总结看下分布式情况下如何实现锁机制。

常见场景

我们来看下一个场景,假设我现在在分布式系统下要做一个业务逻辑的消费动作,我如何保证我的消费动作只被消费一次不重复消费?有的同学第一时间就想到了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方式实现,时间和能力有限,这里就不展开赘述。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分