OceanBase 并行执行的消息处理框架是很有意思的,里面用到了不少面向对象编程思想,值得分析。 DTL 从宏观上看可以分为三大部分:
- DTL 消息发送
- DTL 消息缓存
- DTL 消息处理
本文介绍 DTL 消息处理。
核心组件
DTL 消息缓冲区
DTL 消息缓冲区是 DTL 在网络上收发数据的基本单位。
数据结构:
ObDtlLinkedBuffer
DTL 消息
DTL 消息是业务消息的基本单位,例如行消息、SQC执行完成消息、Datahub Barrier消息等等。
数据结构:
ObBarrierPieceMsg
、ObDynamicSamplePieceMsg
等等
通用 DTL 消息 Processor
通用 Processor 是 DTL 框架层的一个消息分发器,负责从 DTL 消息缓冲区中读出一个 DTL 消息,从消息头识别出消息类型,并调用对应的 DTL 消息 Processor 对象 来进一步处理该消息。
数据结构:
ObDtlChannelLoopProc
DTL 消息 Processor
它负责处理特定类型的 DTL 消息。有多少类消息,就有多少类 Proc 对象。Proc 对象具备两个能力:
- 反序列化 DTL 消息
- 处理 DTL 消息
数据结构:
ObPxFinishSqcResultP
,ObPxInitSqcResultP
,ObDynamicSamplePieceMsgP
等等
DTL Channel
DTL Channel 是点对点的传输通道的抽象结构,DTL 消息缓冲区就是通过 DTL Channel 在机器之间传输。DTL Channel 具备如下功能:
- 从 DTL 网络或DTL Cache 中读出 DTL 消息缓冲区
- 调用通用 DTL 消息 Processor 来处理 DTL 消息缓冲区
数据结构:
ObDtlBasicChannel
,ObDtlLocalChannel
,ObDtlRpcChannel
Loop 对象
Loop 负责驱动整个消息循环。具体地,它会做如下事情:
- Loop 上会注册所有 DTL 消息 Processor 到一个 map 上,并将 map 设置到
通用 DTL 消息 Processor
上。 - 根据用户指示,Loop 在指定 DTL Channel 接收
DTL 消息缓冲区
,并驱动 DTL Channel 处理该消息。DTL Channel
收到DTL 消息缓冲区
后,会调用通用 DTL 消息 Processor
来处理该缓冲区。通用 DTL 消息 Processor
根据缓冲区中读出的 header 信息将缓冲区分发给DTL 消息 Processor
做进一步处理。DTL 消息 Processor
会对DTL 消息
做反序列化、并调用业务层处理逻辑
组件关系图
从这个图中可以看出,Loop 是整个处理流程的入口。无论有多少个 DTL 消息,无论 DTL 缓冲区从网络上到来的顺序是否有并发,因为 Loop 是单线程的,PX 整个框架的消息处理顺序也是单线程的,无需处理并发问题。这个基础的设计保证了 PX 的健壮性。
反思
这个架构有何弊端?
主要问题点在新消息的添加上,每次新加一个消息,要修改多处代码,编程不便。
ObDtlChannelLoopProc
需要一个 proc_map,要求 map 中包含所有消息类型的 Proc。
class ObDtlChannelLoopProc : public ObIDtlChannelProc
{
public:
ObDtlChannelLoopProc(uint16_t &last_msg_type, Proc **proc_map)
: last_msg_type_(last_msg_type), proc_map_(proc_map)
{}
virtual int process(const ObDtlLinkedBuffer &, bool &transferred) override;
uint16_t &last_msg_type_;
Proc **proc_map_;
};
如果ObDtlChannelLoopProc
遇到一个未注册的消息类型,会报错。这导致每增加一个消息类型,就要在用到 Loop 的地方注册一遍消息 Proc。有没有一个更简单的编程方式,新消息处理无需到处修改代码?