如何实现分布式锁

作者:哈里谢顿日期:2026/4/10

分布式锁是分布式系统中协调多个节点对共享资源访问的关键机制。我来从原理到实现,系统性地讲解。


核心要求

一个可靠的分布式锁必须满足:

特性说明
互斥性同一时间只有一个客户端能持有锁
防死锁锁必须有过期机制,避免客户端崩溃后锁永远不被释放
可重入性(可选)同一客户端可以多次获取同一把锁
容错性大部分节点存活时,锁服务仍能正常工作

方案一:基于 Redis(最常用)

1. 基础版(SETNX + EXPIRE)

1SETNX lock:resource "client_id"    # 如果不存在则设置
2EXPIRE lock:resource 30            # 设置30秒过期
3

问题:SETNX 和 EXPIRE 不是原子操作,如果中间崩溃会导致死锁。

2. 改进版(Redis 2.6+ 原子命令)

1SET lock:resource "client_id" NX EX 30
2# NX = Not eXists(不存在才设置)
3# EX = EXpire(秒级过期)
4

3. 生产级实现(Redisson 原理)

1import redis
2import uuid
3import threading
4
5class RedisDistributedLock:
6    def __init__(self, redis_client, lock_key, expire=30):
7        self.redis = redis_client
8        self.lock_key = f"lock:{lock_key}"
9        self.expire = expire
10        self.identifier = str(uuid.uuid4())  # 唯一标识,防止误删他人锁
11    
12    def acquire(self):
13        """获取锁"""
14        # 原子性设置锁
15        result = self.redis.set(
16            self.lock_key, 
17            self.identifier,
18            nx=True,      # 不存在才设置
19            ex=self.expire  # 过期时间
20        )
21        if result:
22            # 启动看门狗线程,自动续期
23            self._start_watchdog()
24            return True
25        return False
26    
27    def release(self):
28        """释放锁 - 使用 Lua 脚本保证原子性"""
29        lua_script = """
30        if redis.call("get", KEYS[1]) == ARGV[1] then
31            return redis.call("del", KEYS[1])
32        else
33            return 0
34        end
35        """
36        # 只有锁的持有者才能删除
37        self.redis.eval(lua_script, 1, self.lock_key, self.identifier)
38        self._stop_watchdog()
39    
40    def _start_watchdog(self):
41        """看门狗:自动续期,防止业务执行超时"""
42        def renew():
43            while self._running:
44                time.sleep(self.expire / 3)
45                # 如果锁还存在,续期
46                lua_script = """
47                if redis.call("get", KEYS[1]) == ARGV[1] then
48                    return redis.call("expire", KEYS[1], ARGV[2])
49                end
50                return 0
51                """
52                self.redis.eval(lua_script, 1, self.lock_key, 
53                              self.identifier, self.expire)
54        
55        self._running = True
56        self._watchdog = threading.Thread(target=renew)
57        self._watchdog.daemon = True
58        self._watchdog.start()
59    
60    def _stop_watchdog(self):
61        self._running = False
62
63# 使用
64redis_client = redis.Redis()
65lock = RedisDistributedLock(redis_client, "order:123")
66
67if lock.acquire():
68    try:
69        # 执行业务逻辑
70        process_order()
71    finally:
72        lock.release()
73

4. RedLock 算法(Redis 作者提出,多主节点)

解决单点 Redis 故障问题,需要奇数个独立 Redis 节点(如 5 个):

1class RedLock:
2    def __init__(self, redis_nodes, lock_key, expire=30):
3        self.nodes = [redis.Redis(**node) for node in redis_nodes]
4        self.lock_key = lock_key
5        self.expire = expire
6        self.quorum = len(redis_nodes) // 2 + 1  # 多数派
7    
8    def acquire(self):
9        identifier = str(uuid.uuid4())
10        locked_nodes = 0
11        
12        for node in self.nodes:
13            try:
14                if node.set(self.lock_key, identifier, nx=True, ex=self.expire):
15                    locked_nodes += 1
16            except:
17                continue
18        
19        # 获取多数派且总耗时小于锁过期时间
20        if locked_nodes >= self.quorum:
21            return True
22        
23        # 失败,释放已获取的锁
24        self.release(identifier)
25        return False
26

方案二:基于 ZooKeeper / etcd

更适合强一致性场景,利用临时顺序节点实现:

ZooKeeper 实现原理

11. 客户端在 /locks/resource 下创建临时顺序节点:/locks/resource/lock-00000001
22. 检查自己是否是最小编号:
3   - 是:获得锁
4   - 否:监听前一个节点(Watcher),等待其删除
53. 业务完成后删除节点,自动唤醒下一个等待者
64. 客户端崩溃  会话超时  临时节点自动删除  锁释放
7
1from kazoo.client import KazooClient
2
3class ZkDistributedLock:
4    def __init__(self, zk_hosts, lock_path):
5        self.zk = KazooClient(hosts=zk_hosts)
6        self.zk.start()
7        self.lock_path = lock_path
8        self.lock = self.zk.Lock(lock_path, "my_identifier")
9    
10    def acquire(self, timeout=None):
11        return self.lock.acquire(blocking=True, timeout=timeout)
12    
13    def release(self):
14        self.lock.release()
15

优势

  • 天然防死锁(临时节点会话断开自动删除)
  • 可重入(同一会话可多次获取)
  • 强一致性(ZAB 协议)

方案三:基于数据库

MySQL 实现

1-- 建表
2CREATE TABLE distributed_lock (
3    lock_name VARCHAR(64) PRIMARY KEY,
4    identifier VARCHAR(64) NOT NULL,
5    expire_time TIMESTAMP NOT NULL
6);
7
8-- 获取锁(插入,利用唯一索引)
9INSERT INTO distributed_lock (lock_name, identifier, expire_time)
10VALUES ('order:123', 'uuid-xxx', DATE_ADD(NOW(), INTERVAL 30 SECOND));
11
12-- 释放锁
13DELETE FROM distributed_lock WHERE lock_name = 'order:123' AND identifier = 'uuid-xxx';
14
15-- 清理过期锁(定时任务)
16DELETE FROM distributed_lock WHERE expire_time < NOW();
17

缺点:性能差,无自动续期,仅适合低频场景。


方案对比

方案优点缺点适用场景
Redis性能极高(10w+ QPS),实现简单单点故障(除非 RedLock),时钟漂移问题高并发、允许短暂不一致
ZooKeeper强一致性,自动故障恢复性能较低(写操作需半数确认),部署复杂强一致性要求(如金融)
etcd类似 ZK,但基于 Raft 更易用性能一般K8s 生态、服务发现
MySQL无需额外组件性能差,单点瓶颈低频、简单场景

关键问题与解决方案

1. 锁续期(看门狗机制)

业务执行时间超过锁过期时间时,需要自动续期:

1# 独立线程定时检查,剩余时间 < 1/3 时续期
2if ttl < expire_time / 3:
3    redis.expire(lock_key, expire_time)
4

2. 主从延迟问题(Redis)

主节点宕机,从节点晋升为主,但锁数据可能未同步 → RedLock 或 Redisson 的 WAIT 命令

3. 可重入实现

1#  value 记录:client_id + 重入次数
2"client_abc:3"  # 表示 client_abc  3 次重入
3
4# 释放时递减,为 0 时才删除
5

4. 公平锁 vs 非公平锁

  • 非公平锁:所有客户端同时抢,谁快谁得(Redis 默认)
  • 公平锁:按请求顺序排队(ZK 临时顺序节点天然支持)

生产建议

场景推荐方案
一般互联网应用Redisson(Redis 单节点 + 看门狗)
要求强一致性(库存扣减)ZooKeeperetcd
极高可用要求RedLock(5 个 Redis 节点)
已有 K8s 集群etcd(原生支持)

如何实现分布式锁》 是转载文章,点击查看原文


相关推荐


Ospf网络类型:P2P和Broadcast
24zhgjx-fuhao2026/4/2

一、P2P (一)、P2P的作用及特点 P2P(点到点)网络类型 1.作用: 适用于连接两台路由器的链路‌,例如通过PPP(点对点协议)或HDLC(高级数据链路控制)封装的串行链路。不需要进行DR(指定路由器)和BDR(备份指定路由器)的选举。路由器之间可以直接建立邻接关系(Full状态),无需通过DR/BDR进行LSA(链路状态通告)的泛洪。 2.特点: 无需选举DR/BDR‌:因为只有两个设备通信。自动发现邻居‌:通过发送Hello报文自动发现邻居。组播发送协议报文‌:使用组播地


Linux中基础IO知识全解
顶点多余2026/3/25

1.什么是文件 文件是指内容+属性,所以文件永远不是只有内容; 1.1狭义上: ①文件的存储特性 永久性存储:磁盘是永久性存储介质,文件在磁盘上的存储是永久性的(断电不丢失) ②磁盘的设备属性 外部设备:磁盘属于外设(I/O设备) 双重角色:既是输出设备(写入数据),也是输入设备(读取数据) ③文件操作的本质 I/O操作:对磁盘上文件的所有操作,本质上都是对外设的输入和输出 统称IO:所有文件读写操作,都可以简称为IO 1.2广义上: Linux 下⼀切皆⽂件(键盘、显⽰器、⽹卡、


Windows 11 上搭建 YouTube 视频下载工具:yt-dlp + FFmpeg
liulilittle2026/3/17

Windows 11 上搭建 YouTube 视频下载工具:yt-dlp + FFmpeg 作为经常需要下载 YouTube 视频的人,你一定遇到过这样的烦恼:在线下载网站不稳定、广告多,或者限制大小。今天我就来分享一套本地搭建的下载方案,使用 yt-dlp(youtube-dl 的活跃分支)配合 FFmpeg,在 Windows 11 上轻松下载单条视频或整个播放列表,全部转为 MP4 格式。最重要的是,完全免费、无广告、速度快! 准备工作 一台 Windows 11 电脑,且有管理


Android MediatorLiveData
louisgeek2026/3/8

Android MediatorLiveData 可以监听多个 LiveData 源,并且能动态添加和移除源 合并多个 LiveData 源 public class CombineViewModel extends ViewModel { private final MutableLiveData<One> _oneLiveData = new MutableLiveData<>(); public final LiveData<One> oneLiveData = _on


【AI个人学习】npm本地安装claude code白嫖minimax模型
汐瀼2026/2/28

安装nodejs 下载 需要自取,下一步傻瓜式操作 通过网盘分享的文件:node-v24.13.0-x64.msi 链接: https://pan.baidu.com/s/1eJhCowFZ211oV2yxAfPvQA?pwd=sayg 提取码: sayg –来自百度网盘超级会员v7的分享 系统变量添加全局包路径 打开CMD敲命令 npm config get prefix # 获取npm全局包路径,获取后复制 添加路径到系统变量即可,添加系统变量网上教程一大堆 安装claude


你是不是觉得 R8 很讨厌,但 Android 为什么选择 R8 ?也许你对 R8 还不够了解
恋猫de小郭2026/2/20

本篇是来自 Android Developers 的播客 《What’s so great about R8?》 的整合,核心是讨论了 Android R8 编译器以及它对性能的影响,参与讨论的嘉宾包括来自 Android 工具团队、R8 团队和平台性能团队的专家(Tor Norby, Romain Guy, Sean, Chris, Shai)。 这是一篇让你对 R8 不再误解的内容。 D8 与 R8 编译器的区别 首先可能不少人还不理解 D8 与 R8 的区别,在 Android 开发里


如何零成本搭建个人站点
mCell2026/2/12

同步至个人站点:如何零成本搭建个人站点 站点地址:stack.mcell.top,包含完整的:写作、评论、部署、MCP支持... 我经常写作,最开始是在一些平台上,比如稀土掘金。后面慢慢写多了,就想有个自己的博客平台。 最初搭建的博客很简单:一个纯静态的 HTML 文件,内容也不复杂,写点自我介绍,当作个人站点。直接托管到 GitHub Pages,域名用的也是它默认那串。 但很快就发现:功能太少了。 比如发布文章?评论?甚至想加点扩展能力都很难——纯 HTML 又没框架,后面越改越痛苦。


PostgreSQL全文检索中文分词器配置与优化实践
MarsBighead2026/2/3

引言 在构建RAG(检索增强生成)系统的过程中,提升检索效率与准确性是一个持续优化的课题。除了常见的嵌入向量检索外,结合全文检索技术能进一步改善系统表现。本文基于PostgreSQL数据库,分享中文全文检索分词器的配置、索引创建与使用实践,记录在真实场景中遇到的问题与解决方案。 一、背景 为了提升RAG系统的检索效果,我们探索了全文检索与向量检索结合的混合检索方案。PostgreSQL内置了强大的全文检索功能,并支持扩展插件实现多语言分词。针对中文场景,我们选用了 zhparser 分词插件,


怎么理解 HttpServletRequest @Autowired注入
それども2026/1/24

在你的代码中,@Autowired(required = false) private HttpServletRequest req 的 required = false 是多余的,甚至可能带来潜在问题。以下是详细分析: 1. 为什么 required = false 是多余的? (1) HttpServletRequest 的特殊性 由 Servlet 容器(如 Tomcat)管理:HttpServletRequest 是 Web 请求的上下文对象,在 Servlet 环境中必然存在(


Objective-C 核心语法深度解析:基本类型、集合类与代码块实战指南
奋斗理想2026/1/16

详细讲解:Objective-C 基本类型、集合类和代码块 一、基本类型详解 1.1 主要基本类型 // MyTypes.m #import <Foundation/Foundation.h> void demonstrateBasicTypes() { NSLog(@"========== 基本类型演示 =========="); // 1. BOOL 类型(实际上是 signed char) BOOL isOpen = YES; // YES = 1,

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 XYZ博客