分布式锁——实现篇

2021年11月25日

一、问题引申

在实际的面试过程中呢,往往会问有没有用过分布式?还有分布式中常见的问题有哪一些之类的。

  • 有没有用过分布式锁?
  • 一般实现分布式锁的方式有哪些?
  • 用redis如何实现分布式锁
  • 用zk如何实现分布式锁
  • 有没有其他的方式?
  • 怎么选择,什么样的场景?

二、实现方式

需要满足的特性

在上一讲中,我们谈到不管是哪一种的分布式锁,都需要满足几个特点:

  • 互斥性
  • 可用性
  • 高效率
  • 可重入
  • 锁超时

当然,技术的实现都是取舍的,很难找到完美的解决方案,都只是在应用层面做一定的取舍。

常见的方式及特点

常见的实现方式可以分为两大类

  • 一个是基于异步复制
  • 另一个是基于paxos协议的分布式一致性

MySQL

MySQL也能实现分布式锁

分布式锁就是在一个公共的地方设置某个值或者某个变量,在同一时刻只允许一个客户端去操作即可

主要利用的是MySQL数据库的特性:

  • 表记录
  • 乐观锁
  • 悲观锁

实际上通过这三个特性,也是实现了三种不同的MySQL锁的分类

表记录

机制说明

  1. 首先,在数据库中创建一个锁的记录表,会有一个lock_name作为唯一键,保证数据的唯一
  2. 一个客户端获取到了某个锁,就是成功插入了一个数据,而期望获取相同lock_name的数据就会插入失败
  1. 而执行完了操作,可以delete掉这个数据,作为释放锁的方式

排他锁

排他锁,是行级锁,其实就是写锁,X锁,如果一个事务获取到了X锁,可以对这个数据进行修改和读取,而其他的事务就不能再对这个数据加其他锁了,对于InnoDB引擎来说,对数据库执行insert update delete操作的时候都会给加一个排他锁,而查询语句是可以查询得到的

机制说明

  1. 首先,在数据库中创建一个锁的记录表,会有一个lock_name作为字段数据
  2. 然后我们有一个查询语句,通过for update给加上排他锁,表示这个事务已经获取到了锁,其他期望加锁的线程就是处于一个失败的状态
  1. 释放锁,就是在事务业务处理完成以后,将当前事务提交,即完成锁的占有

乐观锁

乐观锁本质是一个CAS的操作

机制说明

  1. 建立一个数据库表,并添加一个版本号的字段,然后通过查询数据库表
  2. 查出来数据后,执行相应的操作,然后对数据库中的数据进行更新,过程中会对version进行比较,只有一致才会更新成功

Redis

RedLock算法

redlock算法是redis的大哥保证Redis服务不可用场景下的锁失效问题

机制说明

假设场景为5个redis cluster实例

  1. 获取当前的时间戳,单位是毫秒
  2. 轮流在每个master节点创建锁,过期时间只有几十毫秒,要求大多数节点上都加锁成功,则加锁成功
  1. 客户端计算建立锁的时间,如果建立锁的时间小于超时时间,就算加锁成功
  2. 要是建立锁失败了,就依次删除这个锁

普通的实现方式

就是通过在redis里创建一个key——加锁

机制说明

  1. 通过命令set nx 就是创建一个key
  2. 且只有该key没有存在过才能创建成功,否则创建失败
  1. 一般实现的命令 set my:lock 随机值 nx px 30000
  2. 释放锁往往通过lua脚本去删除这个key,确保这个随机值一样的,才能删除

Zookeeper锁

简单方式

zookeeper加锁是通过创建临时节点来加锁的

机制说明

  1. 某个节点尝试创建临时节点node,此时创建成功了就获取到了这个锁
  2. 此时其他的节点来创建节点就会失败,只能注册监听器监听这个锁
  1. 当锁释放以后,就会以一种非公平的形式进行竞争
  2. 释放锁就是通过删除这个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的分布式锁实现

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值