1. 程式人生 > 其它 >Redis分散式鎖應用

Redis分散式鎖應用

Redis鎖的使用

起因:分散式環境下需對併發進行邏輯一致性控制

架構:springboot2、Redis

IDEA實操

  1. 先新建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());
        }
    }
    
    
    
    
  2. 業務使用
    進入邏輯前先判斷有沒有鎖
    用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();
                    }
                }