`
edishf
  • 浏览: 1191 次
社区版块
存档分类
最新评论

缓存分布式锁探索

 
阅读更多

先描述一下业务场景,

场景1:mq消息处理各个来源的数据,进行用户初始化,写入detail表,由于mq的重试机制以及高并发环境下,用户记录可能存在写入多次的情况,需要保证初始化时,每个用户(以下为pin)只能允许一个事务进行操作,同时每个唯一key也只能允许一个线程进行插入,因此需要使用锁。

场景2:后台计算worker,根据每个时间段对detail表中新插入的数据进行计算,也是只允许同一时间只有一个worker在执行,理论上最优方案是通过worker调度平台+dns负载均衡来实现,但为了简单,还是使用锁来实现(使用锁的方案有一个很严重的问题,文末会提到)。

 

 

 多说无用,直接看第一个版本

//版本1
public Boolean tryLock(String key) {
        Boolean isLock = true;
        try{
            if(cluster.incr(key) > 1){//   1
                return false;
            }
        }catch (Exception e){
            cluster.del(key);
            isLock = false;
        }
        cluster.expire(key,1, TimeUnit.MINUTES);
        return isLock;
    }

一开始想到的就是通过incr的原子操作进行判断,当累加数大于1之后,就获取锁失败,在获取锁成功之后,设置过期时间,防止死锁。这种写法咋一看没啥问题,不过组内review之后,发现由于incr和expire的操作是分开的,并不是一个原子操作,因此在incr之后,expire之前,出现错误,比如实例出错导致未执行expire,将导致这个key永远死锁,因此迭代出下一个版本。

 

//版本2
public Boolean tryLock(String key) {
        Boolean isLock = true;
        try{
            if(cluster.incr(key) > 1){
                if(cluster.incr(key) > 2){//只锁一次,防止实例出错造成死锁
                    cluster.del(key);
                    return false;
                }
                return false;
            }
        }catch (Exception e){
            cluster.del(key);
            isLock = false;
        }
        cluster.expire(key,1, TimeUnit.MINUTES);
        return isLock;
    }

     既然出错会死锁,就判断当再次被锁时,就删除这个锁,想法是很好,但仔细一想就会发现问题,此时的判断死锁的数值为2,如果当有4个实例同时请求锁时,第二个请求会被锁,第三个也会被锁,但是第四个就可以获取到锁,导致锁功能失效。

     使用incr根本问题在于操作和设置过期时间的非原子型,需要其他的办法解决,经过参考网上的其他方案之后,有了版本3。

 

      

//版本3
public Boolean tryLock(String key) {
        try{
            long timeout = TimeUnit.MINUTES.toMillis(15);
            long timestamp = System.currentTimeMillis() + timeout + 1;
            if (cluster.setNX(key, String.valueOf(timestamp))) {
                return true;
            }

            long lockTimestamp = Long.valueOf(cluster.get(key));
            if (System.currentTimeMillis() > lockTimestamp) {
                lockTimestamp = Long.valueOf(cluster.getSet(key, String.valueOf(timestamp)));
                if (System.currentTimeMillis() > lockTimestamp) {
                    return true;
                }
            }

            return false;
        }catch (Exception e){
            cluster.del(key);
            return false;
        }
    }

      通过把过期时间写入value,由于setNx是原子操作,可以保证只要获取到锁,过期时间一定写入,并且保证过期之后,也能立即失效,是一个理想的解决方案。

 

     以为这就结束了吗?太天真了,有加锁就必然会有解锁,此时的解锁代码如下:

 

  

public void unlock(String key) {
      
            if(cluster.get(key) == null){
                return;
            }
            long lockTimestamp = Long.valueOf(cluster.get(key));
            if (System.currentTimeMillis() > lockTimestamp) {
                cluster.del(key);
            }
   

    }

   当判断这个key过期之后,就删除这个锁,也是很合理的逻辑,但是也经不起推敲,当业务处理时间超过过期时间,会导致锁失效,造成数据不一致;

 

//最终版
private long timeout = TimeUnit.MINUTES.toMillis(30);
    private ThreadLocal<Boolean> threadOwner = new ThreadLocal<Boolean>();




    public Boolean tryLock(String key) {
        try {
            long timestamp = System.currentTimeMillis() + timeout + 1;
     
            if (cluster.setNX(key, String.valueOf(timestamp))) {
                threadOwner.set(true);       
                return true;
            }
          
            long lockTimestamp = Long.valueOf(cluster.get(key));
         
            if (System.currentTimeMillis() > lockTimestamp) {
                lockTimestamp = Long.valueOf(cluster.getSet(key, String.valueOf(timestamp)));
                if (System.currentTimeMillis() > lockTimestamp) {
                   
                    return true;
                }
            }
           
            return false;
        } catch (Exception e) {
           
            cluster.del(key);
            return false;
        }
    }


    public void unlock(String key) {
        if(threadOwner.get() != null && threadOwner.get()){
            threadOwner.remove();
            cluster.del(key);
        }else {
            if(cluster.get(key) == null){
                return;
            }
            long lockTimestamp = Long.valueOf(cluster.get(key));
            if (System.currentTimeMillis() > lockTimestamp) {
                cluster.del(key);        
            }
        }

    }

    这里使用线程缓存来保证只有获取锁的实例才能进行删除锁操作,同时把过期时间调长,只要保证能超过业务处理时间即可。

 

 

    最后就是前文提到的使用锁方案的问题,在多实例的环境下,由于后台计算worker是周期性运行,如果某一个实例的时间比其他实例时间早,那么所有的worker都将由一台机器执行,造成性能瓶颈,因此后续将使用调度平台来进行worker的分配。

分享到:
评论

相关推荐

    分布式系统缓存设计

    在分布式系统中,缓存设计是提高系统性能和扩展性的重要技术手段。本文将浅析分布式系统缓存设计的相关知识点,内容涵盖...通过不断的技术探索和实践,我们可以构建出既快速又可靠,同时具有容灾能力的分布式缓存系统。

    083-分布式协议与算法实战

    1. **基于Redis的分布式锁**:利用Redis的单线程模型和操作原子性,实现跨节点的锁服务,防止并发问题。 2. **Zookeeper分布式锁**:通过创建临时节点和监听机制,实现锁的公平性和互斥性。 三、分布式事务 1. **...

    分布式系统互斥与幂等问题的探索与实践v1.0.78d93130-1099-11e7-91a5-d3f85a1f5526.pdf

    分布式锁的实现方式多样,包括利用数据库的行锁或版本乐观锁、外部缓存(如Redis、Tair、Memcached)以及Zookeeper等协调服务。分布式锁需要满足两个基本条件:一是锁存储空间,即在分布式环境中需要有一个全局可见...

    深入探索Zookeeper:实战应用与高效策略

    在本文中,我们将深入探讨Zookeeper在分布式系统中的核心应用,特别是它在实现分布式锁、领导选举和作为Spring Cloud Zookeeper注册中心的角色。 **1. Zookeeper分布式锁** 分布式锁是解决分布式系统中并发控制的...

    分布式数据库在湖南电信IT架构转型中的探索和应用.pdf

    湖南电信在此背景下,积极探索并应用了分布式数据库解决方案,以应对日益增长的业务需求。 分布式数据库是一种创新的数据管理方式,它通过数据切分(垂直切分和水平切分)将数据分散到多台数据库上,减轻单一数据库...

    Alibaba分布式核心原理解析.rar

    5. **分布式锁与状态机复制**:如何实现分布式锁服务,如Zookeeper或者自建的分布式协调服务,以及基于Raft协议的状态机复制,保证在分布式环境下的正确性和一致性。 6. **微服务架构**:阿里巴巴的Dubbo或HSF等...

    典型分布式文件系统概述(一)

    - **Zebra**:斯坦福大学的项目,探索了分布式文件系统的新架构。 - **3.6 xFS** - **概述**:一个高性能的分布式文件系统。 - **体系结构**:采用客户-服务器模型。 - **通信**:通过自定义协议实现。 - **...

    分布式Java应用基础与实践源码.zip

    - 分布式缓存如Redis和Memcached,用于提高高并发场景下的数据访问性能。 4. 分布式计算 - MapReduce:Google提出的并行计算模型,Hadoop实现了这一模型,用于大数据处理。 - Spark:快速、通用且可扩展的大...

    redis缓存数据库windows解压版.zip

    5. **分布式锁**:在多服务环境中,Redis可以作为分布式锁的实现,保证并发操作的安全。 后续的学习和实践中,你可以探索更多Redis的高级特性,如lua脚本、Geo定位、HyperLogLog等,并结合具体业务场景,充分发挥...

    构建高效可伸缩的缓存demo

    解决方法包括设置合理的过期时间、使用随机过期时间和构建分布式锁。 6. **缓存预热**:在系统启动或更新缓存内容时,预先加载部分或全部数据到缓存,以减少用户首次访问时的等待时间。 7. **缓存更新**:如何同步...

    缓存与数据库一致性保证

    此外,还有一些高级技术如**分布式锁**和**分布式事务**等,可以在更大范围内解决一致性问题。 #### 五、结论 综上所述,确保缓存与数据库之间的一致性是一项复杂的任务,需要综合运用多种技术和策略。通过对数据...

    curve分布式存储系统 v2.4.0 beta.zip

    Curve可能会使用锁机制、乐观锁或者分布式事务协议(如Paxos、Raft)来保证数据的一致性。 5. **扩展性**:随着数据量的增长,系统需要能够无缝地添加新的存储节点。这可能涉及到动态负载均衡和数据迁移功能。 6. ...

    Redis相关面试题总结

    ④探索Redis的持久化选项、分布式锁实现、数据持久化方法、缓存一致性保障策略等内容。 其他说明:对于希望提升系统读写性能的企业和个人开发者,本文提供了丰富的案例和技巧指导,旨在帮助读者充分利用Redis的优势...

    缓存竞争缓解机制在边缘计算中的应用.pptx

    综上所述,针对边缘计算场景下的缓存竞争问题,不仅可以从缓存替换算法本身进行优化,还可以探索新的缓存管理机制和技术手段,如分布式一致性协议、虚拟化技术等,来进一步减轻缓存竞争带来的负面影响,提高系统的...

    webtech:web高端技术

    常见的分布式锁实现有基于数据库的乐观锁和悲观锁,基于缓存的Redis Lock,以及基于Zookeeper的分布式协调服务。在Java中,可以使用Jedis库操作Redis实现分布式锁,或者利用Zookeeper的临时节点特性构建锁服务。需要...

    秒杀init包,用这个当基础学习

    2. **分布式锁**:在处理库存减扣时,为了防止超卖,我们需要实现分布式锁。Redis或Zookeeper可以提供这样的功能,通过它们实现的分布式锁可以在多节点之间保证操作的原子性。 3. **限流与熔断**:Hystrix或...

    阿里云Redis技术架构演进.pdf

    5. **分布式锁**:为了解决多实例之间的一致性问题,提供了分布式锁机制。 6. **事务处理**:支持多条命令作为一个原子操作执行,保证数据一致性。 7. **安全性和监控**:集成了防火墙规则、密码认证、日志审计等...

    redis实战-redisLearn.zip

    为了应对大规模数据,Redis支持数据分片(Sharding)和分布式锁,通过集群模式来扩展存储能力。此外,Redis还提供了事务(Transactions)功能,允许一次性执行多个操作,保证原子性。 Redis实战中,常见的应用场景...

    flasher-master.zip

    2. **分布式锁**:利用Flasher的原子操作,可以实现分布式环境下的锁服务,保证并发控制的正确性。 3. **消息队列**:通过发布/订阅模式,Flasher可以作为轻量级的消息中间件,实现异步处理和解耦。 4. **计数器**...

    Redis命令实践:深入探索Redis的强大功能.pdf

    Redis作为一种开源的键值存储系统,凭借其高效的性能和丰富的功能,在缓存、消息队列、分布式锁等多个领域发挥着重要作用。为了帮助开发者更好地理解和运用Redis的强大能力,本文将通过实际操作与案例分析的方式,...

Global site tag (gtag.js) - Google Analytics