Java分布式锁案例怎么实操

wen java案例 26

Java分布式锁案例怎么实操?从理论到代码全解析

📚 目录导读

  1. 分布式锁为什么是必选项?
  2. 核心原理:Redis、ZooKeeper、数据库三种方案对比
  3. 实战案例一:基于Redis的分布式锁(含Redisson优雅实现)
  4. 实战案例二:基于ZooKeeper的分布式锁(含Curator递归锁)
  5. 实战案例三:基于数据库的分布式锁(含悲观锁与乐观锁)
  6. 常见问题与错误规避
  7. 高频问答集(面试必问)

分布式锁为什么是必选项?

在多实例部署的微服务架构中,传统的synchronizedReentrantLock只能锁住单个JVM进程,当出现库存超卖、重复扣费、定时任务重复执行等场景时,必须引入分布式锁来协调跨节点资源互斥访问。

Java分布式锁案例怎么实操

一个真实案例:某电商促销活动,由于未使用分布式锁,并发下单导致库存从100减到-23,直接经济损失超10万元。


核心原理对比表

实现方式 优点 缺点 典型场景
Redis 高性能、支持超时自动释放 主从切换可能丢锁(需Redlock) 高并发扣减库存
ZooKeeper 强一致性、自动释放(临时节点) 性能较低、避免脑裂 配置中心、选主场景
数据库 实现简单、无需额外组件 性能瓶颈、死锁风险 低并发、遗留系统改造

实战案例一:基于Redis的分布式锁

1 基础版(不推荐生产使用)

// 错误示范:非原子操作
if (jedis.setnx(key, value) == 1) {
    jedis.expire(key, 30); // 可能setnx成功但expire失败导致死锁
}

2 正确版(Lua脚本保证原子性)

-- 加锁
EVAL "return redis.call('set',KEYS[1],ARGV[1],'NX','PX',ARGV[2])" 1 lockKey uuid 30000
-- 解锁(必须校验身份)
EVAL "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 lockKey uuid

3 生产级实现:Redisson框架

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.27.1</version>
</dependency>
@Autowired
private RedissonClient redissonClient;
public void seckill(Long productId) {
    RLock lock = redissonClient.getLock("lock:product:" + productId);
    try {
        // 等待100秒,锁30秒自动释放(看门狗自动续期)
        if (lock.tryLock(100, 30, TimeUnit.SECONDS)) {
            // 业务逻辑
            stockService.deduct(productId);
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    } finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}

关键点:Redisson的lock()方法自带自动续期机制(Watch Dog),每10秒检查一次,避免业务超时锁自动释放。


实战案例二:基于ZooKeeper的分布式锁

1 原生写法(不推荐)

// ZK API写临时顺序节点,监听前一个节点删除事件
// 代码量巨大,且需要处理连接丢失重连

2 Curator框架实现(推荐)

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>5.5.0</version>
</dependency>
@Bean
public CuratorFramework curatorFramework() {
    CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181",
            new ExponentialBackoffRetry(1000, 3));
    client.start();
    return client;
}
public void createOrder() {
    InterProcessMutex lock = new InterProcessMutex(curatorFramework, "/lock/order");
    try {
        if (lock.acquire(10, TimeUnit.SECONDS)) {
            // 生成订单业务
            orderService.create();
        }
    } catch (Exception e) {
        log.error("获取ZK锁失败", e);
    } finally {
        try {
            lock.release();
        } catch (Exception e) {
            log.error("释放锁失败", e);
        }
    }
}

对比Redis:ZK锁没有过期时间风险(连接断开自动删除临时节点),适合对一致性要求极高的场景。


实战案例三:基于数据库的分布式锁

1 悲观锁(for update)

-- 需要innodb引擎 + 事务
SELECT * FROM product WHERE id = #{productId} FOR UPDATE;
-- 更新库存
UPDATE product SET stock = stock - 1 WHERE id = #{productId};

2 乐观锁(版本号机制)

UPDATE product 
SET stock = stock - 1, version = version + 1 
WHERE id = #{productId} AND version = #{oldVersion}

适用条件:低并发、数据库是唯一存储载体、不能引入Redis/ZK时使用。


常见问题与错误规避

  • ❌ 忘记设置超时时间:导致锁永远不释放
  • ❌ 锁误删:使用uuid作为value,解锁时校验身份
  • ❌ Redis主从切换丢锁:高安全场景使用Redlock或ZK
  • ❌ 业务未实现幂等:锁只能保证互斥,不能保证业务幂等

关键问题:锁的粒度要细,比如按用户ID+商品ID做锁,避免锁范围过大导致性能下降。


高频问答集

Q: 分布式锁的超时时间如何设置? A: 根据业务最大执行时间 + 20%缓冲,Redisson看门狗机制可自动续期,推荐使用。

Q: 如果业务执行时间超过锁超时时间怎么办? A: 两种方案:1)使用Redisson看门狗自动续期;2)业务逻辑内定期调用expire延长锁时间。

Q: ZooKeeper和Redis谁更好? A: 高并发选Redis(如万级QPS扣库存),强一致选ZK(如分布式选举、配置变更)。

Q: 如何测试分布式锁的正确性? A: 多线程并发压测,验证:1)同一时刻只有一个线程进入 2)异常中断后锁自动释放 3)高并发下无死锁现象。


分布式锁实战的核心在于:

  1. 选型:根据一致性需求和性能要求选择Redis/ZK/数据库
  2. 原子性:避免setnx+expire非原子操作
  3. 安全性:必须校验锁持有者,避免误释放
  4. 容错:设计超时释放和自动续期机制

推荐生产直接使用RedissonCurator框架,避免重复造轮子,务必在压测环境中验证锁的性能与正确性,这是分布式系统稳定运行的基石。

抱歉,评论功能暂时关闭!