目录

分布式锁及其应用场景

为何需要分布式锁

分布式锁的特性

互斥性

不死锁

一致性

可重入性

支持阻塞和非阻塞:

支持公平锁和非公平锁(可选)

使用原生Redis实现分布式锁

加锁

解锁

续租

如何保障一致性

集群问题

1. 主备切换

2. 集群脑裂

集群问题解决

使用红锁(RedLock)

红锁的问题:

使用WAIT命令。


分布式锁及其应用场景

应用开发时,如果需要在同进程内的不同线程并发访问某项资源,可以使用各种互斥锁、读写锁

如果一台主机上的多个进程需要并发访问某项资源,则可以使用进程间同步的原语,例如信号量、管道、共享内存等。

但如果多台主机需要同时访问某项资源,就需要使用一种在全局可见并具有互斥性的锁了。

这种锁就是分布式锁,可以在分布式场景中对资源加锁,避免竞争资源引起的逻辑错误。

为何需要分布式锁

Martin Kleppmann 是英国剑桥大学的分布式系统的研究员,之前和 Redis 之父 Antirez 进行过关于 RedLock(红锁,后续有讲到)是否安全的激烈讨论。

Martin 认为一般我们使用分布式锁有两个场景:

  • 效率:使用分布式锁可以避免不同节点重复相同的工作,这些工作会浪费资源。比如用户付了钱之后有可能不同节点会发出多封短信

  • 正确性:加分布式锁同样可以避免破坏正确性的发生,如果两个节点在同一条数据上面操作,比如多个节点机器对同一个订单操作不同的流程有可能会导致该笔订单最后状态出现错误,造成损失。

为何需要分布式锁

分布式锁的特性

互斥性

在任意时刻,只有一个客户端持有锁。

不死锁

分布式锁本质上是一个基于租约(Lease)的租借锁,如果客户端获得锁后自身出现异常,锁能够在一段时间后自动释放,资源不会被锁死。

和本地锁一样支持锁超时,防止死锁。

一致性

硬件故障或网络异常等外部问题,以及慢查询、自身缺陷等内部因素都可能导致Redis发生高可用切换,replica提升为新的master。

此时,如果业务对互斥性的要求非常高,锁需要在切换到新的master后保持原状态

加锁和解锁需要高效,同时也需要保证高可用防止分布式锁失效,可以增加降级。

可重入性

同一个节点上的同一个线程如果获取了锁之后那么也可以再次获取这个锁

支持阻塞和非阻塞:

和 ReentrantLock 一样支持 lock 和 trylock 以及 tryLock(long timeOut)。

支持公平锁和非公平锁(可选)

公平锁的意思是按照请求加锁的顺序获得锁,非公平锁就相反是无序的。这个一般来说实现的比较少。

使用原生Redis实现分布式锁

加锁

在Redis中加锁非常简便,直接使用SET命令即可。示例及关键选项说明如下:

SET resource_1 random_value NX EX 5

表 1. 关键选项说明

参数/选项 说明 resource_1 分布式锁的key,只要这个key存在,相应的资源就处于加锁状态,无法被其它客户端访问。 random_value 一个随机字符串,不同客户端设置的值不能相同。 EX

设置过期时间,单位为秒。

您也可以使用PX选项设置单位为毫秒的过期时间

NX 如果需要设置的key在Redis中已存在,则取消设置。

示例代码为resource_1这个key设置了5秒的过期时间,如果客户端不释放这个key,5秒后key将过期,锁就会被系统回收,此时其它客户端就能够再次为资源加锁并访问资源了。

解锁

解锁一般使用DEL命令,但可能存在下列问题。

解锁
  1. t1时刻,App1设置了分布式锁resource_1,过期时间为3秒。
  2. App1由于程序慢等原因等待超过了3秒,而resource_1已经在t2时刻被释放。
  3. t3时刻,App2获得这个分布式锁。
  4. App1从等待中恢复,在t4时刻运行DEL resource_1将App2持有的分布式锁释放了。

从上述过程可以看出,一个客户端设置的锁,必须由自己解开。因此客户端需要先使用GET命令确认锁是不是自己设置的,然后再使用DEL解锁。在Redis中通常需要用Lua脚本来实现自锁自解

if redis.call("get",KEYS[1]) == ARGV[1] thenreturn redis.call("del",KEYS[1])
elsereturn 0
end

续租

当客户端发现在锁的租期内无法完成操作时,就需要延长锁的持有时间,进行续租(renew)。同解锁一样,客户端应该只能续租自己持有的锁。在Redis中可使用如下Lua脚本来实现续租:

if redis.call("get",KEYS[1]) == ARGV[1] thenreturn redis.call("expire",KEYS[1], ARGV[2])
elsereturn 0
end

如何保障一致性

集群问题

1. 主备切换

为了保证 Redis 的可用性,一般采用主从方式部署。主从数据同步有异步和同步两种方式,Redis 将指令记录在本地内存 buffer 中,然后异步将 buffer 中的指令同步到从节点,从节点一边执行同步的指令流来达到和主节点一致的状态,一边向主节点反馈同步情况。

在包含主从模式的集群部署方式中,当主节点挂掉时,从节点会取而代之,但客户端无明显感知。当客户端 A 成功加锁,指令还未同步,此时主节点挂掉,从节点提升为主节点,新的主节点没有锁的数据,当客户端 B 加锁时就会成功

2. 集群脑裂

集群脑裂指因为网络问题,导致 Redis master 节点跟 slave 节点和 sentinel 集群处于不同的网络分区,因为 sentinel 集群无法感知到 master 的存在,所以将 slave 节点提升为 master 节点,此时存在两个不同的 master 节点。Redis Cluster 集群部署方式同理。

当不同的客户端连接不同的 master 节点时,两个客户端可以同时拥有同一把锁。如下:

集群问题解决

Redis的主从同步(replication)是异步进行的,如果向master发送请求修改了数据后master突然出现异常,发生高可用切换,缓冲区的数据可能无法同步到新的master(原replica)上,导致数据不一致。如果丢失的数据跟分布式锁有关,则会导致锁的机制出现问题,从而引起业务异常。下文介绍三种保障一致性的方法。

使用红锁(RedLock)

红锁是Redis作者提出的一致性解决方案。红锁的本质是一个概率问题:如果一个主从架构的Redis在高可用切换期间丢失锁的概率是k%,那么相互独立的N个Redis同时丢失锁的概率是多少?如果用红锁来实现分布式锁,那么丢锁的概率是(k%)^N。鉴于Redis极高的稳定性,此时的概率已经完全能满足产品的需求。

说明 红锁的实现并非这样严格,一般保证M(1<M=<N)个同时锁上即可,但通常仍旧可以满足需求。

红锁的问题:

  • 加锁和解锁的延迟较大。
  • 难以在集群版或者标准版(主从架构)的Redis实例中实现。
  • 占用的资源过多,为了实现红锁,需要创建多个互不相关的云Redis实例或者自建Redis。

使用WAIT命令。

Redis的WAIT命令会阻塞当前客户端,直到这条命令之前的所有写入命令都成功从master同步到指定数量的replica,命令中可以设置单位为毫秒的等待超时时间。在云Redis版中使用WAIT命令提高分布式锁一致性的示例如下:

SET resource_1 random_value NX EX 5
WAIT 1 5000

使用以上代码,客户端在加锁后会等待数据成功同步到replica才继续进行其它操作,最大等待时间为5000毫秒。执行WAIT命令后如果返回结果是1则表示同步成功,无需担心数据不一致。相比红锁,这种实现方法极大地降低了成本。

需要注意的是:

  • WAIT只会阻塞发送它的客户端,不影响其它客户端。
  • WAIT返回正确的值表示设置的锁成功同步到了replica,但如果在正常返回前发生高可用切换,数据还是可能丢失,此时WAIT只能用来提示同步可能失败,无法保证数据不丢失。您可以在WAIT返回异常值后重新加锁或者进行数据校验。
  • 解锁不一定需要使用WAIT,因为锁只要存在就能保持互斥,延迟删除不会导致逻辑问题。

参考链接:

https://redis.io/topics/distlock

https://help.aliyun.com/document_detail/146758.html

https://xiaomi-info.github.io/2019/12/17/redis-distributed-lock/

https://github.com/redisson/redisson

Redis实现分布式锁:加锁、解锁、续租和一致相关推荐

  1. Java基于Redis实现分布式锁(原子性操作、续命)——90%以上都搞错了

    在使用分布式锁之前,要先思考一个问题,我们为什么要使用分布式锁? 这是因为,在分布式的部署环境下,原来的这个synchronized 只能在当前的JVM中加锁,不能跨JVM实现加锁,所以这种情况下我们 ...

  2. Redis:分布式锁setnx(只 实现了 互斥性和容错性)

    Redis:分布式锁setnx(只 实现了 互斥性和容错性) 关键词 同时在redis上创建同一个key,只有一个能成功,即获得锁 获取锁:原子性操作 set方法(推荐),谁set成功谁获取到锁(过期 ...

  3. 基于 Redis 实现分布式锁思考

    以下文章来源方志朋的博客,回复"666"获面试宝典 来源:blog.csdn.net/xuan_lu/article/details/111600302 分布式锁 基于redis实 ...

  4. java如何保证redis设置过期时间的原子性_redis专题系列22 -- 如何优雅的基于redis实现分布式锁

    几个概念 线程锁:主要用来给方法.代码块加锁.当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段.线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比 ...

  5. Redis的分布式锁详解

    一.什么是分布式锁: 1.什么是分布式锁: 分布式锁,即分布式系统中的锁.在单体应用中我们通过锁解决的是控制共享资源访问的问题,而分布式锁,就是解决了分布式系统中控制共享资源访问的问题.与单体应用不同 ...

  6. 【Redis学习】Redis实现分布式锁

      目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题.分布式的CAP理论告诉我们"任何一个分布式系统都无法同时满足一致性(Consistenc ...

  7. 用 Redis 实现分布式锁(Java 版)

    用 Redis 实现分布式锁(Java 版) 核心代码 完整代码   分布式锁是一种解决分布式临界资源并发读写的一种技术.本文详细介绍了在 Java 中使用 Redis 实现分布式锁的方法.为了方便, ...

  8. 基于redis实现分布式锁思考

    分布式锁 基于redis实现分布式锁思考几个问题??? synchronized锁为什么不能应用于分布式锁? synchronized虽然能够解决同步问题,但是每次只有一个线程访问,并且synchro ...

  9. Spring Boot + Redis 实现分布式锁,还有谁不会??

    点击关注公众号:互联网架构师,后台回复 2T获取2TB学习资源! 上一篇:Alibaba开源内网高并发编程手册.pdf 一.业务背景 有些业务请求,属于耗时操作,需要加锁,防止后续的并发操作,同时对数 ...

最新文章

  1. opencv reduce函数
  2. php奇数乘法表,PHP九九乘法表
  3. jQuery UI Download
  4. boost::phoenix::ref相关的测试程序
  5. SpringSecurity相关jar包的介绍
  6. Java--线程同步
  7. 21-MySQL-Ubuntu-快速回到SQL语句的行首和行末
  8. day14 java的super
  9. windows运行linux系统,coLinux:在Windows运行Linux系统(教程)
  10. 树莓派4B-Python-控制L298N
  11. xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  12. 基于Spire.PDF将HTML转换为PDF
  13. 云计算机玩端游,拒绝万元显卡 云电脑玩端游又爽又省钱
  14. C# 25. 获取windows串口号对应的串口(设备)名称
  15. CentOS7上Glusterfs的安装及使用(gluster/heketi)
  16. 腾讯SkillNet|NLU任务全能网络,对Pathways架构的初步尝试
  17. 江西省信息产业厅 启用RTX腾讯通
  18. 天眼探空经济发展_前沿|“天眼”探空惊艳全球
  19. 学计算机去什么大学好,去美国学计算机专业什么大学好
  20. 【方法】Marvell 88W8801 WiFi模块创建能上网的热点

热门文章

  1. [BZOJ4883][Lydsy1705月赛]棋盘上的守卫[最小基环树森林]
  2. 树莓派4B 安装 Tensorflow 1.13.1 保姆级教程
  3. 引领数字经济时代,软件企业需要一对“隐形的翅膀”
  4. Ubuntu安装后屏幕倒置,翻转,颠倒的解决
  5. iOS-微信支付商户支付下单id非法
  6. C语言入门笔记 第五讲【循环语句之while】
  7. 从零开始,教你如何安装、配置Python开发环境,Python入门安装教程,超级详细
  8. 手把手教你做一款支付宝收款音箱
  9. ”AMD锐龙3000上市 Intel酷睿i5-9600K处理器大降价
  10. C# string中的copy()CopyTo()