logo 范 · 拾光录
网址收集 关于作者 Github Gitee
杂文随笔4
Hexo博客:基础使用Hexo博客:Next主题Hexo博客:Next进阶使用Hexo博客:Next高级配置
前端编程3
前端基础知识16
UniApp15
Vue框架19
Java编程7
Java基础知识14
SpringBoot31
SpringMVC14
MyBatis8
SpringCloud15
RocketMQ1
MyCat1
数据库4
MySQL6
Redis7
MongoDB10
H21
Java技术栈4
知识整理分布式锁的实现方案MySQL事务MySQL的锁
软件工具箱14
IDEAGitMavenGradleNginx安装Nginx配置JMeter压测OllamaPicGoRustFSVSCodeDockerObsidianObs录制
Linux知识11
树莓派安装及使用ArchLinux:常用软件ArchLinux:基础系统安装ArchLinux:深度优化ArchLInux:图形化界面安装ArchLinux:Nirifrp内网穿透Jar启动脚本Linux常用命令VirtualBox安装CentOSVirtualBox安装Ubuntu
Python编程6
Python基础知识Python语法yolo目标检测OpenCV的使用及树莓派平台condauv
创意设计2
Blender:入门知识UI设计基础知识
AI相关9
Claude CodeHermes AgentOpenAI基本使用OpenAI工具调用OpenAI记忆管理OpenAI推理执行OpenAI开发框架Langchainllama.cpp

一、基于数据库

1. 基于唯一索引(乐观锁)

原理:利用数据库表的唯一约束,插入成功即获取锁,删除即释放锁。

CREATE TABLE distributed_lock (
    lock_key   VARCHAR(64) PRIMARY KEY,
    holder     VARCHAR(64) NOT NULL,
    expire_at  DATETIME    NOT NULL,
    INDEX idx_expire (expire_at)
);

加锁:INSERT INTO distributed_lock VALUES ('order:1001', 'node-1', NOW() + INTERVAL 30 SECOND) → 插入成功=获取锁,唯一键冲突=锁被占用。

释放锁:DELETE FROM distributed_lock WHERE lock_key = 'order:1001' AND holder = 'node-1'

缺陷:没有内置过期机制,需要定时任务清理过期锁;数据库是单点故障。

2. 基于行锁(悲观锁)

SELECT * FROM distributed_lock WHERE lock_key = 'xxx' FOR UPDATE;

事务提交后释放行锁。需要关闭自动提交(autocommit=0),否则 FOR UPDATE 不生效。

缺陷:数据库连接数瓶颈;MySQL 5.7 前 FOR UPDATE 可能导致间隙锁死锁。

数据库方案总结

二、基于 Redis

1. 单节点 Redis 锁

加锁 —— SET NX + EX 原子命令(Redis 2.6.12+):

SET lock_key unique_value NX EX 30

释放锁 —— Lua 脚本保证原子性(先查再删,避免误删他人锁):

if redis.call("GET", KEYS[1]) == ARGV[1] then
    return redis.call("DEL", KEYS[1])
else
    return 0
end

Java 示例(Jedis):

String lockKey = "order:1001";
String requestId = UUID.randomUUID() + ":" + Thread.currentThread().getId();

// 加锁
String result = jedis.set(lockKey, requestId, new SetParams().nx().ex(30));
boolean locked = "OK".equals(result);

// 释放锁
String script = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

2. Redisson 看门狗机制

Redisson 封装了 Redis 锁,核心特性:

RLock lock = redisson.getLock("order:1001");
try {
    lock.lock();  // 阻塞直到获取锁,Watchdog 自动续期
    // 业务逻辑
} finally {
    lock.unlock();
}

也支持指定过期时间(此时不触发 Watchdog):

lock.lock(10, TimeUnit.SECONDS);  // 10 秒后自动释放,不会续期

3. Redlock 算法(多节点)

背景:单节点 Redis 锁在 master 宕机后可能丢失(主从异步复制,已获取的锁未同步到 slave)。

Redlock 算法(Redis 作者 antirez 提出):

  1. 获取当前毫秒时间戳
  2. 依次向 N 个独立 Redis 节点请求锁,每个节点超时时间远小于锁 TTL
  3. 只有当超过半数(N/2+1)节点加锁成功,且总耗时 < 锁 TTL,才算获取成功
  4. 如果失败,向所有节点发送释放命令
  5. 锁有效时间 = TTL - 总耗时,到期后需续期

Java 实现:

RedissonRedLock redLock = redisson.getRedLock(
    redisson.getLock("lock1"),
    redisson.getLock("lock2"),
    redisson.getLock("lock3")
);
redLock.lock();

注意:Redlock 存在争议(Martin Kleppmann 认为缺乏 fencing token 不安全),高安全性场景建议用 ZooKeeper/Etcd。

Redis 方案总结

三、基于 ZooKeeper

核心原理

利用 ZK 的临时顺序节点 + Watch 机制实现:

  1. 所有请求者在 /lock 下创建临时顺序节点(EPHEMERAL_SEQUENTIAL),如 /lock/000000001
  2. 获取 /lock 下所有子节点,按序号排序
  3. 如果自己是最小序号节点 → 获取锁
  4. 如果不是最小 → 对前一个节点(比自己小且序号最接近的)注册 Watch
  5. 前一个节点释放后,Watch 通知自己,重复步骤 2

为什么是安全的

命令交互示意

Client A: create -e -s /lock/request → /lock/request_0001
Client B: create -e -s /lock/request → /lock/request_0002
Client C: create -e -s /lock/request → /lock/request_0003

A 最小 → A 获取锁
B watch A
C watch B

A 释放 → ZK 通知 BB 获取锁 → C watch B

Curator 框架

InterProcessMutex lock = new InterProcessMutex(curatorClient, "/lock");
try {
    if (lock.acquire(10, TimeUnit.SECONDS)) {
        // 业务逻辑
    }
} finally {
    lock.release();
}

Curator 封装了顺序节点创建 + Watch + 重试逻辑,一行代码即可使用。

ZK 方案总结

四、基于 Etcd

与 ZK 同属 CP 系统(Raft 协议),类似机制:

// Jetcd 示例
Lease lease = client.getLeaseClient();
long leaseId = lease.grant(30).get().getID();

Lock lock = new Lock(client, leaseId);  // 注意是 etcd Lock,非 JUC Lock
lock.lock();

优点:Raft 协议比 ZAB 更简洁;Lease 机制比临时节点更灵活;社区活跃 缺点:和 ZK 一样性能不如 Redis;Go 生态原生,Java 封装不如 Curator 成熟

五、方案对比与选型

维度 数据库 Redis ZooKeeper Etcd
性能 高(10w+ QPS)
一致性 取决于DB AP(可能丢失) CP(强一致) CP(强一致)
自动释放 需定时清理 TTL 自动过期 临时节点自动删 Lease 自动过期
公平性 手动实现 手动实现 天然支持 天然支持
可重入 手动实现 Redis Hash 手动实现 手动实现
运维成本 低(复用DB) 中-高
适用场景 低并发/无额外组件 高并发/性能优先 强一致性/金融 云原生/K8s

选型建议:

六、常见面试追问点

  1. Redis 锁过期了但业务还没执行完怎么办? → Redisson Watchdog 自动续期;或业务内异步定时续期
  2. 主从切换导致锁丢失? → Redlock 多节点投票;或 ZooKeeper CP 方案
  3. ZooKeeper 羊群效应(惊群)怎么解决? → 只 Watch 前一个节点,不 Watch 全部子节点
  4. ZK 和 Redis 混合方案什么时候用? → 用 ZK 选主(强一致),Redis 分片锁(高性能)
  5. fencing token 是什么? → 递增令牌,锁持有者携带 token 操作共享资源,资源端校验 token 防止过期锁误操作
一、基于数据库
1. 基于唯一索引(乐观锁)
2. 基于行锁(悲观锁)
数据库方案总结
二、基于 Redis
1. 单节点 Redis 锁
2. Redisson 看门狗机制
3. Redlock 算法(多节点)
Redis 方案总结
三、基于 ZooKeeper
核心原理
为什么是安全的
命令交互示意
Curator 框架
ZK 方案总结
四、基于 Etcd
五、方案对比与选型
六、常见面试追问点