与tryLock())
1. 分布式锁的核心作用与Redisson简介想象一下双十一秒杀场景10万用户同时抢购100台特价手机。如果没有锁机制系统可能会超卖——明明库存只有100台却卖出200单。这就是分布式锁要解决的核心问题在分布式系统中确保同一时刻只有一个服务实例能操作共享资源。Redisson作为Redis官方推荐的Java客户端提供了开箱即用的分布式锁实现。我在电商项目中实测发现相比自己手写Lua脚本实现锁Redisson的锁性能高出23%而且内置了看门狗自动续期、可重入等高级特性。比如下面这段代码就能快速创建一个分布式锁RLock lock redisson.getLock(orderLock); lock.lock(); try { // 处理订单业务 } finally { lock.unlock(); }但很多开发者容易忽略一个关键点Redisson提供了**lock()和tryLock()**两种加锁方式选错可能导致性能下降甚至死锁。去年我们团队就遇到过因为误用lock()导致线程堆积最终拖垮Redis的线上事故。2. lock()简单粗暴的阻塞式锁2.1 基础特性与实现原理lock()是典型的阻塞式锁它的行为就像地铁闸机——不等到空位绝不离开。通过分析源码可以发现当锁被占用时当前线程会通过Redis的pub/sub机制订阅锁释放事件在while循环中持续尝试获取// 简化后的核心逻辑 while (true) { Long ttl tryAcquire(); if (ttl null) { // 获取成功 break; } // 通过Redis频道等待锁释放通知 subscribeToLockRelease(); }这种设计带来三个重要特性无条件等待不设超时时间必须等到锁释放自动续期默认30秒后启动看门狗线程定期延长锁有效期可重入同一线程可重复加锁通过计数器实现2.2 典型应用场景在金融支付系统中我们强制要求资金操作必须使用lock()。比如用户A向B转账时必须同时锁定A和B的账户RLock lockA redisson.getLock(account: A); RLock lockB redisson.getLock(account: B); try { lockA.lock(); lockB.lock(); // 执行转账逻辑 } finally { lockB.unlock(); lockA.unlock(); }这种场景下使用lock()是因为业务强一致性要求必须确保账户锁定成功操作耗时可控转账通常在200ms内完成避免死锁风险通过按固定顺序加锁如ID小的先锁2.3 踩坑记录与最佳实践去年我们遇到过一个典型问题某定时任务使用lock()处理对账结果任务执行时间超过锁默认租期30秒虽然看门狗会续期但网络闪断导致续期失败引发多节点同时执行。改进方案是// 明确指定锁持有时间大于最大预估执行时间 lock.lock(5, TimeUnit.MINUTES);关键建议永远在finally中解锁防止线程异常导致锁泄漏避免嵌套锁容易引发死锁如需多锁应按固定顺序获取监控锁等待时间通过Redisson的getLock(lock).getHoldCount()监控3. tryLock()灵活的非阻塞式锁3.1 核心优势与参数解析tryLock()就像医院取号机——等不及可以放弃。它支持三个关键参数waitTime最大等待时间如3秒leaseTime锁持有时间自动释放unit时间单位if (lock.tryLock(3, 10, TimeUnit.SECONDS)) { try { // 获取锁成功 } finally { lock.unlock(); } } else { // 快速失败处理 }与lock()的差异对比特性lock()tryLock()等待策略死等可设置超时返回值无boolean自动续期支持需显式设置leaseTime适用场景关键业务高并发场景3.2 秒杀场景下的实战应用在618大促时我们通过tryLock()优化了秒杀性能。当库存仅剩100件时1万个请求同时到达public boolean seckill(Long itemId) { RLock lock redisson.getLock(item: itemId); try { // 等待不超过50ms锁持有100ms if (lock.tryLock(50, 100, TimeUnit.MILLISECONDS)) { if (stock 0) { stock--; return true; } } return false; } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } }这样设计带来三大好处快速失败50ms内未获锁直接返回秒杀失败防止雪崩避免线程堆积拖垮Redis自动释放即使业务逻辑异常100ms后锁自动释放3.3 参数调优经验通过压测我们发现waitTime的设置非常关键设置过短如10ms在网络抖动时误判为锁冲突设置过长如1秒失去快速失败的意义建议公式最优waitTime 平均网络耗时 × 2 Redis平均响应时间比如我们环境测得内网延迟2msRedis平均响应3ms最终设置(2×2)37ms实际采用10ms保留余量4. 混合使用策略与进阶技巧4.1 根据业务特征选择锁类型通过分析业务SLA指标来决定锁策略强一致性业务如支付使用lock()leaseTime业务最大耗时×1.5必须配合熔断机制高并发业务如秒杀使用tryLock()waitTime100ms必须实现降级方案批量任务混合使用先tryLock()失败后转lock()示例代码if (!lock.tryLock(1, TimeUnit.SECONDS)) { log.warn(快速获取失败转为强锁模式); lock.lock(5, TimeUnit.MINUTES); }4.2 看门狗机制的注意事项Redisson的看门狗默认在锁持有30秒后启动每10秒续期一次。但要注意tryLock()需显式启用// 正确的启用方式 lock.tryLock(0, -1, TimeUnit.SECONDS);避免GC停顿导致续期失败建议设置JVM参数-XX:MaxGCPauseMillis2004.3 锁监控与问题排查我们团队开发的监控方案Redis监控# 查看锁key数量 redis-cli keys *lock* | wc -l日志追踪// 记录锁等待时间 long start System.currentTimeMillis(); lock.lock(); log.info(锁等待耗时{}ms, System.currentTimeMillis()-start);线程诊断通过jstack检查锁等待线程栈5. 性能对比与压测数据在4核8G的Redis集群上我们模拟不同场景的测试结果场景QPSlock()QPStryLock()平均耗时低竞争10线程12,00011,8002ms高竞争100线程3,2008,50015ms超高竞争500线程1,100出现超时5,20045ms关键发现低并发时差异不大竞争少时两种锁性能接近高并发时tryLock优势明显快速失败避免线程堆积lock()的雪崩风险当线程数超过Redis连接池大小时性能骤降6. 特别注意事项避免锁嵌套// 错误示范可能死锁 public void methodA() { lock.lock(); methodB(); lock.unlock(); } public void methodB() { lock.lock(); // 这里会阻塞 // ... lock.unlock(); }跨服务解锁风险服务A获取锁后崩溃锁被服务B误删解决方案// 设置唯一value String token UUID.randomUUID().toString(); if (lock.tryLock(0, -1, TimeUnit.SECONDS, token)) { // ... lock.unlock(token); }Redis主从切换问题在主节点获取锁后主节点崩溃从节点升级为主但未同步锁信息解决方案使用RedissonRedLock需至少3个主节点