
1. 分布式锁的核心需求与挑战在分布式系统中多个进程或服务需要协调对共享资源的访问时分布式锁就成为了关键基础设施。想象一下电商系统中的库存扣减场景当多个订单同时试图购买最后一件商品时如果没有可靠的锁机制就会导致超卖问题。Redis实现分布式锁之所以流行主要基于三个特性高性能单节点可达10万 QPS原子性操作SETNX、Lua脚本等特性丰富的过期机制避免死锁但真正落地时我们会遇到几个典型问题锁误删线程A删了线程B的锁锁过期但业务未执行完主从切换导致锁失效2. 基础实现方案与缺陷分析2.1 SETNX基础命令方案最基础的实现方式SETNX lock_key unique_value EXPIRE lock_key 30这个方案存在明显缺陷SETNX和EXPIRE不是原子操作没有考虑锁续期问题删除锁时无法验证持有者2.2 Redis 2.8后的优化方案Redis 2.8版本引入了扩展参数SET lock_key unique_value NX PX 30000这个命令实现了NX仅当key不存在时设置PX设置过期时间毫秒原子性操作但依然存在锁误删问题if redis.call(get,KEYS[1]) ARGV[1] then return redis.call(del,KEYS[1]) else return 0 end3. 生产级解决方案设计3.1 Redlock算法实现Redis官方推荐的分布式锁算法需要至少5个独立Redis节点获取当前毫秒级时间戳T1依次向N个节点请求加锁使用相同的key和value计算获取锁耗时 T2 - T1当且仅当获取多数节点N/21的认可总耗时小于锁有效期import time import random def acquire_lock(redis_nodes, resource, ttl): start_time time.time() * 1000 locks_acquired 0 for node in redis_nodes: if node.set(resource, random_value, nxTrue, pxttl): locks_acquired 1 elapsed_time (time.time() * 1000) - start_time if locks_acquired len(redis_nodes)//2 1 and elapsed_time ttl: return True else: release_lock(redis_nodes, resource) return False3.2 锁续期机制实现通过守护线程实现锁续期public class LockRenewal implements Runnable { private Jedis jedis; private String lockKey; private String lockValue; private int ttl; public void run() { while (!Thread.currentThread().isInterrupted()) { try { Thread.sleep(ttl / 3 * 1000); String luaScript if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(pexpire, KEYS[1], ARGV[2]) else return 0 end; jedis.eval(luaScript, 1, lockKey, lockValue, String.valueOf(ttl)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } }4. 性能优化与生产实践4.1 锁粒度控制错误的锁粒度def deduct_stock(): with redis_lock(global_inventory_lock): # 操作所有商品库存正确的细粒度锁def deduct_stock(item_id): with redis_lock(fitem_{item_id}_lock): # 操作特定商品库存4.2 锁等待策略优化对比几种等待策略策略类型实现方式优点缺点立即返回直接返回错误简单快速用户体验差固定间隔重试Thread.sleep固定时间实现简单响应延迟高指数退避等待时间按指数增长平衡负载最大等待时间长随机间隔重试随机时间固定间隔避免惊群效应实现稍复杂推荐实现public boolean tryLock(long waitTime, TimeUnit unit) throws InterruptedException { long startTime System.nanoTime(); long duration unit.toNanos(waitTime); Random random new Random(); while (true) { if (acquireLock()) { return true; } long elapsed System.nanoTime() - startTime; if (elapsed duration) { return false; } // 随机延迟50-100ms固定间隔 Thread.sleep(50 random.nextInt(50)); } }5. 异常处理与故障排查5.1 常见问题速查表问题现象可能原因解决方案锁永久持有客户端崩溃未释放锁确保设置过期时间业务未完成锁已过期业务执行时间超过TTL实现锁续期机制多个客户端获得相同锁主从切换导致锁失效使用Redlock或多实例确认删除他人持有的锁未验证锁持有者使用Lua脚本保证原子性验证删除锁等待时间过长锁竞争激烈优化锁粒度或实现公平锁5.2 监控指标设计关键监控指标锁获取成功率平均等待时间锁持有时间分布锁冲突频率锁续期次数Prometheus配置示例metrics: lock_wait_seconds: type: histogram help: Time spent waiting for lock buckets: [0.1, 0.5, 1, 2, 5] lock_acquire_total: type: counter help: Total number of lock acquisitions labels: [status]6. 高级话题与替代方案6.1 公平锁实现基于Redis List实现排队-- 入队 local result redis.call(RPUSH, KEYS[1], ARGV[1]) redis.call(EXPIRE, KEYS[1], ARGV[2]) return result -- 出队检查 if redis.call(LINDEX, KEYS[1], 0) ARGV[1] then redis.call(LPOP, KEYS[1]) return 1 else return 0 end6.2 与Zookeeper方案对比特性对比表特性RedisZookeeper性能10万/秒1万/秒一致性最终一致强一致锁模型临时键临时节点实现复杂度中等简单适用场景高性能短期锁强一致长期锁6.3 锁的可重入实现使用Redis Hash存储线程标识和重入计数local key KEYS[1] local threadId ARGV[1] local ttl tonumber(ARGV[2]) -- 检查是否已持有锁 if (redis.call(hexists, key, threadId) 1) then redis.call(hincrby, key, threadId, 1) redis.call(expire, key, ttl) return 1 else -- 尝试获取锁 if (redis.call(hlen, key) 0) then redis.call(hset, key, threadId, 1) redis.call(expire, key, ttl) return 1 end return 0 end在实际项目中我们团队发现当锁竞争激烈时采用分段锁策略可以显著提升性能。例如将商品库存锁拆分为16个分段使冲突概率降低到原来的1/16。但要注意这种方案会增加业务逻辑复杂度需要根据实际场景权衡。