Redis实现分布式锁

174 阅读3分钟

在并发编程中,我们通过锁,来避免由于竞争而造成的数据不一致问题

但是Java中的锁,只能保证在同一个JVM进程内中执行。

如果在分布式集群环境下,为了保证共享资源的正确性,就需要用到分布式锁了

分布式锁,是一种思想,它的实现方式有很多

一般实现分布式锁有以下几个方式:

  • MySql
  • Zookeeper
  • Redis

分布式锁需要实现的功能

  • 加锁
  • 解锁
  • 锁超时

redis分布式锁的实现方式

  • setnx
  • redLock
  • redisson

1.setnx

SET key value NX PX 5000

给key设置一个值,实际上就给redis加上了锁,为了避免死锁,给key设置过期时间,释放锁只需要删除key

NX 代表只在键不存在时,才对键进行设置操作
PX 5000 设置键的过期时间为5000毫秒

因为引入了超时时间来避免死锁,同时也引出了其它两个问题

  • 问题1:当业务处理的时间超过过期时间后,A和B都会进入临界区
  • 问题2:进程A会删除进程B设置的锁 image.png

问题1解决方案: 使用延长锁时效的策略

延长锁时效的方案如下:假设锁超时时间是 30 秒,此时程序需要每隔一段时间去扫描一下该锁是否还存在,扫描时间需要小于超时时间,通常可以设置为超时时间的 1/3,在这里也就是 10 秒扫描一次。如果锁还存在,则重置其超时时间恢复到 30 秒。通过这种方案,只要业务还没有处理完成,锁就会一直有效;而当业务一旦处理完成,程序也会马上删除该锁。

问题2解决方案:

在用setnx的时候,key虽然是主要作用,但是value也不能闲着,在创建锁时为其指定一个唯一的标识,可以用UUID这种随机数。

String uuid = xxxx;
// 伪代码,具体实现看项目中用的连接工具
set Test uuid NX PX 3000
try{
// biz handle....
} finally {
    // unlock
    if(uuid.equals(redisTool.get('Test')){
        redisTool.del('Test');
    }
}

当解锁的时候,先获取value判断是否是当前进程加的锁,再去删除

但是在finally代码块中,get和del并非原子操作,还是有进程安全问题,那么删除锁的正确姿势之一,就是使用lua脚本

2.RedLock

想要在集群模式下实现分布式锁,Redis 提供了一种称为 RedLock 的方案

image.png

红锁算法认为,只要(N/2) + 1个节点加锁成功,那么就认为获取了锁, 解锁时将所有实例解锁。

流程为:

  1. 顺序向五个节点请求加锁
  2. 根据一定的超时时间来推断是不是跳过该节点
  3. 三个节点加锁成功并且花费时间小于锁的有效期(超时时间 - 花费时间)
  4. 认定加锁成功

3.Redisson

Redisson 是 Redis 的 Java 客户端,它提供了各种的 Redis 分布式锁的实现

Redisson实现分布式锁(1)---原理 - 雨点的名字 - 博客园 (cnblogs.com)

文章参考(juejin.cn/post/684490…