在分布式环境中,多个服务可能同时竞争同一资源,需要分布式锁来保证操作的原子性。本文将详细介绍分布式锁的实现方案、Redis与ZooKeeper实现、锁粒度设计以及常见问题解决方案。
为什么需要分布式锁
- 多个服务实例可能同时修改同一条数据
- 本地锁只能限制单个JVM内的并发
- 数据库行锁在高并发下性能较差
- 需要跨服务、跨机器的协调能力
分布式锁的核心要求
- 互斥性 - 同一时刻只有一个客户端能获取锁
- 可重入性 - 同一客户端可重复获取同一把锁
- 锁超时 - 防止锁无法释放导致死锁
- 公平性 - 按照请求顺序获取锁(可选)
- 高可用 - 锁服务自身需要高可用
Redis 分布式锁
使用 Redis 的 SET 命令实现分布式锁:
redis-lock.java
// Redis 分布式锁实现 public class RedisDistributedLock { private RedisTemplate<String, String> redisTemplate; private String lockKey; private String lockValue; private long expireMs = 30000; // 默认30秒 private long waitMs = 10000; // 默认10秒 // 获取锁 public boolean tryLock() { lockValue = UUID.randomUUID().toString(); while (System.currentTimeMillis() < waitUntil) { // SET key value NX PX expireTime Boolean success = redisTemplate.opsForValue().setIfAbsent( lockKey, lockValue, Duration.ofMillis(expireMs) ); if (Boolean.TRUE.equals(success)) { return true; } // 短暂等待后重试 Thread.sleep(10); } return false; } // 释放锁(Lua脚本保证原子性) public void unlock() { 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(lockKey), lockValue ); } }
Redisson 使用
Redisson 提供了更完善的分布式锁实现:
redisson-lock.java
// Redisson 分布式锁 @Autowired private RedissonClient redissonClient; public void processOrder(Order order) { // 获取锁,key 粒度细化到订单ID RLock lock = redissonClient.getLock("order:" + order.getId()); try { // 等待锁最多10秒,锁持有30秒自动释放 lock.tryLock(10, 30, TimeUnit.SECONDS); // 业务逻辑 orderService.process(order); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { // 释放锁 if (lock.isHeldByCurrentThread()) { lock.unlock(); } } }
可重入锁实现
reentrant-lock.java
// 可重入锁实现 public class ReentrantRedisLock { public boolean tryLock(String key, String value, long timeout) { // 首次获取锁 if (redisTemplate.opsForValue().setIfAbsent(key, value, Duration.ofMillis(timeout)) { // 记录重入次数 redisTemplate.hashOps().increment(key + ":count", value, 1); return true; } // 重入:当前线程已持有锁 String currentValue = redisTemplate.opsForValue().get(key); if (value.equals(currentValue)) { redisTemplate.hashOps().increment(key + ":count", value, 1); return true; } return false; } public void unlock(String key, String value) { Long count = redisTemplate.hashOps().increment(key + ":count", value, -1); // 重入计数为0时释放锁 if (count <= 0) { redisTemplate.delete(key); } } }
锁粒度设计
锁粒度直接影响并发性能:
| 粒度 | 示例 | 并发度 |
| 全局锁 | lock:global | 最低 |
| 表锁 | lock:order | 较低 |
| 行锁 | lock:order:1001 | 较高 |
| 业务锁 | lock:order:1001:operation | 最高 |
最佳实践
- 锁粒度尽量细化,只锁住需要保护的资源
- 避免锁住整个方法,只锁住关键代码段
- 考虑锁的时效性,持有时间不宜过长
ZooKeeper 分布式锁
ZooKeeper 通过临时顺序节点实现分布式锁:
zk-lock.java
// ZooKeeper 分布式锁 public class ZKDistributedLock { private CuratorFramework client; private String lockPath; // 获取锁 public boolean tryLock() throws Exception { // 创建临时顺序节点 String node = client.create() .creatingParentsIfNeeded() .withMode(CreateMode.EPHEMERAL_SEQUENTIAL) .forPath(lockPath + "/lock-"); // 获取所有锁节点,判断是否是第一个 List<String> children = client.getChildren().forPath(lockPath); Collections.sort(children); if (node.endsWith(children.get(0))) { return true; // 获取到锁 } // 监听前一个节点,阻塞等待 String prevNode = children.get( Collections.binarySearch(children, node.substring(node.lastIndexOf("/") + 1)) - 1 ); // 等待前一个节点删除(锁释放) return waitForLock(prevNode); } private boolean waitForLock(String prevNode) throws Exception { CountDownLatch latch = new CountDownLatch(1); // 注册监听器 client.getData().usingWatcher(new Watcher() { public void process(WatchedEvent event) { if (event.getType() == EventType.NodeDeleted) { latch.countDown(); } } }).forPath(lockPath + "/" + prevNode); return latch.await(30, TimeUnit.SECONDS); } }
常见问题与解决方案
1. 锁超时问题
业务处理时间超过锁有效期,导致锁自动释放被其他线程获取:
- 合理设置锁超时时间
- 使用看门狗自动续期(Redisson支持)
- 将长流程拆分,避免长时间持有锁
2. 脑裂问题
Redis主从切换期间可能出现锁丢失:
- 使用RedLock算法(多节点)
- 或使用ZooKeeper(CP模型)
3. 死锁预防
- 始终在 finally 块释放锁
- 设置锁超时时间
- 使用可重入锁避免自身死锁
IMS系统应用场景
- 库存扣减 - 防止超卖
- 余额操作 - 保证余额一致性
- 定时任务 - 防止重复执行
- 数据同步 - 分布式环境下的数据一致性
总结
分布式锁是分布式系统中保证数据一致性的重要手段。选择合适的锁实现(Redis或ZooKeeper),合理设计锁粒度,正确处理超时和释放,才能构建可靠的高并发系统。