Redis分散式鎖應用
阿新 • • 發佈:2022-12-06
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(); } }