1. 分布式锁的核心概念与必要性
在分布式系统中,多个服务实例需要协调对共享资源的访问时,传统的单机锁机制(如Java的synchronized或ReentrantLock)就完全失效了。我曾经在一个电商秒杀系统中深刻体会过这一点——当部署了三个服务实例后,发现库存出现了严重的超卖问题,这就是典型的分布式环境下的并发控制失效案例。
分布式锁的本质是一种跨进程的互斥机制,它需要满足三个基本特性:
互斥性:这是最基础的要求,任意时刻只能有一个客户端持有锁。在实际项目中,我曾遇到过由于锁实现不当导致两个订单系统同时处理同一笔交易,造成了严重的资金对账问题。
死锁预防:必须设置合理的超时机制。去年我们系统就发生过一起事故:一个获取锁的服务实例崩溃后没有释放锁,导致整个订单系统瘫痪了2小时。后来我们引入了带有TTL的锁设计才解决这个问题。
容错性:锁服务必须能够应对网络分区和节点故障。我们使用Redis集群时就遇到过脑裂情况,导致出现了双主节点同时发放锁的严重问题。
2. 基于数据库的分布式锁实现
2.1 使用SELECT FOR UPDATE实现
MySQL的SELECT FOR UPDATE语句是最直接的实现方式。在我们的支付系统中,初期就是采用这种方案:
BEGIN; SELECT * FROM account WHERE user_id=123 FOR UPDATE; -- 执行余额变更操作 UPDATE account SET balance=balance-100 WHERE user_id=123; COMMIT;关键细节:
- WHERE条件必须使用索引列,否则会锁表
- 事务隔离级别建议设为READ COMMITTED
- 长时间持有锁会导致连接池耗尽
2.2 专用锁表方案
更专业的做法是创建专用的锁表:
CREATE TABLE distributed_lock ( id VARCHAR(64) PRIMARY KEY, owner VARCHAR(128), expire_time DATETIME, INDEX idx_expire (expire_time) );加锁逻辑:
INSERT INTO distributed_lock(id, owner, expire_time) VALUES ('order_lock', 'service1', DATE_ADD(NOW(), INTERVAL 30 SECOND)) ON DUPLICATE KEY UPDATE owner = IF(expire_time < NOW(), VALUES(owner), owner), expire_time = IF(expire_time < NOW(), VALUES(expire_time), expire_time);这种方案在MySQL 5.7+上的QPS大约能到500左右,适合并发不高的场景。
3. ZooKeeper分布式锁的实战细节
3.1 临时顺序节点机制
ZooKeeper通过临时顺序节点实现锁的公平排队。在我们的配置中心系统中,使用Curator框架的实现如下:
InterProcessMutex lock = new InterProcessMutex(client, "/config/lock"); try { if (lock.acquire(30, TimeUnit.SECONDS)) { // 处理关键配置变更 updateConfig(); } } finally { lock.release(); }踩坑经验:
- 一定要设置合理的acquire超时时间
- 节点路径要避免过深,建议不超过3层
- 要处理ConnectionLossException等异常
3.2 锁竞争的性能优化
当大量客户端竞争锁时,会出现"惊群效应"。我们通过以下方式优化:
- 增加锁分片:将单个锁拆分为多个子锁
- 实现锁缓存:本地缓存已获得的锁状态
- 使用异步通知机制替代Watcher轮询
在我们的测试环境中,优化后锁获取时间从平均120ms降到了35ms。
4. Redis分布式锁的完整实现方案
4.1 基础SETNX实现的缺陷
初版的Redis锁实现有很多陷阱:
// 错误示范! public boolean lock(Jedis jedis, String key, String value, int expire) { Long result = jedis.setnx(key, value); if (result == 1) { // 这里可能崩溃导致死锁 jedis.expire(key, expire); return true; } return false; }4.2 正确的原子化实现
Redis 2.6.12+提供了更好的方案:
public boolean lock(Jedis jedis, String key, String value, int expire) { String result = jedis.set(key, value, "NX", "PX", expire); return "OK".equals(result); }关键参数:
- NX:仅当key不存在时设置
- PX:设置过期时间(毫秒)
- value应该是唯一标识(如UUID)
4.3 锁续期与释放的安全机制
我们实现的LockGuard工具类包含以下保护:
public class LockGuard implements AutoCloseable { private String lockKey; private String lockValue; private JedisPool jedisPool; private ScheduledExecutorService scheduler; public LockGuard(String key, int timeout) { this.lockKey = key; this.lockValue = UUID.randomUUID().toString(); this.scheduler = Executors.newSingleThreadScheduledExecutor(); // 获取锁 acquireLock(timeout); // 启动续期线程 scheduler.scheduleAtFixedRate(() -> renewLock(), timeout/3, timeout/3, TimeUnit.MILLISECONDS); } private void renewLock() { try (Jedis jedis = jedisPool.getResource()) { jedis.expire(lockKey, timeout); } } @Override public void close() { scheduler.shutdown(); try (Jedis jedis = jedisPool.getResource()) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " + " return redis.call('del', KEYS[1]) " + "else " + " return 0 " + "end"; jedis.eval(script, 1, lockKey, lockValue); } } }5. Redisson生产级解决方案
5.1 完整分布式锁特性
Redisson提供了开箱即用的解决方案:
RLock lock = redisson.getLock("orderLock"); try { // 尝试加锁,最多等待10秒,锁有效期30秒 if (lock.tryLock(10, 30, TimeUnit.SECONDS)) { // 处理业务逻辑 processOrder(); } } finally { lock.unlock(); }核心优势:
- 自动续期(看门狗机制)
- 可重入锁支持
- 丰富的锁类型(公平锁、联锁等)
5.2 高可用部署方案
对于关键业务,我们采用多主Redis集群+Redlock算法:
Config config1 = new Config(); config1.useClusterServers() .addNodeAddress("redis://node1:6379"); Config config2 = new Config(); config2.useClusterServers() .addNodeAddress("redis://node2:6379"); RedissonClient client1 = Redisson.create(config1); RedissonClient client2 = Redisson.create(config2); RLock lock1 = client1.getLock("lock"); RLock lock2 = client2.getLock("lock"); RedissonRedLock lock = new RedissonRedLock(lock1, lock2); lock.lock(); try { // 处理业务 } finally { lock.unlock(); }在实际压力测试中,这套方案可以承受10,000+ QPS的锁请求。
6. 性能对比与选型建议
根据我们的基准测试数据(基于4核8G云主机):
| 方案 | 平均耗时(ms) | 最大QPS | 适用场景 |
|---|---|---|---|
| MySQL行锁 | 15-25 | 500 | 低并发、强一致需求 |
| ZooKeeper | 30-50 | 3,000 | 需要严格顺序的场景 |
| Redis单机 | 2-5 | 15,000 | 高并发、最终一致 |
| Redis集群+Redlock | 8-12 | 8,000 | 高可用关键业务 |
选型建议:
- 金融交易类系统:ZooKeeper(强一致)
- 电商库存系统:Redis集群+Redisson
- 后台任务调度:MySQL行锁(简单可靠)
- 配置管理系统:ZooKeeper(Watcher机制)
在实际项目中,我们通常会根据业务特点采用混合方案。比如在订单系统中,对核心支付流程使用ZooKeeper锁,而对库存扣减则使用Redis锁。