欢迎光临
我们一直在努力

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

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

在这里插入图片描述

文章目录

    • 一、场景引入:一次秒杀活动的库存超卖
      • 1.1 真实案例
      • 1.2 为什么需要分布式锁?
    • 二、解决方案:两种分布式锁方案对比
      • 2.1 Redis分布式锁(Redisson)vs ZooKeeper分布式锁
      • 2.2 分布式锁的核心要求
    • 三、实战代码:Redisson分布式锁
      • 3.1 Redisson配置
      • 3.2 基础分布式锁使用
      • 3.3 秒杀场景实战:库存扣减
      • 3.4 Redisson红锁(多主Redis)
    • 四、实战代码:ZooKeeper分布式锁
      • 4.1 ZooKeeper分布式锁原理
      • 4.2 ZooKeeper分布式锁实现
    • 五、高级进阶:分布式锁优化策略
      • 5.1 锁粒度优化
      • 5.2 锁续期机制(看门狗)
      • 5.3 分布式锁监控
    • 六、预判问题与解答
      • Q1:Redisson锁的看门狗机制是什么?
      • Q2:Redis主从切换时锁会丢失吗?
      • Q3:分布式锁和数据库乐观锁怎么选?
      • Q4:锁的过期时间怎么设置?
      • Q5:分布式锁的性能瓶颈在哪里?
    • 七、面试高频考点
      • 考点1:Redisson分布式锁的底层原理?
      • 考点2:ZooKeeper分布式锁和Redis分布式锁有什么区别?
      • 考点3:什么是RedLock?有什么问题?
      • 考点4:分布式锁在业务层怎么兜底?
    • 八、总结与最佳实践
      • 8.1 核心要点回顾
      • 8.2 适用场景
      • 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配置

/**
* 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 基础分布式锁使用

/**
* 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 秒杀场景实战:库存扣减

/**
* 秒杀服务(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)

/**
* 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分布式锁实现

/**
* 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 锁粒度优化

/**
* 锁粒度优化
*/

@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 锁续期机制(看门狗)

/**
* 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 分布式锁监控

/**
* 分布式锁监控
*/

@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

九、参考与拓展

  • Redisson官方文档
  • ZooKeeper分布式锁实现
  • Redis分布式锁的正确实现方式

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

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

赞(0)
未经允许不得转载:171主机测评 » 【Java项目技术亮点】分布式锁实现与优化:从Redisson到ZooKeeper,彻底搞懂分布式锁的底层原理
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址