2021年11月25日
一、问题引申
在实际的面试过程中呢,往往会问有没有用过分布式?还有分布式中常见的问题有哪一些之类的。
- 有没有用过分布式锁?
- 一般实现分布式锁的方式有哪些?
- 用redis如何实现分布式锁
- 用zk如何实现分布式锁
- 有没有其他的方式?
- 怎么选择,什么样的场景?
二、实现方式
需要满足的特性
在上一讲中,我们谈到不管是哪一种的分布式锁,都需要满足几个特点:
- 互斥性
- 可用性
- 高效率
- 可重入
- 锁超时
当然,技术的实现都是取舍的,很难找到完美的解决方案,都只是在应用层面做一定的取舍。
常见的方式及特点
常见的实现方式可以分为两大类
- 一个是基于异步复制
- 另一个是基于paxos协议的分布式一致性
MySQL
MySQL也能实现分布式锁
分布式锁就是在一个公共的地方设置某个值或者某个变量,在同一时刻只允许一个客户端去操作即可
主要利用的是MySQL数据库的特性:
- 表记录
- 乐观锁
- 悲观锁
实际上通过这三个特性,也是实现了三种不同的MySQL锁的分类
表记录
机制说明
- 首先,在数据库中创建一个锁的记录表,会有一个lock_name作为唯一键,保证数据的唯一
- 一个客户端获取到了某个锁,就是成功插入了一个数据,而期望获取相同lock_name的数据就会插入失败
- 而执行完了操作,可以delete掉这个数据,作为释放锁的方式
排他锁
排他锁,是行级锁,其实就是写锁,X锁,如果一个事务获取到了X锁,可以对这个数据进行修改和读取,而其他的事务就不能再对这个数据加其他锁了,对于InnoDB引擎来说,对数据库执行insert update delete操作的时候都会给加一个排他锁,而查询语句是可以查询得到的
机制说明
- 首先,在数据库中创建一个锁的记录表,会有一个lock_name作为字段数据
- 然后我们有一个查询语句,通过
for update
给加上排他锁,表示这个事务已经获取到了锁,其他期望加锁的线程就是处于一个失败的状态
- 释放锁,就是在事务业务处理完成以后,将当前事务提交,即完成锁的占有
乐观锁
乐观锁本质是一个CAS的操作
机制说明
- 建立一个数据库表,并添加一个版本号的字段,然后通过查询数据库表
- 查出来数据后,执行相应的操作,然后对数据库中的数据进行更新,过程中会对version进行比较,只有一致才会更新成功
Redis
RedLock算法
redlock算法是redis的大哥保证Redis服务不可用场景下的锁失效问题
机制说明
假设场景为5个redis cluster实例
- 获取当前的时间戳,单位是毫秒
- 轮流在每个master节点创建锁,过期时间只有几十毫秒,要求大多数节点上都加锁成功,则加锁成功
- 客户端计算建立锁的时间,如果建立锁的时间小于超时时间,就算加锁成功
- 要是建立锁失败了,就依次删除这个锁
普通的实现方式
就是通过在redis里创建一个key——加锁
机制说明
- 通过命令
set nx
就是创建一个key - 且只有该key没有存在过才能创建成功,否则创建失败
- 一般实现的命令
set my:lock 随机值 nx px 30000
- 释放锁往往通过lua脚本去删除这个key,确保这个随机值一样的,才能删除
Zookeeper锁
简单方式
zookeeper加锁是通过创建临时节点来加锁的
机制说明
- 某个节点尝试创建临时节点node,此时创建成功了就获取到了这个锁
- 此时其他的节点来创建节点就会失败,只能注册监听器监听这个锁
- 当锁释放以后,就会以一种非公平的形式进行竞争
- 释放锁就是通过删除这个znode,然后就会通知其他的客户端,然后会有一个客户端抢占得到
三、细说redis和zk锁
针对mysql锁就不再细说了,平时工作用的也不多,想了解可以私信,看到会回复
Redis分布式锁
在业界中,考虑分布式锁往往在锁fail的时候,可以问自己几个问题:
- 性能。
- 正确性
也就是说,需要侧重于哪一方面,如果是侧重性能,也就是简单,快速,能接受像锁失效后的故障,反之,就是将侧重应用的正确性,确保不会出现锁失效的情况
基于redis单实例
基于redis单实例,就是很简单的直接通过 set nx
来实现了
特征
在普通的单实例中,强调了加锁的时候,key的value需要使用随机值,且释放锁删除的时候,需要先确保该key的value与期待的是一致的,然后才会对锁的key进行删除,整个比对并删除的过程是通过lua脚本来进行的,保证了原子性
优点
实现起来,还是比较简单的,整个逻辑上性能上都能满足需求
缺点
基于redis单实例,万一redis实例崩了呢???那不是会导致整个业务系统都玩完,做不到高可用,只能用在一些非核心的系统上,分布式锁就玩玩
基于redis主从架构+哨兵
这个就是在上面的基础上增加一个哨兵,保证redis系统的高可用,如果master宕机了,那么slave就会接替上
优点
保证了高可用,如果master崩溃了,不至于整个系统都死掉了
缺点
还是存在隐患的,master和slave之间是异步复制的,如果master宕机的瞬间,没有把锁数据同步给slave,就类似脑裂的场景,会导致其他客户端在新的master上加锁成功,这样就有两个客户端都加锁成功,存在漏洞,导致数据故障
基于redis cluster集群
针对redis cluster的场景,主要使用的是redlock算法
本身redlock算法的出现,就是redis的大哥,发现大家越来越将redis作为数据管理的工作去使用,所研究出来的一个机制,保证在锁失效的情况下,如何保证的高可用
Redlock 算法-主从redis分布式锁主节点宕机锁丢失的问题 - 掘金
优点
在假设的前提下,是能保证的了锁的可用的
假设:
- 即使在没有同步时钟机制的两个进程中,每个进程的本地时间仍然以相同的速率前进,即使有误差,这个误差相较于锁自动释放时间也是极小到可以忽略的
- 确保客户端在
锁过期时间-跨进程时间差
时间内做完自己的工作
错误重试
当一个客户端不能获得锁时,它应该在随机延迟后再次尝试,避免大量客户端同时获取锁的情况出现脑裂的现象,导致无法获取到锁
扩展锁
如果客户端执行的工作由小的步骤组成,那么可以使用比较小的 TTL 时间来设置锁,并在锁快过期时刷新锁有效时间(续约)。但在技术上不会改变算法本质,因此应该限制重新获取锁尝试的最大次数,不然会违反可用性
缺点
实现起来太麻烦了……
Redis实现的优点
有一个好用的redisson框架,是一个知名的、优秀的redis客户端类库,封装了大量的基于redis的复杂的一些操作
- 数据集合的分布式存储
- 多种复杂的分布式锁
- 分布式执行操作以及对象
Redisson
redis分布式锁的支持
- 可重入锁
- 读写锁
- 公平锁
- 信号量
- CountDownLatch
zk分布式锁
zk分布式锁是基于paxos协议实现的分布式一致性的类型
优点
- 锁模型健壮、稳定、可用性高
缺点
- 没有好的开源行框架,只能使用基本的锁,如果要支持多种丰富功能的锁,需要额外自己编写
四、选择
如果能容忍redis实现分布式锁导致的问题,并针对做一些预案的,同时需要redisson的复杂锁类型的支持就选择redis
如果业务场景要求锁的语义绝对没有问题,很健壮,很稳定且对锁的功能没有特别的需求,就可以用zk锁
概念总括部分就这样过去了,后面我们就细细品一下redisson的分布式锁实现