解决MySql布尔型新旧版本兼容问题,采用枚举来表示布尔型的数据表。由正向工程赋值
|
# 背压水位定档与内存测算报告
> 报告日期:2026-09-17 | 基线:v12 工作区 | 环境:i9-10900K / .NET 10.0.12 / Windows 10 22H2
> 口径:回环 TCP,客户端与服务端共享 CPU;基准为 BenchmarkDotNet(Release),实验为手动用例(默认跳过)
---
## 1. 目标
为 `NewLife.Data.Pipe` 背压水位默认值定档——**读侧**(会话接收管道)与**写侧**(发送管道)分侧决策,替换"读写共用 1M/512K 且无论证"的现状;同步修复执行中实测发现的**读饥饿死锁**。
## 2. 背景
- 机制:迟滞水位(达到 `PauseThreshold` 暂停、降到 `ResumeThreshold` 以下恢复)。读侧暂停 = 接收环暂存 SAEA(`ParkReceive`),恢复后重启;写侧暂停 = `FlushAsync` 挂起生产者。
- 原默认:1M/512K(读写共用)。
- 竞品对照(官方资料核验):BCL `PipeOptions.Default` 64K/32K(通用管道);Kestrel `MaxRequestBufferSize` 默认 **1MB**(入站缓冲上限)、`MaxResponseBufferSize` 默认 **64KB**(出站阻塞阈值,与 `FlushAsync` 挂起同语义);Netty `WriteBufferWaterMark` 默认 32K/64K(写侧 `isWritable` 提示语义)。
## 3. 关键结论(摘要)
1. **写侧定档 64K/32K**(原 1M/512K):对齐 BCL/Kestrel 出站档;8MB 流式发送各档位吞吐无差异(4.7–5.4ms);慢对端下每连接排队内存最坏降 16×。
2. **读侧维持 1M/512K**:与 Kestrel 入站 1MB 同量级;1M 档暂停/恢复次数最少(慢消费下 churn 低约一个数量级);32MB 快消费接收各档位无差异(23.1–23.6ms)。
3. **修复读饥饿死锁**(执行中实测发现):暂停一旦触发,整帧读取无法解除——读者无法消费(帧不完整)、消费为零于是永不恢复,帧永远凑不齐;实测范围为"**任何跨过暂停水位边界的帧**"。修复后 32MB 会话压测由 30s 超时变为 <0.5s 完成。
## 4. 缺陷记录与修复(读饥饿让位)
- **复现**:`PipeWatermarkReceiveBenchmark`(32MB/轮,64K 档)两次在第二轮调用挂起;会话级压测用例连续 7 次 30s 超时。停摆现场:`已收 0/1024、暂停=True、未消费=16384`(恰好一个水位)。
- **机理**:整帧读取对不完整帧不消费(`AdvanceTo(0, examined)`);暂停触发后接收停摆;读者等待的后继字节不会到达;消费为零 → `Resumed` 永不触发 → 死锁(对端关闭亦不可感知,直到空闲超时)。
- **修复 A(核心)**:`PipeReader.ReadAsync` 在"无新数据将挂起等待"时调用 `Pipe.ReleasePauseForReaderLocked` 解除暂停并触发 `Resumed`(**读饥饿让位**)——背压约束"已到达未检查的积压",不束缚读者正在等待的数据。
- **修复 B(加固)**:`TcpSession.ParkReceive` 存入暂存后复查暂停态,若在"读取暂停态与暂存落位"之间已发生恢复,则立即重启(丢唤醒防护)。
- **契约变化**:原"整帧大于水位会停摆"不复存在;大帧经整帧路径可完成(该帧驻留内存随帧长增长,超大 payload 建议头部先行流式)。新增用例 `SessionPipe_WholeFrameExceedsPauseThreshold_ReaderStarvationReleases`、`SessionPipe_Backpressure_HighFrequencyPauseResume` 锁定。
- **验证**:`SessionPipeTests` 5/5 连续 3 轮(<0.5s);接收基准 3 档位完整跑完;全量 2920 例中 2904 通过、15 跳过(含本报告 3 个手动实验)、1 例 `TarEntryTests` 为仓库已知并发偶发(单独复跑通过);18 TFM 构建 0 错。
## 5. 基准数据(BenchmarkDotNet,Release)
命令:`dotnet run --project Benchmark/Benchmark.csproj -c Release -- --filter "*PipeWatermark*"`
### 5.1 写侧:8MB `SendAsync(Stream)`(快对端)
| 暂停水位 | Mean | StdDev |
|---------:|-----:|-------:|
| 64K | 4.888 ms | 0.130 ms |
| 128K | 5.288 ms | 0.106 ms |
| 256K | 4.710 ms | 0.043 ms |
| 1M | 5.370 ms | 0.338 ms |
→ 差异在置信区间内,**无档位敏感**。
### 5.2 读侧:32MB 定长帧接收(快消费)
| 暂停水位 | Mean | StdDev |
|---------:|-----:|-------:|
| 64K | 23.61 ms | 0.714 ms |
| 256K | 23.13 ms | 0.203 ms |
| 1M | 23.25 ms | 0.084 ms |
→ ≈1.37GB/s,**无档位敏感**(水位只影响慢消费内存与暂停频率)。
## 6. 实验数据(慢消费 / 慢对端 / 大帧)
命令:`dotnet test XUnitTest.Core -f net10.0 --filter "FullyQualifiedName~WatermarkExperimentTests" --logger "console;verbosity=detailed"`
### 6.1 读侧慢消费(4.9MB,消费≈16MB/s)
| 水位 | 峰值积压 | 内存增量峰值 | 暂停次数 | 恢复次数 | 耗时 |
|-----:|---------:|-------------:|---------:|---------:|-----:|
| 64K | 71 KB | 453 KB | 24 | 99 | 2314 ms |
| 256K | 263 KB | 521 KB | 24 | 29 | 2315 ms |
| 1M | 1031 KB | 8 KB(采样噪声) | 4 | 7 | 2342 ms |
→ 峰值积压 ≈ 水位 + 1 块接收缓冲(8K);恢复次数随水位缩小增长约一个数量级;总耗时由消费方决定,与水位无关。
### 6.2 写侧慢对端(4MB,对端≈6.4MB/s)
| 水位 | 峰值队列 | 恢复次数 | 生产者耗时 | 端到端 |
|-----:|---------:|---------:|-----------:|-------:|
| 64K | 64 KB | 64 | 46 ms | 1027 ms |
| 128K | 64 KB | 31 | 32 ms | 994 ms |
| 256K | 192 KB | 16 | 27 ms | 990 ms |
| 1M | 1024 KB | 3 | 29 ms | 991 ms |
→ 队列峰值 ≤ 水位(低档位由 OS 套接字缓冲吸收,显示更低);生产者与端到端不受档位影响。
### 6.3 大帧流式(8MB 单帧,头部先行,修复后)
| 水位 | 耗时(3 轮) | 峰值积压 | 暂停/恢复 |
|-----:|-------------|---------:|----------:|
| 64K | 9 / 4 / 4 ms | 64 KB | 6–9 / 115–128 |
| 1M | 4 / 4 / 4 ms | 32–64 KB | 0 / 0 |
→ 64K 档完成不受阻,但存在**让位循环**(churn:暂停 → 读者饥饿 → 让位放行,每约一个水位字节一轮);1M 档零暂停——大帧流式场景支持维持 1M 读侧默认。
## 7. 内存测算(最坏驻留)
单连接最坏:读侧 ≈ 水位 + 1×接收块(8K);写侧 ≈ 水位 + 1×流式块(64K)。仅"消费落后 / 对端慢"的连接发生驻留,正常消费不积累。
| 档位 | 单连接(读/写) | 10 万暂停连接(读/写) | 100 万暂停连接(读/写) |
|------|-----------------|------------------------|--------------------------|
| 64K | ~72K / ~128K | 7.2 GB / 12.8 GB | 72 GB / 128 GB |
| 256K | ~264K / ~320K | 26 GB / 32 GB | 264 GB / 320 GB |
| 1M | ~1.03M / ~1.09M | 103 GB / 109 GB | 1030 GB / 1090 GB |
("全部连接暂停"为极端上界,仅用于比较档位间数量级差异。)
## 8. 定档结论
| 侧 | 原值 | 定档 | 依据 |
|----|------|------|------|
| 读侧(入站) | 1M/512K | **维持 1M/512K** | Kestrel 入站同量级(1MB);暂停 churn 最低(1M 档 7 次 vs 64K 档 99 次);大帧经饥饿让位不受阻;慢消费内存安全阀 |
| 写侧(出站) | 1M/512K | **64K/32K** | 对齐 BCL 通用默认与 Kestrel 出站阻塞阈值(64KB);实测吞吐无退化;慢对端排队内存最坏降 16× |
- **读写非对称**(读宽写紧):与 Kestrel 的 `MaxRequestBufferSize`(1MB) / `MaxResponseBufferSize`(64KB) 非对称布局一致。
- **倍数口径答复(BufferSize 倍数提案)**:读侧 1M = 128×8K、512K = 64×8K,可作文档口径(与 `SocketSetting.BufferSize` 注释口径一致);**不做代码层按 `BufferSize` 推导**——写侧与接收块无机理关系,且推导会让调整 `BufferSize` 意外改变背压行为,保持两旋钮独立。
## 9. 局限
- 回环口径(客户端与服务端共享 CPU),跨机/真实网络下的绝对值会不同;结论基于档位之间的相对比较。
- 慢对端实验中 OS 套接字缓冲会吸收部分数据(队列峰值低于档位);管道层最坏驻留以第 7 节算术为准。
- 实验用例为手动采集(默认 `Skip`),用于复现与复核,不参与常规回归。
## 10. 相关产物
- 基准:`Benchmark/StreamingBenchmarks/WatermarkBenchmarks.cs`(`PipeWatermarkSendBenchmark` / `PipeWatermarkReceiveBenchmark`)
- 用例:`XUnitTest.Core/Net/SessionPipeTests.cs`(读饥饿让位 / 丢唤醒压测)、`XUnitTest.Core/Net/WatermarkExperimentTests.cs`(手动实验)
- 修复:`NewLife.Core/Data/Pipe.cs`(`ReleasePauseForReaderLocked`)、`NewLife.Core/Data/PipeReader.cs`(挂起路径让位)、`NewLife.Core/Net/TcpSession.Stream.cs`(`ParkReceive` 复查、出站水位 64K/32K)
|