@@ -6,28 +6,28 @@ sidebar_position: 2
66
77# LinuxSharedTopic 设计
88
9- 基础用法见基础消息系统里的“共享内存 Topic(Linux)”页面。下面直接讨论它与普通 ` Topic ` 的边界,以及现在这套实现的取舍 。
9+ 基础用法见基础消息系统里的“共享内存 Topic(Linux)”页面。本文说明它与普通 ` Topic ` 的分工,以及当前实现的取舍 。
1010
11- ## 1. 为什么不是直接改 ` Topic `
11+ ## 1. 为什么不直接改 ` Topic `
1212
13- ` LinuxSharedTopic<T> ` 解决的是 Linux / Webots 主机进程间通信、 大 payload 共享、零拷贝读取以及多订阅者队列策略;原始 ` Topic ` 更像是进程内发布订阅,语义偏 MCU 和轻量系统,用精确类型分发、回调、同步/异步订阅者去组织数据流。这两条路径的约束根本不同,所以共享内存语义没有继续塞回 ` Topic ` 本体,而是单独做成 ` LibXR::LinuxSharedTopic<T> ` 。这样 ` Topic ` 仍然保持轻量,Linux 主机 IPC 也可以沿着共享内存模型单独演化 。
13+ ` LinuxSharedTopic<T> ` 面向 Linux / Webots 主机的进程间通信: 大 payload 共享、零拷贝读取、多订阅者队列策略。 ` Topic ` 面向进程内发布订阅,偏 MCU 和轻量系统,用精确类型分发、回调、同步/异步订阅者组织数据流。两者约束不同,所以共享内存语义没有塞回 ` Topic ` ,而是单独实现为 ` LibXR::LinuxSharedTopic<T> ` ,让 ` Topic ` 保持轻量, 主机 IPC 也能沿共享内存模型独立演化 。
1414
1515## 2. 数据面和控制面分离
1616
17- ` LinuxSharedTopic<T> ` 的核心结构不是一条简单队列,而是两层 :
17+ ` LinuxSharedTopic<T> ` 的核心结构分两层,而不是一条简单队列 :
1818
19191 . payload slot
20- - 真实数据驻留在共享内存槽位里
20+ - 真实数据存放在共享内存槽位里
21212 . descriptor queue
2222 - 发布时只把“哪个 slot 可读”投递给订阅者
2323
24- 这样做的直接结果是 :
24+ 这样 :
2525
2626- payload 本体不需要在 publisher 和 subscriber 之间重复拷贝
27- - 每个订阅者只需要消费描述符
28- - 同一个 slot 可以被多个订阅者同时持有 ,直到最后一个释放
27+ - 每个订阅者只需消费描述符
28+ - 同一个 slot 可被多个订阅者同时持有 ,直到最后一个释放
2929
30- 这也是它能做到零拷贝的前提 。
30+ 这是它实现零拷贝的前提 。
3131
3232---
3333
@@ -39,171 +39,102 @@ sidebar_position: 2
3939- publish / consume 侧尽量只做原子状态推进
4040- 等待路径再用 futex 睡眠
4141
42- 这意味着它追求的不是“完全无等待”,而是尽量把 mutex 从 publish/consume 热路径里拿掉,把真正的等待压缩到 futex 睡眠那一层。
42+ 它的目标不是完全无等待,而是把 mutex 从 publish/consume 热路径里拿掉,把真正的等待收拢到 futex 睡眠那一层。
4343
4444## 4. 为什么用 refcount reclaim,而不是覆盖写
4545
46- 共享内存队列最危险的问题之一是:
46+ 共享内存队列的一个危险场景是:publisher 想继续写,但某个 subscriber 还没读完旧数据。
4747
48- - publisher 想继续写
49- - 但某个 subscriber 还没把旧数据读完
50-
51- 这里选的是:
52-
53- - refcounted slot reclamation
54- - slot 用尽时对 publisher 施加 backpressure
55-
56- 而不是:
57-
58- - overwrite-in-use
59-
60- 这样选的代价是:
61-
62- - 高压下 publisher 可能因为 slot 耗尽而失败
63-
64- 好处是:
65-
66- - 不会在 subscriber 仍持有数据时把 payload 直接覆盖掉
67-
68- 这是一条很明确的安全优先边界。
48+ 这里用 refcounted slot reclamation,slot 用尽时对 publisher 施加 backpressure,而不是 overwrite-in-use。代价是高压下 publisher 可能因 slot 耗尽而失败;好处是不会在 subscriber 仍持有数据时把 payload 覆盖掉。这是安全优先的取舍。
6949
7050---
7151
72- ## 5. 三种订阅策略在取舍什么
52+ ## 5. 三种订阅策略的取舍
7353
74- 这里的订阅模式有 :
54+ 订阅模式有三种,各自的目标和代价不同 :
7555
7656- ` BROADCAST_FULL `
7757- ` BROADCAST_DROP_OLD `
7858- ` BALANCE_RR `
7959
80- 它们不是“功能不同而已”,而是在取舍不同目标。
81-
8260### ` BROADCAST_FULL `
8361
84- 目标:
85-
86- - 保留所有投递内容
87-
88- 代价:
89-
90- - 某个慢订阅者满队列时,会直接把 publish 成功率拉低
62+ 保留所有投递内容。代价是某个慢订阅者满队列时,会拉低 publish 成功率。
9163
9264### ` BROADCAST_DROP_OLD `
9365
94- 目标:
95-
96- - 尽量保住 publisher 吞吐
97- - 让慢订阅者始终更偏向读到新数据
98-
99- 代价:
100-
101- - 订阅者会丢旧样本
66+ 优先保住 publisher 吞吐,让慢订阅者更偏向读到新数据。代价是订阅者会丢旧样本。
10267
10368### ` BALANCE_RR `
10469
105- 目标:
106-
107- - 多个 worker 间均衡分发
108-
109- 代价:
110-
111- - 同一条消息不会广播给每个 worker
112-
113- 所以它本质上不是广播模式,而是共享负载模式。
70+ 在多个 worker 间均衡分发。同一条消息不会广播给每个 worker,所以它是共享负载模式,不是广播模式。
11471
11572---
11673
11774## 6. ` FULL ` 和 ` DROP_OLD ` 的实际差异
11875
119- 这不是纯概念取舍。 在慢订阅者过载场景下,实际行为是 :
76+ 在慢订阅者过载场景下,两者的实际行为是 :
12077
121- - ` DROP_OLD ` 能保住 publisher throughput
122- - 同时显著降低 delivered sample latency
123- - ` FULL ` 会保住队列内容完整性
124- - 但 publish 成功率会明显下降,延迟也会抬高
78+ - ` DROP_OLD ` 能保住 publisher throughput,同时明显降低 delivered sample latency
79+ - ` FULL ` 保住队列内容完整,但 publish 成功率明显下降,延迟也会抬高
12580
126- 也就是说:
127-
128- - 如果你要“所有样本都不能丢”,选 ` FULL `
129- - 如果你要“尽量快地拿到新数据”,选 ` DROP_OLD `
130-
131- 这和控制场景里 freshness-first 的取舍是一致的。
81+ 所以:所有样本都不能丢就选 ` FULL ` ,要尽快拿到新数据就选 ` DROP_OLD ` 。这和控制场景里 freshness-first 的取舍一致。
13282
13383---
13484
135- ## 7. ` BALANCE_RR ` 不是“随便轮询一下”
85+ ## 7. ` BALANCE_RR ` 的语义
13686
137- ` BALANCE_RR ` 不是在广播路径上随手加个游标,而是独立的 balanced subscriber group。
87+ ` BALANCE_RR ` 是独立的 balanced subscriber group,不是在广播路径上加个游标 。
13888
139- 其行为边界包括 :
89+ 行为边界 :
14090
14191- 一个 publish 最多只投给一个 balanced subscriber
142- - full member 会被跳过,只要组内还有别的成员能接
143- - 如果 balanced group 存在,但没有任何 member 能接,则整个 publish 失败
144-
145- 所以它的语义更接近:
146-
147- - “一组 worker 共享消费同一 topic”
92+ - 只要组内还有别的成员能接,full member 会被跳过
93+ - balanced group 存在但没有任何 member 能接时,整个 publish 失败
14894
149- 而不是:
150-
151- - “广播之后顺便轮流处理”
95+ 它的语义是“一组 worker 共享消费同一 topic”,不是“广播之后顺便轮流处理”。
15296
15397---
15498
15599## 8. 为什么需要 stale subscriber / publisher takeover
156100
157- 共享内存 IPC 最容易留下的垃圾不是日志,而是 :
101+ 共享内存 IPC 容易残留两类垃圾 :
158102
159103- 死掉的 subscriber 还占着 slot
160104- 死掉的 publisher 留下旧 segment
161105
162- 这套实现已经把这两件事都考虑进来了:
163-
164- - dead subscriber recycle
165- - stale publisher takeover
106+ 当前实现对两者都做了处理:
166107
167- subscriber 侧会跟踪 owner identity;
168- publisher 侧会在 create-side reopen 时尝试回收死进程遗留的 segment。
108+ - dead subscriber recycle:subscriber 侧跟踪 owner identity
109+ - stale publisher takeover:publisher 侧在 create-side reopen 时回收死进程遗留的 segment
169110
170- 这一步如果没有 ,Linux IPC 系统跑久了之后一定会出现 “逻辑上没人用了,但共享状态还卡着”的问题。
111+ 没有这一步 ,Linux IPC 跑久了会出现 “逻辑上没人用了,但共享状态还卡着”的问题。
171112
172113---
173114
174- ## 9. ` latency_avg ` 为什么经常不值得直接看
175-
176- 关于性能解读,当前最重要的一条结论是:
177-
178- - standard-case ` latency_avg ` 很容易受 scheduler 和启动 backlog 污染
179-
180- 也就是说,如果 publisher 一开始就灌数据,而 subscriber 尚未完全进入稳态等待,那么:
181-
182- - ` avg latency ` 里会混入一段队列堆积时间
183-
184- 这不等于纯粹的单次投递延迟。
115+ ## 9. 为什么 ` latency_avg ` 经常不值得直接看
185116
186- 因此更有意义的区分通常是:
117+ standard-case 的 ` latency_avg ` 容易受 scheduler 和启动 backlog 污染:如果 publisher 一开始就灌数据、subscriber 还没进入稳态等待, ` avg latency ` 里会混进一段队列堆积时间,不等于单次投递延迟。
187118
188- - saturated-throughput queueing latency
189- - single-outstanding one-way latency
119+ 更有意义的区分是:
190120
191- 前者描述系统满载时的排队表现,后者才更接近“消息本身从 publish 到被 wait 成功拿到”的真实单次路径。
121+ - saturated-throughput queueing latency:系统满载时的排队表现
122+ - single-outstanding one-way latency:消息从 publish 到被 wait 成功拿到的单次路径
192123
193124---
194125
195- ## 10. 适合它,不适合它
126+ ## 10. 适用与不适用场景
196127
197- 适合 ` LinuxSharedTopic<T> ` 的场景 :
128+ 适合:
198129
199130- Linux 主机多进程共享大 payload
200131- 订阅者策略明确,需要 ` FULL / DROP_OLD / RR `
201132- 希望避免用户态额外 payload 拷贝
202133
203- 不适合它的场景 :
134+ 不适合 :
204135
205136- MCU 侧 ISR 驱动路径
206137- 进程内轻量 publish/subscribe
207138- payload 不是 trivially copyable
208139
209- 从定位上看,它不是普通 ` Topic ` 的升级版,而是主机 IPC 的专门实现 。
140+ 它是主机 IPC 的专门实现,不是普通 ` Topic ` 的升级版。
0 commit comments