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);
            }
        }`
相关推荐
倔强的石头_21 小时前
kingbase备份与恢复实战(二)—— sys_dump库级逻辑备份与恢复(Windows详细步骤)
数据库
jiayou642 天前
KingbaseES 实战:深度解析数据库对象访问权限管理
数据库
李广坤3 天前
MySQL 大表字段变更实践(改名 + 改类型 + 改长度)
数据库
初次攀爬者4 天前
ZooKeeper 实现分布式锁的两种方式
分布式·后端·zookeeper
爱可生开源社区4 天前
2026 年,优秀的 DBA 需要具备哪些素质?
数据库·人工智能·dba
随逸1774 天前
《从零搭建NestJS项目》
数据库·typescript
加号35 天前
windows系统下mysql多源数据库同步部署
数据库·windows·mysql
シ風箏5 天前
MySQL【部署 04】Docker部署 MySQL8.0.32 版本(网盘镜像及启动命令分享)
数据库·mysql·docker
李慕婉学姐5 天前
Springboot智慧社区系统设计与开发6n99s526(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
百锦再5 天前
Django实现接口token检测的实现方案
数据库·python·django·sqlite·flask·fastapi·pip