惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

大猫的无限游戏
大猫的无限游戏
博客园 - 【当耐特】
Cloudbric
Cloudbric
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Attack and Defense Labs
Attack and Defense Labs
爱范儿
爱范儿
The Cloudflare Blog
腾讯CDC
Security Archives - TechRepublic
Security Archives - TechRepublic
TaoSecurity Blog
TaoSecurity Blog
云风的 BLOG
云风的 BLOG
Recent Announcements
Recent Announcements
C
Check Point Blog
Schneier on Security
Schneier on Security
S
Schneier on Security
J
Java Code Geeks
B
Blog RSS Feed
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
Stack Overflow Blog
Stack Overflow Blog
博客园_首页
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
A
About on SuperTechFans
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Google DeepMind News
Google DeepMind News
阮一峰的网络日志
阮一峰的网络日志
罗磊的独立博客
A
Arctic Wolf
S
Secure Thoughts
P
Palo Alto Networks Blog
The Last Watchdog
The Last Watchdog
SecWiki News
SecWiki News
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
博客园 - 三生石上(FineUI控件)
D
Darknet – Hacking Tools, Hacker News & Cyber Security
量子位
U
Unit 42
I
InfoQ
D
DataBreaches.Net
P
Privacy International News Feed
T
Troy Hunt's Blog
博客园 - 叶小钗
T
Threatpost
博客园 - Franky
K
Kaspersky official blog
Hugging Face - Blog
Hugging Face - Blog
IT之家
IT之家
www.infosecurity-magazine.com
www.infosecurity-magazine.com
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
C
Cisco Blogs

犀利豆的博客

《SRE google 运维解密》读书笔记 (六) 《SRE google 运维解密》读书笔记 (五) 《SRE google 运维解密》读书笔记 (四) 《SRE google 运维解密》读书笔记 (三) 《SRE google 运维解密》读书笔记 (二) 《SRE google 运维解密》读书笔记 (一) 2021 总结 终于有一个 Java 可以用的微信机器人了 Vertx入门到实战—实现钉钉机器人内网穿透代理 钉钉机器人回调内网穿透代理--使用篇 周末补习(一)trie 树 那些有趣的代码(三)--勤俭持家的 ArrayList 那些有趣的代码(二)--偏不听父母话的 Tomcat 类加载器 那些有趣的代码(一)--有点萌的 Tomcat 的线程池 从需求第三定律说起--为什么知乎的回答质量下降了 如何利用 Spring Hibernate 高级特性设计实现一个权限系统 居然有人能忘记吃饭?写个微信机器人提醒他 我的2018年总结 从 LongAdder 中窥见并发组件的设计思路 徒手撸框架--实现 RPC 远程调用 我的写作工具链 Java 渲染 docx 文件,并生成 pdf 加水印 撸码的福音--变量名生成器的实现 Raft 协议学习笔记 dubbo 源码学习(一)开篇 Redis 命令的执行过程 Redis 中的事件驱动模型 Redis 数据库、键过期的实现 Redis 的基础数据结构(三)对象 Redis 的基础数据结构(二) 整数集合、跳跃表、压缩列表 Redis 的基础数据结构(一) 可变字符串、链表、字典 线程池 execute() 的工作逻辑 JAVA 中的 CAS 徒手撸框架--高并发环境下的请求合并 徒手撸框架--实现Aop 徒手撸框架--实现IoC 2017个人总结 最近遇到的几个问题集合 Redis RedLock 完美的分布式锁么? JAVA 8入门(二)流 JAVA 8入门(一)Lambda表达式 有道 Alfred Workflow 威力加强版 Kafka实现原理笔记 《交易系统:更新与跨越》读后笔记 Netty-Apns接入实现 Future研究 Hystrix入门研究
Redis实现分布式锁
Zhengxin Diao · 2017-10-24 · via 犀利豆的博客

之前我们使用的定时任务都是只部署在了单台机器上,为了解决单点的问题,为了保证一个任务,只被一台机器执行,就需要考虑锁的问题,于是就花时间研究了这个问题。到底怎样实现一个分布式锁呢?

锁的本质就是互斥,保证任何时候能有一个客户端持有同一个锁,如果考虑使用redis来实现一个分布式锁,最简单的方案就是在实例里面创建一个键值,释放锁的时候,将键值删除。但是一个可靠完善的分布式锁需要考虑的细节比较多,我们就来看看如何写一个正确的分布式锁。

单机版分布式锁 SETNX

所以我们直接基于 redis 的 setNX (SET if Not eXists)命令,实现一个简单的锁。直接上伪码

锁的获取:

1
SET resource_name my_random_value NX PX 30000

锁的释放:

1
2
3
4
5
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end

几个细节需要注意:

  • 首先在获取锁的时候我们需要设置设置超时时间。设置超时时间是为了,防止客户端崩溃,或者网络出现问题以后锁一直被持有。真个系统就死锁了。

  • 使用 setNX 命令,保证查询和写入两个步骤是原子的

  • 在锁释放的时候我们判断了KEYS[1]) == ARGV[1],在这里 KEYS[1]是从redis里面取出来的value,ARGV[1]是上文生成的my_random_value。之所以进行以上的判断,是为了保证锁被锁的持有者释放。我们假设不进行这一步校验:

    1. 客户端A获取锁,后发线程挂起了。时间大于锁的过期时间。
    2. 锁过期后,客户端B获取锁。
    3. 客户端A恢复以后,处理完相关事件,向redis发起 del命令。锁被释放
    4. 客户端C获取锁。这个时候一个系统中同时两个客户端持有锁。

    造成这个问题的关键,在于客户端B持有的锁,被客户端A释放了。

  • 锁的释放必须使用lua脚本,保证操作的原子性。锁的释放包含了get,判断,del三个步骤。如果不能保证三个步骤的原子性,分布式锁就会有并发问题。

注意了以上细节,一个单redis节点的分布式锁就达成了。

在这个分布式锁中还是存在一个单点的redis。也许你会说,Redis是 master-slave的架构,发生故障的时候切换到slave就好,但是Redis的复制是异步的。

  • 如果在客户端A在master上拿到了锁。
  • 在master将数据同步到slave上之前,master宕机。
  • 客户端B就从slave上又一次拿到了锁。

这样由于Master的宕机,造成了同时多人持有锁。如果你的系统可用接受短时时间内,有多人持有锁。这个简单的方案就能解决问题。

但是如果解决这个问题。Redis的官方提供了一个Redlock的解决方案。

RedLock 的实现

为了解决,Redis单点的问题。Redis的作者提出了RedLock的解决方案。方案非常的巧妙和简洁。
RedLock的核心思想就是,同时使用多个Redis Master来冗余,且这些节点都是完全的独立的,也不需要对这些节点之间的数据进行同步。

假设我们有N个Redis节点,N应该是一个大于2的奇数。RedLock的实现步骤:

  1. 取得当前时间
  2. 使用上文提到的方法依次获取N个节点的Redis锁。
  3. 如果获取到的锁的数量大于 (N/2+1)个,且获取的时间小于锁的有效时间(lock validity time)就认为获取到了一个有效的锁。锁自动释放时间就是最初的锁释放时间减去之前获取锁所消耗的时间。
  4. 如果获取锁的数量小于 (N/2+1),或者在锁的有效时间(lock validity time)内没有获取到足够的说,就认为获取锁失败。这个时候需要向所有节点发送释放锁的消息。

对于释放锁的实现就很简单了。想所有的Redis节点发起释放的操作,无论之前是否获取锁成功。

同时需要注意几个细节:

  • 重试获取锁的间隔时间应当是一个随机范围而非一个固定时间。这样可以防止,多客户端同时一起向Redis集群发送获取锁的操作,避免同时竞争。同时获取相同数量锁的情况。(虽然概率很低)

  • 如果某master节点故障之后,回复的时间间隔应当大于锁的有效时间。

    1. 假设有A,B,C三个Redis节点。
    2. 客户端foo获取到了A、B两个锁。
    3. 这个时候B宕机,所有内存的数据丢失。
    4. B节点恢复。
    5. 这个时候客户端bar重新获取锁,获取到B,C两个节点。
    6. 此时又有两个客户端获取到锁了。

    所以如果恢复的时间将大于锁的有效时间,就可以避免以上情况发生。同时如果性能要求不高,甚至可以开启Redis的持久化选项。

总结

了解了Redis分布式的实现以后,其实觉得大多数的分布式系统其实原理很简单,但是为了保证分布式系统的可靠性需要注意很多的细节,琐碎异常。
RedLock算法实现的分布式锁就是简单高效,思路相当巧妙。
但是RedLock就一定安全么?我还会写一篇文章来讨论这个问题。敬请大家期待。