chainbuffer fixed
This commit is contained in:
189
chainbuffer.plan.md
Normal file
189
chainbuffer.plan.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# ChainBuffer + 全量 WAL(单一方案,slot 驱动)
|
||||
|
||||
## 1. 目标与约束
|
||||
|
||||
目标:
|
||||
|
||||
- 所有命令都落盘(不再区分 update/get)。
|
||||
- 主线程 A 尽量不做内存分配,不构造 uring task。
|
||||
- 主线程 A 只做:收包、解析命令边界、摘取 payload、写入预分配 slot。
|
||||
- WAL 线程 B 负责:从 slot 取 payload、构造 task、提交 io_uring、归还内存块。
|
||||
|
||||
约束:
|
||||
|
||||
- 只保留一个实现方案,不引入 v1/v2 双路径。
|
||||
- 正确性优先:半包、多包、pipeline、断连、慢盘都可控。
|
||||
|
||||
---
|
||||
|
||||
## 2. ChainBuffer 设计(重点)
|
||||
|
||||
## 2.1 结构定义
|
||||
|
||||
采用“回环 chunk 链表”:
|
||||
|
||||
```c
|
||||
typedef struct cb_chunk {
|
||||
struct cb_chunk *next;
|
||||
uint32_t cap; // 固定大小,默认 4096
|
||||
uint32_t rpos; // 读指针 [0, cap)
|
||||
uint32_t wpos; // 写指针 [0, cap)
|
||||
uint32_t used; // 已用字节数 [0, cap]
|
||||
uint32_t refcnt; // 被 WAL payload 持有的引用计数
|
||||
uint8_t data[];
|
||||
} cb_chunk_t;
|
||||
|
||||
typedef struct chain_buffer {
|
||||
cb_chunk_t *head;
|
||||
cb_chunk_t *tail;
|
||||
size_t total_len;
|
||||
uint32_t chunk_size;
|
||||
|
||||
// 节点池:避免频繁 malloc/free
|
||||
cb_chunk_t *free_list;
|
||||
uint32_t free_count;
|
||||
uint32_t free_limit;
|
||||
} chain_buffer_t;
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- chunk 内部可回环读写(避免 memmove)。
|
||||
- 多 chunk 串联用于扩容。
|
||||
- 空 chunk 不释放到系统,先回收到 `free_list`。
|
||||
|
||||
## 2.2 接收路径(readv 直写)
|
||||
|
||||
主线程不再 `recv -> tmp -> memcpy`,改为:
|
||||
|
||||
1. `chain_buffer_prepare_recv_iov(buf, iov, max_iov)`
|
||||
- 收集可写空闲段(优先尾 chunk,不够就从 free_list 取或新建 chunk)。
|
||||
2. `readv(fd, iov, iovcnt)`
|
||||
3. `chain_buffer_commit_recv(buf, nread)`
|
||||
- 推进 `wpos/used/total_len`。
|
||||
|
||||
这样可去掉接收中转拷贝。
|
||||
|
||||
## 2.3 消费与摘取
|
||||
|
||||
提供三个核心能力:
|
||||
|
||||
- `chain_buffer_iter_*`:按字节流遍历,供 RESP 解析命令边界。
|
||||
- `chain_buffer_detach_prefix(buf, len, cb_payload_t *out)`:把前缀字节摘成 payload(尽量零拷贝,边界切分允许一次小拷贝)。
|
||||
- `chain_buffer_release_payload(buf, payload)`:WAL 线程用完后归还 chunk(refcnt--,归零后回 free_list)。
|
||||
|
||||
---
|
||||
|
||||
## 3. slot 队列设计(主线程零分配)
|
||||
|
||||
## 3.1 预分配 ring
|
||||
|
||||
定义单生产者单消费者 ring(A->B):
|
||||
|
||||
```c
|
||||
#define WAL_SLOT_CAP 65536
|
||||
|
||||
typedef struct wal_slot {
|
||||
uint64_t seq;
|
||||
uint32_t cmd_len;
|
||||
cb_payload_t payload; // 命令字节
|
||||
uint8_t in_use;
|
||||
} wal_slot_t;
|
||||
|
||||
typedef struct wal_ring {
|
||||
wal_slot_t slots[WAL_SLOT_CAP];
|
||||
_Atomic uint32_t head; // producer write
|
||||
_Atomic uint32_t tail; // consumer read
|
||||
_Atomic uint32_t size;
|
||||
} wal_ring_t;
|
||||
```
|
||||
|
||||
特点:
|
||||
|
||||
- slot 全预分配。
|
||||
- 主线程写 slot 不 malloc。
|
||||
- WAL 线程消费后清空 slot 并前移 tail。
|
||||
|
||||
## 3.2 主线程流程(所有命令都落盘)
|
||||
|
||||
对每条完整命令:
|
||||
|
||||
1. 从 chainbuffer 解析得到 `cmd_len`。
|
||||
2. 申请一个空 slot(ring 未满)。
|
||||
3. `detach_prefix(cmd_len)` 得到 `payload`。
|
||||
4. 写入 slot:`seq/cmd_len/payload`。
|
||||
5. 发布 head。
|
||||
|
||||
注意:
|
||||
|
||||
- 不做命令类型判断。
|
||||
- 不构造 uring task。
|
||||
- 不进行额外 payload malloc。
|
||||
|
||||
## 3.3 WAL 线程流程
|
||||
|
||||
循环:
|
||||
|
||||
1. 从 ring 取 slot。
|
||||
2. 生成长度头(4B)+ payload iov。
|
||||
3. 调 `submit_write()`(当前打包拷贝发生在此线程)。
|
||||
4. `chain_buffer_release_payload()` 归还 chunk。
|
||||
5. 清 slot,推进 tail。
|
||||
|
||||
---
|
||||
|
||||
## 4. 一致性与回压
|
||||
|
||||
## 4.1 日志顺序
|
||||
|
||||
- `g_log_off` 只在 WAL 线程维护。
|
||||
- 单消费者天然保证写入顺序与入队顺序一致。
|
||||
|
||||
## 4.2 回压(必须)
|
||||
|
||||
当 ring 达到高水位(例如 80%):
|
||||
|
||||
1. 暂停该连接读事件(优先)。
|
||||
2. 若持续超时(如 200ms)仍高水位,关闭连接保护系统。
|
||||
|
||||
本方案不再做“回退旧路径”。
|
||||
|
||||
## 4.3 异常处理
|
||||
|
||||
- `submit_write` 失败:记录错误并触发保护动作(可选停机/拒绝新连接)。
|
||||
- 连接断开:已入 slot 的 payload 继续由 WAL 线程完成归还。
|
||||
- 进程退出:先停读,再 drain wal_ring,再 shutdown uring。
|
||||
|
||||
---
|
||||
|
||||
## 5. 实施步骤(实际落地)
|
||||
|
||||
1. 重构 `network/chainbuffer.*`
|
||||
- 回环 chunk + free_list
|
||||
- `prepare_recv_iov/commit_recv`
|
||||
- `iter/detach_prefix/release_payload`
|
||||
2. 修改 `reactor.c`
|
||||
- `recv_cb` 使用 `readv` + commit
|
||||
3. 修改 `kvstore.c`
|
||||
- 按命令边界循环
|
||||
- 每条命令统一入 wal_slot(不分类)
|
||||
4. 新增 `dump/wal_slot_queue.*`(或放 `dump/kvs_oplog.c`)
|
||||
- SPSC ring + WAL worker
|
||||
5. 调整 `dump/kvs_oplog.c`
|
||||
- 改为 WAL 线程消费 slot 后调用 `submit_write`
|
||||
6. 收敛退出与错误路径
|
||||
- close_conn / shutdown / submit 失败全覆盖
|
||||
|
||||
---
|
||||
|
||||
## 6. 验收标准
|
||||
|
||||
- 功能:
|
||||
- 半包/多包/pipeline 正确;
|
||||
- 重启回放后数据一致。
|
||||
- 性能:
|
||||
- 主线程不再出现 oplog task 构造热点;
|
||||
- 接收路径 memcpy 次数下降(去掉 tmp 中转)。
|
||||
- 稳定性:
|
||||
- 慢盘压测下无泄漏、无 double free、无 UAF;
|
||||
- 高水位触发时系统行为可预期。
|
||||
Reference in New Issue
Block a user