因为redis是单线程程序,可以天然保证线程安全,只要我们的命令是单条命令,就可以保证操作的安全性,而redis中提供了setnx key value命令,表示当redis中没有key 的键值对时,就会创建这个键值对的值,如果已经有了,就不操作,java中有valueOperations.setIfAbsent(key,value)与redis中的setnx是一样的作用。我们可以使用valueOperations.setIfAbsent实现分布式加锁的操作,也可以给锁设置过期时间,这样就算客户端故障也能在过期时间之后释放锁
Boolean isLock=valueOperations.setIfAbsent("k1",value,120,TimeUnit.SECONDS);
key是任意值,value保证每个使用者唯一性即可,便于锁的释放,因为我们在释放锁也就是del key时,需要保证只有真正获得锁的程序才能去释放对应的锁,而不是谁都可以进行锁的释放,防止自己获得锁被别人释放了,所以我们释放锁的步骤就是:
- 先判断锁变量的值,是否是自己获得锁
- 如果是自己获取的锁,就可以进行锁的释放
- 如果不是自己获取的锁,就无法进行锁的释放
这就不是一个命令了,但是我们需要这些命令原子性执行,所以就用到了lua脚本。Redis对lua脚本的调用是原子性的,所以一些特殊场景,可以放在lua中实现,这里记录下来一个简单的例子
首先在resources目录下创建一个lock.lua文件
if redis.call("get",KEYS[1])==ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
当输入的key[1]在redis中等于输入的argv[1]时,删除这个原有的key,即释放锁,不等则无法释放锁 key[1]:获取锁时输入的key,argv[1]:获取锁时输入的value
@Test
public void testLock03() {
ValueOperations valueOperations=redisTemplate.opsForValue();
String value= UUID.randomUUID().toString();
//如果key为空,就设置值
Boolean isLock=valueOperations.setIfAbsent("k1",value,120,TimeUnit.SECONDS);
if(isLock) {
valueOperations.set("name","xxxx");
String name=(String)valueOperations.get("name");
System.out.println("name="+name);
System.out.println(valueOperations.get("k1"));
Boolean result=(Boolean)redisTemplate.execute(script, Collections.singletonList("k1"),value);
System.out.println(result);
}
else {
System.out.println("有线程在使用,请稍后");
}
}
其中singletonList是一个不可变list,且只有一个元素,就是从redis拿k1对应的value,看与方法传进去的value一样不一样,一样的话就是直接key(释放锁)。
redisConfig类
//将信息序列化,在redis中看起来好看
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
//key的序列化
redisTemplate.setKeySerializer(new StringRedisSerializer());
//value的序列化
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
//hash类型key的序列化
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//hash类型value的序列化
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
//注入连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
@Bean
public DefaultRedisScript<Boolean> script() {
DefaultRedisScript<Boolean> redisScript=new DefaultRedisScript<>();
redisScript.setLocation(new ClassPathResource("lock.lua"));
redisScript.setResultType(Boolean.class);
return redisScript;
}
}
但是使用分布式锁可能存在锁过期释放,业务没执行完的问题。针对这个问题,可以开启一个守护线程,每隔一段时间就检查锁是否还存在,如果存在则对锁的过期时间延长,防止锁过期提前释放。开源框架Redisson就解决了这个问题。只要线程一加锁成功,就会启动一个看门狗,他是一个后台线程,会每隔十秒检查一下,如果线程还持有锁,就会不断延长key的生存时间。所以Redisson就是解决锁过期释放,业务还没执行完的问题。