使用过 Redis 分布式锁么,它是什么回事?
mhr18 2025-03-26 14:09 58 浏览 0 评论
我使用过 Redis 分布式锁。让我来详细解释一下它是什么回事。
首先,什么是分布式锁?
在单体应用中,我们通常使用编程语言提供的锁机制(例如 Java 中的 synchronized 关键字或 ReentrantLock)来控制多线程对共享资源的并发访问,防止数据错乱或资源竞争。
但是,在分布式系统中,应用被部署在多台服务器上,同一个应用的不同实例可能同时运行并尝试访问同一个共享资源(例如数据库中的同一行数据、共享文件、队列等)。 单机锁无法跨进程、跨机器工作,因此我们需要一种分布式锁来协调不同服务器上的进程对共享资源的访问。
Redis 分布式锁的原理和实现
Redis 分布式锁的核心思想是利用 Redis 的原子操作来实现锁的获取和释放。 通常使用以下几个关键 Redis 命令和特性:
- SETNX (SET if Not eXists) 命令:
- 这是实现 Redis 分布式锁最常用的命令。
- SETNX key value 命令的作用是:当且仅当 key 不存在时,将 key 的值设置为 value,并返回 1;如果 key 已经存在,则不做任何操作,并返回 0。
- 这个原子性操作是锁的关键:多个客户端同时尝试 SETNX 同一个 key,只有一个客户端能够成功(返回 1),表示它成功获取了锁。其他客户端会失败(返回 0),表示锁已被占用。
- 设置锁的过期时间 (EXPIRE 命令):
- 为了防止死锁,我们需要为锁设置一个过期时间。
- 如果持有锁的客户端在执行业务逻辑过程中发生崩溃或者网络问题,未能及时释放锁,锁会因为过期时间到期而被 Redis 自动删除,从而避免其他客户端永远无法获取锁的情况。
- 使用 EXPIRE key seconds 命令可以为指定的 key 设置过期时间,单位是秒。
- 释放锁 (DEL 命令 或 Lua 脚本):
- 当持有锁的客户端完成业务逻辑后,需要释放锁,以便其他客户端可以获取锁。
- 最简单的释放锁的方式是使用 DEL key 命令删除锁的 key。
一个简单的 Redis 分布式锁实现示例 (伪代码):
python复制代码import redis
import uuid
import time
class RedisDistributedLock:
def __init__(self, redis_client, lock_name, expire_time=30):
self.redis_client = redis_client
self.lock_name = lock_name
self.expire_time = expire_time
self.lock_key = "lock:" + lock_name
self.lock_value = str(uuid.uuid4()) # 使用 UUID 作为锁的值,确保唯一性
def acquire_lock(self):
while True:
acquired = self.redis_client.setnx(self.lock_key, self.lock_value)
if acquired:
self.redis_client.expire(self.lock_key, self.expire_time)
return True # 获取锁成功
else:
# 锁已被占用,等待一段时间后重试
time.sleep(0.1)
return False # 理论上不会执行到这里
def release_lock(self):
# 释放锁时,需要验证锁是否是自己持有的,防止误删其他客户端的锁
if self.redis_client.get(self.lock_key) == self.lock_value.encode(): # 注意 get 返回的是 bytes
self.redis_client.delete(self.lock_key)
return True
return False
使用示例:
python复制代码redis_client = redis.Redis(host='localhost', port=6379, db=0)
lock = RedisDistributedLock(redis_client, "my_resource_lock")
if lock.acquire_lock():
try:
print("成功获取锁,执行业务逻辑...")
time.sleep(5) # 模拟业务逻辑执行
finally:
if lock.release_lock():
print("成功释放锁")
else:
print("释放锁失败")
else:
print("获取锁失败,资源已被占用")
Redis 分布式锁的优点:
- 高性能: Redis 是内存数据库,读写速度非常快,因此分布式锁的获取和释放操作非常高效。
- 简单易用: 基于 Redis 的命令实现,代码相对简单,容易理解和实现。
- 可靠性: Redis 本身具有持久化和复制机制,可以保证一定的可靠性(但需要注意单点故障问题,后面会提到)。
Redis 分布式锁的挑战和需要注意的问题:
- 误删锁问题:
- 上面的简单示例中,释放锁时只是简单地 DEL key。 如果持有锁的客户端执行业务逻辑的时间超过了锁的过期时间,锁会被 Redis 自动释放。
- 此时,如果另一个客户端获取了锁并开始执行业务逻辑,而之前的客户端仍然在执行,并且在执行完成后尝试释放锁,它可能会错误地删除新客户端持有的锁,导致并发问题。
- 解决方案:释放锁时验证锁的持有者: 在 SETNX 时,我们将一个唯一的 value (例如 UUID) 写入锁的 key。 释放锁时,先 GET 锁的 key 的值,判断是否是自己设置的 value,如果是才执行 DEL 操作。 上面的示例代码中已经加入了这个验证。使用 Lua 脚本原子性地验证和删除: 为了保证验证和删除操作的原子性,可以使用 Lua 脚本将 GET 和 DEL 操作放在一个原子操作中执行。 这是更推荐的做法,可以避免在并发情况下出现问题。
- lua复制代码
- -- Lua 脚本,用于原子性地验证和删除锁 if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
- 在 Python 中使用 Lua 脚本释放锁:
- python复制代码
- def release_lock(self): release_script = """ if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end """ result = self.redis_client.eval(release_script, 1, self.lock_key, self.lock_value) return result == 1
- 锁的续期 (Watchdog):
- 如果业务逻辑执行时间无法预估,或者可能超过锁的过期时间,为了防止锁被意外释放,可以实现锁的续期机制(也称为 Watchdog)。
- 客户端在持有锁期间,启动一个后台线程或定时任务,定期检查锁的剩余过期时间,如果剩余时间过短,则自动延长锁的过期时间。
- 很多 Redis 分布式锁的客户端库(例如 Redisson)都提供了 Watchdog 机制。
- Redis 单点故障问题:
- 如果 Redis 服务本身发生故障(例如宕机),那么所有的锁都会失效,可能会导致并发问题。
- 解决方案:Redis Sentinel 或 Redis Cluster: 使用 Redis Sentinel 或 Redis Cluster 集群模式,提高 Redis 的可用性和容错性。 当主节点故障时,Sentinel 或 Cluster 会自动进行故障转移,将从节点提升为主节点,保证 Redis 服务的持续可用。Redlock (红锁): Redlock 是一种更复杂的分布式锁算法,它使用多个独立的 Redis 实例来获取锁,只有当在大多数 Redis 实例上都成功获取锁时,才认为获取锁成功。 Redlock 可以提高分布式锁的可靠性,但实现和维护也更复杂,性能也会有所下降。 通常情况下,使用 Redis Sentinel 或 Cluster 已经可以满足大部分场景的需求,Redlock 一般在对数据一致性要求极高的场景下才考虑使用。
- 时钟漂移问题:
- 在分布式系统中,不同服务器的时钟可能存在轻微的偏差(时钟漂移)。 如果时钟漂移严重,可能会影响锁的过期时间判断,导致锁提前过期或延迟过期。
- 解决方案:尽量保证服务器时钟的同步,可以使用 NTP 服务进行时钟同步。在设置锁的过期时间时,可以适当留一些余量,避免因为轻微的时钟漂移导致锁提前过期。
- 网络分区问题:
- 在分布式系统中,网络分区是不可避免的。 如果发生网络分区,导致客户端与 Redis 集群的一部分节点断开连接,可能会出现 “脑裂” 情况,导致多个客户端同时认为自己持有锁。
- Redlock 在一定程度上可以缓解网络分区问题,但并不能完全解决。 在设计分布式系统时,需要考虑网络分区的影响,并根据业务场景选择合适的分布式锁方案和容错机制。
总结
Redis 分布式锁是一种常用的实现分布式系统互斥访问共享资源的方案。它基于 Redis 的原子操作和过期时间机制,具有高性能、简单易用的优点。 但在使用 Redis 分布式锁时,需要注意误删锁、锁续期、Redis 单点故障、时钟漂移和网络分区等问题,并根据实际场景选择合适的解决方案。
在实际项目中,为了简化开发和提高可靠性,通常建议使用成熟的 Redis 分布式锁客户端库,例如:
- Redisson (Java): 功能非常强大,提供了各种分布式锁的实现,包括可重入锁、公平锁、读写锁、Redlock 等,并内置了 Watchdog 机制。
- Lettuce (Java): 高性能的 Redis 客户端,也提供了分布式锁的实现。
- redis-py (Python): Python 的 Redis 客户端,可以结合 Lua 脚本实现分布式锁。
- ioredis (Node.js): Node.js 的 Redis 客户端,也提供了分布式锁的实现。
这些客户端库通常会处理一些细节问题,例如锁的续期、重试机制、错误处理等,可以减少开发工作量,并提高分布式锁的可靠性。
相关推荐
- Java面试题及答案总结(2025版)
-
大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Redis、Linux、SpringBoot、Spring、MySQ...
- Java面试题及答案最全总结(2025春招版)
-
大家好,我是Java面试分享最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Spring...
- Java面试题及答案最全总结(2025版持续更新)
-
大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...
- 蚂蚁金服面试题(附答案)建议收藏:经典面试题解析
-
前言最近编程讨论群有位小伙伴去蚂蚁金服面试了,以下是面试的真题,跟大家一起来讨论怎么回答。点击上方“捡田螺的小男孩”,选择“设为星标”,干货不断满满1.用到分布式事务嘛?为什么用这种方案,有其他方案...
- 测试工程师面试必问的十道题目!全答上来的直接免试
-
最近参加运维工程师岗位的面试,笔者把自己遇到的和网友分享的一些常见的面试问答收集整理出来了,希望能对自己和对正在准备面试的同学提供一些参考。一、Mongodb熟悉吗,一般部署几台?部署过,没有深入研究...
- 10次面试9次被刷?吃透这500道大厂Java高频面试题后,怒斩offer
-
很多Java工程师的技术不错,但是一面试就头疼,10次面试9次都是被刷,过的那次还是去了家不知名的小公司。问题就在于:面试有技巧,而你不会把自己的能力表达给面试官。应届生:你该如何准备简历,面试项目和...
- java高频面试题整理
-
【高频常见问题】1、事务的特性原子性:即不可分割性,事务要么全部被执行,要么就全部不被执行。一致性或可串性:事务的执行使得数据库从一种正确状态转换成另一种正确状态隔离性:在事务正确提交之前,不允许把该...
- 2025 年最全 Java 面试题,京东后端面试面经合集,答案整理
-
最近京东搞了个TGT计划,针对顶尖青年技术天才,直接宣布不设薪资上限。TGT计划面向范围包括2023年10月1日到2026年9月30日毕业的海内外本硕博毕业生。时间范围还...
- idGenerator测评
-
工作中遇到需要生成随机数的需求,看了一个个人开发的基于雪花算法的工具,今天进行了一下测评(测试)。idGenerator项目地址见:https://github.com/yitter/IdGenera...
- 2024年开发者必备:MacBook Pro M1 Max深度体验与高效工作流
-
工作机器我使用的是一台16英寸的MacBookProM1Max。这台电脑的表现堪称惊人!它是我用过的最好的MacBook,短期内我不打算更换它。性能依然出色,即使在执行任务时也几乎听不到风扇的...
- StackOverflow 2022 年度调查报告
-
一个月前,StackOverflow开启了2022年度开发者调查,历时一个半月,在6月22日,StackOverflow正式发布了2022年度开发者调查报告。本次报告StackO...
- 这可能是最全面的SpringDataMongoDB开发笔记
-
MongoDB数据库,在最近使用越来越广泛,在这里和Java的开发者一起分享一下在Java中使用Mongodb的相关笔记。希望大家喜欢。关于MongoDB查询指令,请看我的上一篇文章。SpringD...
- Mac M2 本地部署ragflow
-
修改配置文件Dockerfile文件ARGNEED_MIRROR=1//开启国内镜像代理docker/.envREDIS_PORT=6380//本地redis端口冲突RAGFLOW_IMA...
- 别再傻傻分不清!localhost、127.0.0.1、本机IP,原来大有讲究!
-
调试接口死活连不上?部署服务队友访问不了?八成是localhost、127.0.0.1、本机IP用混了!这三个看似都指向“自己”的东西,差之毫厘谬以千里。搞不清它们,轻则调试抓狂,重则服务裸奔。loc...
- 我把 Mac mini 托管到机房了:一套打败云服务器的终极方案
-
我把我积灰的Macmini托管到机房了,有图有真相。没想到吧?一台在家吃灰的苹果电脑,帮我省了大钱!对,就是控制了自己的服务器,省了租用云服务器的钱,重要数据还全捏在自己手里,这感觉真爽。你可...
你 发表评论:
欢迎- 一周热门
-
-
Redis客户端 Jedis 与 Lettuce
-
高并发架构系列:Redis并发竞争key的解决方案详解
-
redis如何防止并发(redis如何防止高并发)
-
Java SE Development Kit 8u441下载地址【windows版本】
-
开源推荐:如何实现的一个高性能 Redis 服务器
-
redis安装与调优部署文档(WinServer)
-
Redis 入门 - 安装最全讲解(Windows、Linux、Docker)
-
一文带你了解 Redis 的发布与订阅的底层原理
-
Redis如何应对并发访问(redis控制并发量)
-
Oracle如何创建用户,表空间(oracle19c创建表空间用户)
-
- 最近发表
- 标签列表
-
- oracle位图索引 (74)
- oracle批量插入数据 (65)
- oracle事务隔离级别 (59)
- oracle主从同步 (56)
- oracle 乐观锁 (53)
- redis 命令 (83)
- php redis (97)
- redis 存储 (67)
- redis 锁 (74)
- 启动 redis (73)
- redis 时间 (60)
- redis 删除 (69)
- redis内存 (64)
- redis并发 (53)
- redis 主从 (71)
- redis同步 (53)
- redis结构 (53)
- redis 订阅 (54)
- redis 登录 (62)
- redis 面试 (58)
- redis问题 (54)
- 阿里 redis (67)
- redis的缓存 (57)
- lua redis (59)
- redis 连接池 (64)