一文搞懂redis分布式锁
我们熟悉在多线程并发编程中,需要对共享资源加锁,使得混乱的并发访问降级为合理的串行访问。同理在分布式系统中,可能存在多个进程对一个共享资源进行并发的修改,比如某个进程的数据库数据;这个时候需要对这个数据库上分布式锁,以保证该数据能被串行访问。
分布式锁的性质
工程上对分布式锁规定需要如下几项核心性质:
- 独占性:在某一时刻,锁只能被一个取锁方持有
- 健壮性:不允许产生死锁,假设持有锁的进程挂了,能够有设施保证其锁被下一个申请的对象所继承。
- 对称性:加锁和解锁的使用方必须为同一身份,不允许非法释放他人持有的锁
- 高可用:提供分布式锁服务的基础组件中存在少量的节点发生故障时,不应该影响到分布式锁服务。
分布式锁的实现模式
工程中常见的分布式锁实现方式有两种:
- 主动轮询型
- watch回调型
在面试中无疑会遇到取舍的问题,那该如何取舍呢?具体问题具体分析,我们只能通过这两种方式的优劣来取舍。
主动轮询式
- 优点:主动轮询特点是短连接,更加灵活轻便;
- 缺点:需要发送多次上锁请求,网络IO压力可能存在问题;
实现思路
原理思路是这样的:
- 针对同一把分布式锁,使用同一条数据进行标识,比如redis的key就表示一把锁,存在就是这把锁上锁了,不存在就是没有上锁;value用于保存使用方的身份标识,保证加解锁的对称性
- 上锁行为对应存储媒质的增加数据,解锁行为对应存储媒质的删除数据
- 轮询行为,当上锁时发现该数据已经存在(被上锁),则持续轮询,直到数据被他人删除,并由完成数据插入的动作为止
- 需要保证查询和插入这两个操作是原子的,在redis中,通过Lua脚本实现
死锁问题
弱一致性问题
这种情况会出现在redis集群部署的情况下,redis集群部署是为了解决高可用问题的,对分布式锁的实现是必须的。这里来介绍一下redis集群的主从复制带来的弱一致性问题。redis集群分为Master节点和Slave节点,其Master和Slave节点之间的数据同步是异步延迟的。假设Master节点在某一时刻被添加一条数据,但是这条数据还未被同步到其他Slave时,Master节点挂了。这时哨兵会选举新的Master,但是这条表示锁信息的数据就丢失了,这样下一个上锁的对象也能成功上锁,这就破坏了分布式锁的独占性原则。
当然是存在方案的,就是redis红锁 / redlock
这里推荐一个go实现的redis分布式锁项目
watch回调型
- 优点:网络IO次数少,避免不必要的轮询压力
- 缺点:需要保持长连接,占用系统资源
实现思路
watch回调实现的基础是订阅发布模型,对于锁的上锁和解锁和主动轮询型是基本一致的。但在watch回调型中,对于上锁失败的操作,不是轮询,而是向锁服务提供者订阅锁释放消息。在锁被释放后,发布者会对所有的订阅者发布锁释放的消息。订阅者接受到消息后,会继续尝试上锁。
技术选型
看门狗模型通常采用etcd和zookeeper。
惊群效应
watch回调,有个回调的过程。当回调的对象太多的时候,会有多个对象在极短的时间点对同一个锁去请求,但是最后只有一个请求者能成功拿到锁,这里是非常浪费网络资源的,并且突发的大流量会对系统的稳定性产生负面效应。