Redis 分布式锁原理及 Redisson 实现

标签: Redis Java Redis | 发表时间:2021-04-25 11:14 | 作者:
出处:https://xxgblog.com/

Redis 分布式锁原理

Redis 分布式锁原理,可以直接看官方文档:
https://redis.io/commands/set#patterns

The command SET resource-name anystring NX EX max-lock-time is a simple way to implement a locking system with Redis.

SET resource-name anystring NX EX max-lock-time 命令可以基于 Redis 实现分布式锁。

  • NX Only set the key if it does not already exist
  • EX seconds Set the specified expire time, in seconds
  • NX 仅当 key 不存在时设置成功
  • EX seconds 失效时间(秒)

A client can acquire the lock if the above command returns OK (or retry after some time if the command returns Nil), and remove the lock just using DEL.

  • 当命令返回 OK 时,该客户端获得锁
  • 当命令返回 Nil 时,客户端未获得锁,需要过一段时间再重试命令尝试获取锁
  • 使用 DEL 删除命令可用来释放锁

The lock will be auto-released after the expire time is reached.

当达到失效时间时,锁自动释放。

It is possible to make this system more robust modifying the unlock schema as follows:

  • Instead of setting a fixed string, set a non-guessable large random string, called token.
  • Instead of releasing the lock with DEL, send a script that only removes the key if the value matches.

This avoids that a client will try to release the lock after the expire time deleting the key created by another client that acquired the lock later.

更加健壮的释放锁的方式:

  • 设置的 value 是一个随机生成的无法预测的值,叫做 token
  • 不再使用 DEL 直接删除 key 来释放锁,而是使用一个 script,仅当 value 匹配 token 时才会删除 key

这样可以防止某个客户端在超过失效时间后尝试释放锁,直接使用 DEL 可能会删除掉别的客户端添加的锁。

下面是释放锁脚本的例子:

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

The script should be called with EVAL ...script... 1 resource-name token-value

执行 EVAL ...script... 1 resource-name token-value 命令释放锁。

以上是官方文档中的内容,阅读到这里可以发现一个问题:

  • 官方的方案中,分布式锁是有个失效时间的,达到失效时间锁会被自动释放,如果此时需要加锁执行的任务还未完成,同时锁又被其他客户端获取到,那么就可能会出现严重的问题;
  • 如果锁不加上失效时间,万一获得锁的客户端突然 crash 了,没有来得及释放锁,那么这个锁就永远不会被释放。

针对这个问题,可以看下 Redisson 是如何解决的。

Redisson 分布式锁

官方文档:
https://github.com/redisson/redisson/wiki/8.-distributed-locks-and-synchronizers

通过以下方式,可以获得一个 key 为 myLockRLock 对象:

1      
2
3
4
Config config = new Config();      
config.useSingleServer().setAddress("redis://localhost:6379");
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("myLock");

获取锁和释放锁:

1      
2
3
4
5
6
lock.lock(); // 获取锁      
try {
...
} finally {
lock.unlock(); // 在 finally 中释放锁
}

RLock 提供了以下多种获取锁的方法:

  • void lock()
  • void lock(long leaseTime, TimeUnit unit)
  • void lockInterruptibly()
  • void lockInterruptibly(long leaseTime, TimeUnit unit)
  • boolean tryLock()
  • boolean tryLock(long time, TimeUnit unit)
  • boolean tryLock(long waitTime, long leaseTime, TimeUnit unit)

RLock 实现了 java.util.concurrent.locks.Lock 接口,所以 RLock 是符合 Java 中的 Lock 接口规范的。以上的方法中,这四个方法是来源于 Java 中的 Lock 接口:

  • void lock() 获取锁,如果锁不可用,则当前线程一直等待,直到获得到锁
  • void lockInterruptibly()lock() 方法类似,区别是 lockInterruptibly() 方法在等待的过程中可以被 interrupt 打断
  • boolean tryLock() 获取锁,不等待,立即返回一个 boolean 类型的值表示是否获取成功
  • boolean tryLock(long time, TimeUnit unit) 获取锁,如果锁不可用,则等待一段时间,等待的最长时间由 long timeTimeUnit unit 两个参数指定,如果超过时间未获得锁则返回 false,获取成功返回 true

除了以上四个方法外,还有三个方法不是来源于 Java 中的 Lock 接口,而是 RLock 中的方法。这三个方法和上面四个方法有一个最大的区别就是多了一个 long leaseTime 参数。 leaseTime 指的就是 Redis 中的 key 的失效时间。通过这三个方法获取到的锁,如果达到 leaseTime 锁还未释放,那么这个锁会自动失效。

回到上面的问题:如果设置了失效时间,当任务未完成且达到失效时间时,锁会被自动释放;如果不设置失效时间,突然 crash 了,锁又会永远得不到释放。Redisson 是怎么解决这个问题的呢?

If Redisson instance which acquired lock crashes then such lock could hang forever in acquired state. To avoid this Redisson maintains lock watchdog, it prolongs lock expiration while lock holder Redisson instance is alive. By default lock watchdog timeout is 30 seconds and can be changed through Config.lockWatchdogTimeout setting.

为了防止 Redisson 实例 crash 导致锁永远不会被释放,针对未指定 leaseTime 的四个方法,Redisson 为锁维护了看门狗(watchdog)。看门狗每隔一段时间去延长一下锁的失效时间。锁的默认失效时间是 30 秒,可通过 Config.lockWatchdogTimeout 修改。延长失效时间的任务的执行频率也是由该配置项决定,是锁的失效时间的 1/3,即默认每隔 10 秒执行一次。

如果 Redisson 实例 crash 了,看门狗也会跟着 crash,那么达到失效时间这个 key 会被 Redis 自动清除,锁也就被释放了,不会出现锁永久被占用的情况。

相关 [redis 分布式锁 原理] 推荐:

Redis 分布式锁原理及 Redisson 实现

- - 叉叉哥的BLOG
Redis 分布式锁原理. Redis 分布式锁原理,可以直接看官方文档:. SET resource-name anystring NX EX max-lock-time 命令可以基于 Redis 实现分布式锁. NX 仅当 key 不存在时设置成功. EX seconds 失效时间(秒). Nil 时,客户端未获得锁,需要过一段时间再重试命令尝试获取锁.

如何用redis实现分布式锁

- - CSDN博客数据库推荐文章
redis作为一个强大的key/value数据库,其实还可以用来实现轻量级的分布式锁. 最早官方在 SETNX命令页给了一个实现:. 不过这个方案有漏洞,就是release lock用的DEL命令不支持cas删除(delete if current value equals old value),这样忽略race condition将会出现问题:.

基于redis分布式锁实现“秒杀”

- - 企业架构 - ITeye博客
来自于  http://blog.5ibc.net/p/28883.html. 所谓秒杀,从业务角度看,是短时间内多个用户“争抢”资源,这里的资源在大部分秒杀场景里是商品;将业务抽象,技术角度看,秒杀就是多个线程对资源进行操作,所以实现秒杀,就必须控制线程对资源的争抢,既要保证高效并发,也要保证操作的正确.

Redis分布式锁解决抢购问题

- - 企业架构 - ITeye博客
废话不多说,首先分享一个业务场景-抢购. 一个典型的高并发问题,所需的最关键字段就是库存,在高并发的情况下每次都去数据库查询显然是不合适的,因此把库存信息存入Redis中,利用redis的锁机制来控制并发访问,是一个不错的解决方案.         //这里抛出的异常要是运行时异常,否则无法进行数据回滚,这也是spring中比较基础的  .

基于Redis实现分布式锁之前,这些坑你一定得知道

- - DockOne.io
【编者的话】基于Redis的分布式锁对大家来说并不陌生,可是你的分布式锁有失败的时候吗. 在失败的时候可曾怀疑过你在用的分布式锁真的靠谱吗. 以下是结合自己的踩坑经验总结的一些经验之谈. 用到分布式锁说明遇到了多个进程共同访问同一个资源的问题,一般是在两个场景下会防止对同一个资源的重复访问:. 比如多个节点计算同一批任务,如果某个任务已经有节点在计算了,那其他节点就不用重复计算了,以免浪费计算资源.

Redis分布式锁的正确实现方式(Java版) - 吴大山的博客 | Wudashan Blog

- -
本博客使用第三方开源组件Jedis实现Redis客户端,且只考虑Redis服务端单机部署的场景. 分布式锁一般有三种实现方式:1. 基于ZooKeeper的分布式锁. 本篇博客将介绍第二种方式,基于Redis实现分布式锁. 虽然网上已经有各种介绍Redis分布式锁实现的博客,然而他们的实现却有着各种各样的问题,为了避免误人子弟,本篇博客将详细介绍如何正确地实现Redis分布式锁.

[原]Redis共享Session原理及示例

- - moxiaomomo的专栏
Redis共享session的作用. 微服务自身可以保持无状态,应用实例数量的多少不会影响用户登录状态;. 可实现单点登录的踢出功能,如可以让上次异地登录的用户下线;. session在多个服务或服务器间共享,实现多站点单点登录(参考SSO服务). Redis缓存session原理简述. 其工作原理,可简单用图描述(假设服务A运行有有个多个实例):.

深入理解Redis主键失效原理及实现机制

- - NoSQLFan
本文来自@ 梁喜健 的热心投稿,原文见作者的 新浪博客,对于 缓存失效,不同的缓存有不同的处理机制,可以说是大同中有小异,作者通过对 Redis 文档与相关源码的仔细研读,为大家详细剖析了 Redis 的缓存过期/失效机制相关的技术原理与实现细节. 作为一种定期清理无效数据的重要机制,主键失效存在于大多数缓存系统中,Redis 也不例外.

一起看懂Redis两种持久化方式的原理

- - 编程学习网
  Redis为持久化提供了两种方式:. RDB:在指定的时间间隔能对你的数据进行快照存储. AOF:记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据.   本文将通过下面内容的介绍,希望能够让大家更全面、清晰的认识这两种持久化方式,同时理解这种保存数据的思路,应用于自己的系统设计中.

Redis cluster集群模式的原理 - __Meng - 博客园

- -
  redis cluster是Redis的分布式解决方案,在3.0版本推出后有效地解决了redis分布式方面的需求.   自动将数据进行分片,每个master上放一部分数据.   提供内置的高可用支持,部分master不可用时,还是可以继续工作的.   支撑N个redis master node,每个master node都可以挂载多个slave node.