Redis面试题 - 分布式锁在未完成逻辑前过期怎么办?

重点回答

若锁在未完成逻辑前就过期,此时可能会产生数据不一致的问题。因为锁过期了,此时如果再出现一个客户端争抢锁,即可拿到锁然后同时进行业务操作,这等于锁失效了。

此时可以在逻辑执行过程中定期续期锁,确保锁在处理过程中不会过期。


引言

在分布式系统中,分布式锁是实现资源共享和协调的关键组件。然而,一个常见且棘手的问题是:当业务逻辑尚未执行完毕时,锁却提前过期了。这种情况可能导致数据不一致、重复操作等一系列问题。本文将深入探讨这一问题的成因、影响及解决方案。

分布式锁的基本原理

分布式锁通常基于以下机制实现:

  1. 基于数据库:如唯一索引或乐观锁
  2. 基于Redis:如SETNX命令或RedLock算法
  3. 基于Zookeeper:如临时顺序节点
1. 获取锁
2. 返回成功
3. 执行业务逻辑
4. 释放锁
客户端
分布式锁服务
业务系统

锁提前过期的问题

问题场景

当业务逻辑执行时间超过锁的过期时间(TTL)时,会出现以下情况:

  1. 锁自动释放,其他客户端可以获取锁
  2. 原客户端仍在执行业务逻辑
  3. 可能导致多个客户端同时操作同一资源
ClientALockServiceClientB获取锁(设置TTL=10s)成功执行业务逻辑(耗时15s)12秒后锁过期获取同一把锁成功执行业务逻辑尝试释放锁(失败)ClientALockServiceClientB

问题影响

  1. 数据不一致:多个客户端同时修改同一数据
  2. 重复操作:同一任务被多次执行
  3. 资源竞争:系统负载增加

解决方案

1. 合理设置锁过期时间

原则:锁的TTL应大于业务逻辑的最长可能执行时间

// 示例:设置足够长的过期时间
redisTemplate.opsForValue().set(key, value, 30, TimeUnit.SECONDS);

缺点:难以准确预估业务逻辑执行时间,设置过长会影响系统可用性

2. 锁续期机制(看门狗)

原理:在锁即将过期前,由持有锁的客户端自动延长锁的过期时间

获取锁
启动看门狗线程
是否仍持有锁?
延长锁过期时间
停止续期

Redisson实现示例

RLock lock = redisson.getLock("myLock");
try {
    // 默认看门狗续期,超时时间30秒
    lock.lock();
    // 执行业务逻辑
} finally {
    lock.unlock();
}

3. 版本号/令牌机制

原理:每次获取锁时生成唯一令牌,释放锁时验证令牌

ClientRedisSET lock_key random_value NX EX 10OK执行业务逻辑GET lock_keyrandom_valueDEL lock_key放弃操作alt[值匹配][值不匹配]ClientRedis

4. 两阶段锁协议

原理

  1. 第一阶段:获取锁并执行不可逆操作
  2. 第二阶段:获取新锁完成可逆操作

5. 业务幂等设计

即使锁过期导致重复操作,业务逻辑本身也能保证结果一致

// 幂等操作示例
public void processOrder(String orderId) {
    Order order = orderDao.findById(orderId);
    if (order.getStatus() == Status.PROCESSED) {
        return; // 已处理则直接返回
    }
    // 处理订单逻辑
}

最佳实践建议

  1. 监控与告警:监控锁的平均持有时间,设置合理告警阈值
  2. 压力测试:评估业务逻辑在不同负载下的执行时间
  3. 熔断机制:当锁频繁过期时,触发熔断保护系统
  4. 日志记录:详细记录锁的获取、续期和释放过程
获取锁
记录开始时间
执行业务逻辑
是否完成?
释放锁
检查剩余时间
时间充足?
尝试续期
续期成功?
中止操作并回滚

结论

分布式锁在未完成逻辑前过期是一个需要认真对待的问题。通过合理设置过期时间、实现锁续期机制、结合业务幂等设计等多种手段,可以有效地解决或缓解这一问题。在实际应用中,应根据业务特点和系统需求选择合适的解决方案或组合方案。

最终,没有放之四海而皆准的完美方案,只有最适合当前业务场景的权衡选择。理解每种方案的优缺点,才能构建出健壮可靠的分布式系统。

更多推荐