【FastDDS】Layer DDS之Domain ( 06-Partitions )
在DDS(Data Distribution Service,数据分发服务)中,Partition(分区) 是一种在“域(Domain)”提供的物理隔离基础上,为发布者(Publisher)和订阅者(Subscriber)新增的逻辑隔离与通信筛选机制。它的核心作用是在“域”和“主题(Topic)”之外,进一步精细化控制哪些发布者和订阅者能够建立通信,本质是通过“分区成员资格”实现端点(Publisher/Subscriber)间的逻辑分组。
一、Partition的核心定位:解决“更灵活的通信隔离”问题
DDS中已存在“域(Domain)”和“主题(Topic)”两种隔离机制:
- 域(Domain):物理隔离,不同域的端点无法通信(依赖域ID区分);
- 主题(Topic):内容隔离,只有订阅与发布完全相同主题的端点才能通信。
但这两种机制存在局限性(如域和主题无法动态修改、一个端点只能关联一个主题)。而Partition正是为弥补这些不足而生,其核心价值体现在以下3个关键特性:
1. 动态可修改,低开销
- 无需重启端点、不分配新内存、不启动新线程,也不影响历史数据记录;
- 修改分区时,仅会触发QoS(服务质量)配置的重新宣告,可能导致新的端点匹配(如切换分区后与新分区的订阅者建立连接);
- 对比:域(Domain)和主题(Topic)一旦创建无法动态修改,修改需销毁并重建端点,开销极高。
2. 多分区归属,减少网络负载
- 一个端点(Publisher/Subscriber)可同时属于多个分区(通过“分区名称列表”配置);
- 对发布者而言,无需为不同主题创建多个发布者,仅通过一个主题的单次数据更新,即可向多个分区推送数据,避免重复发送,显著降低网络开销;
- 对比:主题(Topic)是“一对一”关联,一个发布者只能对应一个主题,若需向不同主题发数据,必须创建多个发布者。
3. 基于“共同分区”的通信规则
- 发布者与订阅者要实现通信,至少需共享一个共同的分区(无论其他分区是否不同);
- 若未显式配置分区,端点会自动加入“默认匿名分区”(无名称),因此“未配置分区的发布者”和“未配置分区的订阅者”可通过默认分区通信。
二、关键注意事项:分区与历史数据的关系
需特别注意:分区是与“端点”绑定的,而非与“数据更新”绑定——端点的历史数据记录不会感知分区的修改。
例如:
- 发布者先在“Partition_A”中发送了一条数据;
- 之后发布者切换到“Partition_B”;
- 若此时需要重发历史数据,该数据会被推送到“Partition_B”(当前分区),而非原始的“Partition_A”;
- 后果:晚加入“Partition_B”的订阅者,可能收到原本在“Partition_A”中产生的历史数据。
三、分区的通配符匹配(灵活分组的核心)
为简化多分区配置,Partition支持POSIX fnmatch API(1003.2-1992 B.6) 定义的通配符规则,允许通过通配符匹配多个分区名称,实现“一键加入多个分区”。
1. 通配符匹配规则
- 匹配是双向检查的:只要两个分区名称(含通配符)中任意一个能匹配另一个,即视为“共享分区”;
- 示例:
- 发布者分区配置为
part*
(匹配以“part”开头的所有分区); - 订阅者分区配置为
partition*
(匹配以“partition”开头的所有分区); - 虽然
partition*
无法匹配part*
,但part*
可以匹配partition*
(“partition”以“part”开头),因此两者可通信。
- 发布者分区配置为
2. 特殊通配符“*”
- 通配符
*
可匹配所有非默认分区(即显式命名的分区,如“Partition_1”“test”等); - 注意:
*
无法匹配“默认匿名分区”,因此配置*
的发布者/订阅者,无法与“未配置分区、依赖默认分区”的端点通信。
四、实例理解:分区匹配逻辑
文档中提供了一个完整示例,通过具体配置可清晰看到分区的通信规则,核心配置与匹配结果如下:
1. 各端点的分区配置
参与者(Participant) | 端点(Publisher/Subscriber) | 分区配置(Partition List) |
---|---|---|
Participant_1 | Pub_11 | {“Partition_1”, “Partition_2”} |
Participant_1 | Pub_12 | {“*”}(匹配所有非默认分区) |
Participant_2 | Pub_21 | {}(默认分区) |
Participant_2 | Pub_22 | {“Partition*”}(匹配以“Partition”开头的分区) |
Participant_3 | Subs_31 | {“Partition_1”} |
Participant_3 | Subs_32 | {“Partition_2”} |
Participant_3 | Subs_33 | {“Partition_3”} |
Participant_3 | Subs_34 | {}(默认分区) |
2. 最终通信矩阵(是否能通信:✓=是,✕=否)
订阅者(Participant_3) | Pub_11(P1) | Pub_12(P1) | Pub_21(P2) | Pub_22(P2) |
---|---|---|---|---|
Subs_31(Partition_1) | ✓(共享Partition_1) | ✓(Pub_12的“*”匹配Partition_1) | ✕(默认分区≠Partition_1) | ✓(Pub_22的“Partition*”匹配Partition_1) |
Subs_32(Partition_2) | ✓(共享Partition_2) | ✓(“*”匹配Partition_2) | ✕(默认分区≠Partition_2) | ✓(“Partition*”匹配Partition_2) |
Subs_33(Partition_3) | ✕(无共享分区) | ✓(“*”匹配Partition_3) | ✕(默认分区≠Partition_3) | ✓(“Partition*”匹配Partition_3) |
Subs_34(默认分区) | ✕(Pub_11无默认分区) | ✕(“*”不匹配默认分区) | ✓(共享默认分区) | ✕(Pub_22无默认分区) |
五、分区的配置方式(代码示例)
在Fast DDS中,可通过C++代码或XML配置文件设置分区,核心是配置PublisherQos
或SubscriberQos
中的partition
成员(一个字符串列表)。
1. C++代码配置
// 配置Pub_11(属于Partition_1和Partition_2)
PublisherQos pub_11_qos;
pub_11_qos.partition().push_back("Partition_1");
pub_11_qos.partition().push_back("Partition_2");// 配置Pub_12(属于“*”分区)
PublisherQos pub_12_qos;
pub_12_qos.partition().push_back("*");// 配置Pub_21(无显式分区,默认分区)
PublisherQos pub_21_qos; // 无需添加任何分区名称// 配置Subs_31(属于Partition_1)
SubscriberQos subs_31_qos;
subs_31_qos.partition().push_back("Partition_1");
2. XML配置(更适合批量管理)
<?xml version="1.0" encoding="UTF-8" ?>
<profiles xmlns="http://www.eprosima.com"><!-- 配置Pub_11 --><data_writer profile_name="pub_11"><qos><partition><names><name>Partition_1</name><name>Partition_2</name></names></partition></qos></data_writer><!-- 配置Pub_12 --><data_writer profile_name="pub_12"><qos><partition><names><name>*</name></names></partition></qos></data_writer><!-- 配置Subs_31 --><data_reader profile_name="subs_31"><qos><partition><names><name>Partition_1</name></names></partition></qos></data_reader>
</profiles>
总结
DDS中的Partition本质是**“动态、多归属、基于通配符匹配的逻辑通信分组”**,其核心价值是:
- 在域和主题之外,提供更灵活的通信隔离与筛选;
- 支持动态修改,降低资源开销;
- 允许一个端点关联多个分区,减少网络负载;
- 通过通配符简化多分区配置,适配复杂场景(如按业务模块、设备类型分组)。
理解Partition的关键是记住:通信的前提是“至少一个共同分区”,且分区与端点绑定、与数据更新无关。