
1. Redis分布式锁的核心价值与应用场景在分布式系统中多个服务实例对共享资源的并发访问是个经典难题。想象一下电商系统中的库存扣减场景当100台手机秒杀时如果没有锁机制可能出现超卖问题。Redis分布式锁正是为解决这类问题而生。与传统单机锁不同Redis分布式锁需要满足三个核心特性互斥性任何时候只能有一个客户端持有锁避免死锁即使客户端崩溃锁也能自动释放容错性只要大部分Redis节点存活客户端就能获取和释放锁我经历过一个真实案例某金融系统在促销活动时由于锁实现不当导致重复转账。后来采用Redis分布式锁方案后不仅解决了并发问题性能还比数据库锁提升了20倍。2. 原子性加锁的实现方案2.1 SETNX命令的局限性很多开发者最初会想到用SETNX命令SETNX lock_key 1但这种方式存在严重缺陷客户端崩溃会导致锁永远不释放没有设置过期时间容易产生死锁非原子性操作设置值和过期时间分两步执行2.2 正确的原子性加锁方案Redis 2.6.12之后SET命令新增了NX和PX选项完美解决了上述问题SET lock_key unique_value NX PX 30000这个命令的每个参数都有讲究unique_value必须是客户端唯一的标识如UUID这是安全释放锁的关键NX只有key不存在时才设置实现互斥PX 30000设置30秒过期时间避免死锁重要提示过期时间需要根据业务操作耗时合理设置。太短会导致业务未完成锁就释放太长会影响系统可用性。建议设置为平均业务耗时的2-3倍。3. Lua脚本实现安全释放锁3.1 为什么需要Lua脚本看下面这个典型错误示例if redis.call(get,KEYS[1]) ARGV[1] then return redis.call(del,KEYS[1]) else return 0 end表面看没问题但实际上存在严重竞态条件客户端A获取锁由于GC停顿A的执行被暂停锁过期自动释放客户端B获取锁A恢复执行错误地删除了B的锁3.2 原子性释放锁的Lua实现正确的Lua脚本应该这样写if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end这个脚本的精妙之处在于比较和删除是原子性执行的通过唯一值验证确保不会误删其他客户端的锁返回删除结果便于客户端判断是否释放成功在Java中的调用示例String script if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end; Object result jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));4. 生产环境中的进阶实践4.1 锁续期机制对于执行时间不确定的长任务需要实现锁续期看门狗机制private void scheduleExpirationRenewal(final String lockKey, final String requestId) { Thread renewalThread new Thread(() - { while (true) { try { // 每10秒续期一次 Thread.sleep(10000); if (checkLockOwnership(lockKey, requestId)) { jedis.expire(lockKey, 30); } else { break; } } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } }); renewalThread.start(); }4.2 红锁算法RedLock在Redis集群环境下为避免主从切换导致锁失效可以采用RedLock算法获取当前时间毫秒依次向N个Redis节点获取锁计算获取锁总耗时只有耗时小于锁过期时间且获得多数节点认可才算成功锁的实际有效时间 初始有效时间 - 获取锁耗时注意RedLock算法存在争议某些场景下可能不如单节点Redis锁可靠。建议仅在确实需要高可用性时使用。5. 常见问题与性能优化5.1 典型问题排查表问题现象可能原因解决方案锁提前释放业务执行时间超过锁过期时间增加过期时间或实现续期机制锁无法释放释放锁时未验证唯一值使用Lua脚本确保原子性验证锁竞争激烈系统负载过高增加随机等待时间实现退避算法Redis连接超时网络问题或Redis负载高优化网络配置增加Redis资源5.2 性能优化技巧锁粒度控制尽可能减小锁的范围比如对商品ID加锁而非全局锁非阻塞尝试使用SET key value NX PX timeout的返回值判断是否获取成功等待策略获取锁失败时采用指数退避算法int retryCount 0; while (!tryLock()) { Thread.sleep(Math.min(100 * (2 ^ retryCount), 5000)); retryCount; }监控告警通过Redis的INFO命令监控锁竞争情况设置合理的告警阈值6. 与其他方案的对比6.1 数据库实现分布式锁基于数据库唯一索引的实现CREATE TABLE distributed_lock ( lock_key varchar(64) NOT NULL, expire_time datetime NOT NULL, PRIMARY KEY (lock_key) );缺点性能较差约Redis的1/10容易产生死锁对数据库压力大6.2 Zookeeper实现分布式锁利用临时顺序节点实现创建临时顺序节点判断自己是否是最小节点监听前一个节点的删除事件优势可靠性高自带watch机制劣势性能低于Redis部署复杂度高在实际项目中我通常会根据业务特点选择方案对一致性要求极高的用Zookeeper对性能要求高的用Redis数据库方案基本不考虑。