使用RedisTemplat实现简单的分布式锁


Posted in Redis onNovember 20, 2021

不使用redisson框架实现Redis分布式锁 准备工作:

导入依赖

<dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-data-redis</artifactId></dependency>

编写RedisConfig类

@Configurationpublic class RedisConfig {    @Bean    public RedisTemplate<String , Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();        //String类型 key序列器        redisTemplate.setKeySerializer(new StringRedisSerializer());        //String类型 value序列器        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());        //Hash类型 key序列器        redisTemplate.setHashKeySerializer(new StringRedisSerializer());        //Hash类型 value序列器        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());        //将连接工厂注入        redisTemplate.setConnectionFactory(redisConnectionFactory);        return redisTemplate;    } }

1.在SpringBootTest中编写测试模块 1.1:使用占位符加锁:

占位符加锁问题:出现异常时无法释放锁,导致后继进入的线程成为死锁

@SpringBootTestclass ApplicationTests {    @Autowired    private RedisTemplate redisTemplate;@Testpublic void lodsTest01(){ValueOperations valueOperations = redisTemplate.opsForValue();        //创建一个占位符,如果key不存在才可以设置成功        Boolean isLock = valueOperations.setIfAbsent("k1", "v1");        //如果占位成功,进行正常操作        if (isLock){        //设置一个name存到redis            valueOperations.set("name","xxxx");            //从redis取出name            String name = (String) valueOperations.get("name");            System.out.println("name = " + name);            //手动制造异常            Integer.parseInt("xxxx");            //操作结束删除锁            redisTemplate.delete("k1");        }else{            System.out.println("有线程在用,请稍后在试");        }}}

测试
第一个线程出现异常无法释放锁:
使用RedisTemplat实现简单的分布式锁
之后所有线程都无法访问:
使用RedisTemplat实现简单的分布式锁

解决方案为锁加一个有效时间。

1.2:使用占位符设置有效时间解决死锁问题:

占位符设置有效时间问题即使某线程出现异常,但占位符过了有效时间,锁就会释放。但是在大量线程同时访问时,如果线程1被外界因素影响(网络波动,服务器出问题等等),线程1的业务还没完成,但锁的有效时间到了的话,下一个线程就会进来,就会出现线程不安全的情况,出现线程互相删锁的情况。

@Test    public void testLock02()  {        ValueOperations valueOperations = redisTemplate.opsForValue();        //如果key不存在才可以设置成功,设置一个有效时间防止线程异常出现死锁        Boolean isLock = valueOperations.setIfAbsent("k1", "v1",5, TimeUnit.SECONDS);        //如果占位成功,进行正常操作        if (isLock){        //设置一个name存到redis            valueOperations.set("name","xxxx");            //从redis取出name            String str = (String) valueOperations.get("name");            System.out.println("name = " + str);            //制造异常            Integer.parseInt("xxxx");            //操作结束删除锁            redisTemplate.delete("k1");        }else{            System.out.println("有线程在用,请稍后在试");        }    }

解决方案: 使用lua脚本,给每个锁的key对应的value设置一个随机数

1.3:使用lua脚本解决线程不安全问题:

lua脚本可以写在Redis服务器上:
优点: 在服务器上运行速度快

缺点: 修改代码时比较麻烦

lua脚本可以通过java发送
优点: 修改代码方便

缺点: 每次发送请求时都需要占用网络资源

1.3.1:编写lua脚本 使用RedisTemplat实现简单的分布式锁

if redis.call("get",KEYS[1])==ARGV[1] then    return redis.call("del",KEYS[1])else    return 0end

1.3.2:修改ReidsConfig类

@Bean    public DefaultRedisScript<Boolean> defaultRedisScript(){        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();        //lock.lua脚本位置和application.yml同级目录        redisScript.setLocation(new ClassPathResource("lock.lua"));        //设置类型为boolean        redisScript.setResultType(Boolean.class);        return redisScript;    }

1.3.3:编写测试模块

@Test    public void testLock03(){        ValueOperations valueOperations = redisTemplate.opsForValue();        String value = UUID.randomUUID().toString();        //如果key不存在才可以设置成功,设置一个value为随机数的值,防止出现线程太多 导致线程不安全        Boolean isLock = valueOperations.setIfAbsent("k1", value, 5, TimeUnit.SECONDS);        //如果占位成功,进行正常操作         if (isLock){        //设置一个name存到redis            valueOperations.set("name","xxxx");            //从redis取出name            String name = (String) valueOperations.get("name");            System.out.println("name = " + name);            //为redis发送lua脚本删除锁对应的value            Boolean aBoolean = (Boolean) redisTemplate.execute(redisScript, Collections.singletonList("k1"), value);            System.out.println(aBoolean);        }else{            System.out.println("有线程在用,请稍后在试");        }    }

测试结果:
顺利把name值存到redis中并把锁删除并返回true
使用RedisTemplat实现简单的分布式锁
锁会被正常删除只留下name:
使用RedisTemplat实现简单的分布式锁

Redis 相关文章推荐
基于Redis过期事件实现订单超时取消
May 08 Redis
Windows下redis下载、redis安装及使用教程
Jun 02 Redis
你真的了解redis为什么要提供pipeline功能
Jun 22 Redis
redis不能访问本机真实ip地址的解决方案
Jul 07 Redis
Redis做数据持久化的解决方案及底层原理
Jul 15 Redis
springboot使用Redis作缓存使用入门教程
Jul 25 Redis
在项目中使用redis做缓存的一些思路
Sep 14 Redis
linux下安装redis图文详细步骤
Dec 04 Redis
解决linux下redis数据库overcommit_memory问题
Feb 24 Redis
Redis之RedisTemplate配置方式(序列和反序列化)
Mar 13 Redis
Grafana可视化监控系统结合SpringBoot使用
Apr 19 Redis
redis缓存存储Session原理机制
CentOS8.4安装Redis6.2.6的详细过程
SpringBoot整合Redis入门之缓存数据的方法
Nov 17 #Redis
Window server中安装Redis的超详细教程
关于SpringBoot 使用 Redis 分布式锁解决并发问题
Redis Stream类型的使用详解
Redis 持久化 RDB 与 AOF的执行过程
You might like
如何解决PHP无法实现多线程的问题
2015/09/25 PHP
PHP单例模式数据库连接类与页面静态化实现方法
2019/03/20 PHP
通过JAVASCRIPT读取ASP设定的COOKIE
2006/11/24 Javascript
jquery 双色表格实现代码
2009/12/08 Javascript
js实现分享到随页面滚动而滑动效果的方法
2015/04/10 Javascript
ajax请求data遇到的问题分析
2018/01/18 Javascript
vue-loader中引入模板预处理器的实现
2019/09/04 Javascript
基于vue实现图片验证码倒计时60s功能
2019/12/10 Javascript
js实现网页随机验证码
2020/10/19 Javascript
javascript中layim之查找好友查找群组
2021/02/06 Javascript
javascript实现简单页面倒计时
2021/03/02 Javascript
[03:00]《DAC最前线》之欧美新秀VS老将
2015/02/01 DOTA
Python与shell的3种交互方式介绍
2015/04/11 Python
Python黑帽编程 3.4 跨越VLAN详解
2016/09/28 Python
python Flask实现restful api service
2017/12/04 Python
Python使用正则表达式获取网页中所需要的信息
2018/01/29 Python
Odoo中如何生成唯一不重复的序列号详解
2018/02/10 Python
django 连接数据库 sqlite的例子
2019/08/14 Python
对YOLOv3模型调用时候的python接口详解
2019/08/26 Python
Python 使用 Pillow 模块给图片添加文字水印的方法
2019/08/30 Python
pytorch中的transforms模块实例详解
2019/12/31 Python
Python3.7.0 Shell添加清屏快捷键的实现示例
2020/03/23 Python
Python字符串对齐、删除字符串不需要的内容以及格式化打印字符
2021/01/23 Python
5分钟弄清楚html5的drag and drop(小结)
2019/04/10 HTML / CSS
Stefania Mode美国:奢华设计师和时尚服装
2018/01/07 全球购物
美国领先的机场停车聚合商:Airport Parking Reservations
2020/02/28 全球购物
如何在.net Winform里面显示PDF文档
2012/09/11 面试题
UNIX文件系统常用命令
2012/05/25 面试题
2014红色之旅心得体会
2014/10/07 职场文书
幼儿园见习报告范文
2014/10/30 职场文书
学生通报表扬范文
2015/05/04 职场文书
感恩父母主题班会
2015/08/12 职场文书
医务人员岗前培训心得体会
2016/01/08 职场文书
2016年秋季趣味运动会开幕词
2016/03/04 职场文书
python获取带有返回值的多线程
2022/05/02 Python
Zabbix6通过ODBC方式监控Oracle 19C的详细过程
2022/09/23 Servers