十步学习 Redis 分布式锁
文章目录
- 1. 单机版没有加锁
- 2. 单机版加锁
- 3. 引入 Redis 分布式锁
- 4. 加锁 解锁,lock/unlock 必须同时出现并保证调用
- 5. 加入锁过期时间
- 6. 加锁且携带锁过期时间 原子性
- 7. 删除自己的锁
- 8.1 Redis 自身事务
- 8.2 Lua 脚本方式
- 9.1 引入 Redisson
- 9.2 Redisson 超高并发解决方案 (推荐)
1. 单机版没有加锁
/*** @author Mr.superbeyone**/
public class RedisLockTest {@AutowiredStringRedisTemplate stringRedisTemplate;private final Lock lock = new ReentrantLock();/*** 单机版 没有加锁,出现超卖现象*/public void test1() {String key = "key";String countVal = stringRedisTemplate.opsForValue().get(key);int count = countVal == null ? 0 : Integer.parseInt(countVal);if (count > 0) {int num = count - 1;stringRedisTemplate.opsForValue().set(key, String.valueOf(num));}}}
问题:出现超卖现象
2. 单机版加锁
/*** @author Mr.superbeyone**/
public class RedisLockTest {@AutowiredStringRedisTemplate stringRedisTemplate;private final Lock lock = new ReentrantLock();/*** synchronized 不见不散*/public void test2() {synchronized (this) {String key = "key";String countVal = stringRedisTemplate.opsForValue().get(key);int count = countVal == null ? 0 : Integer.parseInt(countVal);if (count > 0) {int num = count - 1;stringRedisTemplate.opsForValue().set(key, String.valueOf(num));}}}/*** lock 过期不候*/public void test3() throws InterruptedException {String key = "key";if (lock.tryLock(20, TimeUnit.MILLISECONDS)) {try {String countVal = stringRedisTemplate.opsForValue().get(key);int count = countVal == null ? 0 : Integer.parseInt(countVal);if (count > 0) {int num = count - 1;stringRedisTemplate.opsForValue().set(key, String.valueOf(num));}} finally {lock.unlock();}} else {//没有拿到锁的业务逻辑}}
}
问题:分布式部署后,单机锁还是会出现超卖现象,需要分布式锁
3. 引入 Redis 分布式锁
public class RedisLockTest {@AutowiredStringRedisTemplate stringRedisTemplate;private static final String REDIS_LOCK = "redis_lock";/*** 引入 Redis 分布式锁*/public void test4() {String value = UUID.randomUUID().toString() + Thread.currentThread().getName();//加锁Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);//相当于 setNXif (!flag) {//获取锁失败return;}String key = "key";String countVal = stringRedisTemplate.opsForValue().get(key);int count = countVal == null ? 0 : Integer.parseInt(countVal);if (count > 0) {int num = count - 1;stringRedisTemplate.opsForValue().set(key, String.valueOf(num));//解锁stringRedisTemplate.delete(REDIS_LOCK);}}
}
问题 :有可能不能释放锁
4. 加锁 解锁,lock/unlock 必须同时出现并保证调用
public class RedisLockTest {@AutowiredStringRedisTemplate stringRedisTemplate;private static final String REDIS_LOCK = "redis_lock";/*** 引入 Redis 分布式锁* 加锁 解锁,lock/unlock 必须同时出现并保证调用*/public void test5() {String value = UUID.randomUUID().toString() + Thread.currentThread().getName();try {//加锁Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);//相当于 setNXif (!flag) {//获取锁失败return;}String key = "key";String countVal = stringRedisTemplate.opsForValue().get(key);int count = countVal == null ? 0 : Integer.parseInt(countVal);if (count > 0) {int num = count - 1;stringRedisTemplate.opsForValue().set(key, String.valueOf(num));}} finally {//解锁stringRedisTemplate.delete(REDIS_LOCK);}}
}
问题:机器宕机,不能保证最后的解锁
5. 加入锁过期时间
public class RedisLockTest {@AutowiredStringRedisTemplate stringRedisTemplate;private static final String REDIS_LOCK = "redis_lock";/*** 引入 Redis 分布式锁* 加锁 解锁,lock/unlock 必须同时出现并保证调用* 锁 自动删除*/public void test6() {String value = UUID.randomUUID().toString() + Thread.currentThread().getName();try {//加锁Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);//相当于 setNXstringRedisTemplate.expire(REDIS_LOCK, 10L, TimeUnit.SECONDS);if (!flag) {//获取锁失败return;}String key = "key";String countVal = stringRedisTemplate.opsForValue().get(key);int count = countVal == null ? 0 : Integer.parseInt(countVal);if (count > 0) {int num = count - 1;stringRedisTemplate.opsForValue().set(key, String.valueOf(num));}} finally {//解锁stringRedisTemplate.delete(REDIS_LOCK);}}
}
问题:加锁和设置过期时间 没有保证原子性
6. 加锁且携带锁过期时间 原子性
public class RedisLockTest {@AutowiredStringRedisTemplate stringRedisTemplate;private static final String REDIS_LOCK = "redis_lock";/*** 引入 Redis 分布式锁* 加锁 解锁,lock/unlock 必须同时出现并保证调用* 锁 自动删除 原子性操作*/public void test7() {String value = UUID.randomUUID().toString() + Thread.currentThread().getName();try {//加锁Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);//相当于 setNXif (!flag) {//获取锁失败return;}String key = "key";String countVal = stringRedisTemplate.opsForValue().get(key);int count = countVal == null ? 0 : Integer.parseInt(countVal);if (count > 0) {int num = count - 1;stringRedisTemplate.opsForValue().set(key, String.valueOf(num));}} finally {//解锁stringRedisTemplate.delete(REDIS_LOCK);}}
}
问题:处理业务的时间大于设置的锁过期时间,线程B可能删除线程A的锁,删除别人的锁
7. 删除自己的锁
public class RedisLockTest {@AutowiredStringRedisTemplate stringRedisTemplate;private static final String REDIS_LOCK = "redis_lock";/*** 引入 Redis 分布式锁* 加锁 解锁,lock/unlock 必须同时出现并保证调用* 锁 自动删除 原子性操作* 删除自己的锁*/public void test8() {String value = UUID.randomUUID().toString() + Thread.currentThread().getName();try {//加锁Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);//相当于 setNXif (!flag) {//获取锁失败return;}String key = "key";String countVal = stringRedisTemplate.opsForValue().get(key);int count = countVal == null ? 0 : Integer.parseInt(countVal);if (count > 0) {int num = count - 1;stringRedisTemplate.opsForValue().set(key, String.valueOf(num));}} finally {//解锁//删除自己的锁if(stringRedisTemplate.opsForValue().get(REDIS_LOCK).equals(value)) {stringRedisTemplate.delete(REDIS_LOCK);}}}
}
问题:删除自己的锁判断 与 删除操作 不是原子性 ,可能判断加锁与解锁的不是同一个客户端,有可能会出现误解锁
可以使用 Lua 脚本 解决
if redis.call("get",KEYS[1]) == ARGV[1]
thenreturn redis.call("del",KEYS[1])
elsereturn 0
end
8.1 Redis 自身事务
public class RedisLockTest {@AutowiredStringRedisTemplate stringRedisTemplate;private static final String REDIS_LOCK = "redis_lock";/*** 引入 Redis 分布式锁* 加锁 解锁,lock/unlock 必须同时出现并保证调用* 锁 自动删除 原子性操作* 删除自己的锁* 删除自己的锁判断 与 删除操作 原子性* Redis 自身事务*/public void test9() {String value = UUID.randomUUID().toString() + Thread.currentThread().getName();try {//加锁Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);//相当于 setNXif (!flag) {//获取锁失败return;}String key = "key";String countVal = stringRedisTemplate.opsForValue().get(key);int count = countVal == null ? 0 : Integer.parseInt(countVal);if (count > 0) {int num = count - 1;stringRedisTemplate.opsForValue().set(key, String.valueOf(num));}} finally {//解锁//删除自己的锁//加入 Redis 事务while (true) {stringRedisTemplate.watch(REDIS_LOCK);if (stringRedisTemplate.opsForValue().get(REDIS_LOCK).equals(value)) {stringRedisTemplate.setEnableTransactionSupport(true);stringRedisTemplate.multi();stringRedisTemplate.delete(REDIS_LOCK);List<Object> list = stringRedisTemplate.exec();if (list == null) {continue;}}stringRedisTemplate.unwatch();//删除锁成功break;}}}
}
问题:确保 Redis 过期时间大于业务执行时间的问题,Redis 分布式锁如何续期?
8.2 Lua 脚本方式
public class RedisLockTest {@AutowiredStringRedisTemplate stringRedisTemplate;private static final String REDIS_LOCK = "redis_lock";/*** 引入 Redis 分布式锁* 加锁 解锁,lock/unlock 必须同时出现并保证调用* 锁 自动删除 原子性操作* 删除自己的锁* 删除自己的锁判断 与 删除操作 原子性* Lua 脚本*/public void test10() {String value = UUID.randomUUID().toString() + Thread.currentThread().getName();try {//加锁Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);//相当于 setNXif (!flag) {//获取锁失败return;}String key = "key";String countVal = stringRedisTemplate.opsForValue().get(key);int count = countVal == null ? 0 : Integer.parseInt(countVal);if (count > 0) {int num = count - 1;stringRedisTemplate.opsForValue().set(key, String.valueOf(num));}} finally {//解锁//删除自己的锁Jedis jedis = null; // 从连接池中 获取 jedisString script = "if redis.call('get',KEYS[1]) == ARGV[1]" +"then" +" return redis.call('del',KEYS[1])" +"else" +" return 0" +"end";try {Object eval = jedis.eval(script, Collections.singletonList(REDIS_LOCK), Collections.singletonList(value));if ("1".equals(eval.toString())) {//删除锁成功} else {//删除锁失败}} finally {if (null != jedis) {jedis.close();}}}}
}
问题:确保 Redis 过期时间大于业务执行时间的问题,Redis 分布式锁如何续期?
9.1 引入 Redisson
<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.14.1</version>
</dependency>
public class RedisConfig {@Beanpublic Redisson redisson() {Config config = new Config();config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);return (Redisson) Redisson.create(config);}
}
public class RedisLockTest {@AutowiredStringRedisTemplate stringRedisTemplate;private static final String REDIS_LOCK = "redis_lock";@AutowiredRedisson redisson;/*** 引入 Redis 分布式锁* 加锁 解锁,lock/unlock 必须同时出现并保证调用* 锁 自动删除 原子性操作* 删除自己的锁* Redisson*/public void test11() {//加锁RLock redissonLock = redisson.getLock(REDIS_LOCK);try {redissonLock.lock();String key = "key";String countVal = stringRedisTemplate.opsForValue().get(key);int count = countVal == null ? 0 : Integer.parseInt(countVal);if (count > 0) {int num = count - 1;stringRedisTemplate.opsForValue().set(key, String.valueOf(num));}} finally {//解锁redissonLock.unlock();}}}
问题:超高并发情况下,可能出现 IllegalMonitorStateException: attempt to unlock, no locked by current thread by node id …
9.2 Redisson 超高并发解决方案 (推荐)
/*** 引入 Redis 分布式锁* 加锁 解锁,lock/unlock 必须同时出现并保证调用* 锁 自动删除 原子性操作* 删除自己的锁* Redisson*/public void test12() {//加锁RLock redissonLock = redisson.getLock(REDIS_LOCK);try {redissonLock.lock();String key = "key";String countVal = stringRedisTemplate.opsForValue().get(key);int count = countVal == null ? 0 : Integer.parseInt(countVal);if (count > 0) {int num = count - 1;stringRedisTemplate.opsForValue().set(key, String.valueOf(num));}} finally {//解锁if (redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()) {redissonLock.unlock(); }}}
Mr.superbeyone
十步学习 Redis 分布式锁相关推荐
- 快来学习Redis 分布式锁的背后原理
以前在学校做小项目的时候,用到Redis,基本也只是用来当作缓存.可阿粉在工作中发现,Redis在生产中并不只是当作缓存这么简单.在阿粉接触到的项目中,Redis起到了一个分布式锁的作用,具体情况是这 ...
- 还不知道 Redis 分布式锁的背后原理?还不赶快学习一下
前言 以前在学校做小项目的时候,用到Redis,基本也只是用来当作缓存.可阿粉在工作中发现,Redis在生产中并不只是当作缓存这么简单.在阿粉接触到的项目中,Redis起到了一个分布式锁的作用,具体情 ...
- 关于分布式锁原理的一些学习与思考:redis分布式锁,zookeeper分布式锁
点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:牛人 20000 字的 Spring Cloud 总结,太硬核了~ 作者:队长给我球. 出处:https://w ...
- zookeeper 分布式锁_关于redis分布式锁,zookeeper分布式锁原理的一些学习与思考
编辑:业余草来源:https://www.xttblog.com/?p=4946 首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法 ...
- redis cluster 分布式锁_关于分布式锁原理的一些学习与思考redis分布式锁,zookeeper分布式锁...
首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...
- 关于redis分布式锁,zookeeper分布式锁原理的一些学习与思考
编辑:业余草 来源:https://www.xttblog.com/?p=4946 首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方 ...
- 【使用Redis分布式锁实现优惠券秒杀功能】-Redis学习笔记05
前言 本章节主要实现限时.限量优惠券秒杀功能,并利用分布式锁解决<超卖问题>.<一人一单问题>. 一.优惠券下单基本功能实现 1.功能介绍及流程图 2.代码实现 @Resour ...
- getset原子性 redis_一文看透 Redis 分布式锁进化史(解读 + 缺陷分析)
各个版本的Redis分布式锁 V1.0 V1.1 基于[GETSET] V2.0 基于[SETNX] V3.0 V3.1 分布式Redis锁:Redlock 总结 <Netty 实现原理与源码解 ...
- 一个项目部署多个节点会导致锁失效么_一文看透 Redis 分布式锁进化史(解读 + 缺陷分析)...
各个版本的Redis分布式锁 V1.0 V1.1 基于[GETSET] V2.0 基于[SETNX] V3.0 V3.1 分布式Redis锁:Redlock 总结 <Netty 实现原理与源码解 ...
最新文章
- 关于numpy中eye和identity的区别详解
- 20155328 《信息安全系统设计基础》 课程总结
- SilverLight4:在MVVM架构下实现模式窗口
- react(83)--filter
- mysql数据库序列作用_MySQL 序列使用
- 类间关系有很多种 UML
- C语言中如何将小数或整数和字符串合二为一
- 大数据最核心的关键技术:32个算法
- 五千的手机和两三千的手机使用起来有什么不一样?有必要买贵的吗?
- 上周末Jscex项目介绍的幻灯片
- Git学习笔记--廖雪峰官网教程
- freeswitch安装
- 三星s8 android9.0官方rom,三星S8+港版安卓9官方固件rom刷机包:TGY-G9550ZHU3DSD3
- 【ZYNQ】IP核_DDR4_SDRAM(MIG)的详细介绍
- ORAN C平面 Section Type 0
- 国内技术管理人员批阅google的“春运交通图”项目
- 问卷调查 批量模拟真人填写 爬虫 实战
- [洛谷 P4084 USACO17DEC] Barn Painting G (树形dp经典)
- java设计九宫格拼图软件哪个好用_九宫格拼图软件下载_抖音很火的九宫格拼图软件app下载_易玩网...
- 华大半导体HC32F4A0笔记(一),PWM输入捕获,使用TIM6
热门文章
- TensorFlow训练参数存为npy格式并调用——线性回归
- 帮我写一个拦截APP广告的程序
- 新标准日本语中级下(第6单元)笔记
- 【python】matplotlib画小猪
- Glide源码解析之山清水秀疑无路(一)
- Halcon学习:calibrate_hand_eye_scara_stationary_cam_approx
- 2021年新版电影小程序商业版+前端(含教程、采集)
- Hadoop组件搭建-Hadoop HA高可用
- 全国31个省市自治区交通事故数、死亡人数等公开数据(1998-2017年)
- 如何看懂Apache Pulsar?(究极缝合)