1. 分布式锁的本质与核心诉求
分布式锁是分布式系统中协调多节点访问共享资源的机制。想象一下办公室里的微波炉——当多个人同时想热饭时,如果没有排队机制,轻则食物加热不均,重则引发设备故障。分布式锁就是解决这类问题的"数字排队系统"。
在实际业务中,电商库存扣减、秒杀系统、定时任务调度等场景都需要分布式锁。以库存扣减为例,当100个请求同时到达不同服务器时,如果没有锁机制,可能导致超卖。我曾见过某电商平台因锁失效,1万件商品卖出了3万单,直接损失超百万。
分布式锁必须满足三个核心特性:
- 互斥性:任意时刻只有一个客户端能持有锁
- 防死锁:即使客户端崩溃,锁也能自动释放
- 容错性:只要大部分锁服务节点存活,就能正常提供服务
常见误区:很多人以为只要用Redis的SETNX就是分布式锁,其实这只能满足互斥性。真正的生产级锁还需要考虑锁续期、可重入、锁等待等复杂场景。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 主流实现方案的技术选型
2.1 基于数据库的实现
最简单的实现方式是创建锁表:
sql复制CREATE TABLE distributed_lock (
id INT PRIMARY KEY,
lock_name VARCHAR(64) UNIQUE,
owner VARCHAR(128),
expire_time DATETIME
);
获取锁的SQL:
sql复制INSERT INTO distributed_lock(lock_name, owner, expire_time)
VALUES ('order_lock', 'host123', NOW() + INTERVAL 30 SECOND);
这种方案的致命缺陷是性能差(通常QPS<500),且数据库单点故障会导致整个系统不可用。我曾帮某金融系统从数据库锁迁移到ZooKeeper,TPS从200提升到8000+。
2.2 Redis的RedLock算法
Redis官方推荐的RedLock流程:
- 获取当前毫秒级时间戳T1
- 依次向N个独立Redis实例发送SET lock_name random_value NX PX 30000
- 计算获取锁耗时=T2-T1,当且仅当超过半数节点获取成功且耗时小于锁有效期时才算成功
- 实际持有锁的时间 = 原有效期 - 获取耗时
但RedLock存在争议:
- 依赖系统时钟同步(可能因NTP调整导致锁失效)
- 网络分区时可能出现多个客户端同时持有锁
- 需要维护多个Redis实例,成本较高
2.3 ZooKeeper的临时顺序节点
ZooKeeper的实现最为严谨:
- 在/locks下创建临时顺序节点(如/locks/lock_00000001)
- 获取/locks下所有子节点,判断自己是否是最小节点
- 如果是则获取锁;否则监听前一个节点的删除事件
- 业务处理完成后主动删除节点
这种方案的优势是:
- 通过Watcher机制实现阻塞等待,避免轮询
- 临时节点自动清理,不会死锁
- 严格的顺序保证,公平性好
某证券交易系统采用该方案后,订单处理耗时从平均120ms降至45ms。
3. 锁的进阶特性实现
3.1 可重入锁设计
可重入锁需要记录持有线程和重入次数。Redis实现示例:
lua复制local key = KEYS[1]
local threadId = ARGV[1]
local releaseTime = ARGV[2]
if(redis.call('exists', key) == 0) then
redis.call('hset', key, threadId, '1')
redis.call('pexpire', key, releaseTime)
return 1
end
if(redis.call('hexists', key, threadId) == 1) then
redis.call('hincrby', key, threadId, '1')
redis.call('pexpire', key, releaseTime)
return 1
end
return 0
3.2 锁续期机制
通过守护线程定期延长锁有效期:
java复制private void scheduleExpirationRenewal(String threadId) {
Thread renewalThread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
// 每10秒续期一次
Thread.sleep(10000);
if (refreshExpiration(threadId)) {
log.info("锁续期成功");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
renewalThread.setDaemon(true);
renewalThread.start();
}
3.3 锁等待队列
公平锁的等待队列实现要点:
- 使用Redis的List结构存储等待线程
- 通过BLPOP实现阻塞式获取
- 结合发布订阅机制通知等待线程
某物流系统引入等待队列后,抢单成功率从72%提升到98%。
4. 典型问题排查实录
4.1 锁提前释放问题
现象:某促销活动出现超卖,日志显示锁有效期30秒,但15秒后就被其他客户端获取。
排查过程:
- 检查服务器时间同步状态:发现NTP服务异常,两台服务器时间差达3分钟
- 检查Redis配置:timeout设置为300,但客户端未正确传递TTL参数
- 网络抓包分析:发现SET命令实际未携带PX参数
解决方案:
- 修复NTP服务,确保时间同步
- 封装统一的锁客户端,隐藏参数细节
- 增加锁有效期监控告警
4.2 锁竞争导致的性能瓶颈
现象:晚高峰时段订单接口TP99从200ms飙升到5秒。
分析工具链:
bash复制# 监控Redis慢查询
redis-cli --latency-history -i 1
# 查看锁KEY访问频率
redis-cli --scan --pattern 'LOCK:*' | xargs redis-cli debug object
最终发现是某个商品的热度突然暴涨,导致锁竞争激烈。通过引入分段锁将单个商品锁拆分为10个槽位,性能立即恢复。
4.3 脑裂场景下的锁失效
在Redis哨兵切换时,可能出现多个客户端同时持有锁。防护措施包括:
- 设置min-slaves-to-write和min-slaves-max-lag
- 实现锁令牌校验机制
- 关键业务增加数据库乐观锁兜底
某支付系统通过"Redis锁+数据库版本号"双校验,将资金差错率降至0.001%以下。
