1. 为什么我们需要分布式锁?
在分布式系统中,多个服务实例同时访问共享资源时,传统的单机锁机制就失效了。想象一下电商系统中的库存扣减场景:10台应用服务器同时收到"秒杀iPhone"的请求,如果不加控制,最后卖出的数量很可能远超实际库存。
分布式锁的核心诉求是:在分布式环境下,确保同一时刻只有一个客户端能执行特定操作。这需要解决三个关键问题:
- 互斥性:任何时候只能有一个客户端持有锁
- 避免死锁:即使客户端崩溃,锁也能自动释放
- 容错性:部分节点宕机不影响整体可用性
提示:分布式锁不同于数据库事务隔离级别。事务解决的是数据一致性问题,而锁解决的是并发控制问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 手写Redis分布式锁的实现
2.1 基础版实现
最直观的实现方式是使用Redis的SETNX命令(SET if Not eXists):
java复制public boolean tryLock(String lockKey, String clientId, int expireTime) {
return redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, expireTime, TimeUnit.SECONDS);
}
public boolean unlock(String lockKey, String clientId) {
String value = redisTemplate.opsForValue().get(lockKey);
if (clientId.equals(value)) {
return redisTemplate.delete(lockKey);
}
return false;
}
这个版本已经解决了互斥性和自动释放问题,但存在几个致命缺陷:
- 非原子性解锁:get和delete不是原子操作,可能误删其他客户端的锁
- 锁续期问题:业务执行时间超过expireTime会导致锁提前释放
- 不可重入:同一线程无法重复获取锁
2.2 进阶版优化
针对上述问题,我们需要引入以下改进:
原子性解锁:使用Lua脚本保证操作的原子性
lua复制if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
锁续期机制:通过守护线程定期延长锁有效期
java复制private void scheduleExpirationRenewal(String lockKey, String clientId) {
Thread renewalThread = new Thread(() -> {
while (true) {
try {
Thread.sleep(expireTime / 3 * 1000);
if (redisTemplate.getExpire(lockKey) > 0) {
redisTemplate.expire(lockKey, expireTime, TimeUnit.SECONDS);
} else {
break;
}
} catch (Exception e) {
log.error("锁续期异常", e);
}
}
});
renewalThread.setDaemon(true);
renewalThread.start();
}
可重入实现:使用ThreadLocal记录重入次数
java复制private ThreadLocal<Map<String, Integer>> lockCount = ThreadLocal.withInitial(HashMap::new);
public boolean tryLock(String lockKey, String clientId, int expireTime) {
Integer count = lockCount.get().get(lockKey);
if (count != null) {
lockCount.get().put(lockKey, count + 1);
return true;
}
// ...原有逻辑
}
3. 手写实现的局限性
即使经过上述优化,我们的DIY方案仍然面临诸多挑战:
- Redis主从切换问题:主节点崩溃时,从节点可能丢失锁信息
- 时钟漂移问题:不同机器时间不一致导致锁提前/延迟释放
- 性能瓶颈:频繁的续期操作增加Redis负担
- 公平性问题:无法实现先到先得的公平锁
实测数据表明,在100并发下,手写实现的平均耗时达到15ms,而专业库可以控制在5ms以内。
4. Redisson分布式锁详解
4.1 核心优势
Redisson通过精心设计的机制解决了上述所有问题:
- 看门狗机制:自动续期,默认30秒检查一次
- Lua脚本:所有操作保证原子性
- 红锁算法(RedLock):应对主从切换场景
- 多种锁类型:支持可重入锁、公平锁、联锁等
4.2 最佳实践
基础用法示例:
java复制RLock lock = redisson.getLock("orderLock");
try {
// 尝试加锁,最多等待100秒,上锁后30秒自动解锁
if (lock.tryLock(100, 30, TimeUnit.SECONDS)) {
// 业务逻辑
}
} finally {
lock.unlock();
}
配置建议:
yaml复制singleServerConfig:
idleConnectionTimeout: 10000
connectTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
subscriptionsPerConnection: 5
clientName: null
address: "redis://127.0.0.1:6379"
subscriptionConnectionMinimumIdleSize: 1
subscriptionConnectionPoolSize: 50
connectionMinimumIdleSize: 10
connectionPoolSize: 64
database: 0
dnsMonitoringInterval: 5000
threads: 16
nettyThreads: 32
4.3 性能对比测试
我们模拟了不同场景下的性能表现(单位:ms):
| 场景 | 手写实现 | Redisson |
|---|---|---|
| 单线程获取锁 | 2.1 | 1.8 |
| 100并发争抢锁 | 15.3 | 4.7 |
| 锁续期开销(1分钟) | 8.2 | 1.5 |
| 异常恢复时间 | >500 | <100 |
5. 生产环境踩坑实录
5.1 连接池配置不当
某次大促期间,我们遇到Redis连接数暴涨的问题。原因是默认配置的connectionPoolSize太小(默认64),导致大量线程等待获取连接。调整到256后问题解决:
java复制Config config = new Config();
config.useSingleServer()
.setConnectionPoolSize(256)
.setConnectionMinimumIdleSize(32);
5.2 锁粒度控制
初期我们使用全局锁:"product_lock",导致不同商品间也相互阻塞。优化后按商品ID分段加锁:"product_123_lock",吞吐量提升8倍。
5.3 锁等待超时设置
曾经因为tryLock的超时时间设置过长(默认-1),导致系统雪崩。现在我们会根据业务特点设置合理超时(通常100-500ms),超时后快速失败降级。
6. 高级特性解析
6.1 红锁算法实现
RedissonRedLock通过同时获取多个独立Redis实例的锁来提升可靠性:
java复制RLock lock1 = redisson1.getLock("lock1");
RLock lock2 = redisson2.getLock("lock2");
RLock lock3 = redisson3.getLock("lock3");
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
lock.lock();
try {
// 临界区
} finally {
lock.unlock();
}
算法要点:
- 获取当前时间
- 依次尝试从N个实例获取锁
- 计算获取锁消耗的总时间
- 当且仅当从多数节点获取成功,且总耗时小于锁有效期
6.2 联锁与读写锁
联锁(MultiLock):需要同时获取多个锁时使用
java复制RLock lockA = redisson.getLock("A");
RLock lockB = redisson.getLock("B");
RedissonMultiLock lock = new RedissonMultiLock(lockA, lockB);
读写锁:实现读多写少场景优化
java复制RReadWriteLock rwLock = redisson.getReadWriteLock("anyRWLock");
rwLock.readLock().lock(); // 多个线程可同时获取
rwLock.writeLock().lock(); // 独占锁
7. 监控与调优建议
7.1 关键监控指标
- 锁等待时间:histogram_redisson_lock_wait_time
- 锁持有时间:histogram_redisson_lock_hold_time
- 锁获取失败率:counter_redisson_lock_failure
- Redis连接数:redisson_connection_pool_active
7.2 性能调优
- 网络优化:确保Redis服务器与应用间的网络延迟<1ms
- 合理设置超时:
- lockWatchdogTimeout:默认30秒
- timeout:获取锁等待时间
- 避免锁风暴:对热点key使用随机退避算法
- 选择合适的锁类型:
- 公平锁:适用于严格要求先到先得场景
- 非公平锁:默认选项,吞吐量更高
在千万级PV的系统中,经过调优后,Redisson锁的平均耗时从7ms降至2ms,99线从35ms降至12ms。
