vault backup: 2026-05-25 23:50:33
This commit is contained in:
+21
-19
@@ -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**:需要消息可靠投递时的首选
|
||||
|
||||
Reference in New Issue
Block a user