Java案例如何实现分布式锁?

wen java案例 2

Java案例如何实现分布式锁?从原理到实战全解析

📚 文章导读目录

  1. 为什么需要分布式锁? —— 业务场景与痛点分析
  2. 分布式锁的核心要求 —— 互斥、安全、高可用缺一不可
  3. 基于Redis的分布式锁实现(Redlock算法) — 最主流方案详解
  4. 基于ZooKeeper的分布式锁实现 —— 强一致性场景首选
  5. 基于数据库的分布式锁实现 —— 轻量级但需谨慎
  6. 三大实现方案对比与选型建议 —— 你的场景该用哪种?
  7. 常见问题与避坑指南(FAQ) —— 面试高频考点与实战陷阱
  8. 总结与未来趋势 —— 从分布式锁到无锁化设计

为什么需要分布式锁?

在单体应用中,我们使用 synchronizedReentrantLock 就能解决线程安全问题,但在分布式系统中,多个JVM实例同时访问共享资源(如数据库记录、文件、缓存)时,本地锁完全失效。

Java案例如何实现分布式锁?

  • 秒杀场景:多个服务节点同时扣减库存,导致超卖。
  • 定时任务:多个节点同时执行同一任务,造成重复处理。
  • 数据一致性:多实例并发修改同一行数据库记录,引发脏写。

核心矛盾:分布式环境下,资源竞争从“线程级”上升为“进程级”,必须有一个跨JVM的协调者来保证互斥访问,这就是分布式锁的价值所在。


分布式锁必须满足的四大特性

特性 说明 反例
互斥性 同一时刻只能一个客户端持有锁 两个节点同时获得锁
安全性 锁不会被其他客户端释放(无锁误删) 锁超时后被其他线程释放
可用性 锁服务宕机后仍能正常工作(主从切换) Redis主节点宕机后锁丢失
可重入性 同一线程可多次获取同一把锁 递归调用时发送死锁等待

基于Redis的分布式锁实现(案例+代码)

1 基础版:SETNX + EXPIRE(不推荐)

public boolean tryLock(String key, String value, long timeout) {
    // 原子操作:如果key不存在则设置,并同时设置过期时间
    Boolean result = redisTemplate.opsForValue()
        .setIfAbsent(key, value, timeout, TimeUnit.SECONDS);
    return Boolean.TRUE.equals(result);
}
public void unlock(String key, String value) {
    // 释放锁时需校验持有者:避免误删别人的锁
    String currentValue = redisTemplate.opsForValue().get(key);
    if (value.equals(currentValue)) {
        redisTemplate.delete(key);
    }
}

致命缺陷

  • 锁误删:A线程锁超时自动释放,B线程获取锁,A线程执行完调用unlock删除了B的锁。
  • 不可重入:同线程二次加锁会失败。
  • 主从异步复制:主节点宕机后,从节点还未同步锁数据,导致锁丢失。

2 进阶版:Redlock算法(生产级方案)

核心思想:同时向N个独立Redis节点(通常是5个)申请锁,超过半数(N/2+1)成功才算获得锁。

public class RedissonDistributedLock {
    private RedissonClient redissonClient;
    public boolean tryRedLock(String key, long waitTime, long leaseTime) {
        RLock lock = redissonClient.getLock(key);
        try {
            // waitTime:最多等待时间,leaseTime:锁自动释放时间
            return lock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            return false;
        }
    }
    public void releaseRedLock(String key) {
        RLock lock = redissonClient.getLock(key);
        lock.unlock();
    }
}

Redlock关键机制

  • 看门狗(WatchDog):自动续期锁过期时间,防止业务未完成锁被释放。
  • Redisson封装的RLock:实现了可重入、公平锁、读写锁等高级特性。

适用场景:对锁安全性要求极高(如金融交易),且愿意接受一定性能开销。


基于ZooKeeper的分布式锁实现

1 基本原理

利用ZooKeeper的临时有序节点特性:

  1. 客户端在锁路径下创建临时有序节点。
  2. 获取当前路径下所有子节点,判断自己是否为序号最小的那个。
  3. 如果是,获得锁;否则,监听前一个节点的删除事件。
  4. 释放锁时,删除自身节点(客户端断开连接时自动删除)。

2 代码示例(基于Curator框架)

public class ZkDistributedLock {
    private CuratorFramework client;
    public boolean tryZkLock(String path, long timeout) throws Exception {
        InterProcessMutex lock = new InterProcessMutex(client, path);
        return lock.acquire(timeout, TimeUnit.SECONDS);
    }
    public void releaseZkLock(String path) throws Exception {
        InterProcessMutex lock = new InterProcessMutex(client, path);
        lock.release();
    }
}

优点

  • 强一致性(ZAB协议保证)。
  • 无锁过期时间问题(临时节点随客户端断开自动删除)。
  • 天然的公平锁(节点有序性保证)。

缺点

  • 性能较低(每次锁操作需磁盘写入+Leader选举)。
  • 节点数量多时,Watcher回调风暴可能导致服务压力。

基于数据库的分布式锁实现(MySQL为例)

1 乐观锁(CAS思想)

@Update("UPDATE product SET stock = stock - 1 WHERE id = #{productId} AND stock > 0")
boolean deductStock(Long productId);

原理:利用数据库行锁,更新时检查版本号或库存条件。

缺点

  • 死锁风险(需合理设计索引)。
  • 数据库连接池压力大。
  • 不适合高并发场景(磁盘IO瓶颈)。

2 悲观锁(for update)

SELECT * FROM table WHERE condition FOR UPDATE;
-- 业务逻辑处理...
COMMIT;

使用限制

  • InnoDB引擎才支持行锁。
  • 必须走索引,否则退化为表锁。
  • 连接需手动开启事务。

三大方案对比与选型建议

维度 Redis ZooKeeper 数据库
性能 高(10万+QPS) 中(数千QPS) 低(千级QPS)
可靠性 AP(最终一致性) CP(强一致性) CP(强一致性)
自动续期 需额外实现 临时节点自动释放
可重入 需特殊实现 支持 支持
学习成本 低+Redisson封装 中(Curator封装)
推荐场景 高并发、可接受短暂锁丢失 强一致性、关键资源 小规模、低并发

选型铁律

  • 99%高并发业务 → Redis + Redlock
  • 资金/交易类强一致性 → ZooKeeper
  • 资源极小且无Redis/ZK → 数据库乐观锁

常见问题与避坑指南(FAQ)

❓ Q1:锁超时后业务还没执行完怎么办?

答案

  • Redis方案:开启Redisson WatchDog自动续期。
  • ZK方案:临时节点自动释放,业务会抛出异常需设计幂等重试。
  • 数据库方案:无内置续期,需在代码中定时刷新“锁记录”过期时间。

❓ Q2:主从切换导致的锁丢失如何解决?

答案

  • 终极方案:使用Redlock多节点写入。
  • 降级方案:使用Redis Cluster模式,但依然不能100%避免。
  • 业务兜底:对写操作设计幂等性(如唯一索引、版本号)。

❓ Q3:如何实现“可重入”分布式锁?

答案

  • Redis:记录锁持有者线程ID,每次加锁时判断是否为同一线程。
  • ZK:Curator的InterProcessMutex天然支持。
  • 数据库:在锁表中增加thread_id字段。

❓ Q4:分布式锁中的“死锁”如何避免?

答案

  • 设置锁自动过期时间(Redis的EXPIRE)。
  • 使用ZK临时节点。
  • 确保解锁操作放在finally块中。

总结与未来趋势

分布式锁的本质是分布式共识问题的一种简化实现,本文通过Redis、ZooKeeper、数据库三个维度的案例,完整呈现了从原理到代码的落地过程,核心要点包括:

  • 选择正确的中间件:Redis适合高并发,ZK适合强一致性,数据库适合简单场景。
  • 注意边界情况:锁超时、主从切换、网络分区,必须在设计阶段考虑。
  • Redisson/Curator:成熟的客户端库能规避90%的坑。

2 未来趋势

  1. 无锁化设计:利用Redis原子操作(如INCR)、消息队列顺序消费代替锁。
  2. 多级锁融合:本地锁+分布式锁(如Caffeine+Redis)减少网络开销。
  3. 云原生架构:分布式锁被封装为PaaS服务(如阿里云Tair、腾讯云CKV)。

终极建议:任何分布式锁都不是银弹,优先思考能否用“幂等设计”和“最终一致性”简化问题,如果必须加锁,请从最轻量的方案开始,并在业务逻辑层做好容错。


参考资料(可忽略):

  • Redisson官方文档
  • Apache Curator官方示例
  • 《分布式系统常用技术及案例分析》第5章

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