Post

一文搞懂redis分布式锁

一文搞懂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回调,有个回调的过程。当回调的对象太多的时候,会有多个对象在极短的时间点对同一个锁去请求,但是最后只有一个请求者能成功拿到锁,这里是非常浪费网络资源的,并且突发的大流量会对系统的稳定性产生负面效应。

This post is licensed under CC BY 4.0 by the author.