SlideShare une entreprise Scribd logo
1  sur  29
redis实现分布式锁
1.为什么需要分布式锁
2.redis分布式锁的实现方式
3.Redlock算法解决了什么问题
4.ZooKeeper实现方式比较
分布式锁场景图
目的:对共享资源的互斥访问
单机加锁和释放锁
acquire_lock(){
if lock == 0
lock = 1
return 1
else
return 0
}
release_lock(){
lock = 0
return 1
}
错误的方式
• // 加锁SETNX lock_key 1
• // 业务逻辑DO THINGS
• // 释放锁DEL lock_key
• 问题一:
• 假如某个客户端在执行了 SETNX 命令、加锁之后,紧接着却在操作
共享数据时发生了异常,结果一直没有执行最后的 DEL 命令释放锁。
因此,锁就一直被这个客户端持有,其它客户端无法拿到锁,也无法
访问共享数据和执行后续操作,这会给业务应用带来影响。
• 问题二:
• 如果客户端 A 成功获取到了锁,并且设置了过期时间 30 秒,但线程
A 执行时间超过了 30 秒,锁过期自动释放,此时线程 B 获取到了锁;
随后 A 执行完成,线程 A 使用 DEL 命令来释放锁,但此时线程 B 加
的锁还没有执行完成,线程 A 实际释放的线程 B 加的锁。
使用Lua脚本(包含SETNX + EXPIRE两条指令)
• if redis.call('setnx',KEYS[1],ARGV[1]) == 1
then
• redis.call('expire',KEYS[1],ARGV[2])
• else
• return 0
• end;
redis SET的扩展命令(SET EX PX NX)
• SET key value [EX seconds | PX
milliseconds] [NX]
• 只有 key 不存在时,SET 才会创建 key,
并对 key 进行赋值。另外,key 的存活时间
由 seconds 或者 milliseconds 选项值来决
定
SET EX PX NX + 校验唯一随机值,再删除
• 加锁:SET $lock_key $unique_id EX
$expire_time NX
• 操作共享资源
• 释放锁:Lua 脚本,先 GET 判断锁是否归
属自己,再 DEL 释放锁
释放锁潜在问题
• 客户端1为了释放锁,先执行’GET’操作获取随机字符串的值。
• 客户端1判断随机字符串的值,与预期的值相等。
• 客户端1由于某个原因阻塞住了很长时间。
• 过期时间到了,锁自动释放了。
• 客户端2获取到了对应同一个资源的锁。
• 客户端1从阻塞中恢复过来,执行DEL操纵,释放掉了客户端2持有的
锁。
释放锁
• //释放锁 比较unique_value是否相等,避免
误释放
• if redis.call("get",KEYS[1]) == ARGV[1]
then
• return redis.call("del",KEYS[1])
• else
• return 0
• end
• 读取锁变量、判断值、删除锁变量是多个
操作,而Redis 在执行 Lua 脚本时,可以
以原子性的方式执行,从而保证了锁释放
操作的原子性
锁有效时间
• 加锁时,先设置一个过期时间,然后我们
开启一个「守护线程」,定时去检测这个
锁的失效时间,如果锁快要过期了,操作
共享资源还未完成,那么就自动对锁进行
「续期」,重新设置过期时间。
主从切换redis
基于多个 Redis 节点实现高可靠的分布式锁 redlock算法
Redlock算法流程
• 客户端先获取「当前时间戳T1」
• 客户端依次向这 5 个 Redis 实例发起加锁请求(用前面讲到的 SET
命令),且每个请求会设置超时时间(毫秒级,要远小于锁的有效时
间),如果某一个实例加锁失败(包括网络超时、锁被其它人持有等
各种异常情况),就立即向下一个 Redis 实例申请加锁
• 如果客户端从 >=3 个(大多数)以上 Redis 实例加锁成功,则再次
获取「当前时间戳T2」,如果 T2 - T1 < 锁的过期时间,此时,认为
客户端加锁成功,否则认为加锁失败
• 加锁成功,去操作共享资源(例如修改 MySQL 某一行,或发起一个
API 请求)
• 加锁失败,向「全部节点」发起释放锁请求(前面讲到的 Lua 脚本释
放锁)
• 1.客户端在多个 Redis 实例上申请加锁。
• 2.必须保证大多数节点加锁成功。
• 3.大多数节点加锁的总耗时,要小于锁设置的过期时间。
• 4.释放锁,要向全部节点发起释放锁请求。
假设一共有5个Redis节点:A, B, C, D, E。设想发生了如下的事件序列:
• 客户端1成功锁住了A, B, C,获取锁成功(但D和E没有锁住)。
• 节点C崩溃重启了,但客户端1在C上加的锁没有持久化下来,丢失了。
• 节点C重启后,客户端2锁住了C, D, E,获取锁成功。
Redlock缺陷
Redlock缺陷
• 假设一共有5个Redis节点:A, B, C, D, E。设想发生了如下的事件序列:
• 客户端 1 获取节点 A、B、C 上的锁,但由于网络问题,无法访问 D 和
E节点, C 上的时钟「向前跳跃」,导致锁到期
• 客户端 2 获取节点 C、D、E 上的锁,由于网络问题,无法访问 A 和 B
• 客户端 1 和 2 现在都相信它们持有了锁(冲突)
Martin的结论
• 如果是为了效率(efficiency)而使用分布式锁,允许锁的偶尔失效,那
么使用Redis单节点的锁方案就足够了,简单而且效率高。Redlock则
是个过重的实现(heavyweight)。
• 如果是为了正确性(correctness)在很严肃的场合使用分布式锁,那么
不要使用Redlock。它不是建立在异步模型上的一个足够强的算法,
它对于系统模型的假设中包含很多危险的成分(对于timing)。而且,
它没有一个机制能够提供fencing token。那应该使用什么技术呢?
Martin认为,应该考虑类似Zookeeper的方案,或者支持事务的数据
库。
antirez的反驳
• 手动修改时钟这种人为原因,不要那么做就是了。否则的话,如果有
人手动修改Raft协议的持久化日志,那么就算是Raft协议它也没法正
常工作了。
• 使用一个不会进行“跳跃”式调整系统时钟的ntpd程序(可能是通过
恰当的配置),对于时钟的修改通过多次微小的调整来完成。
ZooKeeper特性
• 临时节点:客户端可以建立一个临时节点,在会话结束或者会话超时
后,ZK 会自动删除该节点。
• 有序节点:假如当前有一个父节点为 /lock,我们可以在这个父节点
下面创建子节点,ZK 提供了一个可选的有序特性。
• 事件监听:在读取数据时,我们可以同时对节点设置事件监听,当节
点数据或结构变化时,ZK 会通知客户端。
ZooKeeper实现分布式锁
• 临时节点方式
• 1.让多个进程(或线程)竞争性地去创建同一个临时节点,由于
ZooKeeper 不允许存在两个完全相同节点,因此必然只有一个进程能
够抢先创建成功 。
• 2、假设是进程 A 成功创建了节点,则它获得该分布式锁。此时其他
进程需要在 parent_node 上注册监听,监听其下所有子节点的变化,
并挂起当前线程。
• 3、当 parent_node 下有子节点发生变化时候,它会通知所有在其上
注册了监听的进程。这些进程需要判断是否是对应的锁节点上的删除
事件。如果是,则让挂起的线程继续执行,并尝试再次获取锁。
• 4.这种方式是非公平锁,也就是说在进程 A 释放锁后,进程 B,C,
D 发起重试的顺序与其收到通知的时间有关,而与其第一次尝试获取
锁的时间无关,即与等待时间的长短无关。
ZooKeeper实现分布式锁
临时顺序节点方式
锁可理解为 ZooKeeper 上的一个节点,当需要获取锁时,就在这个锁
节点下创建一个临时顺序节点。当存在多个客户端同时来获取锁,就按
顺序依次创建多个临时顺序节点,但只有排列序号是第一的那个节点能
获取锁成功,其他节点则按顺序分别监听前一个节点的变化,当被监听
者释放锁时,监听者就可以马上获得锁。
ZooKeeper缺陷
• 客户端 1 创建临时节点 /lock 成功,拿到了锁
• 客户端 1 发生长时间 GC
• 客户端 1 无法给 Zookeeper 发送心跳,Zookeeper 把临时节点「删
除」
• 客户端 2 创建临时节点 /lock 成功,拿到了锁
• 客户端 1 GC 结束,它仍然认为自己持有锁(冲突)
ZooKeeper相比Redis优点
• 在正常情况下,客户端可以持有锁任意长的时间,这可以确保它做完
所有需要的资源访问操作之后再释放锁。这避免了基于Redis的锁对
于有效时间(lock validity time)到底设置多长的两难问题。
• 基于ZooKeeper的锁支持在获取锁失败之后等待锁重新释放的事件。
这让客户端对锁的使用更加灵活。
• ZK 天生设计定位就是分布式协调,强一致性。锁的模型健壮、简单
易用、适合做分布式锁。
• 实现起来有现成的第三方包,比较方便。高可用利用ZK集群进行保证。
• 性能不如 Redis,并发性较差,高并发场景有性能问题。
• 部署和运维成本高
• 客户端与 Zookeeper 的长时间失联,锁被释放问题。
分布式锁在秒杀中的应用
• 秒杀特点
1)瞬时并发访问量非常高。
2)读多写少
当有大量并发请求涌入秒杀系统时,我们就需要使用 Redis 先拦截大部
分请求,避免大量请求直接发送给数据库,把数据库压垮
• 请求队列方式。
• 对于写请求,做请求队列,每次只透传有限的写请求去数据层(下订
单,支付这样的写业务)。只有2000张火车票,即使10w个请求过来,
也只透传2000个去访问数据库。
• 使用分布式锁来支撑秒杀场景的具体做法是,先让客户端向 Redis 申
请分布式锁,只有拿到锁的客户端才能执行库存查验和库存扣减。这
样一来,大量的秒杀请求就会在争夺分布式锁时被过滤掉。
结论
• 加锁包括了读取锁变量、检查锁变量值和设置锁变量值三个操作,但
需要以原子操作的方式完成,所以,我们使用 SET 命令带上 NX 选
项来实现加锁;
• 锁变量需要设置过期时间,以免客户端拿到锁后发生异常,导致锁一
直无法释放,所以,我们在 SET 命令执行时加上 EX/PX 选项,设置
其过期时间;
• 锁变量的值需要能区分来自不同客户端的加锁操作,以免在释放锁时,
出现误释放操作,所以,我们使用 SET 命令设置锁变量值时,每个
客户端设置的值是一个唯一值,用于标识客户端。
• Redlock 的个人看法是,尽量不用它,而且它的性能不如单机版
redis,部署成本也高。
• 相比较redis和ZooKeeper,个人倾向于redis实现。

Contenu connexe

Similaire à redis实现分布式锁.pptx

Showinnodbstatus公开
Showinnodbstatus公开Showinnodbstatus公开
Showinnodbstatus公开longxibendi
 
D2_node在淘宝的应用实践_pdf版
D2_node在淘宝的应用实践_pdf版D2_node在淘宝的应用实践_pdf版
D2_node在淘宝的应用实践_pdf版Jackson Tian
 
2014暑期訓練之Linux kernel power
2014暑期訓練之Linux kernel power2014暑期訓練之Linux kernel power
2014暑期訓練之Linux kernel power冠宇 陳
 
深入解析MySQL之锁机制应用
深入解析MySQL之锁机制应用深入解析MySQL之锁机制应用
深入解析MySQL之锁机制应用banping
 
并发编程实践与思考
并发编程实践与思考并发编程实践与思考
并发编程实践与思考promise6522
 
MySQL自动切换设计与实现
MySQL自动切换设计与实现MySQL自动切换设计与实现
MySQL自动切换设计与实现orczhou
 
主库自动切换 V2.0
主库自动切换 V2.0主库自动切换 V2.0
主库自动切换 V2.0jinqing zhu
 
Python小团队不妨知道的技术
Python小团队不妨知道的技术Python小团队不妨知道的技术
Python小团队不妨知道的技术jie.wang
 
课题一:PHP5.3、PHP5.4的特性介绍与深度挖掘
课题一:PHP5.3、PHP5.4的特性介绍与深度挖掘课题一:PHP5.3、PHP5.4的特性介绍与深度挖掘
课题一:PHP5.3、PHP5.4的特性介绍与深度挖掘Liu Allen
 
4, OCP - oracle networking
4, OCP - oracle networking4, OCP - oracle networking
4, OCP - oracle networkingted-xu
 
Oracle数据库高级安全选件ASO介绍
Oracle数据库高级安全选件ASO介绍Oracle数据库高级安全选件ASO介绍
Oracle数据库高级安全选件ASO介绍jenkin
 
淘宝主备数据库自动切换
淘宝主备数据库自动切换淘宝主备数据库自动切换
淘宝主备数据库自动切换mysqlops
 
Node.js在淘宝的应用实践
Node.js在淘宝的应用实践Node.js在淘宝的应用实践
Node.js在淘宝的应用实践taobao.com
 
Java SE 8 技術手冊第 11 章 - 執行緒與並行API
Java SE 8 技術手冊第 11 章 - 執行緒與並行APIJava SE 8 技術手冊第 11 章 - 執行緒與並行API
Java SE 8 技術手冊第 11 章 - 執行緒與並行APIJustin Lin
 
JavaScript 80+ Programming and Optimization Skills
JavaScript 80+ Programming and Optimization SkillsJavaScript 80+ Programming and Optimization Skills
JavaScript 80+ Programming and Optimization SkillsHo Kim
 
6 事务和并发控制
6 事务和并发控制6 事务和并发控制
6 事务和并发控制Zelin Wang
 
JobTracker Memory Leak Solution
JobTracker  Memory Leak SolutionJobTracker  Memory Leak Solution
JobTracker Memory Leak Solutionjiang yu
 

Similaire à redis实现分布式锁.pptx (17)

Showinnodbstatus公开
Showinnodbstatus公开Showinnodbstatus公开
Showinnodbstatus公开
 
D2_node在淘宝的应用实践_pdf版
D2_node在淘宝的应用实践_pdf版D2_node在淘宝的应用实践_pdf版
D2_node在淘宝的应用实践_pdf版
 
2014暑期訓練之Linux kernel power
2014暑期訓練之Linux kernel power2014暑期訓練之Linux kernel power
2014暑期訓練之Linux kernel power
 
深入解析MySQL之锁机制应用
深入解析MySQL之锁机制应用深入解析MySQL之锁机制应用
深入解析MySQL之锁机制应用
 
并发编程实践与思考
并发编程实践与思考并发编程实践与思考
并发编程实践与思考
 
MySQL自动切换设计与实现
MySQL自动切换设计与实现MySQL自动切换设计与实现
MySQL自动切换设计与实现
 
主库自动切换 V2.0
主库自动切换 V2.0主库自动切换 V2.0
主库自动切换 V2.0
 
Python小团队不妨知道的技术
Python小团队不妨知道的技术Python小团队不妨知道的技术
Python小团队不妨知道的技术
 
课题一:PHP5.3、PHP5.4的特性介绍与深度挖掘
课题一:PHP5.3、PHP5.4的特性介绍与深度挖掘课题一:PHP5.3、PHP5.4的特性介绍与深度挖掘
课题一:PHP5.3、PHP5.4的特性介绍与深度挖掘
 
4, OCP - oracle networking
4, OCP - oracle networking4, OCP - oracle networking
4, OCP - oracle networking
 
Oracle数据库高级安全选件ASO介绍
Oracle数据库高级安全选件ASO介绍Oracle数据库高级安全选件ASO介绍
Oracle数据库高级安全选件ASO介绍
 
淘宝主备数据库自动切换
淘宝主备数据库自动切换淘宝主备数据库自动切换
淘宝主备数据库自动切换
 
Node.js在淘宝的应用实践
Node.js在淘宝的应用实践Node.js在淘宝的应用实践
Node.js在淘宝的应用实践
 
Java SE 8 技術手冊第 11 章 - 執行緒與並行API
Java SE 8 技術手冊第 11 章 - 執行緒與並行APIJava SE 8 技術手冊第 11 章 - 執行緒與並行API
Java SE 8 技術手冊第 11 章 - 執行緒與並行API
 
JavaScript 80+ Programming and Optimization Skills
JavaScript 80+ Programming and Optimization SkillsJavaScript 80+ Programming and Optimization Skills
JavaScript 80+ Programming and Optimization Skills
 
6 事务和并发控制
6 事务和并发控制6 事务和并发控制
6 事务和并发控制
 
JobTracker Memory Leak Solution
JobTracker  Memory Leak SolutionJobTracker  Memory Leak Solution
JobTracker Memory Leak Solution
 

redis实现分布式锁.pptx