From 9cb53bad9c9220654ba65ff90d69d46d75233b70 Mon Sep 17 00:00:00 2001 From: hhs <386998068@qq.com> Date: Thu, 21 May 2026 23:55:49 +0800 Subject: [PATCH] vault backup: 2026-05-21 23:55:49 --- hhs/Redis/03-基本命令速查.md | 101 +++++++++++++++++++++++++++++------ 1 file changed, 85 insertions(+), 16 deletions(-) diff --git a/hhs/Redis/03-基本命令速查.md b/hhs/Redis/03-基本命令速查.md index 8e43de9..2c4db29 100644 --- a/hhs/Redis/03-基本命令速查.md +++ b/hhs/Redis/03-基本命令速查.md @@ -252,16 +252,36 @@ for iter.Next(ctx) { ## 管道 Pipeline vs 事务 Transaction -Redis 协议本身就是 request/response,Pipeline 把多个命令打包一次发送,大幅减少网络往返;而事务通过 `MULTI` / `EXEC` 保证一组命令的**原子执行**。 +### 先理解问题:为什么要"打包"命令? + +Redis 通信遵循**一问一答**(request/response)模式——客户端发一条命令,等回复,再发下一条。每次"发送 → 等待 → 接收"的网络往返叫做一次 **RTT**(Round-Trip Time,往返时延)。 + +> [!QUESTION] RTT 有多贵? +> 假设内网 RTT = 0.5ms,发送 1000 条命令逐条执行需要 500ms。如果把它们**打包一次发送**,只需要 ~0.5ms——**性能差 1000 倍**。 + +Pipeline 和 Transaction 都是"打包"方案,但它们解决的问题不同: + +```mermaid +flowchart LR + A["客户端发送 N 条命令"] --> B{"核心诉求?"} + B -- "快!" --> C["Pipeline
减少网络往返"] + B -- "安全!" --> D["Transaction
保证命令原子执行"] +``` ### Pipeline —— 批量发送,减少 RTT +Pipeline 的原理很简单:客户端先把 N 条命令存在本地缓冲区,**一次性**发送给 Redis 服务端,服务端依次执行后把 N 个回复**一次性**返回。 + +> [!NOTE] Pipeline 的两个关键点 +> 1. **不保证原子性**——Pipeline 中的命令仍然可能被其他客户端的命令穿插执行 +> 2. **不关心业务逻辑**——它纯粹是一个"网络优化层",减少的是等待时间 + ```go pipe := rdb.Pipeline() pipe.Set(ctx, "k1", "v1", 0) pipe.Set(ctx, "k2", "v2", 0) pipe.Set(ctx, "k3", "v3", 0) -results, err := pipe.Exec(ctx) // 一次性发送,一次性接收 +results, err := pipe.Exec(ctx) // 一次性发送,一次性接收所有结果 ``` ```bash @@ -270,37 +290,86 @@ redis-cli --pipeline <<< $'SET k1 v1\nSET k2 v2\nSET k3 v3' ``` > [!TIP] Pipeline 不是银弹 -> 单次 batch 控制在 50~200 条为佳。过大反而会增加 RTT 等待时间,且占用服务端协程。 +> 单次 batch 控制在 50~200 条为佳。过大反而会增加 Redis 服务端的内存压力和等待时间。 ### MULTI / EXEC —— 原子化执行 +事务解决的是另一个问题:**我想让一组命令"一起执行",中间不能被别人插队**。 + +`MULTI` → `命令1` → `命令2` → `EXEC` 的执行流程是: + +```mermaid +sequenceDiagram + participant C as "客户端" + participant S as "Redis 服务端" + C->>S: MULTI(开启事务) + S-->>C: OK + C->>S: SET acc:A 900 + S-->>C: QUEUED(排队,不立即执行) + C->>S: SET acc:B 1100 + S-->>C: QUEUED + C->>S: EXEC(提交) + S-->>C: [OK, OK](依次执行,一次性返回结果) +``` + +> [!QUESTION] "QUEUED" 是什么意思? +> `MULTI` 之后、`EXEC` 之前的命令**不会立即执行**,而是进入服务端的队列。只有收到 `EXEC` 后,Redis 才会**依次**执行队列中的所有命令,并把结果一次性返回。 + ```go -pipe, cancel := rdb.TxPipeline() +pipe, cancel := rdb.TxPipeline() // TxPipeline = Transaction Pipeline,即 MULTI/EXEC 包裹的 Pipeline defer cancel() pipe.Set(ctx, "acc:A", "900", 0) pipe.Set(ctx, "acc:B", "1100", 0) -_, err := pipe.Exec(ctx) // 全部命令依次执行(不被其他命令打断) +_, err := pipe.Exec(ctx) // 触发 EXEC,所有命令原子执行 ``` ```bash # Redis CLI 中的事务 MULTI -> SET acc:A 900 -> SET acc:B 1100 -EXEC # 原子提交 +> SET acc:A 900 # QUEUED +> SET acc:B 1100 # QUEUED +EXEC # 原子提交 ``` -> [!NOTE] Pipeline vs Transaction 对比 -> | 维度 | Pipeline | Transaction (MULTI/EXEC) | -> |------|----------|------------------------| -> | **目的** | 减少网络往返(性能) | 保证原子性(一致性) | -> | **命令失败处理** | 单个失败不影响其他命令 | `EXEC` 前语法错误会取消整个事务 | -> | **WATCH 支持** | 不支持 | 支持乐观锁 (`WATCH key`) | -> | **适用场景** | 批量写入、批量查询 | 转账、库存扣减等需要原子性的操作 | +### WATCH —— 乐观锁(防止"并发踩踏") + +事务保证了"不插队",但如果另一个客户端在我 `WATCH` 到 `EXEC` 之间修改了数据怎么办?`WATCH` 就是为此设计的——它在 `EXEC` 前检查被监视的 key 是否被改过,**如果改了就自动放弃整个事务**。 + +```mermaid +sequenceDiagram + participant A as "客户端 A" + participant S as "Redis 服务端" + participant B as "客户端 B" + A->>S: WATCH stock:1001 + A->>S: MULTI + A->>S: DECR stock:1001 + B->>S: SET stock:1001 999(被其他客户端修改) + A->>S: EXEC + S-->>A: nil(事务被取消,因为 stock:1001 已变) +``` + +> [!NOTE] WATCH 的使用范式 +> 1. `WATCH key` —— 监视目标 key +> 2. 读取 key 的值,在客户端做判断 +> 3. `MULTI` → 写命令 → `EXEC` +> 4. 如果 `EXEC` 返回 `nil`,说明数据被改过,需要**重试整个流程** +> +> 这就是经典的 **CAS(Compare-And-Swap)** 模式。实际开发中,Lua 脚本通常更简洁可靠。 + +### 对比总结 + +| 维度 | Pipeline | Transaction (MULTI/EXEC) | +|------|----------|------------------------| +| **核心目的** | 减少网络往返(**性能优化**) | 保证命令连续执行(**一致性**) | +| **原子性** | ❌ 不保证 | ✅ 保证(不会被其他命令插队) | +| **命令失败处理** | 单个失败不影响其他命令 | 语法错误 → 整个事务取消;运行时错误 → 仅该命令失败,其他继续 | +| **WATCH 支持** | ❌ | ✅ 乐观锁 | +| **适用场景** | 批量写入、批量查询 | 转账、库存扣减等需要原子性的操作 | +| **性能开销** | 几乎无额外开销 | 略高于 Pipeline(需要服务端队列) | > [!WARNING] Redis 事务不是"数据库事务" -> Redis 事务只保证**不中断**(没有回滚),中间执行的命令如果出错,后续命令仍会继续。如果需要"出错就回滚"的行为,请使用 Lua 脚本。 +> 传统数据库的事务有 **ACID** 保证,失败会回滚。Redis 事务只保证**不中断**(队列中的命令会全部执行),中间命令出错**不会回滚**。如果需要"出错就回滚",请使用 Lua 脚本。 ## Pub/Sub — 发布订阅