【原创】MySQL Proxy - 底层实现篇


底层实现篇(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 。提供的功能包括:   

  1. 解析 plugin 所在路径 
  2. 对 plugin 的加载 
  3. 对 plugin 进行版本检查 
  4. 提供 init 和 shutdown 函数 
  5. 向 plugin 暴露配置选项 
  6. 基于线程的 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 时加入下面的参数:  

?


1

--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; 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

--- [ 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 <- EventRequestQueue [ label = "Retrieve Event request" ];

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

    ...;

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

      

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

      

    WorkerThread2 <- EventRequestQueue [ label = "Retrieve Event request" ];

    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 建立了对应关系,采用的轮询方式进行处理:  

  1. 工作线程中的 event_base_dispatch() 函数在其监听的 fd 被触发前处于阻塞监听状态(在具体实现中是有定时唤醒机制的)。 
  2. 定时器事件,信号事件等都不能直接中断 event_base_dispatch() 的运行。 
  3. 上述事件均是通过 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;  

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

--- [ 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 <- EventRequestQueue [ label = "Retrieve Event request" ];

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

    ...;

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

      

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

      

    WorkerThread2 <- EventRequestQueue [ label = "Retrieve Event request" ];

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

    ...;

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

      

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

    ...;

时间: 2024-10-31 04:27:56

【原创】MySQL Proxy - 底层实现篇的相关文章

【原创】MySQL Proxy - 核心篇

核心层篇(Core)          Network Core 构建于 socket 处理实现的基础之上,将 client connection 和 server connection 关联到一起.     [Connection Life Cycle]    connection 可处于下面 4 种协议基本 phase 之一:    connect authentification query disconnect        通过对 plugin 功能的定制实现,可以改变 network

原创】MySQL Proxy - 架构篇

架构篇(architecture)         MySQL Proxy 的定位是存在于 mysql client 和 mysql server 之间的一个简单的程序,能够对从其上通过的数据进行检查.转换和直接进行相应操作.  应用范围包括:  负载均衡(load balancing) 故障处理(fail over) 查询追踪(query tracking) 查询分析(query analysis) (...)        内部实现上讲,MySQL Proxy 是这样的一个协议栈:  (应该

【原创】MySQL Proxy - 内部结构

        在 MySQL Proxy 的脚本元素中有一些基本的内部结构需要知道.其中最主要的结构就是 proxy ,其提供了访问贯穿脚本中的许多公共结构的接口,例如连接列表和配置的 backend server .其他结构,例如来自客户端的包和返回的结果集等,只有在具体的脚本函数的上下文环境中才是可以访问的.  下表中描述了 MySQL proxy 脚本元素的公共属性.  Attribute Description connection A structure containing the

【原创】MySQL Proxy中socketpair的使用

      学习 MySQL Proxy 0.8.3 的源码后可知,其全部事件处理线程均对全局 socketpair 的读端进行了监听,以实现通知管道的功能:threads->event_notify_fds[0] .  ? 1 2 3 4 5 6 7 8 9 10 11 12 13 int chassis_event_threads_init_thread(chassis_event_threads_t *threads, chassis_event_thread_t *event_threa

【原创】MySQL Proxy - query注入动作中的脚本序列

    下图展示了一个如何使用 proxy 将客户端发送过来的 query 注入到 query 队列的例子.因为 proxy 位于客户端和 MySQL 服务器之间,所以经由 proxy 发送到服务器,以及由 proxy 最终返回给客户端的信息,不需要做到完全匹配或者关联.一旦客户端连接到了 proxy ,下图中展现的由客户端发送每一个单独的 query 引起的命令序列将会发生.           当客户端向 proxy 提交了一个 query 的时候,proxy 内部的 read_query(

【原创】MySQL Proxy - 脚本

       你可以通过使用嵌入式 Lua 脚本语言对 MySQL Proxy 的行为进行控制,以使得其能够对发送给 MySQL 服务器的 query 和 response 进行操纵.  下图展现了 MySQL Proxy 中使用的类的总览.           在 MySQL Proxy 和服务器之间的主要交互功能是通过 Lua 脚本定义的一个或者多个函数.根据客户端与一个或者多个 backend MySQL servers 之间通信序列中不同的事件和操作,定义如下一些关键函数:  conne

【原创】MySQL Proxy - 使用

       存在许多种使用 MySQL Proxy 的不同方法.采用最基本用法,你可以允许 MySQL Proxy 将来自客户端的 query 透传到后端的一个服务器.如果打算让 MySQL Proxy 在这种模式下工作,你只需要在命令行上指定 proxy 打算连接的 backend server 的信息:  ? 1 shell> mysql-proxy --proxy-backend-addresses=sakila:3306        如果你指定了多个后端 MySQL 服务器,prox

【原创】MySQL Proxy - 概况

       MySQL Proxy 是一种在网络上使用 MySQL 网络协议进行通信的应用,提供了一或多个 MySQL server 与一或多个 MySQL client 之间的通信功能.由于 MySQL Proxy 使用的是 MySQL 网络协议,故其可以在不做任何修改的情况下,配合任何符合该协议的且与 MySQL 兼容的客户端一起使用.这其中也包括 MySQL 的命令行客户端,任何使用了 MySQL 客户端库的客户端,以及任何支持 MySQL 网络协议的连接器(connector).   

【原创】MySQL Proxy - 命令行选项

       直接从命令行上启动 MySQL Proxy :  ? 1 shell> mysql-proxy        在大多数情况下,你至少应该确定 MySQL Proxy 应该将 query 转发到哪个 backend MySQL server ,即需要指定其 host 名或地址,以及端口号信息.         可以在命令行中直接指定选项,也可以在配置文件中指定,同时使用 --defaults-file 命令行参数指定配置文件的位置.         如果你采用了配置文件的方式,其格式