zl程序教程

您现在的位置是:首页 >  大数据

当前栏目

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

分布式分布式 问题 实现 使用 简单 聊聊
2023-06-13 09:19:49 时间
groupId org.springframework.boot /groupId artifactId spring-boot-starter-data-redis /artifactId /dependency

编写RedisConfig类


@Configuration

public 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:使用占位符加锁:

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


@SpringBootTest

class ApplicationTests {

 @Autowired

 private RedisTemplate redisTemplate;

 @Test

 public 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("有线程在用,请稍后在试");

测试
第一个线程出现异常无法释放锁:

之后所有线程都无法访问:

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

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脚本

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

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

锁会被正常删除只留下name:

到此这篇关于使用RedisTemplat实现简单的分布式锁的文章就介绍到这了,更多相关RedisTemplat分布式锁内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!


我想要获取技术服务或软件
服务范围:MySQL、ORACLE、SQLSERVER、MongoDB、PostgreSQL 、程序问题
服务方式:远程服务、电话支持、现场服务,沟通指定方式服务
技术标签:数据恢复、安装配置、数据迁移、集群容灾、异常处理、其它问题

本站部分文章参考或来源于网络,如有侵权请联系站长。
数据库远程运维 聊聊使用RedisTemplat实现简单的分布式锁的问题