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);
            }
        }`
相关推荐
旺仔不是程序员1 小时前
复合索引最左前缀原则:PostgreSQL 的 WHERE 为什么必须命中第一列
数据库·后端·sql
旺仔不是程序员1 小时前
字段类型不一致:PostgreSQL 报错与索引失效的第一元凶
数据库·后端·sql
白远山1 小时前
货运跑腿搬家平台开发实战:从需求分析到落地部署指南
数据库·数据挖掘·需求分析
geovindu1 小时前
sql: Data Modeling Patterns
数据库·sql·设计模式·sqlserver
上海蓝色星球1 小时前
蓝色星球NG-AIOS新型AI工业操作系统重磅发布——以本体智能为内核,重构“AI+制造“新范式
大数据·数据库·人工智能·机器人
瀚高PG实验室2 小时前
瀚高数据库如何克隆表
数据库·sql·postgresql·瀚高数据库
雨辰AI2 小时前
信创多租户项目 9 大踩坑|数据隔离失效、权限越权终极解决(金仓 / 达梦 / 高斯全库适配)
java·大数据·数据库·后端
suweijie7682 小时前
Redis 7.0.2 Docker Compose 部署
redis·docker
严同学正在努力2 小时前
认识 SQL Server 的 T-SQL 语法
数据库·人工智能·ai·oracle·dba
EatFan2 小时前
我为什么把患者与病例从 1:1 改成 1:N?一次真实数据库模型重构记录
数据库·后端·重构·健康医疗·全栈