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

wen python案例 2

Java案例如何实现分布式锁:从原理到实战的完整指南

目录导读

  1. 为什么需要分布式锁?
  2. 分布式锁的核心设计原则
  3. 基于Redis的分布式锁实现(实战案例)
  4. 基于ZooKeeper的分布式锁实现
  5. 常见问题与解决方案(Q&A)
  6. 性能对比与最佳实践

为什么需要分布式锁?

在单体应用中,Java的synchronizedReentrantLock就能解决线程安全问题,但在微服务架构下,多个服务实例会同时访问共享资源(如数据库、缓存、文件),此时本地锁失效,必须引入分布式锁

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

典型场景

  • 电商秒杀:防止超卖
  • 定时任务:避免同一任务被多个节点重复执行
  • 分布式事务:确保全局唯一性操作

分布式锁的核心设计原则

一个合格的分布式锁必须满足:

  • 互斥性:同一时刻只有一个客户端持有锁
  • 安全性:不能发生死锁,即使持有锁的客户端崩溃
  • 可重入性:同一线程可多次获取同一把锁
  • 高可用:锁服务本身不能成为单点故障

基于Redis的分布式锁实现(实战案例)

Redis因其高性能和原子操作特性,成为最流行的分布式锁实现方案。

1 基础版:SETNX + EXPIRE

public class RedisDistributedLock {
    private Jedis jedis;
    private String lockKey;
    private String requestId; // 用于保证锁的释放属于持锁者
    private int expireTime = 30000; // 锁自动过期时间(毫秒)
    public boolean tryLock() {
        // SET key value NX PX 30000 原子操作
        String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
        return "OK".equals(result);
    }
    public void unlock() {
        // 使用Lua脚本保证原子性:先判断锁是否属于自己再删除
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                        "return redis.call('del', KEYS[1]) else return 0 end";
        jedis.eval(script, Collections.singletonList(lockKey), 
                   Collections.singletonList(requestId));
    }
}

核心要点

  • 使用requestId(UUID)防止误删其他线程的锁
  • Lua脚本保证getdel的原子性
  • 设置过期时间防止死锁

2 进阶:Redisson实现

生产环境推荐使用Redisson框架,它封装了复杂的逻辑:

// 添加依赖:org.redisson:redisson-spring-boot-starter
@Autowired
private RedissonClient redissonClient;
public void businessMethod() {
    RLock lock = redissonClient.getLock("myLock");
    try {
        // 尝试获取锁,最多等待10秒,锁30秒后自动释放
        if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
            // 执行临界区代码
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    } finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}

Redisson优势

  • 内置看门狗机制:自动续期,避免业务超时导致锁自动释放
  • 支持可重入、公平锁、读写锁等高级特性
  • 高可用:支持Redis集群和哨兵模式

基于ZooKeeper的分布式锁实现

ZooKeeper通过临时顺序节点实现分布式锁,原理更可靠。

public class ZkDistributedLock {
    private ZooKeeper zk;
    private String lockPath = "/locks/";
    public boolean tryLock() {
        // 创建临时顺序节点
        String currentPath = zk.create(lockPath + "lock-", 
                                       new byte[0], 
                                       ZooDefs.Ids.OPEN_ACL_UNSAFE,
                                       CreateMode.EPHEMERAL_SEQUENTIAL);
        // 获取所有子节点,判断自己是否最小序号
        List<String> children = zk.getChildren("/locks", false);
        Collections.sort(children);
        if (currentPath.equals("/locks/" + children.get(0))) {
            return true; // 成功获取锁
        }
        // 否则监听前一个节点
        String prevNode = children.get(children.indexOf(currentPath) - 1);
        zk.exists("/locks/" + prevNode, watcher);
        return false;
    }
}

ZK锁特点

  • 强一致性:ZAB协议保证,比Redis更可靠
  • 无需设置过期时间,客户端断开自动释放
  • 性能低于Redis,适合对一致性要求极高的场景

常见问题与解决方案(Q&A)

Q1:Redis锁过期后业务还没执行完怎么办? A:使用Redisson的看门狗机制,它会自动延长锁的过期时间(默认每10秒续期一次),或者手动实现Timer任务进行续期。

Q2:如何避免Redis主从切换导致的锁丢失? A:使用RedLock算法,向多个独立Redis节点同时加锁,超过半数成功才认为加锁成功,但RedLock本身存在争议,更推荐:升级到Redis 7.0+,使用基于复制日志的WAIT命令。

Q3:ZK分布式锁为什么比Redis更可靠? A:ZK的临时节点机制保证:如果客户端崩溃,节点自动删除,锁立即释放,而Redis的锁依赖于过期时间,存在时间差,但ZK性能较低,每秒只能处理几千次加锁。

Q4:分布式锁的超时时间设置多少合适? A:根据业务最大执行时间估算,正常业务2秒,最大超时5秒,设置锁过期时间为5秒,同时配合自动续期机制(如Redisson的watchdog)增加容错。

性能对比与最佳实践

方案 性能 可靠性 适用场景
Redis SETNX 高(10万+ TPS) 中等(可能丢失) 秒杀、缓存操作
Redisson 高(5万+ TPS) 较高(自动续期) 复杂业务、高并发
ZooKeeper 低(3千+ TPS) 极高(强一致) 配置中心、选举

终极建议

  • 90%的场景选择Redisson + Redis集群,它完美平衡了性能和可靠性
  • 对数据一致性要求严格的金融场景,选择ZooKeeper
  • 避免自己造轮子,使用成熟的分布式锁中间件

通过本篇Java案例,相信您已经掌握了分布式锁的核心实现方式,在实际开发中,请根据业务场景选择最合适的方案,并始终牢记:分布式锁是解决并发问题的最后手段,能用无锁设计优化则更优

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