使用过 Redis 分布式锁么,它是什么回事?
mhr18 2025-03-26 14:09 44 浏览 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 客户端,也提供了分布式锁的实现。
这些客户端库通常会处理一些细节问题,例如锁的续期、重试机制、错误处理等,可以减少开发工作量,并提高分布式锁的可靠性。
相关推荐
- Spring Boot3 连接 Redis 竟有这么多实用方式
-
各位互联网大厂的后端开发精英们,在日常开发中,想必大家都面临过系统性能优化的挑战。当系统数据量逐渐增大、并发请求不断增多时,如何提升系统的响应速度和稳定性,成为了我们必须攻克的难题。而Redis,这...
- 隧道 ssh -L 命令总结 和 windows端口转发配置
-
摘要:隧道ssh-L命令总结和windows端口转发配置关键词:隧道、ssh-L、端口转发、网络映射整体说明最近在项目中,因为内网的安全密级比较高,只能有一台机器连接内网数据库,推送...
- 火爆BOOS直聘的13个大厂Java社招面经(5年经验)助你狂拿offer
-
火爆BOOS直聘的13个大厂Java社招面经(5年经验)助你狂拿offer综上所述,面试遇到的所有问题,整理成了一份文档,希望大家能够喜欢!!Java面试题分享(Java中高级核心知识全面解析)一、J...
- 「第五期」游服务器一二三面 秋招 米哈游
-
一面下午2点,35分钟golang内存模型golang并发模型golanggc原理过程channel用途,原理redis数据结构,底层实现跳跃表查询插入复杂度进程,线程,协程kill原理除了kil...
- RMQ——支持合并和优先级的消息队列
-
业务背景在一个项目中需要实现一个功能,商品价格发生变化时将商品价格打印在商品主图上面,那么需要在价格发生变动的时候触发合成一张带价格的图片,每一次触发合图时计算价格都是获取当前最新的价格。上游价格变化...
- Redis 中的 zset 为什么要用跳跃表,而不是B+ Tree 呢?
-
Redis中的有序集合使用的是一种叫做跳跃表(SkipList)的数据结构来实现,而不是使用B+Tree。本文将介绍为什么Redis中使用跳跃表来实现有序集合,而不是B+Tree,并且探讨跳跃表...
- 一文让你彻底搞懂 WebSocket 的原理
-
作者:木木匠转发链接:https://juejin.im/post/5c693a4f51882561fb1db0ff一、概述上一篇文章《图文深入http三次握手核心问题【思维导图】》我们分析了简单的一...
- Redis与Java整合的最佳实践
-
Redis与Java整合的最佳实践在这个数字化时代,数据处理速度决定了企业的竞争力。Redis作为一款高性能的内存数据库,以其卓越的速度和丰富的数据结构,成为Java开发者的重要伙伴。本文将带你深入了...
- Docker与Redis:轻松部署和管理你的Redis实例
-
在高速发展的云计算时代,应用程序的部署和管理变得越来越复杂。面对各种操作系统、依赖库和环境差异,开发者常常陷入“在我机器上能跑”的泥潭。然而,容器化技术的兴起,尤其是Docker的普及,彻底改变了这一...
- Java开发中的缓存策略:让程序飞得更快
-
Java开发中的缓存策略:让程序飞得更快缓存是什么?首先,让我们来聊聊什么是缓存。简单来说,缓存是一种存储机制,它将数据保存在更快速的存储介质中,以便后续使用时能够更快地访问。比如,当你打开一个网页时...
- 国庆临近,字节后端开发3+4面,终于拿到秋招第一个offer
-
字节跳动,先面了data部门,3面技术面之后hr说需要实习转正,拒绝,之后另一个部门捞起,四面技术面,已oc分享面经,希望对大家有所帮助,秋招顺利在文末分享了我为金九银十准备的备战资源库,包含了源码笔...
- “快”就一个字!Redis凭什么能让你的APP快到飞起?
-
咱们今天就来聊一个字——“快”!在这个信息爆炸、耐心越来越稀缺的时代,谁不希望自己手机里的APP点一下“嗖”就打开,刷一下“唰”就更新?谁要是敢让咱用户盯着个小圈圈干等,那简直就是在“劝退”!而说到让...
- 双十一秒杀,为何总能抢到?Redis功不可没!
-
一年一度的双十一“剁手节”,那场面,简直比春运抢票还刺激!零点的钟声一敲响,亿万个手指头在屏幕上疯狂戳戳戳,眼睛瞪得像铜铃,就为了抢到那个心心念念的半价商品、限量版宝贝。你有没有发现一个奇怪的现象?明...
- 后端开发必看!为什么说Redis是天然的幂等性?
-
你在做后端开发的时候,有没有遇到过这样的困扰:高并发场景下,同一个操作重复执行多次,导致数据混乱、业务逻辑出错?别担心,很多同行都踩过这个坑。某电商平台就曾因订单创建接口在高并发时不具备幂等性,用户多...
- 开发一个app需要哪些技术和工具
-
APP开发需要一系列技术和工具的支持,以下是对这些技术的清晰归纳和分点表示:一、前端开发技术HTML用于构建页面结构。CSS用于样式设计和布局。JavaScript用于页面交互和逻辑处理。React...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)