1. 分布式锁的本质与核心挑战在分布式系统中多个服务实例对共享资源进行互斥访问时传统的单机锁机制会完全失效。这就是分布式锁要解决的核心问题——在跨进程、跨主机的环境下实现排他性控制。我经历过一个典型的电商库存扣减场景当100台订单服务器同时检测到某商品剩余库存为1时如果没有分布式锁的防护必然会出现超卖现象。分布式锁必须满足三个刚性条件互斥性任意时刻只有一个客户端能持有锁防死锁即使锁持有者崩溃锁也能自动释放容错性只要大部分锁服务节点存活客户端就能正常获取和释放锁在实际工程中我们还需要额外考虑锁的自动续期避免业务未处理完锁已过期可重入性同一线程可重复获取锁锁等待超时避免线程长时间阻塞锁释放校验防止误删其他客户端的锁2. Redis分布式锁深度实现2.1 基础实现方案最基础的Redis锁实现只需要一个SETNX命令SETNX lock_key unique_value当返回1表示获取锁成功0则表示失败。但这种方式存在致命缺陷——如果客户端崩溃锁将永远无法释放。因此必须给锁设置过期时间SET lock_key unique_value NX PX 30000这个原子命令在Redis 2.6.12后支持解决了锁的自动释放问题。关键细节unique_value必须是全局唯一标识如UUID这是实现锁释放安全校验的基础。2.2 锁续期与可重入实现对于可能长时间持有锁的场景需要实现锁的自动续期。我推荐使用Redisson的看门狗机制Config config new Config(); config.useSingleServer().setAddress(redis://127.0.0.1:6379); RedissonClient redisson Redisson.create(config); RLock lock redisson.getLock(orderLock); try { // 看门狗默认30秒过期每10秒续期一次 lock.lock(); // 业务处理 } finally { lock.unlock(); }可重入锁的实现需要记录持有线程和重入次数。Redisson通过Lua脚本维护了Hash结构local counter redis.call(hincrby, KEYS[1], ARGV[1], 1); redis.call(pexpire, KEYS[1], ARGV[2]); return counter;2.3 集群环境下的特殊考量在Redis Cluster模式下我们需要特别注意节点故障时可能出现锁状态不一致网络分区可能导致多个客户端同时持有锁RedLock算法试图解决这些问题其核心流程获取当前毫秒级时间戳T1依次向N个独立节点请求锁使用相同key和value当在(N/2 1)个节点上获取成功且总耗时小于锁有效期时认为获取成功锁的实际有效时间 初始有效时间 - 获取锁总耗时但RedLock也存在争议Martin Kleppmann曾指出其可能违反互斥性。我的实践经验是对一致性要求极高的场景慎用适当增加重试次数和超时时间配合业务层的幂等设计3. ZooKeeper分布式锁专业实现3.1 基于临时顺序节点的锁ZooKeeper通过临时顺序节点实现分布式锁更加严谨每个客户端在锁目录下创建临时顺序节点获取目录下所有子节点判断自己是否是最小编号如果是则获取锁否则监听前一个节点的删除事件处理完业务后主动删除节点public class ZkDistributedLock { private final String lockPath; private final ZooKeeper zk; private String currentPath; public boolean lock() throws Exception { currentPath zk.create(lockPath /lock-, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); ListString children zk.getChildren(lockPath, false); Collections.sort(children); if (currentPath.endsWith(children.get(0))) { return true; } else { String prevPath lockPath / children.get( Collections.binarySearch(children, currentPath.substring(currentPath.lastIndexOf(/) 1)) - 1); final CountDownLatch latch new CountDownLatch(1); Stat stat zk.exists(prevPath, event - { if (event.getType() EventType.NodeDeleted) { latch.countDown(); } }); if (stat ! null) { latch.await(); } return true; } } }3.2 锁的特性对比特性Redis锁ZooKeeper锁实现复杂度简单较复杂性能高内存操作中需要创建节点可靠性依赖Redis持久化高ZAB协议保证锁释放机制依赖超时会话结束自动释放惊群效应存在无顺序监听适用场景高频短时锁低频长时锁3.3 生产环境调优建议对于ZooKeeper锁的优化设置合理的sessionTimeout建议10-30秒使用Curator框架的InterProcessMutex它已经处理了连接丢失、重试等边界情况监控znode数量避免历史节点堆积对于读写锁场景使用InterProcessReadWriteLockCuratorFramework client CuratorFrameworkFactory.newClient( zk.example.com:2181, new ExponentialBackoffRetry(1000, 3)); client.start(); InterProcessMutex lock new InterProcessMutex(client, /order-lock); try { if (lock.acquire(10, TimeUnit.SECONDS)) { // 业务处理 } } finally { lock.release(); }4. 关键问题与实战经验4.1 时钟漂移问题在Redis锁实现中严重依赖系统时钟。我曾遇到过一个案例某台服务器时钟快了10分钟导致锁提前过期。解决方案使用NTP服务同步时钟在Redis配置中设置timeout 0禁用命令超时适当增加锁的有效期冗余4.2 锁等待优化当大量线程争抢锁时会导致Redis或ZK压力过大。我采用的优化策略实现分级锁全局锁局部锁加入随机等待时间jitter对于非关键路径使用乐观锁// 分级锁示例 public void processOrder(String orderId) { // 全局锁控制库存扣减 RLock globalLock redisson.getLock(global:order: orderId); // 局部锁控制订单状态修改 RLock localLock redisson.getLock(local:order: orderId); try { globalLock.lock(); // 扣减库存 localLock.lock(); // 修改订单状态 } finally { localLock.unlock(); globalLock.unlock(); } }4.3 监控与告警完善的监控体系应包括锁等待时间超过100ms需要告警锁持有时间异常长时间持有可能是死锁锁获取失败率突增可能预示系统问题ZK的znode数量监控我常用的Prometheus监控指标# Redis锁监控 redis_lock_wait_seconds{nameorder_lock} 0.3 redis_lock_held_seconds{nameorder_lock} 1.5 redis_lock_fail_total{nameorder_lock} 42 # ZK锁监控 zk_lock_wait_count{namepayment_lock} 128 zk_lock_acquire_errors{namepayment_lock} 75. 技术选型决策树根据我的经验选择分布式锁方案时可参考以下决策流程是否需要强一致性是 → 选择ZooKeeper否 → 进入下一步性能要求是否极高5000 TPS是 → 选择Redis否 → 进入下一步是否已有Redis/ZK基础设施有Redis → 优先考虑Redis锁有ZK → 优先考虑ZK锁锁持有时间通常多长1秒 → Redis1秒 → ZooKeeper对于金融支付等关键系统我建议采用ZK锁业务幂等的双重保障。而在电商秒杀等场景Redis锁的高性能优势更加明显。