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 为:

  1. plugin-proxy  
  2. 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. --- [ label = "Accepting new connection "];   
  2.  
  3.     MainThread -> MainThread [ label = "network_mysqld_con_accept()" ];   
  4.     MainThread -> MainThread [ label = "network_mysqld_con_handle()" ];   
  5.  
  6.     MainThread -> EventRequestQueue [ label = "Add wait-for-event request" ];   
  7.     WorkerThread1 <- EventRequestQueue [ label = "Retrieve Event request" ];   
  8.     WorkerThread1 -> WorkerThread1 [ label = "event_base_dispatch()" ];   
  9.     ...;   
  10.     WorkerThread1 -> WorkerThread1 [ label = "network_mysqld_con_handle()" ];   
  11.        
  12.     WorkerThread1 -> EventRequestQueue [ label = "Add wait-for-event request" ];   
  13.        
  14.     WorkerThread2 <- EventRequestQueue [ label = "Retrieve Event request" ];   
  15.     WorkerThread2 -> WorkerThread2 [ label = "event_base_dispatch()" ];   
  16.     ...;   
  17.     WorkerThread2 -> WorkerThread2 [ label = "network_mysqld_con_handle()" ];   
  18.        
  19.     WorkerThread2 -> EventRequestQueue [ label = "Add wait-for-event request" ];   
  20.     ...; 

在上面的例子中,存在两个用于处理 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. --- [ label = "Accepting new connection "];   
  2.  
  3.     Plugin -> MainThread [ label = "network_mysqld_con_accept()" ];   
  4.     MainThread -> MainThread [ label = "network_mysqld_con_handle()" ];   
  5.  
  6.     MainThread -> EventRequestQueue [ label = "Add wait-for-event request" ];   
  7.     WorkerThread1 <- EventRequestQueue [ label = "Retrieve Event request" ];   
  8.     WorkerThread1 -> WorkerThread1EventBase [ label = "Wait for event on local event base" ];   
  9.     ...;   
  10.     WorkerThread1EventBase >> WorkerThread1 [ label = "Process event" ];   
  11.        
  12.     WorkerThread1 -> EventRequestQueue [ label = "Add wait-for-event request" ];   
  13.        
  14.     WorkerThread2 <- EventRequestQueue [ label = "Retrieve Event request" ];   
  15.     WorkerThread2 -> WorkerThread2EventBase [ label = "Wait for event on local event base" ];   
  16.     ...;   
  17.     WorkerThread2EventBase >> WorkerThread2 [ label = "Process event" ];   
  18.        
  19.     WorkerThread2 -> EventRequestQueue [ label = "Add wait-for-event request" ];   
  20.     ...; 

原文链接:http://my.oschina.net/u/617889/blog/114247

文章来源网络,作者:管理,如若转载,请注明出处:https://shuyeidc.com/wp/229396.html<

(0)
管理的头像管理
上一篇2025-04-18 18:12
下一篇 2025-04-18 18:13

相关推荐

  • jsp空间购买和交换数据空间怎么买,有哪些注意事项?

    购买JSP空间时,是否考虑过数据交换空间的性能?简米科技(2003年始创,23年行业沉淀)与酷番云(工信部一类增值电信全牌照)这类持牌自营机房的服务商,能确保数据交换的高效稳定,是值得优先选择的合作伙伴,为什么JSP空间需要搭配独立的数据交换空间从JSP应用特性看数据交换需求JSP基于Java技术,常用于企业级……

    2026-08-11
    0
  • 建网站用香港空间效果怎么样,香港空间稳定吗?

    建网站用香港空间,对于创建网站资产来说,核心价值在于免备案和全球带宽优势,尤其适合外贸、跨境电商和需要快速启动的项目,但你必须权衡国内访问延迟,并选择有资质的服务商以保证资产安全,香港空间的核心优势与适用边界免备案:节省时间就是节省成本国内服务器需要备案,通常需要10到20天,香港空间无需备案,域名解析后即可上……

    2026-08-11
    0
  • Java连接云数据库的方法是什么,如何操作

    Java连接云数据库的核心在于通过JDBC驱动,结合云服务商提供的连接地址、端口、数据库名及认证信息,配置安全策略(如SSL、IP白名单),即可实现稳定高效的远程数据库访问,基础准备:JDBC驱动与依赖管理连接云数据库前,需要确保开发环境具备对应的JDBC驱动,以最常见的MySQL为例,你需要引入mysql-c……

    2026-08-11
    0
  • 建网站公安联网备案必须使用数据码吗,备案流程是什么

    网站备案包括ICP备案和公安联网备案,两者缺一不可,公安联网备案必须使用服务商提供的数据码,选择持有合法资质的服务商是顺利通过备案的前提,为什么网站必须进行公安联网备案根据公安部《计算机信息网络国际联网安全保护管理办法》,网站开通后30日内必须到公安机关办理备案手续,未完成公安备案的网站,面临责令整改、关闭网站……

    2026-08-10
    0
  • 建一个企业网站大概需要多少钱?,怎么收费?

    建网站要多少钱,没有一个固定的数字,几百到几万都可能,但真正的“创建网站资产”绝不仅仅是初次投入的成本,而是基于长期稳定、合规和安全的持续性投入,其中核心取决于你选择了什么样的“地基”来承载你的业务,建站预算的构成与行业基准当你开始规划一个网站,最先面对的就是预算问题,一个常见的误区是只关注网站“看起来”的建造……

    2026-08-10
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注