分布式 | Redis 实现分布式锁
1.分布式 | Redis 实现分布式锁
2.分布式 | Redisson实现分布式锁实现分布式锁-Redis
乐尚代驾学习笔记-Redis司机抢单分布式锁
1、setnx+过期时间实现
@Override public void testLock() { //从redis里面获取数据 //1 获取当前锁 setnx Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent("lock", "lock"); //2 如果获取到锁,从redis获取数据 数据+1 放回redis里面 if(ifAbsent) { //获取锁成功,执行业务代码 //1.先从redis中通过key num获取值 key提前手动设置 num 初始值:0 String value = redisTemplate.opsForValue().get("num"); //2.如果值为空则非法直接返回即可 if (StringUtils.isBlank(value)) { return; } //3.对num值进行自增加一 int num = Integer.parseInt(value); redisTemplate.opsForValue().set("num", String.valueOf(++num)); //3 释放锁 redisTemplate.delete("lock"); } else { try { Thread.sleep(100); this.testLock(); } catch (InterruptedException e) { e.printStackTrace(); } } }
1.1、问题:
- 如果业务执行过程中心出现异常,导致锁无法释放
1.2、解决方案:
- 设置过期时间,到时间之后自动释放锁
//1 获取当前锁 setnx + 设置过期时间 // Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent("lock", "lock"); Boolean ifAbsent = redisTemplate.opsForValue() .setIfAbsent("lock", "lock",10, TimeUnit.SECONDS);
2、UUID防止误删
- 使用setnx+过期时间实现分布式锁,存在问题:删除不是自己的锁,锁误删
场景:如果业务逻辑的执行时间是7s。执行流程如下
-
index1业务逻辑没执行完,3秒后锁被自动释放。
-
index2获取到锁,执行业务逻辑,3秒后锁被自动释放。
-
index3获取到锁,执行业务逻辑
index1业务逻辑执行完成,开始调用del释放锁,这时释放的是index3的锁, 导致index3的业务只执行1s就被别人释放。
最终等于没锁的情况
//uuid防止误删 @Override public void testLock() { //从redis里面获取数据 String uuid = UUID.randomUUID().toString(); //1 获取当前锁 setnx + 设置过期时间 // Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent("lock", "lock"); Boolean ifAbsent = redisTemplate.opsForValue() .setIfAbsent("lock", uuid,3, TimeUnit.SECONDS); //2 如果获取到锁,从redis获取数据 数据+1 放回redis里面 if(ifAbsent) { //获取锁成功,执行业务代码 //1.先从redis中通过key num获取值 key提前手动设置 num 初始值:0 String value = redisTemplate.opsForValue().get("num"); //2.如果值为空则非法直接返回即可 if (StringUtils.isBlank(value)) { return; } //3.对num值进行自增加一 int num = Integer.parseInt(value); redisTemplate.opsForValue().set("num", String.valueOf(++num)); //出现异常 //3 释放锁 String redisUuid = redisTemplate.opsForValue().get("lock"); if(uuid.equals(redisUuid)) { redisTemplate.delete("lock"); } } else { try { Thread.sleep(100); this.testLock(); } catch (InterruptedException e) { e.printStackTrace(); } } }
3、LUA脚本保证原子性
- 通过uuid防止误删,但是还是存在问题,不具备原子性的
//lua脚本保证原子性 @Override public void testLock() { //从redis里面获取数据 String uuid = UUID.randomUUID().toString(); //1 获取当前锁 setnx + 设置过期时间 // Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent("lock", "lock"); Boolean ifAbsent = redisTemplate.opsForValue() .setIfAbsent("lock", uuid,3, TimeUnit.SECONDS); //2 如果获取到锁,从redis获取数据 数据+1 放回redis里面 if(ifAbsent) { //获取锁成功,执行业务代码 //1.先从redis中通过key num获取值 key提前手动设置 num 初始值:0 String value = redisTemplate.opsForValue().get("num"); //2.如果值为空则非法直接返回即可 if (StringUtils.isBlank(value)) { return; } //3.对num值进行自增加一 int num = Integer.parseInt(value); redisTemplate.opsForValue().set("num", String.valueOf(++num)); //出现异常 //3 释放锁 lua脚本实现 DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(); //lua脚本 String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" + "then\n" + " return redis.call(\"del\",KEYS[1])\n" + "else\n" + " return 0\n" + "end"; redisScript.setScriptText(script); //设置返回结果 redisScript.setResultType(Long.class); redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid); } else { try { Thread.sleep(100); this.testLock(); } catch (InterruptedException e) { e.printStackTrace(); } } }
4 总结
4.1、加锁
// 1. 从Redis中获取锁,set k1 v1 px 20000 nx String uuid = UUID.randomUUID().toString(); Boolean lock = this.redisTemplate.opsForValue() .setIfAbsent("lock", uuid, 2, TimeUnit.SECONDS);
4.2、使用lua释放锁
// 2. 释放锁 del String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; // 设置lua脚本返回的数据类型 DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(); // 设置lua脚本返回类型为Long redisScript.setResultType(Long.class); redisScript.setScriptText(script); redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid);
4.3、重试
Thread.sleep(500); testLock();
为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
第一个:互斥性,在任何时刻,只有一个客户端能持有锁。
第二个:不会发生死锁,即使有一个客户端在获取锁操作时候崩溃了,也能保证其他客户端能获取到锁。
第三个:解铃还须系铃人,解锁加锁必须同一个客户端操作。
第四个:加锁和解锁必须具备原子性
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· 面试官:你是如何进行SQL调优的?