首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
开篇词 | 阅读Redis源码能给你带来什么?
01 | 带你快速攻略Redis源码的整体架构
02 | 键值对中字符串的实现,用char*还是结构体?
03 | 如何实现一个性能优异的Hash表?
04 | 内存友好的数据结构该如何细化设计?
05 | 有序集合为何能同时支持点查询和范围查询?
06 | 从ziplist到quicklist,再到listpack的启发
07 | 为什么Stream使用了Radix Tree?
08 | Redis server启动后会做哪些操作?
09 | Redis事件驱动框架(上):何时使用select、poll、epoll?
10 | Redis事件驱动框架(中):Redis实现了Reactor模型吗?
11 | Redis事件驱动框架(下):Redis有哪些事件?
12 | Redis真的是单线程吗?
13 | Redis 6.0多IO线程的效率提高了吗?
14 | 从代码实现看分布式锁的原子性保证
15 | 为什么LRU算法原理和代码实现不一样?
16 | LFU算法和其他算法相比有优势吗?
17 | Lazy Free会影响缓存替换吗?
18 | 如何生成和解读RDB文件?
19 | AOF重写(上):触发时机与重写的影响
20 | AOF重写(下):重写时的新写操作记录在哪里?
21 | 主从复制:基于状态机的设计与实现
22 | 哨兵也和Redis实例一样初始化吗?
23 | 从哨兵Leader选举学习Raft协议实现(上)
24 | 从哨兵Leader选举学习Raft协议实现(下)
25 | Pub/Sub在主从故障切换时是如何发挥作用的?
26 | 从Ping-Pong消息学习Gossip协议的实现
27 | 从MOVED、ASK看集群节点如何处理命令?
28 | Redis Cluster数据迁移会阻塞吗?
29 | 如何正确实现循环缓冲区?
30 | 如何在系统中实现延迟监控?
31 | 从Module的实现学习动态扩展功能
32 | 如何在一个系统中实现单元测试?
当前位置:
首页>>
技术小册>>
Redis源码剖析与实战
小册名称:Redis源码剖析与实战
### 14 | 从代码实现看分布式锁的原子性保证 在分布式系统中,资源访问的同步和互斥是确保数据一致性和系统稳定性的关键。分布式锁作为解决这类问题的有效手段,其实现需确保操作的原子性,即锁的申请、持有、释放等过程必须作为一个不可分割的整体执行,以防止并发访问时的数据冲突。本章将深入Redis源码,探讨Redis实现分布式锁时如何保证原子性,并结合实际代码和场景进行解析。 #### 1. 分布式锁的基本概念与挑战 **分布式锁的定义**:分布式锁是控制分布式系统或不同进程间共享资源访问的一种机制。它允许在多个客户端之间以互斥的方式访问共享资源,防止数据的不一致性和损坏。 **面临的挑战**: - **网络延迟与分区**:网络延迟或分区可能导致锁的状态更新不及时或不一致。 - **时钟同步**:分布式系统中的时钟同步问题可能影响锁的超时机制。 - **锁的释放失败**:在持有锁的客户端崩溃或网络中断时,锁可能无法被正常释放,导致死锁。 - **原子性保证**:确保锁的获取、持有、释放等操作的原子性,防止中间状态被其他客户端利用。 #### 2. Redis实现分布式锁的基础 Redis作为一个高性能的键值存储系统,支持多种数据结构如字符串、列表、集合等,并通过原子操作命令(如`SETNX`、`GETSET`、`Lua脚本`等)提供了实现分布式锁的基础。 **SETNX命令**:`SETNX key value` 是“SET if Not eXists”的缩写,当且仅当`key`不存在时,设置`key`的值。这是早期实现Redis分布式锁的一种简单方式,但单独使用`SETNX`不足以处理锁的释放问题(如客户端崩溃)。 **过期时间**:为了解决`SETNX`无法自动释放锁的问题,通常会配合`EXPIRE`命令为锁设置一个过期时间。然而,这种方式在`SETNX`和`EXPIRE`之间可能存在窗口,导致锁未设置成功却已设置过期时间。 **SET命令的扩展选项**:Redis 2.6.12及以上版本引入了`SET key value [EX seconds] [PX milliseconds] [NX|XX]`命令,其中`EX`和`PX`用于设置键的过期时间,`NX`表示仅当键不存在时设置值,实现了`SETNX`和`EXPIRE`的原子操作。 #### 3. Redis分布式锁的原子性实现 **使用SET命令的NX和PX选项** Redis官方推荐的分布式锁实现方式之一是利用`SET`命令的`NX`和`PX`选项。这种方式能够确保锁的获取和设置过期时间的原子性,是较为安全的实现方式。 ```bash SET lock_key unique_value NX PX 30000 ``` 此命令尝试设置`lock_key`,仅当`lock_key`不存在时(`NX`)成功,并设置过期时间为30秒(`PX 30000`)。`unique_value`是锁的持有者的唯一标识,用于在释放锁时进行验证,防止误释放。 **Lua脚本的原子性** 对于更复杂的锁逻辑,如需要同时检查锁是否存在、检查锁持有者、更新锁过期时间等,Redis提供了Lua脚本功能,可以在Redis服务器端以原子方式执行一系列命令。 例如,使用Lua脚本实现锁的续期: ```lua if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("expire", KEYS[1], ARGV[2]) else return 0 end ``` 这个脚本首先检查`lock_key`的值是否等于`unique_value`(即当前客户端是否持有锁),如果是,则更新锁的过期时间。由于Lua脚本在Redis中是作为单个命令执行的,因此这一系列操作是原子的。 #### 4. 原子性保证的关键点 **1. 命令的原子性**:选择具有原子性保证的Redis命令,如`SET`的`NX`和`PX`选项,或使用Lua脚本。 **2. 锁持有者的唯一标识**:为每个锁请求生成一个唯一的标识(如UUID),并在获取锁时设置该标识。释放锁时,需验证当前客户端是否确实持有该锁,防止误释放。 **3. 锁的自动过期**:为锁设置一个合理的过期时间,避免因客户端崩溃等原因导致的死锁。 **4. 锁的续期机制**:在需要长时间持有锁的场景下,实现锁的续期机制,确保锁在业务逻辑完成前不会过期。 **5. 错误处理与重试机制**:在锁的获取、续期、释放等过程中,实现完善的错误处理逻辑和重试机制,提高系统的健壮性。 #### 5. 实战案例分析 假设我们有一个分布式系统,需要确保某个资源在任意时刻只能被一个服务实例访问。我们可以使用Redis的分布式锁来实现这一需求。 **锁的获取**: ```python import redis import uuid # 连接到Redis r = redis.Redis(host='localhost', port=6379, db=0) # 生成唯一值作为锁的持有者标识 lock_value = str(uuid.uuid4()) # 尝试获取锁,设置过期时间为30秒 lock_key = 'my_lock' result = r.set(lock_key, lock_value, nx=True, px=30000) if result: print("锁获取成功") # 执行需要同步的代码块 # ... # 释放锁 r.delete(lock_key) else: print("锁获取失败,资源已被其他客户端锁定") ``` **锁的续期**: 在实际应用中,如果业务逻辑执行时间较长,可能需要实现锁的续期机制。这可以通过定时任务调用Lua脚本来实现,或者根据业务逻辑的具体情况在适当的位置调用续期逻辑。 #### 6. 总结 分布式锁的原子性保证是确保分布式系统数据一致性和稳定性的关键。通过Redis的原子操作命令和Lua脚本,我们可以有效地实现分布式锁的获取、持有、续期和释放等操作,同时避免并发访问时的数据冲突和死锁问题。在实际应用中,还需结合具体的业务场景和需求,选择合适的实现方式和参数配置,以提高系统的性能和可靠性。
上一篇:
13 | Redis 6.0多IO线程的效率提高了吗?
下一篇:
15 | 为什么LRU算法原理和代码实现不一样?
该分类下的相关小册推荐:
Redis的Lua脚本编程
Redis零基础到实战
Redis面试指南
Redis核心技术与实战