数据库修仙元婴篇三——一文读懂openguass dcf网络模块

一文读懂openguass dcf网络模块

0. mec概要

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

mec通信

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
acceptor
reactor
agent
queue
acceptor reactor agent queue acceptor接受一个连接后 会将socket绑定到一个pipe上并交给reactor监听 reactor使用epoll检测到socket IO事件时, 将pipe attach到agent上执行, 并发送信号通知agent agent执行job(send或者recv), 会将消息放到队列里 agent线程会执行pipe的job, 执行完成后将pipe deattach agent, reactor会重新监听pipe的socket queue执行线程会不停的取出消息去处理 (即应用层的数据处理其实是在队列线程里执行的) acceptor reactor agent queue

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
channel
reactor
agent
listener
queue

mec运行初始化流程如下:

初始化内存分配系统
初始化mec配置
初始化reactor
创建agent
创建reactor
初始化加密
初始化消息队列
初始化channel
初始化分片
启动tcp监听
连接其他节点,实际是连接发送pipe
运行mec守护线程
mec_init
init_buddy_pool
init_mec_profile
mec_init_reactor
agent_create_pool
reactor_create_pool
mec_init_core
mec_init_core2
mec_init_ssl
mec_init_mq
mec_init_channels
fragment_ctx_init
mec_start_lsnr
mec_connect_by_profile
mec_daemon_proc

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。

agent线程循环函数
等待信号唤醒线程
执行pipe的job
attach_agent
try_attach_agent
try_create_agent
create_agent
start_agent
agent_entry
try_process_multi_channels
cm_event_timedwait
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;
2.2.1 初始化channel
初始化 send job and recv job
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我在数据库世界里修仙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值