zoukankan      html  css  js  c++  java
  • springboot 中单机 redis 实现分布式锁

    在微服务中经常需要使用分布式锁,来执行一些任务。例如定期删除过期数据,在多个服务中只需要一个去执行即可。

    以下说明非严格意义的分布式锁,因为 redis 实现严格意义的分布式锁还是比较复杂的,对于日常简单使用使用如下简单方法即可。即偶尔不执行任务不影响业务。

    实现要点

    1)获得锁、释放锁需要是原子操作。要么获取成功,要么失败。释放要么成功,要么失败

    2)任务完成需要自己释放自己的锁,不能释放别人的锁。

    3)锁要有过期时间限制,防止任务崩溃没有释放锁,导致其他节点无法获得锁。

    4)执行节点超时长时间不释放锁,到下次任务开始执行并行存在的情况

    要考虑的风险点

    1)获取锁失败,偶尔不执行任务要不影响业务或告警人工干预

    2)redis 宕机,导致无法获取锁

    方案一:低版本使用 jedis 实现

    1 添加依赖,高版本或低版本有些方法可能没有

    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>compile</scope>
    </dependency>
    <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.10.2</version>
    </dependency>

    2 实现方法

    @Slf4j
    public abstract class AbsDistributeLock {
    
        private Jedis jedis;
    
        public void initDistributeLock(String ip, int port, Integer database, String password) {
            jedis = new Jedis(ip, port);
            if (password != null && !password.isEmpty()) {
                jedis.auth(password.trim());
            }
            if (database == null || database < 0 || database > 15) {
                database = 0;
            }
            jedis.select(database);
        }
    
        private static final String LOCK_SUCCESS = "OK";
        private static final String SET_IF_NOT_EXIST = "NX";
        private static final String SET_WITH_EXPIRE_TIME = "PX";
    
        private static final Long RELEASE_SUCCESS = 1L;
    
        /**
         * 具体的任务需要子类去实现
         *
         * @throws RTException 分布式锁异常
         */
        public abstract void taskService() throws RTException;
    
        /**
         * 任一执行,ANY OF
         * 所有节点任意一个执行任务 taskService 即可,没有获得锁的节点不执行任务
         *
         * @param lockKey    lockKey
         * @param keyValue   keyValue
         * @param expireTime 过期时间 ms
         */
        public void onlyOneNodeExecute(String lockKey, String keyValue, int expireTime) {
            boolean getLock = false;
            try {
                if ((getLock = getDistributedLock(jedis, lockKey, keyValue, expireTime))) {
                    taskService();
                }
            } finally {
                if (getLock) {
                    releaseDistributedLock(jedis, lockKey, keyValue);
                }
            }
        }
    
        /**
         * 所有串行执行,ALL IN LINE
         * 所有节点都必须执行该任务,每次只能一个执行。
         *
         * @param lockKey    lockKey
         * @param keyValue   keyValue
         * @param expireTime 过期时间 ms
         */
        public void allNodeExecute(String lockKey, String keyValue, int expireTime) {
            try {
                while (!(getDistributedLock(jedis, lockKey, keyValue, expireTime))) {
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        log.info(e.getMessage());
                    }
                }
                taskService();
            } finally {
                releaseDistributedLock(jedis, lockKey, keyValue);
            }
        }
    
        /**
         * @param jedis      客户端
         * @param lockKey    key
         * @param keyValue   key值
         * @param expireTime 过期时间,ms
         */
        public static boolean getDistributedLock(Jedis jedis, String lockKey, String keyValue, int expireTime) {
            String result = jedis.set(lockKey, keyValue, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
            if (LOCK_SUCCESS.equals(result)) {
                log.info("ip:[{}] get lock:[{}], value:[{}], getLock result:[{}]", IpUtil.getLocalIpAddr(), lockKey, keyValue, result);
                return true;
            } else {
                return false;
            }
        }
    
        public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String keyValue) {
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(keyValue));
            log.info("ip:[{}] release lock:[{}], value:[{}], release result: [{}]", IpUtil.getLocalIpAddr(), lockKey, keyValue, result);
            if (RELEASE_SUCCESS.equals(result)) {
                return true;
            }
            return false;
        }
    
    }

    方案二:高版本的springboot,使用 lua 脚本执行

    1 添加依赖

    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.2.0.RELEASE</version>
          </dependency>

    2 代码实现

    @Slf4j
    public abstract class AbsDistributeLockLua {
    
        private RedisTemplate<String, String> redisTemplate;
    
        public AbsDistributeLockLua(RedisTemplate<String, String> redisTemplate) {
            this.redisTemplate = redisTemplate;
        }
    
        /**
         * 具体的任务需要子类去实现
         *
         * @throws RTException 分布式锁异常
         */
        public abstract void taskService() throws RTException;
    
        /**
         * 任一执行,ANY OF
         * 所有节点任意一个执行任务 taskService 即可,没有获得锁的节点不执行任务
         *
         * @param lockKey    lockKey
         * @param keyValue   keyValue
         * @param expireTime 过期时间 ms
         */
        public void onlyOneNodeExecute(String lockKey, String keyValue, int expireTime) {
            boolean getLock = false;
            try {
                if ((getLock = getDistributeLock(redisTemplate, lockKey, keyValue, expireTime))) {
                    taskService();
                }
            } finally {
                if (getLock) {
                    releaseDistributeLock(redisTemplate, lockKey, keyValue);
                }
            }
        }
    
        /**
         * 所有串行执行,ALL IN LINE
         * 所有节点都必须执行该任务,每次只能一个执行。
         *
         * @param lockKey    lockKey
         * @param keyValue   keyValue
         * @param expireTime 过期时间 ms
         */
        public void allNodeExecute(String lockKey, String keyValue, int expireTime) {
            try {
                while (!(getDistributeLock(redisTemplate, lockKey, keyValue, expireTime))) {
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        log.info(e.getMessage());
                    }
                }
                taskService();
            } finally {
                releaseDistributeLock(redisTemplate, lockKey, keyValue);
            }
        }
    
        /**
         * 通过lua脚本 加锁并设置过期时间
         *
         * @param key    锁key值
         * @param value  锁value值
         * @param expire 过期时间,单位毫秒
         * @return true:加锁成功,false:加锁失败
         */
        public boolean getDistributeLock(RedisTemplate<String, String> redisTemplate, String key, String value, int expire) {
            DefaultRedisScript<String> redisScript = new DefaultRedisScript<String>();
            redisScript.setResultType(String.class);
            String strScript = "if redis.call('setNx',KEYS[1],ARGV[1])==1 then return redis.call('pexpire',KEYS[1],ARGV[2]) else return 0 end";
            redisScript.setScriptText(strScript);
            try {
                Object result = redisTemplate.execute(redisScript, redisTemplate.getStringSerializer(), redisTemplate.getStringSerializer(), Collections.singletonList(key), value, expire);
                System.out.println("redis返回:" + result);
                return "1".equals("" + result);
            } catch (Exception e) {
                //可以自己做异常处理
                return false;
            }
        }
    
        /**
         * 通过lua脚本释放锁
         *
         * @param key   锁key值
         * @param value 锁value值(仅当redis里面的value值和传入的相同时才释放,避免释放其他线程的锁)
         * @return true:释放锁成功,false:释放锁失败(可能已过期或者已被释放)
         */
        public boolean releaseDistributeLock(RedisTemplate<String, String> redisTemplate, String key, String value) {
            DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
            redisScript.setResultType(String.class);
            String strScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            redisScript.setScriptText(strScript);
            try {
                Object result = redisTemplate.execute(redisScript, redisTemplate.getStringSerializer(), redisTemplate.getStringSerializer(), Collections.singletonList(key), value);
                return "1".equals("" + result);
            } catch (Exception e) {
                //可以自己做异常处理
                return false;
            }
        }
    }

    代码地址:https://github.com/crazyCodeLove/distribute-lock

    参考文献:

    https://www.cnblogs.com/bcde/p/11132479.html

    https://blog.csdn.net/u013985664/article/details/94459529

  • 相关阅读:
    。。。。。。
    数据库
    python基础
    。。。。
    drf
    CRM笔记梳理
    人生苦短,我学PYTHON
    React的初步了解
    递归与迭代比较
    有没有大佬会很标准的三层架构
  • 原文地址:https://www.cnblogs.com/zhaopengcheng/p/12144022.html
Copyright © 2011-2022 走看看