利用redis实现分布式锁(单个redis)

  • Post author:
  • Post category:其他


因为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时,需要保证只有真正获得锁的程序才能去释放对应的锁,而不是谁都可以进行锁的释放,防止自己获得锁被别人释放了,所以我们释放锁的步骤就是:

  1. 先判断锁变量的值,是否是自己获得锁
  2. 如果是自己获取的锁,就可以进行锁的释放
  3. 如果不是自己获取的锁,就无法进行锁的释放

这就不是一个命令了,但是我们需要这些命令原子性执行,所以就用到了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就是解决锁过期释放,业务还没执行完的问题。



版权声明:本文为weixin_51656756原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。