• redis分布式锁实现


    最近项目中使用到了redis实现的分布式锁,自定义的分布式锁支持自旋和可冲入等,是一个不错的实践,这里记录下

    /**
     * @description: redis分布式锁
     * @author: cc.wang
     * @createDate: 2022-04-22 12:13
     * @version: 1.0
     */
    public class RedisLock {
        private static Logger logger = LoggerFactory.getLogger(RedisLock.class);
    
        private RedisTemplate<String, Object> redisTemplate;
    
        private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100;
    
        /**
         * Lock key path.
         */
        private String lockKey;
    
        /**
         * 锁超时时间,防止线程在入锁以后,无限的执行等待
         */
        private int expireMsecs = 3000;
    
        /**
         * 锁等待时间,防止线程饥饿
         */
        private int timeoutMsecs = 100;
    
        private volatile boolean locked = false;
    
        private String requestClientId = "";
        protected static final String UNLOCK_LUA;
    
        static {
            StringBuilder sb = new StringBuilder();
            sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
            sb.append("then ");
            sb.append("    return redis.call(\"del\",KEYS[1]) ");
            sb.append("else ");
            sb.append("    return 0 ");
            sb.append("end ");
            UNLOCK_LUA = sb.toString();
        }
    
        /**
         * Detailed constructor with default acquire timeout 10000 msecs and lock expiration of 60000 msecs.
         *
         * @param lockKey lock key (ex. account:1, ...)
         */
        public RedisLock(RedisTemplate<String, Object> redisTemplate, String lockKey) {
            this.redisTemplate = redisTemplate;
            this.lockKey = lockKey + "_lock";
        }
    
        /**
         * Detailed constructor with default lock expiration of 60000 msecs.
         */
        public RedisLock(RedisTemplate<String, Object> redisTemplate, String lockKey, int timeoutMsecs) {
            this(redisTemplate, lockKey);
            this.timeoutMsecs = timeoutMsecs;
        }
    
        /**
         * Detailed constructor.
         */
        public RedisLock(RedisTemplate<String, Object> redisTemplate, String lockKey, int timeoutMsecs, int expireMsecs) {
            this(redisTemplate, lockKey, timeoutMsecs);
            this.expireMsecs = expireMsecs;
        }
    
        /**
         * @return lock key
         */
        public String getLockKey() {
            return this.lockKey;
        }
    
        private String get(final String key) {
            try {
                RedisCallback<String> callback = (connection) -> {
                    JedisCommands commands = (JedisCommands) connection.getNativeConnection();
                    return commands.get(key);
                };
                String result = redisTemplate.execute(callback);
                return result;
            } catch (Exception e) {
                logger.error("获取锁内容异常", e);
            }
            return "";
        }
    
        private boolean setNX(final String key, final String value) {
            try {
                RedisCallback<String> callback = (connection) -> {
                    JedisCommands commands = (JedisCommands) connection.getNativeConnection();
                    return commands.set(lockKey, value, "NX", "PX", this.expireMsecs);
                };
                String result = redisTemplate.execute(callback);
                logger.debug("locked result: {}", result);
                return StringUtils.isNotBlank(result);
            } catch (Exception e) {
                logger.error("加锁失败", e);
            }
            return false;
        }
    
        /**
         * @return true if lock is acquired, false acquire timeouted
         * @throws InterruptedException in case of thread interruption
         */
        public synchronized boolean lock() throws InterruptedException {
            int timeout = timeoutMsecs;
            if (StringUtils.isBlank(this.requestClientId)) {
                requestClientId = UUID.randomUUID().toString();
                logger.debug("redis lock requestClientId: {}", requestClientId);
            }
            //支持自旋
            while (timeout >= 0) {
                boolean isLocked = this.setNX(this.lockKey, this.requestClientId);
                if (isLocked) {
                    this.locked = isLocked;
                    return true;
                }
    
                if (this.locked) {
                    //支持重入
                    String value = this.get(this.lockKey);
                    logger.debug("locked requestClientId: {}", value);
                    if (StringUtils.equalsIgnoreCase(this.requestClientId, value)) {
                        return true;
                    }
                }
    
                timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;
                if (timeout > 0) {
                    doSleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);
                }
    
            }
            return false;
        }
    
        /**
         * 休眠一段时间
         *
         * @param milliseconds
         * @throws InterruptedException
         */
        private void doSleep(long milliseconds) throws InterruptedException {
            Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);
        }
    
        /**
         * Acqurired lock release.
         */
        public synchronized boolean unlock() {
            if (!this.locked) {
                //未取得锁时直接返回
                return false;
            }
    
            try {
                List<String> keys = Lists.newArrayList(this.lockKey);
                List<String> args = Lists.newArrayList(this.requestClientId);
    
                // 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
                // spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本
                RedisCallback<Long> callback = (connection) -> {
                    Object nativeConnection = connection.getNativeConnection();
                    // 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
    
                    if (nativeConnection instanceof JedisCluster) { // 集群模式
                        return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);
                    } else if (nativeConnection instanceof Jedis) { // 单机模式
                        return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
                    }
                    return 0L;
                };
                Long result = redisTemplate.execute(callback);
    
                boolean reply = result != null && result > 0;
                if (reply) {
                    this.locked = false;
                }
                return reply;
            } catch (Exception e) {
                logger.error("release lock occured an exception", e);
            }
            return false;
        }
    }
    

    image

  • 相关阅读:
    读《构建之法》阅读与思考
    软工沉浮沉沉沉沉沉沉…记事
    四则运算截图and代码
    2016012000郭慕然+散列函数的应用及其安全性
    结对作业之四则运算网页版
    阅读《构建执法》第四章及第十七章有感
    2016012000小学四则运算练习软件项目报告
    有关读《构建之法》的部分思考与疑问
    遇见·软件
    我的——今日学习内容
  • 原文地址:https://www.cnblogs.com/lovelywcc/p/16178357.html
Copyright © 2020-2023  润新知