使用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五大数据结构和使用场景
Apr 12 Redis
Redis IP地址的绑定的实现
May 08 Redis
浅谈redis缓存在项目中的使用
May 20 Redis
Redis缓存-序列化对象存储乱码问题的解决
Jun 21 Redis
Redis读写分离搭建的完整步骤
Sep 14 Redis
详解Redis在SpringBoot工程中的综合应用
Oct 16 Redis
redis中lua脚本使用教程
Nov 01 Redis
Redis模仿手机验证码发送的实现示例
Nov 02 Redis
Redis命令处理过程源码解析
Feb 12 Redis
面试分析分布式架构Redis热点key大Value解决方案
Mar 13 Redis
redis复制有可能碰到的问题汇总
Apr 03 Redis
Redis基本数据类型哈希Hash常用操作命令
Jun 01 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
win7计划任务定时执行PHP脚本设置图解
2014/05/09 PHP
PHP YII框架开发小技巧之模型(models)中rules自定义验证规则
2015/11/16 PHP
Yii使用migrate命令执行sql语句的方法
2016/03/15 PHP
Windows平台实现PHP连接SQL Server2008的方法
2017/07/26 PHP
搜索附近的人PHP实现代码
2018/02/11 PHP
js中opener与parent的区别详细解析
2014/01/14 Javascript
绑定回车enter事件代码
2014/05/18 Javascript
jQuery实现有动画淡出效果的二级折叠菜单代码
2015/10/17 Javascript
Bootstrap表单控件使用方法详解
2017/01/11 Javascript
JS回调函数基本定义与用法实例分析
2017/05/24 Javascript
Require.JS中的几种define定义方式示例
2017/06/01 Javascript
JavaScript条件判断_动力节点Java学院整理
2017/06/26 Javascript
原生JavaScrpit中异步请求Ajax实现方法
2017/11/03 Javascript
使用Vue写一个datepicker的示例
2018/01/27 Javascript
详解基于webpack&amp;gettext的前端多语言方案
2019/01/29 Javascript
vue19 组建 Vue.extend component、组件模版、动态组件 的实例代码
2019/04/04 Javascript
JS实现点击发送验证码 xx秒后重新发送功能
2019/07/30 Javascript
微信小程序 可搜索的地址选择实现详解
2019/08/28 Javascript
浅谈vue的第一个commit分析
2020/06/08 Javascript
如何手动实现一个 JavaScript 模块执行器
2020/10/16 Javascript
js实现随机点名
2021/01/19 Javascript
python实现根据月份和日期得到星座的方法
2015/03/27 Python
python魔法方法-属性转换和类的表示详解
2016/07/22 Python
Python中__init__.py文件的作用详解
2016/09/18 Python
Python基础练习之用户登录实现代码分享
2017/11/08 Python
flask + pymysql操作Mysql数据库的实例
2017/11/13 Python
widows下安装pycurl并利用pycurl请求https地址的方法
2018/10/15 Python
Python-while 计算100以内奇数和的方法
2019/06/11 Python
对Python强大的可变参数传递机制详解
2019/06/13 Python
python输出电脑上所有的串口名的方法
2019/07/02 Python
Pandas时间序列重采样(resample)方法中closed、label的作用详解
2019/12/10 Python
pytorch获取模型某一层参数名及参数值方式
2019/12/30 Python
竞聘书模板
2014/03/31 职场文书
初中学校军训方案
2014/05/09 职场文书
初中班级口号
2014/06/09 职场文书
学习党的群众路线对照检查材料
2014/09/29 职场文书