使用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
基于Redis延迟队列的实现代码
May 13 Redis
Windows中Redis安装配置流程并实现远程访问功能
Jun 07 Redis
redis实现的四种常见限流策略
Jun 18 Redis
Redis 彻底禁用RDB持久化操作
Jul 09 Redis
解析redis hash应用场景和常用命令
Aug 04 Redis
Redis 常见使用场景
Aug 30 Redis
基于Redis的List实现特价商品列表功能
Aug 30 Redis
详解Redis在SpringBoot工程中的综合应用
Oct 16 Redis
聊聊redis-dump工具安装问题
Jan 18 Redis
在Centos 8.0中安装Redis服务器的教程详解
Mar 21 Redis
浅谈Redis的事件驱动模型
May 30 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+Ajax异步通讯实现用户名邮箱验证是否已注册( 2种方法实现)
2011/12/28 PHP
PHP register_shutdown_function函数的深入解析
2013/06/03 PHP
php启用zlib压缩文件的配置方法
2013/06/12 PHP
PHP错误机制知识汇总
2016/03/24 PHP
php二维数组按某个键值排序的实例讲解
2019/02/15 PHP
jQuery+CSS 半开折叠效果原理及代码(自写)
2013/03/04 Javascript
jquery关于事件冒泡和事件委托的技巧及阻止与允许事件冒泡的三种实现方法
2015/11/27 Javascript
Node.js检测端口(port)是否被占用的简单示例
2016/09/29 Javascript
深入理解Node.js中通用基础设计模式
2017/09/19 Javascript
Vue路由钩子之afterEach beforeEach的区别详解
2018/07/15 Javascript
详解微信小程序canvas圆角矩形的绘制的方法
2018/08/22 Javascript
详解JavaScript之ES5的继承
2020/07/08 Javascript
[37:23]DOTA2上海特级锦标赛主赛事日 - 3 胜者组第二轮#2Secret VS EG第二局
2016/03/04 DOTA
python使用内存zipfile对象在内存中打包文件示例
2014/04/30 Python
Python实现遍历数据库并获取key的值
2015/05/17 Python
python抓取需要扫微信登陆页面
2019/04/29 Python
python对指定字符串逆序的6种方法(小结)
2020/04/02 Python
Python如何自动获取目标网站最新通知
2020/06/18 Python
学习Python爬虫的几点建议
2020/08/05 Python
详解Python中Pyyaml模块的使用
2020/10/08 Python
CSS3 3D位移translate效果实例介绍
2016/05/03 HTML / CSS
岳父生日宴会答谢词
2014/01/13 职场文书
简短大学毕业感言
2014/01/18 职场文书
房地产营销策划方案
2014/02/08 职场文书
餐饮部总监岗位职责范文
2014/02/13 职场文书
环保建议书作文
2014/03/12 职场文书
解除合同协议书
2014/04/17 职场文书
2014年安全生产目标责任书
2014/07/23 职场文书
2014乡镇机关党员个人对照检查材料思想汇报
2014/10/09 职场文书
公司行政主管岗位职责
2015/04/09 职场文书
幼师辞职信范文大全
2015/05/12 职场文书
党支部培养考察意见
2015/06/02 职场文书
致我们终将逝去的青春观后感
2015/06/10 职场文书
公司宣传语大全
2015/07/13 职场文书
golang interface判断为空nil的实现代码
2021/04/24 Golang
Windows环境下实现批量执行Sql文件
2021/10/05 SQL Server