【NoSql】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();
                    }
                }
    
posted @ 2022-12-06 15:08  simonlee_java  阅读(18)  评论(0编辑  收藏  举报