当前位置: 首页 > news >正文

Redis 事务机制:Pipeline、ACID、Lua脚本

文章目录

  • 一、Redis Pipeline
    • 1.1 概念与目的
    • 1.2 工作原理
    • 1.3 实现机制与使用
  • 二、Redis 事务
    • 2.1 什么是事务?
    • 2.2 Redis 事务的基本命令
    • 2.3 乐观锁机制
    • 2.4 事务的局限性
    • 2.5 应用场景
  • 三、Lua 脚本
    • 3.1 概念与原理
    • 3.2 脚本的加载与执行方式
    • 3.3 注意事项
  • 四、ACID特性
    • 4.1 A 原子性 (Atomicity)
    • 4.2 一致性 (Consistency)
    • 4.3 隔离性 (Isolation)
    • 4.4 持久性 (Durability)
  • 五、Redis 发布订阅
    • 5.1 定义与目的
    • 5.2 应用要点(连接处理)
    • 5.3 主要缺点(消息可靠性问题)
    • 5.4 核心命令

一、Redis Pipeline

1.1 概念与目的

Redis Pipeline 是一种客户端技术,其核心目的在于节约网络传输时间,提升批量操作的效率。

在默认情况下,客户端每发送一个Redis命令,都需要等待服务器回应后,才能发送下一个命令。这个过程会消耗大量的网络往返时间。Pipeline 的机制是,允许客户端一次性将多个命令请求包打包发送给Redis服务器,然后服务器会按顺序处理这些命令,并一次性将所有的回应包返回给客户端。

这种机制在本质上与 HTTP 1.1 中的“管线化请求”非常类似,都用于提高网络利用率。
在这里插入图片描述

1.2 工作原理

在 Pipeline 模式下,客户端会将多条命令打包成一个请求,一次性发送给 Redis:

SET key1 value1
SET key2 value2
GET key1
GET key2

Redis 会顺序执行这些命令,并将结果依次打包返回。
注意:Pipeline 不具备事务性,即使多条命令一起发送,它们之间仍可能被中断,不存在“要么全部成功,要么全部失败”的保证。

1.3 实现机制与使用

在底层实现上,Redis 官方提供了一个名为 hiredis 的客户端驱动库,用于实现与 Redis 的交互。
我们在自己的 C/C++ 服务中引入 hiredis 库,就能使用 Pipeline 机制来批量发送命令,显著减少网络延迟。

Pipeline 的典型使用场景:

  • 需要一次执行大量读写操作;
  • 对实时性要求高,但每条命令彼此独立;
  • 不需要事务一致性。

Pipeline 解决的是性能问题,而不是数据一致性问题。


二、Redis 事务

2.1 什么是事务?

Redis 事务是一组用户定义的数据库操作,这些操作被视为一个完整的逻辑工作单元,要求“要么全部执行,要么全部不执行”。

在单机数据库中,事务的主要目的是保证并发情况下数据的逻辑一致性

例如:

1. GET gulu  --> 100
2. SET gulu 200

如果这两条命令分开执行,在第一次 GET 后如果有其他客户端修改了 gulu,就可能导致逻辑错误。因此我们希望这两步操作能被当作一个整体执行,这就是事务要解决的问题。

2.2 Redis 事务的基本命令

Redis事务通过四个核心命令实现:

  • MULTI:用于开启一个事务。执行后,客户端发送的命令不会立即执行,而是被放入一个队列中。
  • EXEC:用于提交事务。执行后,服务器会按顺序执行事务队列中的所有命令。
  • DISCARD:用于取消事务(相当于回滚)。它会清空事务队列,并退出事务状态。
  • WATCH:是实现 Redis 乐观锁的关键。它可以在MULTI之前监视一个或多个 key,如果在事务执行(EXEC)之前,有任何被监视的键被其他客户端修改,那么整个事务将会被取消,EXEC返回nil

执行过程:

127.0.0.1:6379> WATCH gulu
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> SET gulu 2000
QUEUED
127.0.0.1:6379(TX)> EXEC
(nil)   # 因为 gulu 被其他连接修改,事务失败

2.3 乐观锁机制

Redis事务基于乐观锁。它乐观地假设客户端在执行事务过程中,不会有其他客户端同时操作对应的 key,事务不会被干扰,因此不加锁。只在最终事务提交时 Redis 会检查监控的 key 是否被修改。

  • 如果被改动,则事务自动取消(返回 nil)。
  • 如果没有修改,事务提交成功。

MySQL 采用的就是悲观的,在执行事务过程中加锁,不允许其他连接去操作。
这种机制在多并发环境下能有效避免数据竞争,但如果竞争频繁,事务会多次重试,增加业务复杂度。

2.4 事务的局限性

Redis事务的一个显著特点是不支持回滚。如果事务中的某个命令在执行时出错(例如,对一个字符串键执行列表的LPUSH操作),这个命令会失败,但事务不会中止,队列中的其他命令依然会继续执行。这意味着 Redis 事务无法保证绝对的原子性。

2.5 应用场景

事务可用于保证一系列逻辑操作的原子性,如:

1. 模拟 zpop 操作(安全删除最小元素)

WATCH zset
element = ZRANGE zset 0 0
MULTI
ZREM zset element
EXEC

2. 加倍操作(安全更新数值)

WATCH score:10001
val = GET score:10001
MULTI
SET score:10001 val*2
EXEC

不过在实际开发中,这种逻辑通常由 Lua 脚本 实现更高效的原子性执行。


三、Lua 脚本

为了克服事务的局限性,Redis支持使用Lua脚本来实现更复杂的原子操作。

3.1 概念与原理

Redis 服务器内置了一个 Lua 虚拟机。当执行一个 Lua 脚本时,整个脚本会被当作一个单独的命令来处理。在脚本执行期间,Redis 的单线程服务器不会执行任何其他命令或脚本,从而保证了原子性和隔离性
在这里插入图片描述

3.2 脚本的加载与执行方式

Redis 提供两种执行 Lua 的方式:

  • EVAL:直接执行一段Lua脚本代码。适用于测试。
  • EVALSHA:通过脚本内容的SHA1哈希值来执行脚本。

示例脚本(将指定 key 的值加倍):

local key = KEYS[1];
local val = redis.call("get", key);
redis.call("set", key, 2*val);
return 2*val;

还可以优化脚本(防止 key 不存在):

local key = KEYS[1];
local val = redis.call("get", key);
if not val then val = 1000 end;
redis.call("set", key, 2*val);
return 2*val;

在 Redis 中执行:

127.0.0.1:6379> EVAL "127.0.0.1:6379> EVAL "local key = KEYS[1];local val = redis.call("get",key);if not val then val = 1000 end;redis.call("set", key, 2*val);return 2*val;" 1 score" 1 score
(integer) 200

实际业务中的 lua 代码要复杂的多,我们会用一串哈希值来代表 lua 脚本的代码

脚本管理

  • SCRIPT LOAD将脚本加载到服务器缓存,返回其SHA1值
  • SCRIPT EXISTS:检查脚本是否在缓存中。
  • SCRIPT FLUSH:清空所有脚本缓存。
  • SCRIPT KILL:用于终止正在执行的长耗时脚本(如陷入死循环)。

在生产环境中,为了节省网络带宽,通常先在服务端缓存脚本(SCRIPT LOAD),然后使用EVALSHA通过哈希值调用。

在生产环境中,一般先加载脚本:

script load 'local key = KEYS[1];local val = redis.call("get",key);if not val then val = 1000 end;redis.call("set", key, 2*val);return 2*val;'

然后会返回对应的哈希值:

"fb01696519c3663aa60e7b705e5584e2e8cf5da3"

返回的哈希值可用于之后调用:

evalsha fb01696519c3663aa60e7b705e5584e2e8cf5da3 1 gulu

其中 1 表示键的数量,gulu 为目标键,最终会返回翻倍后的结果(如 (integer) 2000)。

3.3 注意事项

  1. 项目启动时预加载:在应用启动建立 Redis 连接后,通过SCRIPT LOAD将所有需要用到的 Lua脚本预先加载到Redis服务器,并将返回的SHA1值存储在本地(如HashMap中),后续使用EVALSHA调用。
  2. 热更新:如果需要更新脚本,先通过 redis-cli script flush 清空 Redis 中的脚本缓存,然后重新加载新脚本,并通过发布订阅机制通知所有客户端应用重新加载,确保所有节点使用的脚本版本一致。
  3. 错误处理:Lua脚本虽然原子性执行,但不会自动回滚。如果脚本中间出错,已经执行成功的命令就已经修改了数据库状态,需要开发者在脚本内部自行实现逻辑上的“回滚”或确保逻辑正确。
  4. 避免阻塞:若 Lua 脚本出现阻塞(如死循环),可通过 script kill 终止脚本执行;同时需避免在脚本中编写过于复杂的逻辑,减少阻塞风险。
# 从文件中读取 lua脚本内容
cat test1.lua | redis-cli script load --pipe
# 加载 lua脚本字符串 生成 sha1
> script load 'local val = KEYS[1]; return val'
"b8059ba43af6ffe8bed3db65bac35d452f8115d8"
# 检查脚本缓存中,是否有该 sha1 散列值的lua脚本
> script exists "b8059ba43af6ffe8bed3db65bac35d452f8115d8"
1) (integer) 1
# 清除所有脚本缓存
> script flush
OK
# 如果当前脚本运行时间过长(死循环),可以通过 script kill 杀死当前运行的脚本
> script kill
(error) NOTBUSY No scripts in execution right now.

四、ACID特性

4.1 A 原子性 (Atomicity)

事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败回滚);Redis 不支持回滚;即使事务队列中的某个命令在执行期间出现了错误,整个事务也会继续执行下去,直到将事务队列中的所有命令都执行完毕为止。

  • Redis事务:不满足。因为即使队列中某个命令执行失败,其他命令仍然会继续执行,没有回滚机制。
  • Lua脚本:满足。整个脚本作为一个命令执行,要么全部成功,要么全部失败(脚本出错会停止,但已执行的命令无法撤销)。

4.2 一致性 (Consistency)

事务的前后,所有的数据都保持一个一致的状态,不能违反数据的一致性检测;这里的一致性是指预期的一致性而不是异常后的一致性;

  • 逻辑上一致性
  • 数据库一致性(完整约束)

所以 Redis 也不满足。Redis 能确保事务执行前后的数据的完整约束(例如,拒绝用字符串命令操作列表键),但是并不满足业务逻辑的一致性。比如转账功能,一个扣钱一个加钱,可能出现扣钱执行错误,加钱执行正确,那么最终还是会加钱成功,系统凭空多了钱。

127.0.0.1:6379> type gulu
string
127.0.0.1:6379> lpush gulu 1 2 3 4 
(error) WRONGTYPE Operation against a key holding the wrong kind of value

Lua脚本两者都不保证。例如,在Lua脚本中,如果前半部分逻辑成功修改了数据,后半部分逻辑出错,数据就处于一个中间状态,破坏了业务逻辑的一致性。

4.3 隔离性 (Isolation)

  • Redis 是单线程的,因此所有命令都串行执行,天然具备隔离性;
  • Lua 脚本也在同一线程中执行,因此也具备隔离性。

4.4 持久性 (Durability)

Redis事务与Lua脚本通常不满足。只有在使用AOF持久化且配置appendfsync = always时,每个写命令都会同步到磁盘,才具备持久性。但这个配置会严重性能,生产环境中极少使用,通常使用everysec或RDB,因此在服务器故障时可能丢失数据。

特性Redis 事务Lua 脚本
原子性不完全支持(无回滚)完全原子
一致性仅类型一致性逻辑一致性需自行保证
隔离性单线程保证单线程保证
持久性取决于 AOF 策略取决于 AOF 策略

五、Redis 发布订阅

5.1 定义与目的

Redis 发布订阅(Pub/Sub)是为支持消息多播机制而设计的模块,本质是一种分布式消息队列方案。它的核心作用是让消息生产者(发布者)向指定“频道”发送消息,多个消息消费者(订阅者)可通过订阅该频道接收消息,实现“一对多”的消息传递。

需要注意的是,Redis 发布订阅的消息不保证可达性,若需确保消息一定被接收,可选择 Redis 的 Stream 模块,后者通过持久化机制解决了消息丢失问题。

5.2 应用要点(连接处理)

在实际项目中使用发布订阅功能时,必须为其单独开启一条连接,不能复用执行普通命令的连接。原因在于普通命令连接严格遵循“请求-回应”模式,即客户端发送请求后才能接收 Redis 的回应;而发布订阅需要接收 Redis 主动推送的消息,若复用普通连接,会导致连接模式冲突,无法正常接收推送内容。因此,通常的做法是:一条连接处理 get/set 等普通命令,另一条独立连接专门处理发布订阅的消息收发。

5.3 主要缺点(消息可靠性问题)

Redis 发布订阅的核心短板在于消息可靠性较低,无法保障消息不丢失,具体体现在三个场景:

  1. 无消费者时消息丢弃:若生产者向某个频道发送消息,但此时没有任何消费者订阅该频道,Redis 会直接丢弃这条消息,不会暂存。
  2. 消费者断开期间消息丢失:若某个频道原本有 2 个消费者,其中一个消费者意外断开连接,另一个消费者仍能正常接收消息;但当断开的消费者重新连接后,它无法获取断开期间该频道收到的消息,这部分消息已被永久丢弃。
  3. Redis 重启后消息丢失:发布订阅的消息不支持持久化,若 Redis 服务器停机重启,所有未被消费的消息(及历史消息)都会被清空,重启后无法恢复。

5.4 核心命令

Redis 提供了一套专门用于发布订阅的命令,按功能可分为订阅、取消订阅、发布、接收四类,具体命令及用途如下:

命令类型命令格式功能说明
订阅类subscribe 频道1 频道2 ...订阅一个或多个指定的“具体频道”,接收该频道的消息
订阅类psubscribe 模式频道订阅符合指定模式的“模式频道”(如 news.* 匹配 news.it/news.showbiz 等)
取消订阅类unsubscribe 频道1 频道2 ...取消对一个或多个具体频道的订阅
取消订阅类punsubscribe 模式频道取消对指定模式频道的订阅
发布类publish 频道 消息内容向指定的具体频道或模式频道发送消息
接收类message 具体频道 消息内容客户端被动接收具体频道推送的消息(无需主动调用)
接收类pmessage 模式频道 具体频道 消息内容客户端被动接收模式频道推送的消息(无需主动调用,会显示匹配的模式频道和具体频道)

示例
1)订阅多个具体频道和一个模式频道:

# 订阅 news.it(科技新闻)、news.showbiz(娱乐新闻)、news.car(汽车新闻)三个具体频道
subscribe news.it news.showbiz news.car
# 订阅所有以 "news." 开头的模式频道(如上述三个具体频道均会被匹配)
psubscribe news.*

2)向指定频道发布消息:

# 向 news.showbiz 频道发布消息 "football team win"
publish news.showbiz 'football team win'

3)客户端接收消息:
执行上述发布命令后,已订阅 news.showbiz 频道或 news.* 模式频道的客户端,会被动收到如下消息(具体频道接收):

message news.showbiz football team win

同时,订阅 news.* 模式频道的客户端还会收到如下消息(模式频道接收):

pmessage news.* news.showbiz football team win
http://www.dtcms.com/a/485796.html

相关文章:

  • 【实时Linux实战系列】在实时系统中安全地处理浮点运算
  • 基于仿真和运行时监控的自动驾驶安全分析
  • Java-Spring入门指南(二十七)Android Studio 第一个项目搭建与手机页面模拟器运行
  • Highcharts 绘制之道(2):高级绘图技术与连通关系
  • 学习笔记——GPU训练
  • 数据结构——二叉搜索树Binary Search Tree(介绍、Java实现增删查改、中序遍历等)
  • 网站个人主页怎么做wordpress 网银支付
  • 网站建设常州青之峰陕西西安网站设计公司
  • FTP 抓包分析实战,命令、被动主动模式要点、FTPS 与 SFTP 区别及真机取证流程
  • Linux下的权限与文件
  • 《算法闯关指南:优选算法--二分查找》--19.x的平方根,20.搜索插入位置
  • 从中序与后序遍历序列构造二叉树
  • 【超分辨率专题】DOVE:特色双阶段训练的单步Real-World视频超分辨
  • 《Linux基础入门指令(二)》:从零开始理解Linux系统
  • 响应式网站开发图标郑州网站商城建设
  • 仓颉编程(3)基本操作符
  • 潍坊网站建设推广公司上海公司注销的流程及需提供的材料2023
  • 《算法通关指南---C++编程篇(1)》
  • Go语言:记录一下Go语言系统学习的第一天
  • GraphRAG 与 Neo4j 社区版:能力边界与适用场景学习总结
  • 【OC】计算器的仿写
  • 东莞工厂网站建设网站建设要买哪些软件
  • 5-3〔OSCP ◈ 研记〕❘ SQL注入攻击▸基于错误的SQLi 盲注SQLi
  • AWS Redshift 数据仓库完整配置与自动化管理指南
  • 《C++ 手搓list容器底层》:从结构原理深度解析到功能实现(附源码版)
  • 成都那家做网站好注册网约车主需要什么条件
  • Wireshark:HTTP、MQTT、WebSocket 抓包详细教程
  • Linux内核架构浅谈36-Linux页帧描述:struct page数据结构的设计与关键成员
  • 道路车辆功能安全标准(FuSa)基础(七)
  • 【Linux系列】解码 Linux 内存地图:从虚拟到物理的寻宝之旅