MySQL代理层_MySQL Proxy:底层实现篇

本文详细介绍了MySQL Proxy的底层实现,包括glib2提供的配置文件和命令行选项解析,以及Chassis的Plugin接口和线程化IO。Chassis支持多种类型的plugin,并提供网络IO线程化处理,通过设置--event-threads参数来实现线程级扩展。线程化的网络IO使得proxy能够根据CPU和网卡数量进行线性扩展,每个event-thread通过event_base_dispatch()进行事件循环,实现并发处理网络事件。
摘要由CSDN通过智能技术生成

底层实现篇(chassis)

【Configfile and Commandline Options】

glib2提供了config-file 解析和command-line option 解析功能。 其提供了将option 以相同方式暴露给调用者的方法,以及从Configfile 和Commandline获取option 的功能。

所有option的解析过程都可以分为三步:

1. 提取 command-line 上的 basic option

--help

--version

--defaults-file

2. 处理 defaults-file 文件

3. 处理其余 command-line option 并覆盖 defaults-file 文件中的相同内容

【 Plugin Interface 】

chassis 为 plugin 接口调用提供了基础结构。值得注意的是,其不是专门用于 MySQL 的,而是可以用于任何符合其接口要求的 plugin 。提供的功能包括:

解析 plugin 所在路径

对 plugin 的加载

对 plugin 进行版本检查

提供 init 和 shutdown 函数

向 plugin 暴露配置选项

基于线程的 i/o

由于 chassis 不是仅针对于 MySQL 设计的,所以其可以用于加载任何种类的 plugin ,只要该 plugin 提供了符合 chassis 要求的 init 和 shutdown 函数。

就 MySQL Proxy 本身而言,一般情况下加载的 plugin 为:

plugin-proxy

plugin-admin

【Threaded IO 】

从 MySQL Proxy 0.8 版本开始,已经添加了基于线程的 network-io 以使 proxy 能够按照可用 CPU 和网卡的数量进行线性扩展。

使能 network-threading 功能只需要在启动 proxy 时加入下面的参数:

--event-threads={2 * no-of-cores} (default: 0)

每一个 event-thread 都通过 "event_base_dispatch()" 进行 loop ,并针对 network-event 或者 time-event 执行相关函数。这些线程只具有两种状态:执行函数状态和 idle 状态。如果其处于 idle 状态,则其能够从 event-queue 中获取要进行等待的新 event ,然后将其添加到自身的等待列表中。

connection 是可以在多个 event-thread 之间“跳跃”的:因为只要是 idle 状态的 event-thread 就能够获取到 wait-for-event request - 即具体的事件 - 并进行等待,触发后执行相关代码。无论何时,只要当前 connection 需要重新等待事件(也就是之前事件所对应的操作已经完成),其就会将自身从所在线程中 unregister ,之后重新向全局 event-queue 发送 wait-for-event request 以获取新事件。

一直到 MySQL Proxy 0.8 版本,脚本代码的执行都是单线程方式:通过一个全局 mutex 来保护 plugin 的接口操作。因为 connection 或者是处于发送包的状态,或者是处于调用 plugin 函数的状态,所以网络事件将会按照并行方式被处理,仅在多个 connection 需要调用同一个 plugin 函数的时候才会无法并行。

chassis_event_thread_loop() 函数就是 event-thread 的主循环实体(其中调用 event_base_dispatch() 函数),而函数 chassis_event_threads_init_thread() 用于设置要监听的事件和对应的回调。

下面的描述的是一种典型控制流(不包含连接池的情况)

涉及到的实体:EventRequestQueue, MainThread, WorkerThread1, WorkerThread2;

--- [ label = "Accepting new connection "];

MainThread -> MainThread [ label ="network_mysqld_con_accept()"];

MainThread -> MainThread [ label ="network_mysqld_con_handle()"];

MainThread -> EventRequestQueue [ label ="Add wait-for-event request"];

WorkerThread1 

WorkerThread1 -> WorkerThread1 [ label ="event_base_dispatch()"];

...;

WorkerThread1 -> WorkerThread1 [ label ="network_mysqld_con_handle()"];

WorkerThread1 -> EventRequestQueue [ label ="Add wait-for-event request"];

WorkerThread2 

WorkerThread2 -> WorkerThread2 [ label ="event_base_dispatch()"];

...;

WorkerThread2 -> WorkerThread2 [ label ="network_mysqld_con_handle()"];

WorkerThread2 -> EventRequestQueue [ label ="Add wait-for-event request"];

...;

在上面的例子中,存在两个用于处理 event 的工作线程(设置 --event-threads=2 ),每个线程都有自己的 event_base 。以 Proxy plugin 为例,首先将 network_mysqld_con_accept() 函数设置为被监听 socket 的回调,当有新连接发生时被触发。该回调函数是注册在主线程的 event_base 上的(同时也是全局 chassis 的 event_base)。在设置了连接相关结构 network_mysqld_con 后,程序将进入到状态机处理函数 network_mysqld_con_handle() 中,此时仍然处于主线程中。

状态机将进行入起始状态:CON_STATE_INIT ,在当前代码实现中该状态是主线程所必进入的***个状态。接下来 MySQL Proxy 要做的事,要么是和 client 交互,要么是和 server 进行交互(即或者等待 socket 可读,或者主动向 backend server 建立连接),而状态机函数 network_mysqld_con_handle() 将设置等待处理事件(对应结构体为 chassis_event_op_t)。简单来说就是将 event 结构添加到异步队列中,具体讲,就是通过向之前创建的 wakeup-pipe 的写文件描述符写入一个字节,以产生一个文件描述符事件。这样就可以向所有线程通知有新事件请求需要处理。

该 pipe 的实现是 libevent 对应实现的一个翻版,其将各种事件与基于文件描述符的 event-handler 建立了对应关系,采用的轮询方式进行处理:

工作线程中的 event_base_dispatch() 函数在其监听的 fd 被触发前处于阻塞监听状态(在具体实现中是有定时唤醒机制的)。

定时器事件,信号事件等都不能直接中断 event_base_dispatch() 的运行。

上述事件均是通过 write(pipe_fd, ".", 1); 来触发 fd-event 的可读,从而通过回调来进行处理。

在文件 chassis-event-thread.c 中可以看到,通过 pipe 实现了向工作线程通知:在全局 event-queue 中有东东需要处理。从函数 chassis_event_handle() 可以看出,所有处于 idle 状态的线程都有平等机会进行事件处理,所以这些线程就能够“并行的”从全局事件队列中拉取 event ,并将其添加到自身的监听事件列表中。

通过调用 chassis_event_add() 或者 chassis_event_add_local() 函数可以将 event 添加到 event-queue 中。一般情况下,所有事件都由全局 event_base 负责处理。只有在使用 connection pool 的情况下,才会强制将与特定 server connection 对应的 events 投递到特定线程,即将当前 connection 加入到 connection pool 中的那个线程。

如果event 被投递到全局 event_base 中,那么不同的线程都可以获取这个事件,并可以对无保护的 connection pool 数据结构进行修改,可能会导致竞争冒险和崩溃。令这个内部数据结构成为具有线程安全性质是 0.9 release 版本的工作,当前只提供了最小限度的线程安全性。

典型情况是,某个线程会从 event queue 中获取 request 信息(理论上,发送 wait request 的线程很可能也是处理这个 request 的线程),并将其添加到自身以 thread-local-store 方式保存的event_base 中,并在对应 fd 有事件触发时获得通知。

该处理过程将一直持续到当前 connection 被 client 或者 server 关闭,或者发生了导致的 socket 关闭的网络错误。此后将无法处理任何新的 request 。

单独一个线程就足以处理任何添加到其 thread-local 的 event_base 上面的 event 。只有在一个新的 blocking I/O 操作发生时(一般来说也就是重新进入 event_base_dispatch() 阻塞时),event 才会在不同线程间被“跳跃着”处理,除此外没有其他例外。所以理论上讲,可能会出现一个线程处理了所有活跃的 socket 事件,而另一个线程一直处于 idle 状态。

然而,由于等待网络事件的发生的状态是常态(意思就是实际处理的速度都很快),所以(从概率上讲)活跃 connection 在所有线程中的分布必定是很均匀的,也就会减轻单个线程处理活跃 connection 的压力。

值得注意的是,尽管在下面的说明中没有具体指出,主线程当前会在 accept 状态后参与到对后续 event 的处理中。这不是一个非常理想的实现方式,因为所有 accept 动作本身就需要在主线程中完成。但从另一方面讲,这个问题暂时也没成为实际工作中的瓶颈显现出来:

涉及到的实体:Plugin, MainThread, MainThreadEventBase, EventRequestQueue, WorkerThread1, WorkerThread1EventBase, WorkerThread2, WorkerThread2EventBase;

--- [ label = "Accepting new connection "];

Plugin -> MainThread [ label ="network_mysqld_con_accept()"];

MainThread -> MainThread [ label ="network_mysqld_con_handle()"];

MainThread -> EventRequestQueue [ label ="Add wait-for-event request"];

WorkerThread1 

WorkerThread1 -> WorkerThread1EventBase [ label ="Wait for event on local event base"];

...;

WorkerThread1EventBase >> WorkerThread1 [ label ="Process event"];

WorkerThread1 -> EventRequestQueue [ label ="Add wait-for-event request"];

WorkerThread2 

WorkerThread2 -> WorkerThread2EventBase [ label ="Wait for event on local event base"];

...;

WorkerThread2EventBase >> WorkerThread2 [ label ="Process event"];

WorkerThread2 -> EventRequestQueue [ label ="Add wait-for-event request"];

...;

【编辑推荐】

【责任编辑:小林 TEL:(010)68476606】

点赞 0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值