• 分布式应用下的Redis单机锁设计与实现


    背景

    最近写了一个定时任务,期望是同一时间只有一台机器运行即可。因为是应用是在集群环境下跑的,所以需要自己实现类一个简陋的Redis单机锁。

    原理

    主要是使用了Redis的SET NX特性,成功设置的那个客户端则被认为拿到了锁,没有设置成功的其他客户单则认为没有拿到锁。
    在分布式环境下使用锁是挺危险的一件事情,我们可能会遇到一些问题:

    1. Redis单点故障;
    2. 应用与Redis网络不通;
    3. 应用异常导致锁没有得到释放;
    4. 误操作锁。

    对于问题1,避免Redis单点故障,可以使用Redis分布式锁的实现,提供了多种语言的开源实现(http://redis.io/topics/distlock),也可以使用其他配置管理类组件实现(比如:zk、consul、etcd等),不在此文讨论。
    对于问题2,我们需要把业务限定在不强依赖Redis锁的范围,虽然绝大多数情况不会发生问题,但是不能完全保证Redis锁不出问题。
    对于问题3,为了防止异常引起的死锁情况,需要为每个锁设置超时时间,以确保不会因为应用问题导致无法释放锁。同时要设置一个合理的超时时间以免,达不到或者削弱锁的效果。
    对于问题4,为每个锁存储一个标记,当解锁的时候,进行验证,用以保证每个客户端只能操作自己的锁。

    实现

    public class RedisLock {
        private static final Logger logger = LoggerFactory.getLogger(RedisLocker.class);
        // update on 2019-11-08 22:55:00 修改成线程安全版本
        private final Map<String, String> LOCK_MAP = new ConcurrentHashMap<String, String>(4); 
        private JedisPool pool;
        public enum EXPX {
            EX,PX
        }
        public boolean tryAcquire(String topic, EXPX expx,long time) throws Exception{
            if (!LOCK_MAP.containsKey(topic)) {
                String uuid = UUID.randomUUID().toString();
                Jedis jedis = null;
                // 存在key,返回空,不存在返回OK
                try {
                    jedis = pool.getResource();
                    String result = jedis.set(topic, uuid, "NX", expx.name(), time);
                    logger.error("获取返回值,result {}", result);
                    if ("OK".equals(result)) {
                        LOCK_MAP.put(topic, uuid);
                        return true;
                    }
                    logger.error("获取锁失败,result {}", result);
                } catch (Exception e) {
                    pool.returnBrokenResource(jedis);
                    throw e;
                }finally {
                    pool.returnResource(jedis);
                }
            }
            return false;
        }
        public boolean unlock(String topic) {
            Jedis jedis = null;
            try {
                jedis = pool.getResource();
                String random = jedis.get(topic);
                if (random != null && random.equals(LOCK_MAP.get(topic))) {
                    jedis.del(topic);
                }
                LOCK_MAP.remove(topic);
            } catch (Exception e) {
                pool.returnBrokenResource(jedis);
                throw e;
            } finally {
                pool.returnResource(jedis);
            }
            return true;
        }
        public void setPool(JedisPool pool) {
            this.pool = pool;
        }
    }
    
    

    总结

    本文做了一种简单暴力的锁的实现。没有做高可用的方案,主要胜在轻量,简单,在平时场景可以使用。
    除了使用配置中心相关的组件也是可以实现锁的,并且是分布式的锁。

    关于RedisPool的两点需要注意:

    1. 在锁的场景,testOnBorrow应该为true;
    2. 设置合理的空闲连接数;
    3. 设置连接上限,防止占用过多资源。
  • 相关阅读:
    理想中的分布式架构
    nginx+tomcat动静分离的核心配置
    Liunx下Tomcat+MYSQL+Nginx配置
    Nginx+Tomcat动静态资源分离
    Nginx+Keepalived+Tomcat之动静分离的web集群
    linux tomca几个配置文件及点
    redis实现spring-redis-data的入门实例
    Redis缓存 ava-Jedis操作Redis,基本操作以及 实现对象保存
    Oracle VM Virtual Box 4.3 小巧精悍的虚拟机软件
    Ubuntu下配置 keepalived+nginx+tomcat 负载均衡
  • 原文地址:https://www.cnblogs.com/liushijie/p/5405212.html
Copyright © 2020-2023  润新知