分布式架构中的互斥锁构建与实践
分布式环境下的锁机制需求
在单体应用中,利用 JVM 内部的同步机制(如 Synchronized 或 ReentrantLock)即可有效处理多线程竞争。然而,一旦系统演进为集群部署或多进程架构,本地锁便失效了。此时,为了保证共享资源的数据一致性,必须引入分布式锁。
典型的应用场景包括电商交易系统中的库存扣减或定时取消未支付订单任务。如果多个服务实例同时触发该任务,可能会导致重复扣减或状态异常。因此,需要确保在同一时刻,全局范围内仅有一个进程能持有执行权。这通常依赖于第三方的共享存储组件,如 Redis、ZooKeeper 或关系型数据库。
基于 Redis 的原子性方案
早期实现常使用 SETNX 指令配合过期时间。但存在一个明显的竞争窗口:若获取锁成功,但在设置过期时间前进程宕机,将导致死锁。虽然可以通过轮询检查锁的有效期来补救,但这增加了网络开销和逻辑复杂度。
更优的实践是利用 Redis 提供的复合命令,在一个原子操作中完成"设值"与"设置过期时间"。这种方式消除了中间步骤的隐患。以下是一个优化后的工具类示例:
public class RedisDistributedLock {
private static final String LOCK_PREFIX = "dist_lock:";
private final JedisPool jedisPool;
public boolean acquire(String businessKey, long timeoutSeconds) {
String lockKey = LOCK_PREFIX + businessKey;
String uniqueValue = UUID.randomUUID().toString();
try (Jedis jedis = jedisPool.getResource()) {
// 使用 SET 命令的 NX (仅不存在时设置) 和 PX (毫秒级过期) 参数
Long result = jedis.set(lockKey, uniqueValue, "NX", "PX", timeoutSeconds * 1000);
return Boolean.TRUE.equals(result);
} catch (Exception e) {
logger.error("Redis 锁操作失败", e);
}
return false;
}
public void release(String businessKey) {
String lockKey = LOCK_PREFIX + businessKey;
// 实际生产中建议通过 Lua 脚本判断 owner 再删除,防止误删其他线程的锁
// 此处简化演示逻辑
try (Jedis jedis = jedisPool.getResource()) {
if (Boolean.TRUE.equals(jedis.exists(lockKey))) {
jedis.del(lockKey);
}
}
}
}
集成 Redisson 框架
手写底层逻辑容易忽略并发边界条件,Redisson 提供了成熟的 API 封装,支持可重入锁、看门狗自动续期等高级特性。
首先在项目中配置依赖:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.0</version>
</dependency>
接着在服务层注入并使用锁对象:
@Component
public class TaskScheduler {
@Autowired
private RedissonClient redissonClient;
public void processOrderTimeout() {
String lockName = "ORDER_CANCEL_LOCK";
RLock rLock = redissonClient.getLock(lockName);
try {
// 尝试获取锁,等待时间 0,持有时间 30 秒
if (rLock.tryLock(0, 30, TimeUnit.SECONDS)) {
try {
// 执行业务逻辑:检查并关闭超时订单
executeCancelLogic();
} finally {
// 确保释放锁
rLock.unlock();
}
} else {
logger.warn("当前有其他节点正在执行取消任务");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.error("加锁过程中被中断", e);
}
}
private void executeCancelLogic() {
// 核心业务处理代码
}
}
ZooKeeper 临时节点实现
除了 Redis,ZooKeeper 也是实现强一致性分布式锁的首选方案。其原理依赖于临时顺序节点的创建。
当客户端试图获取锁时,会在指定路径下创建一个临时顺序节点。若该节点是子节点中序号最小的,则视为获得锁;否则注册监听器,监控比自己序号小的节点变化。当节点因客户端断开连接而自动删除后,后续节点会自动收到通知并重新尝试获取锁。这种机制天然避免了脑裂问题,且不需要手动处理锁续期。
