【Java项目技术亮点】分布式锁实现与优化:从Redisson到ZooKeeper,彻底搞懂分布式锁的底层原理

秒杀活动中,100万人同时抢购100件商品,库存从100变成-999------这就是没有分布式锁的灾难。本文将深入剖析Redis和ZooKeeper两种分布式锁方案,从原理到源码,手把手教你构建高可靠的分布式锁。

文章目录


一、场景引入:一次秒杀活动的库存超卖

1.1 真实案例

某电商平台秒杀活动,100件商品被100万人抢购:

复制代码
时间线:
T+0ms:秒杀开始,100万人同时点击购买
T+10ms:服务A读取库存:100件
T+11ms:服务B读取库存:100件(同时!)
T+12ms:服务C读取库存:100件(同时!)
...
T+20ms:服务A扣减库存:99件
T+21ms:服务B扣减库存:99件(基于旧的100!)
T+22ms:服务C扣减库存:99件(基于旧的100!)
...
T+1s:100万个请求都扣减成功
T+2s:库存显示:-999900件
T+1天:商家崩溃,平台赔偿,上热搜

问题根源:单机锁(synchronized、ReentrantLock)只能锁住单个JVM进程,在分布式环境下多个服务实例同时操作共享资源,导致数据不一致。

1.2 为什么需要分布式锁?

复制代码
单机锁 vs 分布式锁:

单机环境(单JVM):
┌─────────────────────────────────┐
│           JVM进程                │
│  ┌─────────┐                    │
│  │ 线程A   │──→ synchronized ──→│──→ 共享资源(库存)
│  │ 线程B   │──→ synchronized ──→│──→ 同一时刻只有一个线程访问
│  │ 线程C   │──→ synchronized ──→│
│  └─────────┘                    │
└─────────────────────────────────┘

分布式环境(多JVM):
┌─────────────┐  ┌─────────────┐  ┌─────────────┐
│   服务A     │  │   服务B     │  │   服务C     │
│  (JVM1)   │  │  (JVM2)   │  │  (JVM3)   │
│             │  │             │  │             │
│ synchronized│  │ synchronized│  │ synchronized│
│   只能锁    │  │   只能锁    │  │   只能锁    │
│   自己JVM   │  │   自己JVM   │  │   自己JVM   │
└──────┬──────┘  └──────┬──────┘  └──────┬──────┘
       │                │                │
       └────────────────┴────────────────┘
                        │
                    ┌───┴───┐
                    │ Redis │ ←── 需要分布式锁!
                    │ 库存  │
                    └───────┘

二、解决方案:两种分布式锁方案对比

2.1 Redis分布式锁(Redisson)vs ZooKeeper分布式锁

复制代码
┌─────────────────────────────────────────────────────────────────────┐
│                        分布式锁方案对比                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│   Redis + Redisson                        ZooKeeper                 │
│   ┌─────────────────────┐                ┌─────────────────────┐    │
│   │  性能极高(10万QPS)   │                │  可靠性极高(CP)    │    │
│   │  支持自动续期          │                │  临时顺序节点        │    │
│   │  支持红锁(多主)      │                │  监听机制无轮询      │    │
│   │  主从切换可能丢锁      │                │  性能较低(万级QPS)  │    │
│   │  需要Redis集群         │                │  需要ZK集群          │    │
│   └─────────────────────┘                └─────────────────────┘    │
│                                                                      │
│   适用场景:                             适用场景:                    │
│   - 高并发、性能敏感                     - 强一致性要求              │
│   - 已有Redis基础设施                    - 已有ZK基础设施            │
│   - 允许极小概率的锁失效                  - 不允许锁失效              │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

2.2 分布式锁的核心要求

特性 说明 重要性
互斥性 同一时刻只有一个客户端能获取锁 ⭐⭐⭐ 必需
防死锁 锁必须有过期时间,防止客户端崩溃导致死锁 ⭐⭐⭐ 必需
可重入 同一客户端可以多次获取同一把锁 ⭐⭐ 重要
自动续期 业务未完成时自动延长锁的持有时间 ⭐⭐ 重要
高可用 锁服务本身不能单点故障 ⭐⭐⭐ 必需

三、实战代码:Redisson分布式锁

3.1 Redisson配置

java 复制代码
/**
 * Redisson配置
 */
@Configuration
public class RedissonConfig {
    
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        
        // 单机模式
        config.useSingleServer()
            .setAddress("redis://127.0.0.1:6379")
            .setPassword("your_password")
            .setDatabase(0)
            // 连接池配置
            .setConnectionMinimumIdleSize(10)
            .setConnectionPoolSize(64)
            .setIdleConnectionTimeout(10000)
            .setConnectTimeout(10000)
            .setTimeout(3000)
            .setRetryAttempts(3)
            .setRetryInterval(1500);
        
        // 集群模式
        // config.useClusterServers()
        //     .addNodeAddress("redis://192.168.0.1:7000", "redis://192.168.0.2:7000");
        
        return Redisson.create(config);
    }
}

3.2 基础分布式锁使用

java 复制代码
/**
 * Redisson分布式锁基础使用
 */
@Component
@Slf4j
public class RedissonLockService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    /**
     * 基础加锁(阻塞等待)
     */
    public void lockDemo(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            // 加锁,默认30秒过期,看门狗自动续期
            lock.lock();
            
            log.info("🔒 获取锁成功: {}", lockKey);
            
            // 执行业务逻辑
            doBusiness();
            
        } finally {
            // 释放锁
            lock.unlock();
            log.info("🔓 释放锁: {}", lockKey);
        }
    }
    
    /**
     * 尝试加锁(非阻塞,带超时)
     */
    public boolean tryLockDemo(String lockKey, long waitTime, long leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            // 尝试获取锁,最多等待waitTime秒,持有leaseTime秒后自动释放
            boolean locked = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
            
            if (!locked) {
                log.warn("⏳ 获取锁超时: {}", lockKey);
                return false;
            }
            
            log.info("🔒 获取锁成功: {}", lockKey);
            
            // 执行业务逻辑
            doBusiness();
            
            return true;
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("❌ 获取锁被中断: {}", lockKey);
            return false;
            
        } finally {
            // 只有持有锁才释放
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
                log.info("🔓 释放锁: {}", lockKey);
            }
        }
    }
    
    /**
     * 可重入锁演示
     */
    public void reentrantDemo(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            lock.lock();
            log.info("🔒 第一次加锁");
            
            // 同一线程可以再次获取锁(可重入)
            lock.lock();
            log.info("🔒 第二次加锁(可重入)");
            
            // 业务逻辑...
            
            lock.unlock();
            log.info("🔓 第一次解锁");
            
            lock.unlock();
            log.info("🔓 第二次解锁");
            
        } catch (Exception e) {
            log.error("❌ 锁操作异常", e);
        }
    }
    
    private void doBusiness() {
        // 业务逻辑...
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

3.3 秒杀场景实战:库存扣减

java 复制代码
/**
 * 秒杀服务(Redisson分布式锁)
 */
@Service
@Slf4j
public class SeckillService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private OrderMapper orderMapper;
    
    private static final String STOCK_KEY = "seckill:stock:";
    private static final String LOCK_KEY = "lock:seckill:";
    
    /**
     * 秒杀下单(分布式锁保证库存不超卖)
     */
    public SeckillResult seckill(Long userId, Long skuId) {
        String lockKey = LOCK_KEY + skuId;
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            // 1. 获取分布式锁(最多等待3秒,持有10秒自动释放)
            boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
            if (!locked) {
                log.warn("⏳ 获取锁失败,秒杀太火爆: userId={}, skuId={}", userId, skuId);
                return SeckillResult.fail("系统繁忙,请重试");
            }
            
            try {
                // 2. 检查库存(Redis预减库存)
                String stockKey = STOCK_KEY + skuId;
                Long stock = redisTemplate.opsForValue().decrement(stockKey);
                
                if (stock == null || stock < 0) {
                    // 库存不足,回滚
                    redisTemplate.opsForValue().increment(stockKey);
                    log.info("❌ 库存不足: skuId={}", skuId);
                    return SeckillResult.fail("库存不足");
                }
                
                // 3. 创建订单(异步处理,减少锁持有时间)
                Long orderId = createOrderAsync(userId, skuId);
                
                log.info("✅ 秒杀成功: userId={}, skuId={}, orderId={}, remainStock={}", 
                    userId, skuId, orderId, stock);
                
                return SeckillResult.success(orderId);
                
            } finally {
                // 4. 释放锁
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("❌ 秒杀被中断: userId={}, skuId={}", userId, skuId);
            return SeckillResult.fail("系统异常");
        }
    }
    
    /**
     * 优化版:减少锁粒度(分段锁)
     */
    public SeckillResult seckillOptimized(Long userId, Long skuId) {
        // 使用用户ID对锁分段,不同用户用不同锁
        int lockIndex = (userId.intValue() % 100);
        String lockKey = LOCK_KEY + skuId + ":" + lockIndex;
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            boolean locked = lock.tryLock(3, 5, TimeUnit.SECONDS);
            if (!locked) {
                return SeckillResult.fail("系统繁忙");
            }
            
            try {
                // 先检查用户是否已抢购(幂等性)
                String userKey = "seckill:user:" + skuId + ":" + userId;
                Boolean hasOrdered = redisTemplate.hasKey(userKey);
                if (Boolean.TRUE.equals(hasOrdered)) {
                    return SeckillResult.fail("您已抢购过该商品");
                }
                
                // 扣减库存...
                Long stock = redisTemplate.opsForValue().decrement(STOCK_KEY + skuId);
                if (stock == null || stock < 0) {
                    redisTemplate.opsForValue().increment(STOCK_KEY + skuId);
                    return SeckillResult.fail("库存不足");
                }
                
                // 标记用户已抢购
                redisTemplate.opsForValue().set(userKey, "1", 24, TimeUnit.HOURS);
                
                // 异步创建订单
                createOrderAsync(userId, skuId);
                
                return SeckillResult.success(null);
                
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return SeckillResult.fail("系统异常");
        }
    }
    
    /**
     * 异步创建订单(减少锁持有时间)
     */
    @Async("seckillExecutor")
    public void createOrderAsync(Long userId, Long skuId) {
        try {
            Order order = new Order();
            order.setUserId(userId);
            order.setSkuId(skuId);
            order.setStatus(OrderStatus.PENDING_PAYMENT);
            order.setCreateTime(LocalDateTime.now());
            orderMapper.insert(order);
            
            log.info("✅ 异步创建订单完成: orderId={}", order.getId());
            
        } catch (Exception e) {
            log.error("❌ 异步创建订单失败: userId={}, skuId={}", userId, skuId, e);
            // 发送告警,人工介入
        }
    }
}

3.4 Redisson红锁(多主Redis)

java 复制代码
/**
 * Redisson红锁(多主Redis,防止单点故障)
 */
@Component
@Slf4j
public class RedissonRedLockService {
    
    @Autowired
    private RedissonClient redissonClient1;
    
    @Autowired
    private RedissonClient redissonClient2;
    
    @Autowired
    private RedissonClient redissonClient3;
    
    /**
     * 使用红锁(需要至少3个独立的Redis主节点)
     */
    public void redLockDemo(String lockKey) {
        RLock lock1 = redissonClient1.getLock(lockKey);
        RLock lock2 = redissonClient2.getLock(lockKey);
        RLock lock3 = redissonClient3.getLock(lockKey);
        
        RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
        
        try {
            // 尝试获取红锁
            boolean locked = redLock.tryLock(10, 30, TimeUnit.SECONDS);
            
            if (!locked) {
                log.warn("⏳ 获取红锁失败");
                return;
            }
            
            log.info("🔒 红锁获取成功");
            
            // 执行业务逻辑...
            doCriticalBusiness();
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("❌ 获取红锁被中断");
            
        } finally {
            // 释放红锁
            redLock.unlock();
            log.info("🔓 红锁释放");
        }
    }
    
    private void doCriticalBusiness() {
        // 关键业务逻辑...
    }
}

四、实战代码:ZooKeeper分布式锁

4.1 ZooKeeper分布式锁原理

复制代码
ZooKeeper分布式锁原理:

1. 创建临时顺序节点:
   /locks/order/lock_0000000001
   /locks/order/lock_0000000002
   /locks/order/lock_0000000003

2. 获取所有子节点,排序:
   lock_0000000001(最小序号,获取锁)
   lock_0000000002(监听前一个节点)
   lock_0000000003(监听前一个节点)

3. 最小序号的节点获取锁:
   - 执行业务逻辑
   - 完成后删除自己的节点

4. 其他节点监听前一个节点:
   - 前一个节点删除时收到通知
   - 重新检查自己是否为最小序号

优势:
- 临时节点:客户端断开自动删除,防死锁
- 顺序节点:天然排队,公平锁
- 监听机制:无需轮询,事件驱动

4.2 ZooKeeper分布式锁实现

java 复制代码
/**
 * ZooKeeper分布式锁
 */
@Component
@Slf4j
public class ZooKeeperDistributedLock {
    
    private static final String LOCK_ROOT = "/distributed-locks";
    private static final int SESSION_TIMEOUT = 5000;
    private static final int CONNECTION_TIMEOUT = 5000;
    
    private CuratorFramework client;
    
    @PostConstruct
    public void init() {
        // 创建Curator客户端
        client = CuratorFrameworkFactory.builder()
            .connectString("zk1:2181,zk2:2181,zk3:2181")
            .sessionTimeoutMs(SESSION_TIMEOUT)
            .connectionTimeoutMs(CONNECTION_TIMEOUT)
            .retryPolicy(new ExponentialBackoffRetry(1000, 3))
            .build();
        
        client.start();
        
        // 创建锁根目录
        try {
            if (client.checkExists().forPath(LOCK_ROOT) == null) {
                client.create().creatingParentsIfNeeded()
                    .withMode(CreateMode.PERSISTENT)
                    .forPath(LOCK_ROOT);
            }
        } catch (Exception e) {
            log.error("❌ 创建锁根目录失败", e);
        }
    }
    
    /**
     * 获取分布式锁
     */
    public boolean lock(String lockName, long timeout) {
        String lockPath = LOCK_ROOT + "/" + lockName;
        
        try {
            // 创建临时顺序节点
            String nodePath = client.create()
                .creatingParentsIfNeeded()
                .withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
                .forPath(lockPath + "/lock_");
            
            String nodeName = nodePath.substring(nodePath.lastIndexOf("/") + 1);
            
            // 尝试获取锁
            return tryAcquireLock(lockPath, nodeName, timeout);
            
        } catch (Exception e) {
            log.error("❌ 获取锁失败: lockName={}", lockName, e);
            return false;
        }
    }
    
    /**
     * 尝试获取锁
     */
    private boolean tryAcquireLock(String lockPath, String nodeName, long timeout) 
            throws Exception {
        long deadline = System.currentTimeMillis() + timeout;
        
        while (System.currentTimeMillis() < deadline) {
            // 获取所有子节点
            List<String> children = client.getChildren().forPath(lockPath);
            Collections.sort(children);
            
            // 检查自己是否为最小序号
            int index = children.indexOf(nodeName);
            
            if (index == 0) {
                // 获取锁成功
                log.info("🔒 获取ZK锁成功: node={}", nodeName);
                return true;
            }
            
            // 监听前一个节点
            String prevNode = children.get(index - 1);
            String prevPath = lockPath + "/" + prevNode;
            
            CountDownLatch latch = new CountDownLatch(1);
            
            // 注册监听器
            Stat stat = client.checkExists().usingWatcher((Watcher event) -> {
                if (event.getType() == Event.EventType.NodeDeleted) {
                    latch.countDown();
                }
            }).forPath(prevPath);
            
            // 前一个节点已删除,重新竞争
            if (stat == null) {
                continue;
            }
            
            // 等待前一个节点删除或超时
            long waitTime = deadline - System.currentTimeMillis();
            if (waitTime <= 0) {
                return false;
            }
            
            latch.await(waitTime, TimeUnit.MILLISECONDS);
        }
        
        return false;
    }
    
    /**
     * 释放锁
     */
    public void unlock(String lockName, String nodePath) {
        try {
            client.delete().guaranteed().forPath(nodePath);
            log.info("🔓 释放ZK锁: node={}", nodePath);
        } catch (Exception e) {
            log.error("❌ 释放锁失败: node={}", nodePath, e);
        }
    }
}

/**
 * 使用Curator的InterProcessMutex(推荐)
 */
@Component
@Slf4j
public class CuratorLockService {
    
    @Autowired
    private CuratorFramework curatorFramework;
    
    /**
     * 使用Curator的分布式锁
     */
    public void lockWithCurator(String lockPath) {
        InterProcessMutex lock = new InterProcessMutex(curatorFramework, lockPath);
        
        try {
            // 获取锁(带超时)
            if (!lock.acquire(10, TimeUnit.SECONDS)) {
                log.warn("⏳ 获取Curator锁超时");
                return;
            }
            
            try {
                log.info("🔒 Curator锁获取成功");
                
                // 执行业务逻辑...
                doBusiness();
                
            } finally {
                // 释放锁
                lock.release();
                log.info("🔓 Curator锁释放");
            }
            
        } catch (Exception e) {
            log.error("❌ Curator锁操作异常", e);
        }
    }
    
    private void doBusiness() {
        // 业务逻辑...
    }
}

五、高级进阶:分布式锁优化策略

5.1 锁粒度优化

java 复制代码
/**
 * 锁粒度优化
 */
@Component
@Slf4j
public class LockOptimization {
    
    /**
     * 优化前:大锁(锁住整个库存)
     */
    public void coarseLock(Long skuId) {
        String lockKey = "lock:stock"; // 所有商品共用一把锁
        // ...
    }
    
    /**
     * 优化后:细粒度锁(按商品ID加锁)
     */
    public void fineGrainedLock(Long skuId) {
        String lockKey = "lock:stock:" + skuId; // 每个商品一把锁
        // ...
    }
    
    /**
     * 优化后:分段锁(减少锁竞争)
     */
    public void segmentedLock(Long userId, Long skuId) {
        // 将用户ID分段,不同段用不同锁
        int segment = (userId.intValue() % 100);
        String lockKey = "lock:stock:" + skuId + ":" + segment;
        // ...
    }
}

5.2 锁续期机制(看门狗)

java 复制代码
/**
 * Redisson看门狗机制解析
 */
@Component
@Slf4j
public class WatchDogDemo {
    
    @Autowired
    private RedissonClient redissonClient;
    
    /**
     * 看门狗自动续期演示
     */
    public void watchDogDemo() {
        RLock lock = redissonClient.getLock("watchdog:demo");
        
        try {
            // 不指定leaseTime,启用看门狗(默认30秒过期,每10秒续期)
            lock.lock();
            
            log.info("🔒 获取锁,看门狗启动");
            
            // 模拟长时间业务(60秒)
            for (int i = 0; i < 60; i++) {
                Thread.sleep(1000);
                log.info("⏱️ 业务执行中... {}/60", i + 1);
            }
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock();
            log.info("🔓 释放锁");
        }
    }
    
    /**
     * 手动续期(特殊场景)
     */
    public void manualRenew() {
        RLock lock = redissonClient.getLock("manual:renew");
        
        try {
            // 获取锁,10秒过期
            lock.lock(10, TimeUnit.SECONDS);
            
            log.info("🔒 获取锁,10秒过期");
            
            // 业务执行5秒后,手动续期
            Thread.sleep(5000);
            
            // 续期到30秒
            lock.expire(30, TimeUnit.SECONDS);
            log.info("🔄 手动续期到30秒");
            
            // 继续执行业务...
            Thread.sleep(20000);
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock();
            log.info("🔓 释放锁");
        }
    }
}

5.3 分布式锁监控

java 复制代码
/**
 * 分布式锁监控
 */
@Component
@Slf4j
public class DistributedLockMonitor {
    
    @Autowired
    private RedissonClient redissonClient;
    
    @Autowired
    private MeterRegistry meterRegistry;
    
    /**
     * 监控锁等待时间
     */
    public void recordLockWaitTime(String lockName, long waitTimeMs) {
        meterRegistry.timer("lock.wait.time", "lock", lockName)
            .record(waitTimeMs, TimeUnit.MILLISECONDS);
    }
    
    /**
     * 监控锁持有时间
     */
    public void recordLockHoldTime(String lockName, long holdTimeMs) {
        meterRegistry.timer("lock.hold.time", "lock", lockName)
            .record(holdTimeMs, TimeUnit.MILLISECONDS);
    }
    
    /**
     * 监控锁获取失败
     */
    public void recordLockFailure(String lockName) {
        meterRegistry.counter("lock.failure", "lock", lockName).increment();
    }
    
    /**
     * 定时检查死锁
     */
    @Scheduled(fixedRate = 60000)
    public void checkDeadLock() {
        // 检查是否有锁长时间未释放
        // 可以通过Redisson的RKeys获取所有锁信息
        // 或者通过监控指标告警
    }
}

六、预判问题与解答

Q1:Redisson锁的看门狗机制是什么?

A:看门狗是Redisson的自动续期机制:

复制代码
看门狗机制:

1. 默认配置:
   - 锁过期时间:30秒(lockWatchdogTimeout)
   - 续期间隔:10秒(过期时间的1/3)

2. 工作流程:
   获取锁 ──→ 启动看门狗定时任务 ──→ 每10秒检查
                                      │
                                      ├── 业务未完成 → 续期到30秒
                                      │
                                      └── 业务完成 → 停止看门狗

3. 注意事项:
   - 只有不指定leaseTime时才启用看门狗
   - 指定leaseTime后不会自动续期
   - 业务完成必须释放锁,否则看门狗一直续期

Q2:Redis主从切换时锁会丢失吗?

A:会,这是Redis分布式锁的固有缺陷:

复制代码
问题场景:
1. 客户端A在主节点获取锁
2. 主节点宕机,锁还未同步到从节点
3. 从节点晋升为主节点
4. 客户端B在新主节点获取锁(成功!)
5. 此时A和B都持有锁,违反互斥性

解决方案:
1. RedLock(红锁):在多个独立主节点上加锁
2. ZooKeeper分布式锁:CP模型,强一致性
3. 业务层补偿:锁失效后通过版本号等机制兜底

Q3:分布式锁和数据库乐观锁怎么选?

A

场景 推荐方案 说明
高并发秒杀 分布式锁 防止大量请求打到数据库
库存扣减 分布式锁 + 缓存预减 双重保障
订单状态变更 数据库乐观锁 天然幂等
防重复提交 Token令牌 无需加锁
定时任务调度 分布式锁 防止多实例同时执行

Q4:锁的过期时间怎么设置?

A

复制代码
过期时间设置原则:

1. 业务执行时间 < 过期时间 < 业务最大容忍时间

2. 推荐公式:
   过期时间 = 平均业务时间 × 3 + 网络延迟

3. 示例:
   - 业务平均执行时间:500ms
   - 网络延迟:100ms
   - 过期时间:500 × 3 + 100 = 1600ms ≈ 2秒

4. 使用看门狗:
   - 不设置过期时间,让看门狗自动续期
   - 适合业务时间不确定的场景

5. 避免:
   - 过期时间太短:业务未完成锁已释放
   - 过期时间太长:故障时长时间阻塞

Q5:分布式锁的性能瓶颈在哪里?

A

复制代码
性能瓶颈分析:

1. 网络IO:
   - 每次加锁/解锁需要网络往返
   - 优化:本地缓存 + 批量操作

2. 锁竞争:
   - 大量线程竞争同一把锁
   - 优化:锁分段、减少锁粒度

3. 续期开销:
   - 看门狗定时续期
   - 优化:合理设置过期时间

4. 监控建议:
   - 锁等待时间 > 100ms 告警
   - 锁持有时间 > 5s 告警
   - 锁获取失败率 > 1% 告警

七、面试高频考点

考点1:Redisson分布式锁的底层原理?

参考答案

复制代码
Redisson分布式锁原理:

1. 加锁:
   - 使用Lua脚本原子性执行:
     if redis.call('exists', KEYS[1]) == 0 then
         redis.call('hset', KEYS[1], ARGV[2], 1);
         redis.call('pexpire', KEYS[1], ARGV[1]);
         return nil;
     end;
   - 使用Hash结构存储锁:key -> {线程标识: 重入次数}

2. 解锁:
   - 使用Lua脚本原子性执行:
     if redis.call('exists', KEYS[1]) == 0 then
         redis.call('publish', KEYS[2], ARGV[1]);
         return 1;
     end;
   - 先检查是否持有锁,再删除

3. 看门狗:
   - 获取锁后启动定时任务
   - 每10秒续期到30秒
   - 业务完成停止定时任务

考点2:ZooKeeper分布式锁和Redis分布式锁有什么区别?

参考答案

特性 Redis(Redisson) ZooKeeper
一致性 AP(最终一致) CP(强一致)
性能 高(10万QPS) 中(万级QPS)
可靠性 中(主从切换可能丢锁) 高(不会丢锁)
实现复杂度 低(Redisson封装好)
死锁处理 过期时间 临时节点自动删除
排队机制 自旋重试 顺序节点+监听
可重入 支持 需自行实现

考点3:什么是RedLock?有什么问题?

参考答案

复制代码
RedLock算法:
1. 向N个独立的Redis节点申请锁
2. 计算获取锁的总耗时
3. 如果成功获取多数节点(N/2+1)且耗时小于锁过期时间,则获取成功

问题:
1. 时钟漂移:各节点时钟不一致可能导致锁判断错误
2. 网络延迟:获取多数节点锁的耗时难以控制
3. 故障恢复:节点重启后可能丢失锁信息

争议:
- Redis作者antirez提出
- 分布式专家Martin Kleppmann质疑
- 实际生产中较少使用,多使用Redisson普通锁+业务兜底

考点4:分布式锁在业务层怎么兜底?

参考答案

复制代码
业务层兜底策略:

1. 数据库乐观锁:
   - 加锁失败时,使用版本号控制并发
   - UPDATE stock SET count = count - 1, version = version + 1 
     WHERE sku_id = ? AND version = ?

2. 唯一索引:
   - 防止重复操作(如重复下单)
   - 数据库层面最终保证一致性

3. 状态机校验:
   - 操作前检查业务状态
   - 非法状态直接拒绝

4. 对账补偿:
   - 定时任务对账
   - 发现不一致后补偿

八、总结与最佳实践

8.1 核心要点回顾

复制代码
分布式锁实现方案:

┌─────────────────────────────────────────────────────────────┐
│  1. Redis + Redisson(推荐,高并发场景)                      │
│     ├── 使用RLock获取锁                                       │
│     ├── 看门狗自动续期                                        │
│     ├── 支持可重入                                            │
│     └── 红锁防止单点故障                                      │
│                                                              │
│  2. ZooKeeper(推荐,强一致场景)                             │
│     ├── 临时顺序节点                                          │
│     ├── 监听机制无轮询                                        │
│     ├── 客户端断开自动释放                                    │
│     └── 使用Curator简化开发                                   │
│                                                              │
│  3. 优化策略                                                  │
│     ├── 锁粒度细化(按业务ID)                                │
│     ├── 锁分段(减少竞争)                                    │
│     ├── 异步处理(减少锁持有时间)                            │
│     └── 缓存预减(减少锁压力)                                │
│                                                              │
│  4. 监控告警                                                  │
│     ├── 锁等待时间                                            │
│     ├── 锁持有时间                                            │
│     └── 锁获取失败率                                          │
└─────────────────────────────────────────────────────────────┘

8.2 适用场景

场景 推荐方案 说明
秒杀库存扣减 Redisson 高并发,性能敏感
订单号生成 Redisson 需要全局唯一
定时任务调度 Redisson/ZK 防止多实例同时执行
分布式事务 ZK 强一致性要求
配置中心选举 ZK 需要Leader选举

8.3 性能数据

某秒杀系统实测:

方案 QPS 平均延迟 99分位延迟
无锁 100000 1ms 5ms
Redisson 50000 5ms 20ms
ZooKeeper 10000 15ms 50ms
数据库乐观锁 5000 30ms 100ms

九、参考与拓展


互动讨论:你在项目中使用过哪种分布式锁方案?有没有遇到过锁失效或死锁的问题?欢迎在评论区分享!

如果本文对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,持续获取更多Java后端技术干货!

相关推荐
ANnianStriver1 小时前
PetLumina 04 — 管理后台 UI 全面升级
java·ui·ai编程
winlife_1 小时前
全程用 AI 做一款商业级手游 · EP9 收尾与复盘:做到了哪,没做到哪,边界在哪
java·开发语言·人工智能·unity·ai编程·游戏开发·mcp
云恒要逆袭1 小时前
Hello World背后的秘密:Java程序是这样运行的
java·后端·程序员
JAVA9651 小时前
JAVA面试-并发篇 09-LockSupport 和 waitnotify 的区别
java·开发语言·面试
蝎子莱莱爱打怪1 小时前
XZLL-IM干货系列 01|万字拆解分布式 IM 架构:7 个微服务 + 自研 Flutter SDK
java·后端·面试
程序员小羊!2 小时前
07Java IO 流
java·开发语言
ZC跨境爬虫2 小时前
跟着 MDN 学JavaScript day_10:数组——数据的有序集合
android·java·开发语言·前端·javascript
亦暖筑序2 小时前
Java 8老系统旁路接入AI Gateway:不升级JDK也能用AI
java·spring boot·aigc·企业架构·ai gateway
IT龟苓膏2 小时前
Java 集合进阶:ConcurrentHashMap、HashSet、LinkedHashMap、TreeMap 和 fail-fast 一篇讲清
java·开发语言·jvm