# 分布式锁解决方案

## 为什么需要分布式锁

在单机环境中，我们可以使用 `synchronized` 或 `ReentrantLock` 等本地锁来保护共享资源。但在分布式系统中，多个进程可能运行在不同的机器上，本地锁无法跨进程协调，因此需要分布式锁。

### 典型场景

1. **库存扣减**：防止超卖
2. **订单创建**：保证订单号唯一性
3. **定时任务**：避免多实例重复执行
4. **资源抢占**：选举 Leader 节点

## 分布式锁的要求

一个可靠的分布式锁应该满足：

1. **互斥性**：任意时刻只有一个客户端能持有锁
2. **安全性**：只有加锁的客户端才能释放锁
3. **可用性**：
   * 加锁和释放锁必须高可用
   * 必须支持容错（部分节点故障仍可工作）
4. **避免死锁**：即使客户端崩溃未释放锁，锁也能自动释放
5. **可重入性**：同一客户端可以重复获取同一把锁

## 方案一：基于 Redis 的分布式锁

### 1. 简单实现（SETNX）

```java
// 加锁
Boolean result = redisTemplate.opsForValue()
    .setIfAbsent(lockKey, requestId, expireTime, TimeUnit.SECONDS);

if (Boolean.TRUE.equals(result)) {
    try {
        // 执行临界区代码
    } finally {
        // 释放锁（需要校验 requestId）
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                       "return redis.call('del', KEYS[1]) else return 0 end";
        redisTemplate.execute(script, keys, args);
    }
}
```

**问题**：

* SETNX 和设置过期时间不是原子操作
* 单机 Redis 故障时可能丢失锁

### 2. Redisson 实现

Redisson 是 Redis 的 Java 客户端，提供了完整的分布式锁实现：

```java
RLock lock = redisson.getLock("myLock");

try {
    // 获取锁，最多等待 10 秒，锁定 30 秒后自动释放
    boolean isLocked = lock.tryLock(10, 30, TimeUnit.SECONDS);
    if (isLocked) {
        // 执行临界区代码
    }
} finally {
    if (lock.isHeldByCurrentThread()) {
        lock.unlock();
    }
}
```

**特性**：

* 支持可重入锁
* 自动续期（WatchDog 机制）
* 支持公平锁、读写锁
* 原子操作保证

### 3. RedLock 算法

RedLock 是 Redis 官方推荐的分布式锁算法，用于解决单点故障问题：

**算法流程**：

1. 获取当前时间（毫秒）
2. 依次向 N 个独立的 Redis 实例尝试获取锁（使用相同的 key 和 value）
3. 计算获取锁消耗的时间（当前时间 - 步骤1的时间）
4. 如果在大多数（N/2 + 1）实例上都成功获取锁，且总耗时小于锁有效期，则加锁成功
5. 否则，向所有实例释放锁

**问题**：

* 需要多个独立的 Redis 实例
* 性能开销较大
* 时钟跳跃可能导致锁失效

## 方案二：基于 ZooKeeper 的分布式锁

### 1. 实现原理

利用 ZooKeeper 的特性实现分布式锁：

* **临时顺序节点**：保证锁的自动释放和公平性
* **Watcher 机制**：实现锁的等待和通知

### 2. 实现方式

#### 非公平锁

```java
// 尝试创建临时节点
try {
    zk.create("/lock", data, ZooDefs.Ids.OPEN_ACL_UNSAFE,
              CreateMode.EPHEMERAL);
    // 创建成功，获得锁
} catch (NodeExistsException e) {
    // 创建失败，等待锁释放
    // 注册 Watcher 监听 /lock 节点
    // 当 /lock 被删除时，重新尝试创建
}
```

#### 公平锁

```java
// 创建临时顺序节点
String path = zk.create("/lock/lock_", data, ZooDefs.Ids.OPEN_ACL_UNSAFE,
                        CreateMode.EPHEMERAL_SEQUENTIAL);

// 获取 /lock 下所有子节点并排序
List<String> children = zk.getChildren("/lock", false);
Collections.sort(children);

// 判断自己是否是最小序号
if (isMyTurn(path, children)) {
    // 获得锁
} else {
    // 监听前一个节点的删除事件
    String prevNode = getPrevNode(path, children);
    zk.exists("/lock/" + prevNode, watchedEvent -> {
        // 前一个节点被删除，重新检查是否轮到自己
    });
}
```

### 3. Curator 实现

Curator 是 Apache 提供的 ZooKeeper 客户端，内置分布式锁实现：

```java
InterProcessMutex lock = new InterProcessMutex(client, "/lock");

try {
    lock.acquire();  // 获取锁
    // 执行临界区代码
} finally {
    lock.release();  // 释放锁
}
```

## 方案三：基于数据库的分布式锁

### 1. 唯一索引实现

```sql
-- 创建锁表
CREATE TABLE distributed_lock (
    lock_name VARCHAR(64) PRIMARY KEY,
    owner VARCHAR(64),
    expire_time TIMESTAMP
);

-- 加锁：插入记录
INSERT INTO distributed_lock (lock_name, owner, expire_time)
VALUES ('my_lock', 'node_1', NOW() + INTERVAL 30 SECOND);

-- 释放锁：删除记录
DELETE FROM distributed_lock WHERE lock_name = 'my_lock' AND owner = 'node_1';
```

### 2. 乐观锁实现

```sql
-- 使用版本号控制
UPDATE resource SET version = version + 1, data = 'new_data'
WHERE id = 1 AND version = 1;
```

## 方案对比

| 特性    | Redis        | ZooKeeper     | 数据库   |
| ----- | ------------ | ------------- | ----- |
| 性能    | 高            | 中             | 低     |
| 可靠性   | 中（RedLock 高） | 高             | 中     |
| 实现复杂度 | 中            | 低（使用 Curator） | 低     |
| 可重入   | 支持（Redisson） | 支持            | 不支持   |
| 公平锁   | 支持           | 支持            | 不支持   |
| 锁等待   | 轮询           | Watcher 通知    | 轮询    |
| 适用场景  | 高性能要求        | 高可靠要求         | 已有数据库 |

## 最佳实践

### 1. 锁的粒度

* 尽量减小锁的范围
* 使用细粒度锁提高并发度

### 2. 超时设置

* 设置合理的锁超时时间
* 超时时间应大于业务执行时间
* 避免死锁，但要防止误释放

### 3. 锁的标识

* 使用唯一标识（如 UUID）标记锁的持有者
* 释放锁时必须校验持有者身份

### 4. 监控和告警

* 监控锁的获取和释放
* 检测锁的竞争情况
* 告警死锁和长锁

## 总结

分布式锁是分布式系统中的核心组件。Redis 适合高性能场景，ZooKeeper 适合高可靠场景，数据库方案适合已有基础设施的场景。选择合适的方案需要综合考虑性能、可靠性、实现复杂度等因素。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://qiangrens-organization.gitbook.io/qkd90/8-fen-bu-shi-li-lun/fen-bu-shi-suo-jie-jue-fang-an.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
