Redis锁的使用
起因:分布式环境下需对并发进行逻辑一致性控制
架构:springboot2、Redis
IDEA实操
-
先新建RedisLock组件
注:释放锁使用lua脚本保持原子性@Component @Slf4j public class RedisLock { private final RedisTemplate redisTemplate; public RedisLock(RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } /** * 如果已经存在返回false,否则返回true * * @param key * @param value * @return */ public Boolean setNx(String key, String value, Long expireTime, TimeUnit mimeUnit) { if (key == null || value == null) { return false; } // 在spiring boot 2 可以直接使用 redisTemplate的setIfAbsent设置key-value和过期时间,是原子性 Boolean tf = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, mimeUnit); return tf; } /** * 获取数据 * * @param key * @return */ public Object get(String key) { if (key == null) { return null; } return redisTemplate.opsForValue().get(key); } /** * 删除 * * @param key * @return */ public void remove(Object key) { if (key == null) { return; } redisTemplate.delete(key); } /** * 加锁 * * @param key key * @param waitTime 等待时间,在这个时间内会多次尝试获取锁,超过这个时间还没获得锁,就返回false * @param interval 间隔时间,每隔多长时间尝试一次获的锁 * @param expireTime key的过期时间 */ public Boolean lock(String key, Long waitTime, Long interval, Long expireTime) { String value = UUID.randomUUID().toString().replaceAll("-", "").toLowerCase(); Boolean flag = setNx(key, value, expireTime, TimeUnit.MILLISECONDS); // 尝试获取锁 成功返回 if (flag) { return true; } else { // 获取失败 // 现在时间 long newTime = System.currentTimeMillis(); // 等待过期时间 long loseTime = newTime + waitTime; // 不断尝试获取锁成功返回 while (System.currentTimeMillis() < loseTime) { Boolean testFlag = setNx(key, value, expireTime, TimeUnit.MILLISECONDS); if (testFlag) { return true; } try { Thread.sleep(interval); } catch (InterruptedException e) { log.error("获取锁异常", e); } } } return false; } /** * 释放锁 * * @param key * @return */ public void unLock(String key) { remove(key); } public Boolean setIfAbsent(String key, String value) { Boolean tf = redisTemplate.opsForValue().setIfAbsent(key, value); redisTemplate.expire(key, 60, TimeUnit.DAYS); return tf; } /** * 获取分布式锁 * @param key * @param value * @param expireTime * @return */ public Boolean lock(String key, String value, Long expireTime) { Boolean flag = setNx(key, value, expireTime, TimeUnit.MILLISECONDS); if (flag == null || !flag) { return false; } return true; } /*** * lua释放分布式锁 * @param key * @param value */ public void unLock(String key, String value) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class); Long result = (Long) redisTemplate.execute(redisScript, Arrays.asList(key),value); if (result == null || result == 0) { log.info("释放锁(" + key + "," + value + ")失败,该锁不存在或锁已经过期"); } else { log.info("释放锁(" + key + "," + value + ")成功"); } } /*** * lua不存在才插入队列 * @param key * @param values * jackson序列化后String会变成\"xxx\",不要直接用ARGV去转成number使用 */ public void putAllIfAbsent(String key, List<String> values,long timeout,TimeUnit unit) { Long rawTimeout = TimeoutUtils.toMillis(timeout, unit); String script = "if redis.call('exists', KEYS[1]) == 0 then local listValues = ARGV" + " for k,v in ipairs(listValues) do " + " redis.call('RPUSH',KEYS[1],v) " + " end " + " redis.call('expire',KEYS[1],KEYS[2])" + " end"; RedisScript<Void> redisScript = new DefaultRedisScript<>(script); redisTemplate.execute(redisScript, Arrays.asList(key,String.valueOf(rawTimeout)),values.toArray()); } }
-
业务使用
进入逻辑前先判断有没有锁
用try-catch包住业务逻辑,finally释放锁,以防抛错直接占有锁if (redisLock.lock(redisLockKey, value, 60 * 1000L)) { try { return reactiveMongoTemplate.find(query, JrRedPacketTask.class) .collectList() .flatMap(taskList -> { if (taskList.size() > 0) { //缓存到redis中 return reactiveRedisTemplate.opsForValue().set(redisKey, taskList, Duration.ofDays(1)) .then(Mono.just(taskList)); } return Mono.just(taskList); }); } finally { redisLock.unLock(redisLockKey, value);// 释放锁 } } else { try { //休息,休息一会儿 Thread.sleep(8); } catch (InterruptedException e) { e.printStackTrace(); } }