技术专栏丨Redis分布式锁的小坑踩一踩
Posted TalkingData
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了技术专栏丨Redis分布式锁的小坑踩一踩相关的知识,希望对你有一定的参考价值。
目前市场上很多应用都是分布式部署,这些应用中的部分业务需要定时去执行,虽然有幂等性保护,由于不想让一次任务被调度多次(打印太多错误日志,数据库主键约束,耗费资源),因此可以选择使用基于 Redis 的分布式锁。
- 互斥性--在任意时刻,只有一个客户端能持有锁。
- 不会发生死锁--即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
- 具有容错性--只要大部分的 Redis 节点正常运行,客户端就可以加锁和解锁。
首先选择 Redis 中的 SETNX 来获取锁:
Set key to hold string value if key does not exist.
In that case, it is equal to SET.
When key already holds a value, no operation is performed.
SETNX is short for "SET if Not eXists".
Return value
Integer reply, specifically:
1 if the key was set
0 if the key was not set
代码如下:
在获取 Redis 锁的时候使用 setnx 和 expire 两条命令来实现, 因为不是原子操作,所以可能会导致过程中出现很多问题。比如当执行上述代码第3步的时候,由于网络波动或者别的异常原因,导致锁的 TTL 设置失败。因此加了第一步:判断锁的 TTL 值。
虽然对上面逻辑进行了判断,但这些判断不能保证两条命令的原子性。
比如:节点1执行完第2步,节点2开始执行任务;这时候节点2执行第1步获取锁的 TTL 就会是-1,并执行 if 内的程序。最终执行第2步、第3步。此时节点1、节点2都会定时该任务的逻辑,这就违背了业务初衷。
节点1:
2018-08-14 15:00:00,003 INFO [DefaultQuartzScheduler_Worker-7] UploadScheduleTask.startTask(41)|ttl:-2
2018-08-14 15:00:00,004 INFO [DefaultQuartzScheduler_Worker-7] UploadScheduleTask.startTask(48)|redis setnx flag: 1
2018-08-14 15:00:00,005 INFO [DefaultQuartzScheduler_Worker-7] UploadScheduleTask.startTask(52)|### start a new upload schedule task , now is 1534230000004
节点2:
2018-08-14 15:00:00,003 INFO [DefaultQuartzScheduler_Worker-7] UploadScheduleTask.startTask(41)|ttl:-1
2018-08-14 15:00:00,004 INFO [DefaultQuartzScheduler_Worker-7] UploadScheduleTask.startTask(48)|redis setnx flag: 1
2018-08-14 15:00:00,005 INFO [DefaultQuartzScheduler_Worker-7] UploadScheduleTask.startTask(52)|### start a new upload schedule task , now is 1534230000005
重新翻看 SETNX 的官方文档:
Design pattern: Locking with SETNX
Please note that:
The following pattern is discouraged in favor of the Redlock algorithm which is only a bit more complex to implement, but offers better guarantees and is fault tolerant.
We document the old pattern anyway because certain existing implementations link to this page as a reference. Moreover it is an interesting example of how Redis commands can be used in order to mount programming primitives.
Anyway even assuming a single-instance locking primitive, starting with 2.6.12 it is possible to create a much simpler locking primitive, equivalent to the one discussed here, using the SET command to acquire the lock, and a simple Lua script to release the lock. The pattern is documented in the SET command page.
文档说明从2.6.12版本后, 就可以使用 set 来获取锁, Lua 脚本来释放锁
接下来看 set 命令的说明, 发现可以有nx,xx等参数, 去实现 setnx 的功能:
SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
Options
Starting with Redis 2.6.12 SET supports a set of options that modify its behavior:
EX seconds -- Set the specified expire time, in seconds.
PX milliseconds -- Set the specified expire time, in milliseconds.
NX -- Only set the key if it does not already exist.
XX -- Only set the key if it already exist.
Note: Since the SET command options can replace SETNX, SETEX, PSETEX, it is possible that in future versions of Redis these three commands will be deprecated and finally removed.
这样就可以保证了原子性。
修改后的逻辑代码:
以上就是关于 Redis 分布式锁的完整内容,欢迎有兴趣的小伙伴进行尝试,并在本文下进行探讨。
相关阅读:
以上是关于技术专栏丨Redis分布式锁的小坑踩一踩的主要内容,如果未能解决你的问题,请参考以下文章