目录
Data
Data目录下有如下类:
DataFusion,AllLatest,ChannelBuffer ,CacheBuffer ,DataDispatcher,DataVisitor,VisitorConfig,DataNotifier等 。
主要功能包括
-
各个msg的缓冲区,由ChannelBuffer组合CacheBuffer 实现。
-
消息融合,以及融合消息的缓存,DataFusion
-
消息分发,Reader侧接收到Writer侧的消息后,由DataDispatcher进行消息存储至指定buffer。DataDispatcher同时会唤醒Processor线程,走协程调度器的逻辑。
-
VisitorConfig用于用户自定义配置,主要包括ringBuffer大小以及channel_id
-
消息融合策略的定义AllLatest,多路消息下设定msg0为主消息,由msg0根据条件是否满足触发多路消息融合
DataFusion类
// DataFusion基类,提供fusion融合接口
template <typename M0, typename M1 = NullType, typename M2 = NullType,
typename M3 = NullType>
class DataFusion {
public:
virtual ~DataFusion() {}
virtual bool Fusion(uint64_t* index, std::shared_ptr<M0>& m0, // NOLINT
std::shared_ptr<M1>& m1, // NOLINT
std::shared_ptr<M2>& m2, // NOLINT
std::shared_ptr<M3>& m3) = 0; // NOLINT
};
// 实现DataFusion的具体Fusion方法
template <typename M0, typename M1, typename M2>
class AllLatest<M0, M1, M2, NullType> : public DataFusion<M0, M1, M2> {
using FusionDataType =
std::tuple<std::shared_ptr<M0>, std::shared_ptr<M1>, std::shared_ptr<M2>>;
public:
AllLatest(const ChannelBuffer<M0>& buffer_0,
const ChannelBuffer<M1>& buffer_1,
const ChannelBuffer<M2>& buffer_2)
: buffer_m0_(buffer_0),
buffer_m1_(buffer_1),
buffer_m2_(buffer_2),
buffer_fusion_(buffer_m0_.channel_id(),
new CacheBuffer<std::shared_ptr<FusionDataType>>(
buffer_0.Buffer()->Capacity() - uint64_t(1))) {
// 在构造函数中设置fusion_callback,该callback在buffer_m0_传入消息后触发
// 该回调将对多路消息进行校验,若能够获取多路消息的back,则将各路数据最新值构造成tuple
// 将消息放置到buffer_fusion_的缓冲区
buffer_m0_.Buffer()->SetFusionCallback(
[this](const std::shared_ptr<M0>& m0) {
std::shared_ptr<M1> m1;
std::shared_ptr<M2> m2;
if (!buffer_m1_.Latest(m1) || !buffer_m2_.Latest(m2)) {
return;
}
auto data = std::make_shared<FusionDataType>(m0, m1, m2);
std::lock_guard<std::mutex> lg(buffer_fusion_.Buffer()->Mutex());
buffer_fusion_.Buffer()->Fill(data);
});
}
// Fusion函数用于获取buffer_fusion_里对应index的缓存消息,由协程执行函数tryFetch调用
bool Fusion(uint64_t* index, std::shared_ptr<M0>& m0, std::shared_ptr<M1>& m1,
std::shared_ptr<M2>& m2) override {
std::shared_ptr<FusionDataType> fusion_data;
// 用于获取消息
if (!buffer_fusion_.Fetch(index, fusion_data)) {
return false;
}
m0 = std::get<0>(*fusion_data);
m1 = std::get<1>(*fusion_data);
m2 = std::get<2>(*fusion_data);
return true;
}
private:
// 多路消息缓冲区
ChannelBuffer<M0> buffer_m0_;
ChannelBuffer<M1> buffer_m1_;
ChannelBuffer<M2> buffer_m2_;
// 存放由各路消息均到达,满足触发条件的消息所组成消息缓冲区
ChannelBuffer<FusionDataType> buffer_fusion_;
};
ChannelBuffer类
// 消息缓冲区,组合CacheBuffer对象
template <typename T>
class ChannelBuffer {
public:
using BufferType = CacheBuffer<std::shared_ptr<T>>;
ChannelBuffer(uint64_t channel_id, BufferType* buffer)
: channel_id_(channel_id), buffer_(buffer) {}
// 根据索引获取对应的最新的那条消息
bool Fetch(uint64_t* index, std::shared_ptr<T>& m); // NOLINT
// 最新消息
bool Latest(std::shared_ptr<T>& m); // NOLINT
// 获取消息至vec中,可指定消息个数
bool FetchMulti(uint64_t fetch_size, std::vector<std::shared_ptr<T>>* vec);
uint64_t channel_id() const { return channel_id_; }
std::shared_ptr<BufferType> Buffer() const { return buffer_; }
private:
uint64_t channel_id_;
std::shared_ptr<BufferType> buffer_;
};
// ringbuffer结构,是存储消息的队列。提供添加以及获取消息的方法。
// 容量最大值可由外部配置
template <typename T>
class CacheBuffer {
}
DataDispatcher类
DataDispatcher主要用于消息的分发,收到来自pub端消息后,由GetReceiver所封装的Receiver回调触发执行。分发也就是按照channel_id将消息放入对应的缓冲区内。
template <typename MessageT>
auto ReceiverManager<MessageT>::GetReceiver(
const proto::RoleAttributes& role_attr) ->
typename std::shared_ptr<transport::Receiver<MessageT>> {
std::lock_guard<std::mutex> lock(receiver_map_mutex_);
// because multi reader for one channel will write datacache multi times,
// so reader for datacache we use map to keep one instance for per channel
const std::string& channel_name = role_attr.channel_name();
if (receiver_map_.count(channel_name) == 0) {
receiver_map_[channel_name] =
transport::Transport::Instance()->CreateReceiver<MessageT>(
role_attr, [](const std::shared_ptr<MessageT>& msg,
const transport::MessageInfo& msg_info,
const proto::RoleAttributes& reader_attr) {
(void)msg_info;
(void)reader_attr;
PerfEventCache::Instance()->AddTransportEvent(
TransPerf::DISPATCH, reader_attr.channel_id(),
msg_info.seq_num());
// 触发消息的Dispatch
data::DataDispatcher<MessageT>::Instance()->Dispatch(
reader_attr.channel_id(), msg);
PerfEventCache::Instance()->AddTransportEvent(
TransPerf::NOTIFY, reader_attr.channel_id(),
msg_info.seq_num());
});
}
return receiver_map_[channel_name];
}
由于DataDispatcher是单例模板类,表示每一个类型的消息将对应一个DataDispatcher。
template <typename T>
class DataDispatcher {
public:
using BufferVector =
std::vector<std::weak_ptr<CacheBuffer<std::shared_ptr<T>>>>;
~DataDispatcher() {}
// 添加缓冲区
void AddBuffer(const ChannelBuffer<T>& channel_buffer);
// 分发消息至对应的buffer
bool Dispatch(const uint64_t channel_id, const std::shared_ptr<T>& msg);
private:
DataNotifier* notifier_ = DataNotifier::Instance();
std::mutex buffers_map_mutex_;
// 存储channel_id和buffer的映射关系
AtomicHashMap<uint64_t, BufferVector> buffers_map_;
// 单例声明
DECLARE_SINGLETON(DataDispatcher)
};
template <typename T>
inline DataDispatcher<T>::DataDispatcher() {}
template <typename T>
void DataDispatcher<T>::AddBuffer(const ChannelBuffer<T>& channel_buffer) {
std::lock_guard<std::mutex> lock(buffers_map_mutex_);
auto buffer = channel_buffer.Buffer();
BufferVector* buffers = nullptr;
// 添加channel_buffer至buffers_map_
// 创建DataVisitor对象时调用,将多个相同channel_id而属于不同DataVisitor的存入vector
if (buffers_map_.Get(channel_buffer.channel_id(), &buffers)) {
buffers->emplace_back(buffer);
} else {
BufferVector new_buffers = {buffer};
buffers_map_.Set(channel_buffer.channel_id(), new_buffers);
}
}
template <typename T>
bool DataDispatcher<T>::Dispatch(const uint64_t channel_id,
const std::shared_ptr<T>& msg) {
BufferVector* buffers = nullptr;
if (apollo::cyber::IsShutdown()) {
return false;
}
if (buffers_map_.Get(channel_id, &buffers)) {
// 基于channel_id获取buffers,并添加对应msg至buffer
for (auto& buffer_wptr : *buffers) {
if (auto buffer = buffer_wptr.lock()) {
std::lock_guard<std::mutex> lock(buffer->Mutex());
buffer->Fill(msg);
}
}
} else {
return false;
}
return notifier_->Notify(channel_id);
}
// 这里的Notify将调用单例DataNotifier里的Notify。
// 将调用NotifyProcessor函数。会唤醒相关联的协程,后续由Processor调度
class DataNotifier {
public:
using NotifyVector = std::vector<std::shared_ptr<Notifier>>;
~DataNotifier() {}
void AddNotifier(uint64_t channel_id,
const std::shared_ptr<Notifier>& notifier);
bool Notify(const uint64_t channel_id);
private:
std::mutex notifies_map_mutex_;
// channel_id<-->NotifyProcessor回调函数
AtomicHashMap<uint64_t, NotifyVector> notifies_map_;
// 单例声明
DECLARE_SINGLETON(DataNotifier)
};
DataVisitor类
// 用于用户设置channel_id和ringbuffer最大容量
struct VisitorConfig {
VisitorConfig(uint64_t id, uint32_t size)
: channel_id(id), queue_size(size) {}
uint64_t channel_id;
uint32_t queue_size;
};
template <typename M0, typename M1 = NullType, typename M2 = NullType,
typename M3 = NullType>
class DataVisitor : public DataVisitorBase {
public:
explicit DataVisitor(const std::vector<VisitorConfig>& configs)
: buffer_m0_(configs[0].channel_id,
new BufferType<M0>(configs[0].queue_size)),
buffer_m1_(configs[1].channel_id,
new BufferType<M1>(configs[1].queue_size)),
buffer_m2_(configs[2].channel_id,
new BufferType<M2>(configs[2].queue_size)),
buffer_m3_(configs[3].channel_id,
new BufferType<M3>(configs[3].queue_size)) {
// 构造各路msg的缓冲区
DataDispatcher<M0>::Instance()->AddBuffer(buffer_m0_);
DataDispatcher<M1>::Instance()->AddBuffer(buffer_m1_);
DataDispatcher<M2>::Instance()->AddBuffer(buffer_m2_);
DataDispatcher<M3>::Instance()->AddBuffer(buffer_m3_);
// msg0路消息为主要消息,为其添加一个唤醒对象
// 只有msg0消息来了之后再进行融合等操作
data_notifier_->AddNotifier(buffer_m0_.channel_id(), notifier_);
// data_fusion存储的是满足融合条件的all msgs构成的tuple
data_fusion_ = new fusion::AllLatest<M0, M1, M2, M3>(
buffer_m0_, buffer_m1_, buffer_m2_, buffer_m3_);
}
~DataVisitor() {
if (data_fusion_) {
delete data_fusion_;
data_fusion_ = nullptr;
}
}
// 该函数由协程调度器调用。用于获取最新的各路融合消息
bool TryFetch(std::shared_ptr<M0>& m0, std::shared_ptr<M1>& m1, // NOLINT
std::shared_ptr<M2>& m2, std::shared_ptr<M3>& m3) { // NOLINT
if (data_fusion_->Fusion(&next_msg_index_, m0, m1, m2, m3)) {
next_msg_index_++;
return true;
}
return false;
}
private:
fusion::DataFusion<M0, M1, M2, M3>* data_fusion_ = nullptr;
ChannelBuffer<M0> buffer_m0_;
ChannelBuffer<M1> buffer_m1_;
ChannelBuffer<M2> buffer_m2_;
ChannelBuffer<M3> buffer_m3_;
};
总结
-
component在初始化时按照msg个数创建Reader个数,每个Reader对应一个datavistor;为每个Reader创建协程函数,该函数用于将消息放置Block中。
-
component会创建一个具有多个消息的datavisitor,包含多个消息单独的buffer以及融合buffer,其对应的协程函数为尝试获取融合消息,若满足融合条件则执行用户回调Processor。融合事件由msg0触发,满足所有消息均到达的条件后,将所有消息构造成tuple放置在data_fusion的ChannelBuffer内。
-
普通的msg到达后,由DataDispatcher进行分发,将msg存储至每个datavisitor缓冲区即ChannelBuffer内。
-
由DataNotify单例进行协程任务的唤醒。当消息到达后进行唤醒。
CyberRT所提供多路消息融合的方式有一定的局限性,融合的消息最多仅支持四路消息,同时融合算法较为单一,提供了FetchMulti方式可用于获取指定个数的消息数目,并且Fusion条件满足都是由msg0触发,用户很难构造更为灵活的“表达式”来进行触发的条件设置以及消息获取方式设置。