暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

技术专栏丨Redis分布式锁的小坑踩一踩

TalkingData 2018-10-11
197


TalkingData
领先的数据智能服务商



本文作者:TalkingData张浩,转载请注明出处。


◆◆
概述
◆◆

目前市场上很多应用都是分布式部署,这些应用中的部分业务需要定时去执行,虽然有幂等性保护,由于不想让一次任务被调度多次(打印太多错误日志,数据库主键约束,耗费资源),因此可以选择使用基于 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 分布式锁的完整内容,欢迎有兴趣的小伙伴进行尝试,并在本文下进行探讨。

相关阅读:

技术专栏丨集合管道模式(上)

技术专栏 | 集合管道模式(下)

技术专栏丨基于Spark、NoSQL的实时数据处理实践(下)


文章转载自TalkingData,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论