7.2 分布式锁:Redis和Zookeeper的皇位争夺战

88 阅读4分钟

① 分布式锁的江湖地位

pie
    title 2024分布式锁使用率
    "Redis" : 68
    "Zookeeper" : 25
    "Etcd" : 5
    "其他" : 2

经典场景
"当100个程序员同时点击秒杀按钮,如何让系统觉得他们很有素质在排队?"


② Redis分布式锁(Redisson实战)

完整可运行代码

// 前置:pom.xml添加依赖
// <dependency>
//     <groupId>org.redisson</groupId>
//     <artifactId>redisson</artifactId>
//     <version>3.17.0</version>
// </dependency>

public class RedisLockDemo {
    public static void main(String[] args) throws InterruptedException {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        
        RedissonClient redisson = Redisson.create(config);
        RLock lock = redisson.getLock("myLock");

        // 尝试加锁,最多等待10秒,锁自动释放时间30秒
        boolean isLocked = lock.tryLock(10, 30, TimeUnit.SECONDS);
        if (isLocked) {
            try {
                System.out.println("抢锁成功!开始执行秒杀逻辑");
                // 模拟业务处理
                Thread.sleep(2000); 
                System.out.println("已扣除库存:iPhone 15 Pro 1台");
            } finally {
                lock.unlock();
                System.out.println("释放锁,深藏功与名");
            }
        } else {
            System.out.println("手速太慢,下次一定!");
        }
        redisson.shutdown();
    }
}

代码彩蛋
运行后查看Redis数据变化:

127.0.0.1:6379> keys *
1) "myLock"  # 看门狗机制自动续期

③ Zookeeper分布式锁(Curator实现)

// 前置:pom.xml添加依赖
// <dependency>
//     <groupId>org.apache.curator</groupId>
//     <artifactId>curator-recipes</artifactId>
//     <version>5.3.0</version>
// </dependency>

public class ZkLockDemo {
    public static void main(String[] args) {
        CuratorFramework client = CuratorFrameworkFactory.newClient(
            "localhost:2181",
            new ExponentialBackoffRetry(1000, 3)
        );
        client.start();

        InterProcessMutex lock = new InterProcessMutex(client, "/locks/iphone15");
        try {
            if (lock.acquire(10, TimeUnit.SECONDS)) {
                System.out.println("获得zk锁,开始处理订单");
                // 模拟业务处理
                Thread.sleep(2000);
                System.out.println("订单处理完毕,准备发货");
            }
        } finally {
            lock.release();
            System.out.println("优雅释放锁");
        }
        client.close();
    }
}

执行过程可视化

graph TD
    A[客户端1] --> B(创建/locks/iphone15/_c_123)
    C[客户端2] --> D(创建/locks/iphone15/_c_456)
    B --> E{检查是否最小节点}
    D --> E
    E -->|是| F[获得锁]
    E -->|否| G[监听前一个节点]

④ 皇位争夺战对比表

维度Redis(红皇帝)Zookeeper(动物园国王)
加锁速度快如闪电(内存操作)较慢(需要创建节点)
一致性异步复制可能丢锁(需RedLock)强一致(ZAB协议)
死锁处理依赖超时自动释放会话结束自动删除节点
惊群效应发布订阅通知精准唤醒Watch机制可能引发羊群效应
运维成本简单(单节点)需要集群部署
适用场景高频低精度(秒杀/缓存)低频高可靠(资金交易)

⑤ 血泪案例库

案例1:某电商秒杀事故

  • 选择:Redis单节点锁
  • 现象:主从切换导致超卖
  • 数据:多卖出127台Switch
  • 尸检报告
    [ERROR] 库存校验失败!
    |-- 预期库存: 100
    |-- 实际库存: -27
    |-- 事故原因: 主节点宕机,从节点未同步锁状态
    

案例2:银行转账系统故障

  • 选择:Zookeeper锁未处理异常
  • 现象:客户端崩溃导致锁永久持有
  • 监控数据
    lock_hold_time = 86400s  # 长达24小时
    blocked_transactions = 3562
    customer_complaints = 9527
    

⑥ 高频面试八连击

  1. Redis分布式锁一定要用Redisson吗?

    答:生产环境必须用,原生SETNX存在原子性问题和锁续期难题(展示错误案例:SETNX + EXPIRE非原子操作)

  2. Zookeeper锁如何避免羊群效应?

    答:使用临时有序节点,每个客户端只监听前一个节点(对比旧版做法:所有客户端监听同一个节点)

  3. RedLock算法真的安全吗?

    答:Martin Kleppmann曾质疑其时钟依赖问题,但在无时钟跳跃场景下仍可用(需至少3个独立Redis实例)

  4. CP和AP系统如何选择锁?

    答:CP系统(如ZK)适合金融交易,AP系统(如Redis)适合互联网高并发

  5. 如何实现可重入锁?

    答:Redis用Hash结构记录线程ID和计数,ZK在节点数据中维护计数器

  6. 锁超时时间设置多少合适?

    答:建议:业务最大耗时×2,配合看门狗自动续期(如Redisson默认30秒)

  7. 分布式锁和数据库锁有什么区别?

    答:数据库锁(如行锁)粒度粗且影响性能,分布式锁更细粒度(对比案例:秒杀系统QPS从200提升到5000)

  8. 如果ZK锁客户端断连了怎么办?

    答:会话超时(默认60s)后自动删除临时节点,需业务方处理中断异常(演示代码中try-finally块)


⑦ 灵魂拷问:你的系统适合哪种锁?

决策树

graph TD
    A[需要强一致性?] -->|是| B[选Zookeeper]
    A -->|否| C{QPS超过1万?}
    C -->|是| D[选Redis]
    C -->|否| E{需要自动释放?}
    E -->|是| D
    E -->|否| B

经典组合

  • 金融系统:ZK锁 + 数据库事务
  • 电商秒杀:Redis锁 + 库存预扣 + 队列削峰
  • 物联网:Redis锁 + 本地缓存降级

⑧ 自测实验:制造锁竞争

步骤

  1. 同时启动10个线程运行RedisLockDemo
  2. 观察输出:
    线程1:抢锁成功!
    线程2:手速太慢,下次一定!
    ...
    线程5:释放锁,深藏功与名
    
  3. 查看Redis监控:
    redis-cli info stats | grep instantaneous_ops_per_sec
    # 可见QPS突增现象
    

进阶挑战
用Jepsen框架模拟网络分区,观察两种锁的可靠性差异


下一讲剧透
《7.3 熔断降级:系统崩溃前的紧急逃生通道》——用Hystrix实现服务熔断,并手撕Sentinel的滑动窗口算法,教你设计一个会"装死"的智能系统!