一文读懂openguass dcf网络模块
0. mec概要
通信模块主要是基于MEC实现(Message Exchange Component),提供整个DCF组件实例间通信能力,以及异步事件处理框架。主要功能有:可扩展的多种通信协议,单播、广播、环回的发送接口,消息异步处理的框架,支持多channel机制和多优先级队列,支持压缩和批量发送等mec主要通过channel来进行通信,节点之间可能存在多个channel通道。

channel通过队列进行消息的收发,消息收发支持批量收发。channel内部采用pipe通信,pipe又分为高优先级和低优先级。每一个pipe内部有两条tcp链路,一条链路专门用于发送,一条链路专门用于接收。
消息收发流程如下图所示(此处队列只画出了一个,实际有多个队列,默认16个):

mec消息处理采用多线程加队列来实现,队列的访问采用锁加信号量来避免多线程冲突,mq和agent均采用这种模式来实现。
mec网络通信采用epoll I/O多路复用来实现,并使用reactor模型来实现服务端监听机制。其架构与下图(图片来自https://blog.csdn.net/u013256816/article/details/115388239)类似,采用主从reactor多线程模型只不过在mec中将mainreactor称作listener(acceptor)。

mec使用tcp监听链路来接收消息,消息接收采用reactor模型实现,最终交给agent来执行。实际流程与上图的reactor主从模型类似,消息接收执行流程如下:

mec的消息处理主要依靠如下四个组件来完成,而channel主要是用来传输,因而在下图中未体现。
mec重点由五大组件组成,分别是:
-
listener(acceptor)
listener负责监听和接受客户端的连接,充当epoll多路复用reactor模型中的acceptor角色。
-
reactor
负责监听acceptor建立好连接后添加过来的socket,当有事件到来时会将对应的socket绑定到agent运行。
-
agent
agent是一个线程池,负责执行channel中pipe的job。
-
queue
队列用于存放接收和发送的消息。
-
channel
channel是实际的通信通道,其内部由pipe来进行通信,pipe分为高优先级pipe和低优先级pipe。pipe在socket层面又分为发送pipe和接收pipe。节点之间就是通过channel来进行收发包,发送时通过发送pipe发送,接收时通过接收pipe来接收。
mec运行初始化流程如下:
mec模块的网络通信采用epoll I/O多路复用的reactor模型,同时结合线程池和代理池来实现。reactor负责消息的通知,分为高优先级reactor和低优先级reactor。内部通信采用channel的概念,channel内部采用pipe来进行连接通信。每一个代理运行在一个线程上,每一个pipe会附着到一个代理上运行。acceptor接受新的连接后,会创建channel,并将channel添加到reactor池中,reactor负责监听I/O事件的到来。
每一个acceptor接受到新的客户端连接请求后,接受连接并初始化pipe,同时将pipe添加到reactor中。reactor会监听每个pipe是否有事件到来,有事件到来时,reactor会把pipe附着到一个agent上运行。
每一个节点都有一个全局的静态mec_instance_t,用于保存mec相关的信息。
static mec_instance_t *g_mec = NULL;
/// @brief 消息交换实例
typedef struct st_mec_instance {
mec_profile_t profile; ///< mec主要信息
mq_context_t send_mq; ///< 发送队列
mq_context_t recv_mq; ///< 接收队列
mec_context_t mec_ctx; ///< mec上下文
fragment_ctx_t fragment_ctx; ///< 分片上下文
thread_t daemon_thread;///< 守护线程id
reactor_pool_t reactor_pool[PRIV_CEIL]; ///< epoll反应堆数组
agent_pool_t agent_pool[PRIV_CEIL]; ///< 代理池
ssl_ctx_t *ssl_acceptor_fd; ///< ssl接受器
ssl_ctx_t *ssl_connector_fd;///< ssl连接器
} mec_instance_t;
1. compress
压缩模块主要是对传输数据进行压缩,以及对接收到的数据进行解压缩,以提高网络传输数量。当前支持zstd压缩和lz4压缩。
2. mec
mec是Message Exchange Component消息交换组件的简称。主要通过mec.h对外提供使用接口。
2.1 agent
agent实际上是线程池模式,利用互斥锁和信号量加队列来实现。一旦有新的pipe到来的时候,就会唤醒一个线程去执行。如果没有空闲的agent,就会新创建一个agent。也就是说,agent是动态创建的,只有agent处理不过来时,才会创建新的agent。
所有新加入的pipe会先加入到队列里,每个线程去队列里取出一个pipe来处理。
agent与reactor对应,也分为高优先级agent和低优先级agent,并与reactor相对应。
2.1.1 初始化agent
初始化agent主要是初始化互斥锁和信号量。
evnt->status = CM_FALSE;
if (pthread_condattr_init(&evnt->attr) != 0) {
(void)pthread_cond_destroy(&evnt->cond);
return CM_ERROR;
}
if (pthread_mutex_init(&evnt->lock, 0) != 0) {
/// 初始化锁
(void)pthread_cond_destroy(&evnt->cond);
return CM_ERROR;
}
if (pthread_condattr_setclock(&evnt->attr, CLOCK_MONOTONIC) != 0) {
(void)pthread_cond_destroy(&evnt->cond);
return CM_ERROR;
}
if (pthread_cond_init(&evnt->cond, &evnt->attr) != 0) {
/// 初始化信号量
(void)pthread_cond_destroy(&evnt->cond);
return CM_ERROR;
}
2.1.2 agent执行
当reactor监听到有pipe就绪时,会将该pipe附着到agent上执行。
- 已有空闲agent
此时先看idle_agents是否有空闲的agent,有的话直接出队一个agent,并把pipe绑定到这个agent上执行,设置pipe是发送还是接收,激活pipe。
- 没有空闲agent
如果之前还没有创建过agent或者没有空闲的agent,就需要新建一个agent。
创建一个agent也是创建一个线程,这个线程会不停的空转或者执行pipe。创建时会用cm_event_init初始化锁和信号量,然后会在线程中用cm_event_timedwait等待信号量唤醒agent线程。
在创建pipe的时候会初始化send_mode和recv_mode的job,当线程被唤醒就会执行对应的job。
当调用attach_agent时,就意味着有一个pipe要放到agent里来运行。若在attach_agent成功后,同时调用cm_event_notify,则会发送信号给agent线程,表示有任务可以执行,此时会唤醒线程并执行pipe的job。
在网络模块中有两个地方会将pipe attach到agent,分别是客户端在connect成功后和reactor接收到事件之后。
-
客户端connect成功
if (attach_agent(pipe, get_mec_agent(pipe->priv), SEND_MODE, &agent) != CM_SUCCESS) { LOG_RUN_ERR("[MEC]attached agent failed inst [%u], channel id [%u], priv [%d]", MEC_INSTANCE_ID(pipe->channel->id), MEC_CHANNEL_ID(pipe->channel->id), pipe->priv); cm_thread_unlock(&pipe->send_lock); return CM_ERROR; }此时是将SEND_MODE模式的pipe attach到agent上执行,attach到agent成功后调用cm_event_notify通知agent有任务可以处理。此时流程进入agent_entry的循环里并执行加入的pipe,
pipe->attach[agent->mode].job((void *)pipe, &is_continue);agent收到信号后就会执行job,这里加入的pipe的mode是SEND_MODE,pipe的job在创建channel时赋值,SEND_MODE对应的job是mec_proc_send_pipe。
此时对应的是客户端行为,在连接时客户端是主动发送消息的一方。
-
reactor接收到事件
status_t status = attach_agent(pipe, reactor->agent_pool, RECV_MODE, &agent);当acceptor在建立好tcp连接并初始化好pipe后就会将pipe交给reactor进行处理,reactor会适用epoll_wait监听pipe的socket是否有事件到来。当有socket准备就绪时,就将其对应的pipe attach到agent上去执行,此时的mode为RECV_MODE。
agent收到信号后就会执行job,这里加入的pipe的mode是RECV_MODE,pipe的job在创建channel时赋值,RECV_MODE对应的job是mec_proc_recv_pipe。
2.2 channel
节点之间通过channel来通信,两个节点之间可以存在多个channel,channel个数可以通过配置参数配置。
channel是对pipe的封装,每个channel有它自己的id标识。
一个channel有两个pipe,一个高优先级,一个低优先级。

/// @brief channel
typedef struct st_mec_channel {
uint32 id; ///< id
atomic32_t serial_no;
mec_pipe_t pipe[PRIV_CEIL]; ///< pipe数组,高优先级和低优先级
} mec_channel_t;
创建channel时会根据实际的实例个数(节点个数)创建。
channel存放在mec上下文里,用一个二维数组来存放。其中一维是nodeid,二维是channel索引。比如三个节点的集群,若每个节点之间有三个channel,则有9个channel(实际使用的没有那么多,自己不会连自己的channel),因为在分布式集群中,各节点互相之间都需要通信。
typedef struct st_mec_context {
mec_lsnr_t lsnr;
mec_channel_t **channels; ///< channel二维数组,一维是node_id,二维是channel索引
bool8 is_connect[CM_MAX_NODE_COUNT][MEC_MAX_CHANNEL_NUM]; ///< 表示二维数组channels的每个channel是否连接过
mec_cb_t cb_processer[MEC_CMD_CEIL];
shutdown_phase_t phase;
} mec_context_t;

1361

被折叠的 条评论
为什么被折叠?



