分布式锁在支付关闭订单场景下的思考
背景:
为何需要分布式锁?
在我们的内部支付系统中,存在一个典型的并发问题场景:支付单既可能因为到期而自动关闭,也可能被用户主动取消。这两种关闭操作在某些情况下会同时发生,导致重复关单和重复向下游发送消息,最终造成下游处理失败。
注:其实这种并发问题本质上是因为多个线程或进程同时操作同一份数据但缺乏有效的互斥机制就会出现并发问题。但是在分布式系统中,由于服务器实例基本会有很多节点部署,单机部署的锁机制没办法满足需求,这就需要引入分布式锁来解决跨进程的这种互斥的问题。
技术选型:
为什么要选择Redis和Redisson?
分布式锁的实现通常需要借助第三方系统来协调互斥性,一版的主流方案有:
1. 基于数据库实现
通过创建锁表,利用唯一性约束防止重复插入
这种方式实现起来比较简单,但是缺点也比较明显,对数据的压力会比较大,性能也会比较差
,对业务也会产生影响,这边不建议用悲观锁,可能导致锁表线程阻塞,所以不采用
2. 基于Zookeeper实现
利用顺序节点实现锁机制,优点是可靠性高,具备强一致性,但是需要引入新组件,增加了系统的复杂度
3. 基于Redis实现
通过SETNX命令实现锁获取,优点 性能高,拿来即用Redis。缺点的话就是需要处理锁超时和自动续期问题
我们最后是考虑到系统已使用Redis且无Zookeeper集群,我们选择了Redis,避免引入新组件成本。但原生SETNX方案存在锁提前释放的问题,最终我们采用了Redisson,主要是他提供了看门狗机制能实现自动续期
扩展实践:
从SETNX到Redisson的演进的过程:
一、我们最初使用Redis的SETNX命令实现分布式锁:
public boolean tryLock(String key, long expireTime) {String result = jedis.set(key, "1", "NX", "PX", expireTime);return "OK".equals(result);
}public void unlock(String key) {jedis.del(key);
}
这个方案在实际运行中经常出现并发问题,原因是:
- 业务处理时间超过锁超时时间,导致锁提前释放
- 拉长超时时间又降低了系统吞吐量,然后就比较尴尬
二、Redisson看门狗机制
Redisson提供了自动续期机制(看门狗),解决了锁超时问题:
public void processWithLock(String orderId) {RLock lock = redissonClient.getLock("order_lock:" + orderId);try {// 尝试加锁,最多等待100秒,上锁后30秒自动解锁if (lock.tryLock(100, 30, TimeUnit.SECONDS)) {try {// 此处省略业务processOrder(orderId);} finally {lock.unlock();}}} catch (InterruptedException e) {Thread.currentThread().interrupt();}
}
看门狗机制会在锁即将过期时自动续期,避免了业务处理时间过长导致锁超时
三、风险
虽然Redisson解决了并发和锁超时的问题,但是仍然存在一些潜在风险:
- Redis主节点故障:在主从的这种架构下,主节点故障可能导致锁信息丢失
- 脑裂:网络分区可能导致多个客户端同时持有锁
四、优化
RedLock算法
public void processWithRedLock(String orderId) {RLock lock1 = redissonClient1.getLock("order_lock:" + orderId);RLock lock2 = redissonClient2.getLock("order_lock:" + orderId);RLock lock3 = redissonClient3.getLock("order_lock:" + orderId);RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);try {if (redLock.tryLock(100, 30, TimeUnit.SECONDS)) {// 此处省略业务processOrder(orderId);}} finally {redLock.unlock();}
}
原理:RedLock通过多个独立的Redis节点协同工作,需要超过一半节点加锁成功才算真正获取到锁,大大提高了可靠性
总结
-Redisson单节点:适用于业务量不大、可用性要求不是很高的场景
-RedLock多节点方案:适用于金融行业等对一致性要求较高的核心业务
-分布式锁:适用于并发量不大且已具备数据库能力的简单场景
-Zookeeper:适用于已经有ZK集群且对可靠性要求较高的系统
一般情况下,我们需要先评估业务场景,不是所有并发场景都需要分布式锁,先考虑是否可以通过业务设计避免并发冲突,还要考虑锁超时问题,要根据业务处理时间合理设置锁超时时间,避免设置过长影响系统可用性,还有一个重点,必须要明确释放锁的动作,确保获取锁后用完释放锁,防止死锁