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

Posted TalkingData

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了技术专栏丨Redis分布式锁的小坑踩一踩相关的知识,希望对你有一定的参考价值。


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分布式锁的小坑踩一踩

 代码如下:

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

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


在获取 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
技术专栏丨Redis分布式锁的小坑踩一踩

重新翻看 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.

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

文档说明从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分布式锁的小坑踩一踩

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


以上就是关于 Redis 分布式锁的完整内容,欢迎有兴趣的小伙伴进行尝试,并在本文下进行探讨。

相关阅读:





以上是关于技术专栏丨Redis分布式锁的小坑踩一踩的主要内容,如果未能解决你的问题,请参考以下文章

锁的基本概念到 Redis 分布式锁实现

Redis实现分布式锁(设计模式应用实战)

Redis实现分布式锁(设计模式应用实战)

80% 人不知道的 Redis 分布式锁的正确实现方式(Java 版)

关于Redis分布式锁的那些事

Redis进阶学习03---Redis完成秒杀和Redis分布式锁的应用