Redis学习笔记:分布式锁

        在使用redis加锁的函数:setifAbsent(key,value)时,如果加锁成功,则对当前锁加一个过期时间:expire(key,timeout),而这时会出现一个问题,二者的原子性问题,如果在加上锁之后,服务器宕机了,这时还没有进行加过期时间的操作,这样锁就会永久存在,所以要解决这个问题;

(1)使用setNxEx命令

        使用redis2.1.6以上版本里面的函数 setifAbsent(key,value,timeout),这个函数将加锁和过期时间封装在一起使用:

redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue,20,TimeUnit.SECONDS);

 注意事项:

        当更新redis版本到2.1.6以上版本的时候,如果使用了jedis,在启动的时候可能会报错,提示找不到一个类,原因是因为jedis的版本过低,所以要升级jedis的版本;

jedis、spring-data-redis和Spring的版本匹配表:

Redis学习笔记:分布式锁_第1张图片

 (2)lua脚本

        脚本编写:(首先调用setnx,检测加锁成功之后,则调用expire加过期时间)

    private static final String LOCK_LUA = "if redis.call('setnx', KEYS[1], ARGV[1]) then
 redis.call('expire', KEYS[1], ARGV[2], ARGV[3]) return 'true' else return 'false' end";

        脚本使用:(在redisTemplate传参的时候的参数为:redisScirpt,key,obj,obj....,其中key这个数组在lua脚本里可以用key[1]来使用,而key后面的参数可以为多个,在脚本里用ARGV[1]、ARGV[2]来使用)

public boolean lock(String key,String value,long expireTime) {
    DefaultRedisScript redisScript = new DefaultRedisScript<>(LOCK_LUA, String.class);
    String result = redisTemplate.execute(redisScript, Collections.singletonList(key), value, expireTime, TimeUnit.SECONDS);
    if (Boolean.parseBoolean(result)) {
        return true;
    }
    return false;
}

        当然也可以直接使用脚本文件:

// 执行 lua 脚本
    DefaultRedisScript redisScript = new DefaultRedisScript<>();
// 指定 lua 脚本
    redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/SetKey.lua")));
// 指定返回类型
    redisScript.setResultType(Long.class);
// 参数一:redisScript,参数二:key列表,参数三:arg(可多个)
    Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey));

 (3)事务?

        使用multi()和exec(),其中multi表示事务的开始,这时程序会将后面的代码加到一个事务里,当碰到exec函数的时候,释放执行;

        注意:但是redis并没有完全意义上的事务,事务中的方法在执行的时候,如果执行到一半,也就是加锁成功了之后,服务器宕机了,这样后面的加过期时间并不会执行,并且redis并不支持回滚,所以并不能算原子性的操作。 

    private Boolean setLock(RecordEventModel event) {
        String lockKey = event.getModel() + ":" + event.getAction() + ":" + event.getId() + ":" + event.getMessage_id();
        log.info("lockKey : {}" , lockKey);
        SessionCallback sessionCallback = new SessionCallback() {
            List exec = null;
            @Override
            @SuppressWarnings("unchecked")
            public Boolean execute(RedisOperations operations) throws DataAccessException {
                operations.multi();
                stringRedisTemplate.opsForValue().setIfAbsent(lockKey,JSON.toJSONString(event));
                stringRedisTemplate.expire(lockKey,Constants.REDIS_KEY_EXPIRE_SECOND_1_HOUR, TimeUnit.SECONDS);
                exec = operations.exec();
                if(exec.size() > 0) {
                    return (Boolean) exec.get(0);
                }
                return false;
            }
        };
        return stringRedisTemplate.execute(sessionCallback);
    } 
  

                            
                        
                    
                    
                    

你可能感兴趣的:(Redis,redis,分布式,java)