在现代分布式系统中,多个服务实例同时访问共享资源时会产生竞态条件。去年我们电商平台的库存超卖事故,就是因为秒杀场景下多个节点同时扣减库存导致的。传统单机锁在分布式环境下完全失效,这时就需要引入分布式锁机制。
Redis凭借其高性能和丰富的数据结构,成为实现分布式锁的首选方案。相比Zookeeper等方案,Redis的TPS高出1-2个数量级,这对高并发场景至关重要。我曾测试过,单节点Redis在普通服务器上就能轻松达到10W+ QPS的锁操作吞吐量。
典型应用场景包括:
关键认知:分布式锁本质是通过在Redis中设置一个全局唯一的"标记"来实现互斥。这个标记需要满足三个核心特性:互斥性(唯一持有)、无死锁(自动释放)、容错性(服务宕机不影响)。
最基础的Redis锁使用SETNX命令:
bash复制SETNX lock_key unique_value
当返回1表示获取锁成功。释放锁时执行DEL命令。但这种方式存在严重问题:如果客户端崩溃,锁将永远无法释放。
改进方案是加入过期时间:
bash复制SET lock_key unique_value NX PX 30000
这虽然解决了死锁问题,但又引入了新的隐患。比如客户端A执行时间超过30秒,锁自动释放后,客户端B获取锁,此时A完成任务后可能错误删除B的锁。
去年我们金融系统就遭遇过这样的生产事故:
经过多次踩坑,我总结出原生实现的主要问题:
Redisson通过组合多种机制解决了上述问题:
java复制// 数据结构示例
"myLock": {
"thread_1": 3 // 线程1重入了3次
}
以获取锁为例,核心Lua脚本逻辑:
lua复制-- 参数:KEYS[1]锁名, ARGV[1]超时时间, ARGV[2]线程标识
if (redis.call('exists', KEYS[1]) == 0) then
redis.call('hset', KEYS[1], ARGV[2], 1)
redis.call('pexpire', KEYS[1], ARGV[1])
return nil
end
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
redis.call('hincrby', KEYS[1], ARGV[2], 1)
redis.call('pexpire', KEYS[1], ARGV[1])
return nil
end
return redis.call('pttl', KEYS[1])
这段脚本实现了:
通过压测对比发现,Redisson相比原生实现有显著优势:
| 指标 | 原生方案 | Redisson |
|---|---|---|
| 获取锁平均耗时(ms) | 2.1 | 1.8 |
| 可支撑QPS | 12,000 | 15,000 |
| 网络往返次数 | 2-3 | 1 |
优势来源于:
Spring Boot集成配置:
java复制@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redisson() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setConnectionPoolSize(64);
return Redisson.create(config);
}
}
@Service
public class PaymentService {
@Autowired
private RedissonClient redisson;
public void processPayment(String orderId) {
RLock lock = redisson.getLock("order:" + orderId);
try {
// 等待时间10秒,锁自动释放时间30秒
if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
// 业务逻辑
}
} finally {
lock.unlock();
}
}
}
在application.yml中建议配置:
yaml复制redisson:
single-server-config:
idle-connection-timeout: 10000
connect-timeout: 3000
timeout: 3000
retry-attempts: 3
retry-interval: 1500
subscriptions-per-connection: 5
connection-minimum-idle-size: 32
connection-pool-size: 64
对于金融级场景,建议采用多活架构:
java复制RLock lock1 = redisson1.getLock("lock");
RLock lock2 = redisson2.getLock("lock");
RLock lock3 = redisson3.getLock("lock");
RLock multiLock = redisson.getMultiLock(lock1, lock2, lock3);
现象:日志中出现"Watchdog timeout"警告
排查步骤:
TTL myLockping redis-hostjava复制Config config = new Config();
config.setLockWatchdogTimeout(15000);
我们总结的"四要一不要"原则:
典型性能瓶颈及解决方案:
| 问题现象 | 优化方案 |
|---|---|
| 获取锁耗时波动大 | 升级Redis到6.0+使用多线程模型 |
| 高并发下成功率下降 | 增加连接池大小,使用红锁分流 |
| 主从切换后锁丢失 | 启用Redisson的RedLock模式 |
| 大量WAIT命令堆积 | 调整netty线程数,隔离锁操作连接池 |
库存查询(读锁)与扣减(写锁)分离:
java复制RReadWriteLock rwLock = redisson.getReadWriteLock("stock:1001");
// 读锁(共享)
rwLock.readLock().lock();
try {
// 查询库存
} finally {
rwLock.readLock().unlock();
}
// 写锁(排他)
rwLock.writeLock().lock();
try {
// 扣减库存
} finally {
rwLock.writeLock().unlock();
}
跨服务事务控制案例:
java复制// 订单服务锁
RLock orderLock = redisson.getLock("order:"+orderId);
// 库存服务锁
RLock stockLock = redisson.getLock("stock:"+skuId);
RedissonMultiLock multiLock = new RedissonMultiLock(orderLock, stockLock);
try {
if (multiLock.tryLock(100, 10, TimeUnit.SECONDS)) {
// 创建订单
// 扣减库存
}
} finally {
multiLock.unlock();
}
秒杀场景下的并发控制:
java复制RSemaphore semaphore = redisson.getSemaphore("flash_sale");
// 设置许可数量(库存)
semaphore.trySetPermits(100);
// 获取许可(非阻塞)
if (semaphore.tryAcquire()) {
try {
// 处理秒杀请求
} finally {
semaphore.release();
}
}
在实际使用Redisson三年多的时间里,最大的体会是:分布式锁不是银弹,必须根据业务特点选择合适的锁类型和参数。对于金融等高敏感场景,建议额外增加数据库乐观锁作为兜底方案。最近我们还尝试将Redisson与Sentinel规则联动,当锁竞争激烈时自动触发限流,这个实践效果相当不错。