vault backup: 2026-05-25 23:50:33

This commit is contained in:
hhs
2026-05-25 23:50:33 +08:00
parent 268ee58de6
commit 1e8dbf06b8
5 changed files with 304 additions and 86 deletions
+21 -19
View File
@@ -27,8 +27,8 @@ pipe.Set(ctx, "key", "value", 0)
results, _ := pipe.Exec(ctx)
```
> [!TIP] Go 的 TxPipeline vs MULTI
> `rdb.TxPipeline()` 在 go-redis 中**并不发送 MULTI/EXEC**——它只是普通 pipeline。这是为了兼容 Cluster 环境(集群不支持事务)。两者区别:
> [!TIP] Go 的 Pipeline vs TxPipeline
> `rdb.Pipeline()` 只批量发送命令,不加事务包裹;`rdb.TxPipeline()` 会在批量命令前后自动加上 `MULTI` / `EXEC`,等价于上文的事务语义。在 Cluster 模式下,go-redis 会按 slot 分组,对每个节点单独发送 MULTI/EXEC(因此同一 pipeline 内的 key 必须在相同 hash slot)。两者区别:
| 维度 | MULTI/EXEC | Pipeline |
|------|-----------|----------|
@@ -206,8 +206,8 @@ os.date() -- 依赖系统时间
-- redis.call('set', KEYS[1], ARGV[1]) -- 带 token 写入
```
> [!NOTE] Redis 7.2+ 更新
> Redis 7.2 起开放了部分确定性数学函数:`math.random`(需自行 set seed)、`math.max`、`math.min`、`math.log`。但不建议在状态机类脚本中使用随机性。
> [!NOTE] 关于 Lua 沙箱中的数学函数
> `math.max`、`math.min`、`math.log`、`math.abs` 等纯函数在沙箱中可用——它们的输出完全由输入决定,属于确定性函数。但 `math.random` **始终不可用**:即使设置相同 seed,伪随机序列也属于非确定性行为,会破坏主从一致性和 AOF 重放。随机值必须由客户端生成后通过 ARGV 传入。
> [!QUESTION] 为什么 Lua 脚本不能有随机函数?
> Lua 脚本在主线程中同步执行,如果引入随机性或时间依赖,会导致两个严重问题:
@@ -244,25 +244,27 @@ pipe.HSet(ctx, "user:1:profile", "email", "a@x.com")
results, _ := pipe.Exec(ctx) // 返回 []redis.Result
_ = results // 实际使用中检查 error
// 方式二:Watch CAS(乐观锁 —— 读-改-写原子的核心模式)
var balance int64
// 方式二:Watch + TxPipeline(乐观锁 —— 读-改-写原子的核心模式)
// 核心思路:WATCH 监听 key → 在回调内读-判断-写 → 如果 key 被别人改了则整个回调失败重试
err := rdb.Watch(ctx, func(tx *redis.Tx) error {
// 1. 读:获取当前余额
acc, err := tx.Get(ctx, "account:alice").Result()
if err != nil { return err }
balance, _ = strconv.ParseInt(acc, 10, 64)
return nil // 释放 Watch 锁
balance, _ := strconv.ParseInt(acc, 10, 64)
if balance < 100 {
return fmt.Errorf("余额不足")
}
// 2. 改-写:在 Watch 回调内用 TxPipeline 保证原子性
pipe := tx.TxPipeline()
pipe.DecrBy(ctx, "account:alice", 100)
pipe.IncrBy(ctx, "account:bob", 100)
_, err = pipe.Exec(ctx)
return err
}, "account:alice")
if err != nil { /* NOSCRIPT / watch abort */ return }
if balance < 100 {
log.Fatal("余额不足")
if err == redis.TxFailedErr {
// 被 Watch 的 key 被其他客户端修改了,需要重试整个操作
log.Println("并发冲突,重试...")
}
_, err = rdb.ExecFunc(ctx, func(tx *redis.Tx) error {
tx.DecrBy(ctx, "account:alice", 100)
tx.IncrBy(ctx, "account:bob", 100)
return nil
}).Result()
```
> [!WARNING] Pipeline 的注意事项
@@ -456,7 +458,7 @@ flowchart TD
| 实时通知、WebSocket 广播 | Pub/Sub | 无状态、低延迟 |
> [!TIP] 一句话总结
> - **Pipeline**:省网络,不保证顺序
> - **Pipeline**:省网络,不保证原子性
> - **MULTI**:保证顺序,不保证逻辑原子
> - **Lua**:真正的原子性,但阻塞主线程(脚本要短小精悍)
> - **Stream**:需要消息可靠投递时的首选