先描述一下业务场景,
场景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的分配。
相关推荐
在分布式系统中,缓存设计是提高系统性能和扩展性的重要技术手段。本文将浅析分布式系统缓存设计的相关知识点,内容涵盖...通过不断的技术探索和实践,我们可以构建出既快速又可靠,同时具有容灾能力的分布式缓存系统。
1. **基于Redis的分布式锁**:利用Redis的单线程模型和操作原子性,实现跨节点的锁服务,防止并发问题。 2. **Zookeeper分布式锁**:通过创建临时节点和监听机制,实现锁的公平性和互斥性。 三、分布式事务 1. **...
分布式锁的实现方式多样,包括利用数据库的行锁或版本乐观锁、外部缓存(如Redis、Tair、Memcached)以及Zookeeper等协调服务。分布式锁需要满足两个基本条件:一是锁存储空间,即在分布式环境中需要有一个全局可见...
在本文中,我们将深入探讨Zookeeper在分布式系统中的核心应用,特别是它在实现分布式锁、领导选举和作为Spring Cloud Zookeeper注册中心的角色。 **1. Zookeeper分布式锁** 分布式锁是解决分布式系统中并发控制的...
湖南电信在此背景下,积极探索并应用了分布式数据库解决方案,以应对日益增长的业务需求。 分布式数据库是一种创新的数据管理方式,它通过数据切分(垂直切分和水平切分)将数据分散到多台数据库上,减轻单一数据库...
5. **分布式锁与状态机复制**:如何实现分布式锁服务,如Zookeeper或者自建的分布式协调服务,以及基于Raft协议的状态机复制,保证在分布式环境下的正确性和一致性。 6. **微服务架构**:阿里巴巴的Dubbo或HSF等...
- 分布式缓存如Redis和Memcached,用于提高高并发场景下的数据访问性能。 4. 分布式计算 - MapReduce:Google提出的并行计算模型,Hadoop实现了这一模型,用于大数据处理。 - Spark:快速、通用且可扩展的大...
5. **分布式锁**:在多服务环境中,Redis可以作为分布式锁的实现,保证并发操作的安全。 后续的学习和实践中,你可以探索更多Redis的高级特性,如lua脚本、Geo定位、HyperLogLog等,并结合具体业务场景,充分发挥...
解决方法包括设置合理的过期时间、使用随机过期时间和构建分布式锁。 6. **缓存预热**:在系统启动或更新缓存内容时,预先加载部分或全部数据到缓存,以减少用户首次访问时的等待时间。 7. **缓存更新**:如何同步...
此外,还有一些高级技术如**分布式锁**和**分布式事务**等,可以在更大范围内解决一致性问题。 #### 五、结论 综上所述,确保缓存与数据库之间的一致性是一项复杂的任务,需要综合运用多种技术和策略。通过对数据...
Curve可能会使用锁机制、乐观锁或者分布式事务协议(如Paxos、Raft)来保证数据的一致性。 5. **扩展性**:随着数据量的增长,系统需要能够无缝地添加新的存储节点。这可能涉及到动态负载均衡和数据迁移功能。 6. ...
综上所述,针对边缘计算场景下的缓存竞争问题,不仅可以从缓存替换算法本身进行优化,还可以探索新的缓存管理机制和技术手段,如分布式一致性协议、虚拟化技术等,来进一步减轻缓存竞争带来的负面影响,提高系统的...
常见的分布式锁实现有基于数据库的乐观锁和悲观锁,基于缓存的Redis Lock,以及基于Zookeeper的分布式协调服务。在Java中,可以使用Jedis库操作Redis实现分布式锁,或者利用Zookeeper的临时节点特性构建锁服务。需要...
2. **分布式锁**:在处理库存减扣时,为了防止超卖,我们需要实现分布式锁。Redis或Zookeeper可以提供这样的功能,通过它们实现的分布式锁可以在多节点之间保证操作的原子性。 3. **限流与熔断**:Hystrix或...
5. **分布式锁**:为了解决多实例之间的一致性问题,提供了分布式锁机制。 6. **事务处理**:支持多条命令作为一个原子操作执行,保证数据一致性。 7. **安全性和监控**:集成了防火墙规则、密码认证、日志审计等...
为了应对大规模数据,Redis支持数据分片(Sharding)和分布式锁,通过集群模式来扩展存储能力。此外,Redis还提供了事务(Transactions)功能,允许一次性执行多个操作,保证原子性。 Redis实战中,常见的应用场景...
2. **分布式锁**:利用Flasher的原子操作,可以实现分布式环境下的锁服务,保证并发控制的正确性。 3. **消息队列**:通过发布/订阅模式,Flasher可以作为轻量级的消息中间件,实现异步处理和解耦。 4. **计数器**...
Redis作为一种开源的键值存储系统,凭借其高效的性能和丰富的功能,在缓存、消息队列、分布式锁等多个领域发挥着重要作用。为了帮助开发者更好地理解和运用Redis的强大能力,本文将通过实际操作与案例分析的方式,...
Toto库将Zookeeper的强大功能引入到Python环境中,使得Python开发者能够更容易地处理分布式环境下的各种挑战,如配置管理、命名服务、分布式锁、集群协调等。 云原生(Cloud Native)是现代软件开发的一种趋势,...