一、概述
分布式锁,即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题,而分布式锁,就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是,分布式系统中竞争共享资源的最小粒度从线程升级成了进程。
基于 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则没有获取到锁
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脚本如下:
if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then redis.call('expire',KEYS[1],ARGV[2]) else return 0 end;
加锁代码如下:
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、引入依赖:
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.19.2</version> </dependency>
2、配置类实现
@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、工具类
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、测试代码:
@Autowired RedissonUtil redissonUtil; String key = "leo"; long extime = 10; boolean islock = redissonUtil.lock(key, extime); if (islock) { try { } finally { redissonUtil.unLock(key); } }
文章下方有交流学习区!一起学习进步!也可以前往官网,加入官方微信交流群
创作不易,如果觉得文章不错,可以点赞 收藏 评论
搞点副业,希望大家多多支持!!!你的支持和鼓励是我创作的动力❗❗❗
官网:Doker 多克; 官方旗舰店:Doker 多克 技术人自己的3C旗舰店 全品优惠