三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

分布式锁实战:Redis与ZooKeeper对比选型与实现方案

分布式锁实战:Redis与ZooKeeper对比选型与实现方案

分布式锁实战:Redis与ZooKeeper对比选型与实现方案

大家好,我是迪哥。分布式锁是分布式系统中最基础也是最重要的组件之一,从 Redis Redlock 到 ZooKeeper 临时节点,从数据库乐观锁到 etcd,我们尝试过多种方案。今天就聊聊分布式锁的选型和实现经验。

分布式锁需求分析

核心特性

特性说明
互斥性同一时刻只能有一个客户端持有锁
超时释放防止死锁
可重入同一客户端可重复获取同一把锁
高可用锁服务本身要高可用
公平性按请求顺序获取锁

Redis 分布式锁

基础实现

@Service public class RedisLockService { @Autowired private StringRedisTemplate redisTemplate; private static final String LOCK_PREFIX = "lock:"; private static final long DEFAULT_EXPIRE = 30000; // 30秒 public boolean tryLock(String lockKey, String requestId, long expire) { Boolean result = redisTemplate.opsForValue() .setIfAbsent(LOCK_PREFIX + lockKey, requestId, expire, TimeUnit.MILLISECONDS); return Boolean.TRUE.equals(result); } public void unlock(String lockKey, String requestId) { String script = """ if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end """; redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Collections.singletonList(LOCK_PREFIX + lockKey), requestId); } }

Redlock 算法

public class RedlockService { private final List<StringRedisTemplate> redisTemplates; public boolean acquireLock(String lockKey, String requestId, long expire) { int quorum = redisTemplates.size() / 2 + 1; int successCount = 0; for (StringRedisTemplate redis : redisTemplates) { try { Boolean result = redis.opsForValue() .setIfAbsent(lockKey, requestId, expire, TimeUnit.MILLISECONDS); if (Boolean.TRUE.equals(result)) { successCount++; } } catch (Exception e) { // 节点不可用 } } return successCount >= quorum; } }

Redis 锁的问题

  1. 单点故障:主从切换时可能丢失锁
  2. 超时问题:业务执行时间超过锁过期时间
  3. 续期问题:需要看门狗机制

ZooKeeper 分布式锁

基于临时节点的实现

public class ZkLockService { private final CuratorFramework client; public boolean tryLock(String lockKey) throws Exception { String lockPath = "/locks/" + lockKey; // 创建临时有序节点 String nodePath = client.create() .creatingParentContainersIfNeeded() .withMode(CreateMode.EPHEMERAL_SEQUENTIAL) .forPath(lockPath + "/lock-"); // 获取所有子节点 List<String> children = client.getChildren().forPath(lockPath); Collections.sort(children); // 判断是否是最小节点 if (nodePath.endsWith(children.get(0))) { return true; } // 监听前一个节点 String prevNode = children.get(findPrevIndex(children, nodePath)); CountDownLatch latch = new CountDownLatch(1); Watcher watcher = event -> { if (event.getType() == Watcher.Event.EventType.NodeDeleted) { latch.countDown(); } }; client.getData().usingWatcher(watcher).forPath(lockPath + "/" + prevNode); latch.await(); // 重新检查 return checkMinNode(nodePath, lockPath); } }

ZooKeeper 锁的优势

  1. 天生分布式:基于 ZAB 协议,天然高可用
  2. 自动释放:会话断开自动删除临时节点
  3. 公平锁:按节点顺序获取锁

对比选型

维度RedisZooKeeper
性能高(百万级 QPS)中等(万级 QPS)
可靠性依赖主从复制基于 ZAB,更可靠
实现复杂度简单复杂
锁类型可实现公平/非公平天然公平锁
适用场景高吞吐、非强一致场景强一致、低吞吐场景

数据库分布式锁

乐观锁

@Service public class OptimisticLockService { @Autowired private OrderMapper orderMapper; @Transactional public boolean updateOrder(Long orderId, Integer expectedVersion) { int rows = orderMapper.updateWithVersion(orderId, expectedVersion); return rows > 0; } }
UPDATE orders SET status = 'PAID', version = version + 1 WHERE id = ? AND version = ?;

悲观锁

@Transactional public void processOrder(Long orderId) { Order order = orderMapper.selectForUpdate(orderId); // 处理业务 orderMapper.update(order); }

最佳实践

选择建议

┌──────────────────┐ │ 性能要求高? │ └────────┬─────────┘ │ ┌────────┴────────┐ ▼ ▼ Yes No │ │ ┌────────▼────────┐ ┌─────▼─────┐ │ 使用 Redis │ │ZooKeeper │ │ Redlock 模式 │ │ 或 │ └─────────────────┘ │ 数据库锁 │ └───────────┘

注意事项

  1. 锁粒度:尽量细粒度,避免锁住整个资源
  2. 超时设置:合理设置过期时间,考虑业务执行时间
  3. 异常处理:获取锁失败时的降级策略
  4. 监控告警:监控锁的获取成功率、等待时间

说到分布式锁,我家那只叫 Docker 的哈士奇最近学会了"锁机制"——它会把喜欢的玩具咬在嘴里,谁要都不给,这独占性比我们的 Redis 锁还强 😂

我是迪哥,我们下期再见!

← 返回列表