3.5.1. Topics, keys and instances

按照Topic的定义,一个Topic只和一个数据类型绑定,因此在该Topic上产生的每一个数据样本(sample)都可以被视为对该数据类型的实例的信息更新,在同一个Topic中,是可以存在同一个数据类型的不同数据实例的,在该Topic上收到的数据样本会被用于更新该Topic绑定的数据类型的某一个数据实例。因此,一个Topic可以识别其绑定数据类型的数据,从单一数据实例到整个数据集合,这个关系如下:

相同数据类型的不同的数据实例聚集在同一个topic中,这些数据实例可以通过其key来进行识别和区分,key可以包含一个或者多个数据成员(在数据类型中)。key的描述(包含哪些数据成员)必须要向中间件注册(这样,中间件收到sample才可以分别出是对应哪个instance的)。中间件实用key的规则很简单:拥有相同key的数据样本(sample)属于同一个数据实例(instance),反之则属于不同的数据实例。

如果这个topic绑定的数据类型没有指定key成员,那么,这个topic上的每一个数据样本自身就是一个独立的数据实例。

3.5.1.1 Instance advantages

原文所描述的优势完全看不懂,不知道使用数据实例和DataWriter, DataReader以及topic有什么关系?下面是原文:

The advantage of using instances instead of creating a new DataWriter, DataReader, and Topic is that the corresponding entity is already created and discovered. Consequently, there is less memory usage, and no new discovery (with the related metatraffic involved as explained in Discovery) is necessary. Another advantage is that several QoS are applied per topic instance; e.g. the HistoryQosPolicy is kept for each instance in the DataWriter. Thus, instances could be tuned to a wide range of applications.

3.5.1.2. Instance lifecycle

当从DataReader读取数据时,会返回一个SampleInfo 数据。 SampleInfo提供了关于数据实例生命周期的一些额外信息(view_state, instance_state, disposed_generation_count和no_writers_generation_count)。下面这个图展示了单个数据实例的instance_state和view_state的迁移情况:

SampleInfo的结构如下:

struct SampleInfo
{
    //! indicates whether or not the corresponding data sample has already been read
    // 表示数据样本内容是否已经被读取  (READ_SAMPLE_STATE / NOT_READ_SAMPLE_STATE)
    SampleStateKind sample_state;
​
    //! indicates whether the DataReader has already seen samples for the most-current generation of the related instance.
    // 表示本样本数据是否为对应数据实例的第一包样本数据 (NEW / NOT_NEW)
    ViewStateKind view_state;
​
    //! indicates whether the instance is currently in existene or, if it has been disposed, the reason why it was disposed.
    // 表示该样本数据对应的数据实例是否存在还是已经被销毁了
    // ALIVE  实例仍然存货
    // NOT_ALIVE_DISPOSED   表示产生该实例的Writer已经销毁该实例
    // NOT_ALIVE_NO_WRITERS  表示产生该实例的Writer已经不存在了
    InstanceStateKind instance_state;
​
    //! number of times the instance had become alive after it was disposed
    // 表示该样本数据对应实例被销毁后收到的样本数据中instance_state=alive的次数
    int32_t disposed_generation_count;
​
    //! number of times the instance had become alive after it was disposed because no writers
    // 表示产生该样本数据对应的实例的Writer被销毁后收到的样本数据中instance_state=alive的次数
    int32_t no_writers_generation_count;
​
    //! number of samples related to the same instance that follow in the collection
    // 当前未使用该成员,常设置为0
    int32_t sample_rank;
​
    //! the generation difference between the time the sample was received, and the time the most recent sample in the collection was received.
    // 当前未使用该成员,常设置为0
    int32_t generation_rank;
​
    //! the generation difference between the time the sample was received, and the time the most recent sample was received.
    //! The most recent sample used for the calculation may or may not be in the returned collection
    // 当前未使用该成员,常设置为0
    int32_t absolute_generation_rank;
​
    //! time provided by the DataWriter when the sample was written
    // DataWriter发布该数据样本的时间点
    fastrtps::rtps::Time_t source_timestamp;
​
    //! time provided by the DataReader when the sample was added to its history
    // DDS DataReader将该数据样本添加到ReaderHistory的时间点
    fastrtps::rtps::Time_t reception_timestamp;
​
    //! identifies locally the corresponding instance
    // 表示数据实例的句柄,和本地数据实例关联
    InstanceHandle_t instance_handle;
​
    //! identifies locally the DataWriter that modified the instance
    //!
    //! Is the same InstanceHandle_t that is returned by the operation get_matched_publications on the DataReader
    // 用于表示发布该数据样本的DataWriter
    InstanceHandle_t publication_handle;
​
    //! whether the DataSample contains data or is only used to communicate of a change in the instance
    // 表示该样本数据是否包含了数据实例的变化,例如有的样本数据只是用于更新数据实例的状态,那么这种情况下,valid_data就是false
    bool valid_data;
​
    //!Sample Identity (Extension for RPC)
    // 当DDS被用作RPC通信时才会使用到该成员(用在REQUEST消息中,包含了发出REQUEST消息的DataWriter的GUID和REQUEST消息的序列号)
    fastrtps::rtps::SampleIdentity sample_identity;
​
    //!Related Sample Identity (Extension for RPC)
    // 当DDS被用作RPC通信时才会使用到该成员(用在RESPONSE消息中,内容和对应的REQUEST消息中的sample_identity一样)
    fastrtps::rtps::SampleIdentity related_sample_identity;
​
};

3.5.1.3. Practical applications

这个章节提供了一些范例用于理解DDS 数据实例的使用。

3.5.1.3.1. Commercial flights tracking

空域和空中交通管制是由空中交通管制中心来管理的,空中交通管制中心负责管理空中交通,预防飞行器碰撞以及提供空域信息。在这个场景下,每个空中交通管制中心负责指定的空域,并且提供该空域的信息给到空域交通管理系统,该系统负责统一收到的空域信息。

当空中交通管制中心检测到有航班进入器管辖空域时,会跟踪该航班的飞行信息并且通知给空域交通管理系统,这个信息流的传输可以使用DDS来完成,可以创建一个Topic,在这个Topic上可以发布航班的位置信息。在这个场景下,空中交通管制中心会创建对应的Topic(如果不存在的话),DataReader以获取航班的位置信息,这个会消耗一定的内存,并且要求支持服务发现的元数据通信。另一种方式是使用Topic上的实例数据将航班信息从空中交通管制中心传输到空域交通管理系统,该Topic上的数据实例使用航班名称以及航班编号(例如 IBERIA 1234)来唯一标识,这个表示就是该Topic上数据实例的Key。该Topic上传输的是每时每刻每个航班的位置信息,下面的IDL文件定义了这个数据模型:

struct FlightPosition
{
    // Unique ID: airline name  航班名称   KEY
    @key string<256> airline_name;
​
    // Unique ID: flight number   航班编号  KEY
    @key short flight_number;
​
    // Coordinates   经纬度和海拔
    double latitude;
    double longitude;
    double altitude;
};

一旦一个新的航班被空中交通管制中心检测到,,与该航班关联的一个数据实例就会被注册到空域交通管理系统中:

// Create data sample
FlightPosition first_flight_position;
​
// Specify th flight instance
first_flight_position.airline_name("IBERIA");
first_flight_position.flight_number(1234);
​
// Register instance
eprosima::fastdds::rtps::InstanceHandle_t first_flight_handle = data_writer->register_instance(&first_flight_position);

register_instance() 返回一个 InstanceHandle_t 对象,这是个句柄对象,用于表示一个数据实例。该句柄对象在后续的操作中(write, dispose, unregister_instance)中会被使用到。返回的句柄对象包含了Key的Hash值,因此不需要从数据样本中重新计算。而在下面这种场景下,当我们需要手动将数据实例的句柄和数据实例关联起来:

// Update position value received from the plane
first_flight_position.latitude(39.08);
first_flight_position.longitude(-84.21);
first_flight_position.altitude(1500);
​
// Write sample to the instance
// 因为这个case的情况是建立在没有注册过first_flight_position,因此,write的时候,需要通过制定实例句柄将更新手动绑定到一个数据实例上
data_writer->write(&first_flight_position, first_flight_handle);

另一方面,用户的应用可以通过NIL空数据实例句柄来直接调用DataWriter实例的函数接口,在这种场景下,数据实例句柄在会在每次该实例的数据样本被操作(write, dispose等)时重新计算。

// New data sample
FlightPosotion second_flight_position;
​
// New instance
second_flight_position.airline_name("RYANAIR");
second_flight_position.flight_number(4321);
​
// Update plane position
second_flight_position.latitude(40.02);
second_flight_position.longitude(-84.21);
second_flight_position.altitude(5000);
​
// Write sample directly without rigister the instance 
data_writer->write(&second_flight_position);   //因为没有register_handler,因此发送sample的时候会重新计算handle,并且更新到对应的数据实例上

DDS应用程序正确管理数据实例句柄的方式是非常重要的,否则,如果一个数据样本关联了多个数据实例,那么如果用户传错了数据实例句柄,可能导致数据样本被更新到错误的数据实例上去了。下面这个例子就展示了这种情况

data_writer->write(&second_flight_position, first_flight_handle);   // 第二个航班的数据样本被更新到第一个航班的数据实例上去了

一旦航班离开空中交通管制中心管理的空域时,空中交通管制中心应该取消该航班的数据实例的注册,取消实例的注册意味着DataWriter对于该航班的实例数据不会再有更新了,然后,空域交通管理系统会通知关注该topic的DataReader这个信息。注销该航班数据实例的注册后,代表该航班已经不在发出取消操作的DataWriter所属的空中交通管制中心管辖的空域内了。

data_writer->unregister_instance(&first_flight_position, first_flight_handle);
data_writer->unregister_instance(&second_flight_position, HANDLE_NIL);

最终,当航班降落后,就意味着航班的信息不再被任何空管中心所需要了,此时可以对该航班的数据实例进行销毁,该航班数据实例的状态就不会再变回ALIVE的状态,并且数据实例也将不复存在。通过Dispose操作,DataWriter会将这个信息转达给所有匹配的DataReaders。

data_writer->dispose(&first_flight_position, first_flight_handle);
data_writer->dispose(&second_flight_position, HANDLE_NIL);

站在空中交通管制中心的角度来看,所有的航班的位置信息都是通过同一个DataReader读取出来的,这个DataReader订阅的Topic正是航班信息发布的Topic。当DataReader读取到的数据样本后,需要首先校验valid_data ,确保收到的数据样本中是有效的,反之,这次收到的数据样本只是用来更新数据实例状态的,而不是更新数据实例内容的。数据实例的生命周期可以在接收数据样本的代码中体现出来:

if (RETCODE_OK == data_reader->take_next_sample(&data, &info))
{
    if (info.valid_data)
    {
        // Data sample has been received  //收到正常的数据样本,更新数据实例
    }
    else if (info.instance_state == NOT_ALIVE_DISPOSED_INSTANCE_STATE)
    {
        // A remote DataWriter has disposed the instance   // 暂时没有Writer会发布该数据实例的更新
    }
    else if (info.instance_state == NOT_ALIVE_NO_WRITERS_INSTANCE_STATE)
    {
        // None of the matched DataWriters are writing in the instance.
        // The instance can be safely disposed.    // 数据实例不会再被更新,并且将被销毁 
    }
}

3.5.1.3.2. Relational databases

考虑一种情况,现在空中交通管理中心想要将被跟踪的航班的位置信息保存到数据库,使用DDS实例来维护一个关系数据库是比较直接的方式。因为数据实例的KEY效果类似于关系数据库中的主键的概念,因此,空管中心只需要维护每个航班最后的位置信息即可:

Instance handle [PK]Data
1Position1
2Position2
3Position3
4Position4
5Position5

在这个场景中,每当有新的数据要本收到时,数据库中该样本关联的数据实例将使用样本中最新的位置信息进行更新。销毁数据实例意味着从数据库中移除该数据。注册数据实例/取消注册数据实例对于数据库不影响,因为数据实例仍然在那里,因为instance_stateview_state 仍然在样本数据中存在,因此仍然可以跟踪数据实例的生命周期。DataWriter对于数据库来说,就是发布新的数据实例到数据库中的。

历史数据一样可以被存放到数据库中,在这种场景下就是一个时间序列数据库,每个航班数据样本的时间点可以被使用起来存放到数据库中作为主键的一部分,这样就可以根据时间来查找某个航班在某个时刻的位置信息。

Instance handle [PK]Source Timestamp [PK]Data
11Position1
21Position2
12Position3
13Position4
22Position5

在这个时序数据库中,可以查询到某个航班的一个轨迹信息:

Instance handle [Fixed]Source TimestampData
11Position1
12Position3
13Position4

我们也可以查看某个时间点上不同航班的位置信息:

Instance handleSource Timestamp [Fixed]Data
12Position3
22Position5
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值