zoukankan      html  css  js  c++  java
  • redis实现分布式锁

    一 场景

    分布式环境,一共三台机器,跑批时,为了保证跑批触发时间点只有一个机器进行job跑批,故增加分布式锁来控制防重跑。

    二 redis实现分布式锁

    /**
    * 对于分布式加锁本例中使用 setnx()含义是SET if Not Exists,其主要有两个参数 setnx(key, value)。该方法是原子的,如果key不存在,则设置当前key成功,返回1;如果当前key已经存在,则设置当前key失败,返回0。
    * 另外使用 getset()也可以:这个命令主要有两个参数 getset(key, newValue)。该方法是原子的,对key设置newValue这个值,并且返回key原来的旧值。
    * ttl命令:当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 。 否则,以毫秒为单位,返回 key 的剩余生存时间。(在 Redis 2.8 以前,当 key 不存在,或者 key 没有设置剩余生存时间时,命令都返回 -1 )。
    */

      

    @Component
    public class RedisLock {
    
        private static final String PREFIX_LOCK_KEY = "RULE:LOCK:"; //缓存 key前辍
        private static final Integer EXPIRE_SECOND = 300; //缓存时间
    
        @Resource
        private RedisClusterClient redisClusterClient;
    
        public boolean tryLock(String key) {
            String redisKey = PREFIX_LOCK_KEY + key;
            Long val = redisClusterClient.tryLock(redisKey);
            LOGGER.info("获取锁,key:{},val:{}", redisKey, val);
            if (val > 0) {
                redisClusterClient.expire(redisKey, EXPIRE_SECOND);
                return true;
            } else {
                if (redisClusterClient.ttl(redisKey) < 0) {
                    LOGGER.info("delete lock,key:{}", redisKey);
                    redisClusterClient.expire(redisKey, EXPIRE_SECOND);
                    return true;
                }
            }
            return false;
        }
    
        public void unLock(String key) {
            redisClusterClient.del(PREFIX_LOCK_KEY + key);
            LOGGER.info("释放锁 unLock,key:{}", key);
        }
    }
    

      

      

    应用代码如下,

    public void xx() {
            if (redisLock.tryLock(key)) {
                try {
                    LOGGER.info("拿到redis分布式锁!");
                    .....
                } catch (Exception e) {
                    LOGGER.error(e);
                } finally {
                    redisLock.unLock(key);
                }
            } else {
                LOGGER.info("请稍后再试!");
    }
    

      


    缺点:

    1. getSet与expire不是一个原子操作,可能执行完setnx该进程就挂了。
    2. 当锁过期后,该进程还没执行完,可能造成同时多个进程取得锁。(貌似这个问题目前还没有很优雅的解决方案)

    三 3种分布式锁方案对比:

    数据库锁:

    优点:直接使用数据库,使用简单。
    缺点:分布式系统大多数瓶颈都在数据库,使用数据库锁会增加数据库负担。

    缓存锁:

    优点:性能高,实现起来较为方便,在允许偶发的锁失效情况,不影响系统正常使用,建议采用缓存锁。
    缺点:通过锁超时机制不是十分可靠,当线程获得锁后,处理时间过长导致锁超时,就失效了锁的作用。

    zookeeper锁:

    优点:不依靠超时时间释放锁;可靠性高;系统要求高可靠性时,建议采用zookeeper锁。
    缺点:性能比不上缓存锁,因为要频繁的创建节点删除节点。

    上面几种方式,哪种方式都无法做到完美。就像CAP一样,在复杂性、可靠性、性能等方面无法同时满足,所以,根据不同的应用场景选择最适合自己的才是王道。

    从理解的难易程度角度(从低到高)

    数据库 > 缓存 > Zookeeper

    从实现的复杂性角度(从低到高)

    Zookeeper >= 缓存 > 数据库

    从性能角度(从高到低)

    缓存 > Zookeeper >= 数据库

    从可靠性角度(从高到低)

    Zookeeper > 缓存 > 数据库

  • 相关阅读:
    go语言之goroute协程
    Vue中Computed和Watch的用法及区别
    php判断复选框是否被选中的方法
    基于workerman的实时推送
    织梦引入公共模板
    织梦快速建站首页模板
    golang解决中文乱码的方法
    Vue项目中使用可视化图表echarts
    解决for循环中异步请求顺序不一致的问题
    layui多图上传实现删除功能的方法
  • 原文地址:https://www.cnblogs.com/wanghongsen/p/9792234.html
Copyright © 2011-2022 走看看