写在开篇·蓉儿继续挖坑
上回说到,郭靖搞清楚了DDS发现阶段的完整流程------SPDP找人、SEDP找内容、本地匹配、单播传输。
郭靖合上笔记本,若有所思:"蓉儿,我发现一个细节------匹配的时候,除了Topic名字和数据类型,还要检查QoS。这个QoS到底是什么?为什么这么重要?"
黄蓉咬了口糖葫芦:"问得好!QoS是DDS的灵魂。 今天就把QoS讲透------它是什么,有哪些常用策略,什么场景该用哪个。"
一、QoS是什么------发布者和订阅者的"服务约定"
黄蓉在白板上写下定义:
QoS = Quality of Service = 服务质量
简单说:发布者和订阅者之间的"服务约定"------你承诺给我什么样的服务质量。
郭靖问:"能不能举个例子?"
黄蓉画了一张对比图:
┌─────────────────────────────────────────────────────────────────────┐
│ 没有QoS:大家都一样 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 刹车信号(紧急)—— 排队 ——> [慢悠悠发送] ——> 刹车延迟! │
│ 娱乐信号(不紧急)—— 排队 ——> [正常发送] │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 有QoS:刹车可以插队 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 刹车信号(高优先级)—— 插队!——> [立即发送] ——> 刹车及时! │
│ 娱乐信号(低优先级)—— 排队等待 ——> [稍后发送] │
│ │
└─────────────────────────────────────────────────────────────────────┘
QoS的核心作用:告诉DDS"这个数据很重要,那个数据可以随便"。
二、QoS是谁定的?怎么生效的?
郭靖问出了关键问题:"这个QoS是谁定的?发布者说了算,还是订阅者说了算?"
黄蓉画了一张表:
| 问题 | 答案 |
|---|---|
| 谁定QoS? | 架构师在设计阶段定,写在设计文档里 |
| 怎么生效? | 发布者和订阅者在代码里创建Writer/Reader时,分别指定QoS |
| 不一致怎么办? | DDS会检查兼容性。能协商的就取交集,不能协商的就匹配失败 |
QoS不是运行时协商出来的,是设计阶段就定好的"规矩"。两边都要遵守,不一致就配对不了。
三、最常用的QoS策略(工程师必知)
黄蓉列了一张表,只讲最常用的几个:
| QoS策略 | 作用 | 可选值 | 适用场景 |
|---|---|---|---|
| 可靠性 | 数据必须送到,还是尽力而为 | RELIABLE / BEST_EFFORT | 刹车指令用RELIABLE,摄像头图像用BEST_EFFORT |
| 优先级 | 数据在网络中的传输优先级 | 数字(0-255,越大越优先) | 刹车指令高优先级,娱乐信号低优先级 |
| 延迟预算 | 数据必须在多少毫秒内送达 | 毫秒数(如1ms、100ms) | 刹车指令1ms,视频100ms |
| 持久性 | 订阅者启动晚了,还能不能收到历史数据 | TRANSIENT_LOCAL / VOLATILE | 状态数据用TRANSIENT,实时数据用VOLATILE |
| 历史记录 | 保留多少条历史数据 | KEEP_LAST(n) / KEEP_ALL | 最新数据用KEEP_LAST(1),需要全量用KEEP_ALL |
四、可靠性:RELIABLE vs BEST_EFFORT
黄蓉把这组QoS单独拿出来讲,因为这是最常用的:
| 策略 | 说明 | 优点 | 缺点 |
|---|---|---|---|
| RELIABLE | 数据必须送到,DDS自动重传 | 不丢数据 | 有重传开销,延迟可能变大 |
| BEST_EFFORT | 尽力而为,丢了不重传 | 快,无重传开销 | 可能丢数据 |
┌─────────────────────────────────────────────────────────────────────┐
│ RELIABLE vs BEST_EFFORT │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 刹车指令(RELIABLE): │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ 发 │────→│ 收 │────→│ 确认 │ ← 必须确认,不确认就重发 │
│ └─────┘ └─────┘ └─────┘ │
│ │
│ 摄像头图像(BEST_EFFORT): │
│ ┌─────┐ ┌─────┐ │
│ │ 发 │────→│ 收 │ ← 发了不管,丢了就丢了 │
│ └─────┘ └─────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
郭靖的理解:
"RELIABLE是挂号信------必须签收,丢了重发。BEST_EFFORT是平信------扔进邮筒就不管了,丢了不补。"
五、优先级------刹车插队,视频排队
黄蓉用一张图解释优先级的作用:
┌─────────────────────────────────────────────────────────────────────┐
│ 优先级:谁插队,谁排队 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 网络通道(优先级队列): │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 高优先级队列(0ms处理) │ │
│ │ ┌──────┐ ┌──────┐ ┌──────┐ │ │
│ │ │ 刹车 │ │ 刹车 │ │ 刹车 │ ← 立即发送 │ │
│ │ └──────┘ └──────┘ └──────┘ │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ 低优先级队列(排队等待) │ │
│ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ │
│ │ │ 视频 │ │ 视频 │ │ 视频 │ │ 视频 │ ← 慢慢发 │ │
│ │ └──────┘ └──────┘ └──────┘ └──────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
郭靖的理解:
"优先级高的数据可以插队。刹车指令必须插队,视频流不着急,排队就行。"
六、延迟预算------超过时间就别要了
郭靖问:"延迟预算是什么意思?"
黄蓉画了一个例子:
┌─────────────────────────────────────────────────────────────────────┐
│ 延迟预算:超过时间就别要了 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 刹车指令:延迟预算 = 1ms │
│ ┌─────┐ ┌─────────────┐ ┌─────┐ │
│ │ 发 │────→│ 网络传输 │────→│ 收 │ ✅ 1ms内到了,有效 │
│ └─────┘ │ 耗时0.8ms │ └─────┘ │
│ └─────────────┘ │
│ │
│ 刹车指令:延迟预算 = 1ms │
│ ┌─────┐ ┌─────────────┐ ┌─────┐ │
│ │ 发 │────→│ 网络拥堵 │────→│ 收 │ ❌ 1.5ms才到,丢弃! │
│ └─────┘ │ 耗时1.5ms │ └─────┘ (太晚了,不能用) │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
延迟预算告诉DDS:超过这个时间的数据我不要了,直接丢。
郭靖的理解:
"刹车指令必须在1ms内到,晚了就不要了,因为车可能已经撞了。"
七、持久性------晚来的订阅者还能收到历史数据吗
郭靖问:"如果域控启动晚了,摄像头已经发了10帧图像,域控能收到之前的数据吗?"
黄蓉用例子回答:
| 持久性策略 | 行为 | 场景 |
|---|---|---|
| VOLATILE(易失) | 只收订阅之后的数据 | 实时数据,如摄像头图像(晚了就不需要了) |
| TRANSIENT_LOCAL(临时持久) | 保留最后几条历史数据给晚来的订阅者 | 状态数据,如当前车速、车门状态(晚了也要知道) |
┌─────────────────────────────────────────────────────────────────────┐
│ TRANSIENT_LOCAL vs VOLATILE │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ VOLATILE(易失): │
│ 摄像头发:帧1 帧2 帧3 帧4 帧5 │
│ ↑ │
│ 域控启动 │
│ 只能收到帧5之后的数据(帧1-4丢了) │
│ │
│ TRANSIENT_LOCAL(临时持久): │
│ BMS发:电压=12V 电压=12.1V 电压=12.2V │
│ ↑ │
│ 座舱启动 │
│ 还能收到最近保留的电压值(比如最后3条) │
│ │
└─────────────────────────────────────────────────────────────────────┘
郭靖的理解:
"摄像头图像是实时的,晚来的帧没必要看。BMS电压是状态,晚来的也要知道当前电压是多少。"
八、QoS配置示例(只讲场景,不写代码)
黄蓉用两个场景把QoS策略串起来:
场景一:刹车指令(安全关键)
| QoS策略 | 配置值 | 原因 |
|---|---|---|
| 可靠性 | RELIABLE | 不能丢,一帧都不能丢 |
| 优先级 | HIGH(255) | 必须插队,优先发送 |
| 延迟预算 | 1ms | 必须在1ms内到达 |
| 持久性 | VOLATILE | 只需要新的刹车指令,历史不需要 |
场景二:摄像头图像(大数据、实时)
| QoS策略 | 配置值 | 原因 |
|---|---|---|
| 可靠性 | BEST_EFFORT | 丢几帧没关系,人眼看不出 |
| 优先级 | NORMAL(100) | 正常排队,不需要插队 |
| 延迟预算 | 100ms | 100ms以内到达即可,30fps够用了 |
| 历史记录 | KEEP_LAST(1) | 只保留最新一帧,旧的不要 |
九、QoS不匹配会发生什么
郭靖问:"如果摄像头配置了BEST_EFFORT,域控要求RELIABLE,会怎样?"
黄蓉画了结果:
| 发布者QoS | 订阅者QoS | DDS行为 |
|---|---|---|
| BEST_EFFORT | BEST_EFFORT | ✅ 兼容,正常连接 |
| RELIABLE | RELIABLE | ✅ 兼容,正常连接 |
| BEST_EFFORT | RELIABLE | ❌ 不兼容!不会建立连接! |
| RELIABLE | BEST_EFFORT | ⚠️ 降级为BEST_EFFORT(如果策略支持协商) |
发布者和订阅者的QoS必须兼容,否则配对失败,收不到任何数据。
十、黄蓉的小本本
郭靖翻开她的笔记本,上面写着:
QoS核心要点:
1. QoS是什么:发布者和订阅者之间的"服务约定"
2. 谁定的:架构师在设计阶段定,写在设计文档里
3. 怎么生效:发布者和订阅者分别指定,DDS检查兼容性
4. 最常用的QoS策略:
可靠性(RELIABLE vs BEST_EFFORT)
优先级(HIGH vs NORMAL)
延迟预算(1ms vs 100ms)
持久性(TRANSIENT_LOCAL vs VOLATILE)
历史记录(KEEP_LAST vs KEEP_ALL)
5. 一句话总结:
刹车指令:RELIABLE + HIGH + 1ms + VOLATILE
摄像头图像:BEST_EFFORT + NORMAL + 100ms + KEEP_LAST(1)
写在最后
郭靖合上笔记本:"QoS是发布者和订阅者之间的服务约定。可靠性决定丢不丢数据,优先级决定能不能插队,延迟预算决定超时要不要,持久性决定晚来的能不能补。刹车指令和摄像头图像的QoS完全不一样,各有各的配置。"
黄蓉咬了口糖葫芦:"全明白了?"
郭靖点头:"明白了。刹车用RELIABLE+插队,视频用BEST_EFFORT+排队。QoS搞错了,轻则性能差,重则收不到数据。"
黄蓉眨眨眼:"DDS从概念到报文到发现到QoS,你算是完整过了一遍。那你知道QoS在DDS数据帧中是如何实现内连以及哪些字段、如何体现的吗?"
郭靖摇头。
黄蓉眨眨眼:"那就下期分解"
打完收工,886。