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

文章目录
-
- 一、场景引入:一次秒杀活动的库存超卖
-
- [1.1 真实案例](#1.1 真实案例)
- [1.2 为什么需要分布式锁?](#1.2 为什么需要分布式锁?)
- 二、解决方案:两种分布式锁方案对比
-
- [2.1 Redis分布式锁(Redisson)vs ZooKeeper分布式锁](#2.1 Redis分布式锁(Redisson)vs ZooKeeper分布式锁)
- [2.2 分布式锁的核心要求](#2.2 分布式锁的核心要求)
- 三、实战代码:Redisson分布式锁
-
- [3.1 Redisson配置](#3.1 Redisson配置)
- [3.2 基础分布式锁使用](#3.2 基础分布式锁使用)
- [3.3 秒杀场景实战:库存扣减](#3.3 秒杀场景实战:库存扣减)
- [3.4 Redisson红锁(多主Redis)](#3.4 Redisson红锁(多主Redis))
- 四、实战代码:ZooKeeper分布式锁
-
- [4.1 ZooKeeper分布式锁原理](#4.1 ZooKeeper分布式锁原理)
- [4.2 ZooKeeper分布式锁实现](#4.2 ZooKeeper分布式锁实现)
- 五、高级进阶:分布式锁优化策略
-
- [5.1 锁粒度优化](#5.1 锁粒度优化)
- [5.2 锁续期机制(看门狗)](#5.2 锁续期机制(看门狗))
- [5.3 分布式锁监控](#5.3 分布式锁监控)
- 六、预判问题与解答
- 七、面试高频考点
- 八、总结与最佳实践
-
- [8.1 核心要点回顾](#8.1 核心要点回顾)
- [8.2 适用场景](#8.2 适用场景)
- [8.3 性能数据](#8.3 性能数据)
- 九、参考与拓展
一、场景引入:一次秒杀活动的库存超卖
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后端技术干货!