Redis 分布式锁实现方案

一、概述

分布式锁,即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题,而分布式锁,就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是,分布式系统中竞争共享资源的最小粒度从线程升级成了进程。

基于 Redis 单机实现的分布式锁,其方式和 Memcached 的实现方式类似,利用 Redis 的 SETNX 命令,此命令同样是原子性操作,只有在 key 不存在的情况下,才能 set 成功。而基于 Redis 多机实现的分布式锁Redlock,是Redis 的作者 antirez 为了规范 Redis 分布式锁的实现,提出的一个更安全有效的实现机制。

二、基于 Redis 单机实现的分布式锁

1、 使用 SETNX 指令

使用 Redis 的 SETNX 指令,该指令只在 key 不存在的情况下,将 key 的值设置为 value,若 key 已经存在,则 SETNX 命令不做任何动作。key 是锁的唯一标识,可以按照业务需要锁定的资源来命名。

2、SETNX + value值是(系统时间+过期时间)

setnx(lockkey, 当前时间+过期超时时间) ,如果返回1,则获取锁成功;如果返回0则没有获取到锁

java 复制代码
`long expires = System.currentTimeMillis() + expireTime; //系统时间+设置的过期时间
String expiresStr = String.valueOf(expires);
// 如果当前锁不存在,返回加锁成功
if (jedis.setnx(key_resource_id, expiresStr) == 1) {
        return true;
} 
// 如果锁已经存在,获取锁的过期时间
String currentValueStr = jedis.get(key_resource_id);
// 如果获取到的过期时间,小于系统当前时间,表示已经过期
if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
     // 锁已过期,获取上一个锁的过期时间,并设置现在锁的过期时间(不了解redis的getSet命令的小伙伴,可以去官网看下哈)
    String oldValueStr = jedis.getSet(key_resource_id, expiresStr);    
    if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
         // 考虑多线程并发的情况,只有一个线程的设置值和当前值相同,它才可以加锁
         return true;
    }
}        
//其他情况,均返回加锁失败
return false;
}`

3、Redis分布式锁方案三:使用Lua脚本(包含SETNX + EXPIRE两条指令)

lua脚本如下:

java 复制代码
`if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then
   redis.call('expire',KEYS[1],ARGV[2])
else
   return 0
end;`

加锁代码如下:

java 复制代码
` String lua_scripts = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then" +
            " redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end";   
Object result = jedis.eval(lua_scripts, Collections.singletonList(key_resource_id), Collections.singletonList(values));
//判断是否成功
return result.equals(1L);`

三、基于Redisson框架

其实 Redisson 也封装 可重入锁(Reentrant Lock)、公平锁(Fair Lock)、联锁(MultiLock)、红锁(RedLock)、读写锁(ReadWriteLock)、 信号量(Semaphore)、可过期性信号量(PermitExpirableSemaphore)、 闭锁(CountDownLatch)等。具体参考:Redisson详解及开发实例

大体流程如下:

watch dog自动延期机制:
看门狗启动后,对整体性能也会有一定影响,默认情况下看门狗线程是不启动的。如果使用redisson进行加锁的同时设置了锁的过期时间,也会导致看门狗机制失效

1、引入依赖:

plain 复制代码
`<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>3.19.2</version>
</dependency>  `

2、配置类实现

java 复制代码
`@Bean
public RedissonClient  redissonClient(){
Config config = new Config();
config.setTransportMode(TransportMode.EPOLL); // 默认是NIO的方式
config.useClusterServers()
      //可以用"rediss://"来启用SSL连接,前缀必须是redis:// or rediss://
      .addNodeAddress("redis://127.0.0.1:7181");
return Redisson.create(config);
}`

3、工具类

java 复制代码
`import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.UUID;
 
/**
 * Redisson 加锁
 */
@Component
public class RedissonUtil {
 
    @Resource
    private RedissonClient redissonClient;
 
    public String getKey(){
        return UUID.randomUUID().toString();
    }
 
    public String getKey(Class<?> tClass, Thread thread){
        return tClass.toString() + "_" + thread.getStackTrace()[2].getMethodName();
    }
 
    public RLock getClint(String key){
        RReadWriteLock lock = redissonClient.getReadWriteLock(key);
        return lock.writeLock();
    }
 
    public void lock(String key) {
        this.getClint(key).lock();
    }
 
    public void unLock(String key) {
        this.getClint(key).unlock();
    }

    public void lock(String key, long expire) {
        try {
            this.getClint(key).tryLock(expire, TimeUnit.SECONDS);
        } catch (Exception e) {

        }
    }
 
}
 `

4、测试代码:

java 复制代码
` @Autowired
 RedissonUtil redissonUtil;
 String key = "leo";
        long extime = 10;
        boolean islock = redissonUtil.lock(key, extime);
        if (islock) {
            try {

            } finally {
                redissonUtil.unLock(key);
            }
        }`
相关推荐
Arbori_262151 分钟前
获取oracle表大小
数据库·oracle
王强你强8 分钟前
MySQL 高级查询:JOIN、子查询、窗口函数
数据库·mysql
草巾冒小子9 分钟前
brew 安装mysql,启动,停止,重启
数据库·mysql
用户62799471826215 分钟前
南大通用GBase 8c分布式版本gha_ctl 命令-HI参数详解
数据库
斯汤雷24 分钟前
Matlab绘图案例,设置图片大小,坐标轴比例为黄金比
数据库·人工智能·算法·matlab·信息可视化
SQLplusDB31 分钟前
Oracle 23ai Vector Search 系列之3 集成嵌入生成模型(Embedding Model)到数据库示例,以及常见错误
数据库·oracle·embedding
喝醉酒的小白1 小时前
SQL Server 可用性组自动种子设定失败问题
数据库
chem41111 小时前
Conmon lisp Demo
服务器·数据库·lisp
爱的叹息1 小时前
Java 连接 Redis 的驱动(Jedis、Lettuce、Redisson、Spring Data Redis)分类及对比
java·redis·spring
m0_555762901 小时前
QT 动态布局实现(待完善)
服务器·数据库·qt