文档章节

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

摩云飞
 摩云飞
发布于 2013/03/17 11:06
字数 2032
阅读 2722
收藏 70

底层实现篇(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 时加入下面的参数: 
--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 <- 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; 
--- [ 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" ]; 
    ...;




© 著作权归作者所有

摩云飞
粉丝 375
博文 534
码字总数 952694
作品 0
徐汇
程序员
私信 提问
加载中

评论(5)

walden
walden
谢谢哈
摩云飞
摩云飞 博主

引用来自“walden”的评论

那分布式是怎样的

可以参考这个:http://blog.csdn.net/cuidiwhere/article/details/7882244
walden
walden
那分布式是怎样的
摩云飞
摩云飞 博主

引用来自“walden”的评论

看起来很牛逼的样子 我也准备搞分布式 哈哈

分布式是王道,不过这个还真不属于分布式,这个只能算是服务器编程模型中的一种。另外,Proxy 也没那么牛逼了,要不早就有文章铺天盖地了。学习这个的目的在于启发下思路~~
walden
walden
看起来很牛逼的样子 我也准备搞分布式 哈哈
【原创】MySQL Proxy - 架构篇

架构篇(architecture) MySQL Proxy 的定位是存在于 mysql client 和 mysql server 之间的一个简单的程序,能够对从其上通过的数据进行检查、转换和直接进行相应操作。 应用范围包括: 负载...

摩云飞
2013/03/16
885
0
【原创】MySQL Proxy - 核心篇

核心层篇(Core) Network Core 构建于 socket 处理实现的基础之上,将 client connection 和 server connection 关联到一起。 【Connection Life Cycle】 connection 可处于下面 4 种协议基...

摩云飞
2013/03/19
153
0
SQL请求路由器--Amoeba

amoeba是一个以MySQL为底层数据存储,并对应用提供MySQL协议接口的proxy。它集中地响应应用的请求,依据用户事先设置的规则,将SQL请求发送到特定的数据库上执行。基于此可以实现负载均衡、读...

匿名
2008/11/13
16.1K
4
Amoeba和mysqlproxy对比

Amoeba for mysql的优劣 2014-03-23 21:42:09 分类: MySQL 转载地址:http://baike.baidu.com/link?url=RSqbHpbctiA2tDMU3UhWOH302J3-Td8eNdsCiDJSJmu2KXbHcluIDrspPUaVlJGGgI78ArIU2gIaxXT......

泡海椒
2016/01/23
217
0
【原创】服务器开发之 Daemon 和 Keepalive

由于业务开发需要,需要对数据库代理进行研究,在研究 MySQL Proxy 实现原理的过程中,对一些功能点进行了分析总结。本文主要讲解下 MySQL Proxy 的 daemon 和 keepalive 功能实现原理。 My...

摩云飞
2013/03/24
3.3K
5

没有更多内容

加载失败,请刷新页面

加载更多

Spring Cloud Alibaba 实战(二) - 关于Spring Boot你不可不知道的实情

0 相关源码 1 什么是Spring Boot 一个快速开发的脚手架 作用 快速创建独立的、生产级的基于Spring的应用程序 特性 无需部署WAR文件 提供starter简化配置 尽可能自动配置Spring以及第三方库 ...

JavaEdge
今天
7
0
TensorFlow 机器学习秘籍中文第二版(初稿)

TensorFlow 入门 介绍 TensorFlow 如何工作 声明变量和张量 使用占位符和变量 使用矩阵 声明操作符 实现激活函数 使用数据源 其他资源 TensorFlow 的方式 介绍 计算图中的操作 对嵌套操作分层...

ApacheCN_飞龙
今天
7
0
五、Java设计模式之迪米特原则

定义:一个对象应该对其他对象保持最小的了解,又叫最小知道原则 尽量降低类与类之间的耦合 优点:降低类之间的耦合 强调只和朋友交流,不和陌生人说话 朋友:出现在成员变量、方法的输入、输...

东风破2019
昨天
23
0
jvm虚拟机结构

1:jvm可操作数据类型分为原始类型和引用类型,因此存在原始值和引用值被应用在赋值,参数,返回和运算操作中,jvm希望在运行时 明确变量的类型,即编译器编译成class文件需要对变量进行类型...

xpp_ba
昨天
5
0
聊聊nacos Service的processClientBeat

序 本文主要研究一下nacos Service的processClientBeat Service.processClientBeat nacos-1.1.3/naming/src/main/java/com/alibaba/nacos/naming/core/Service.java public class Service ex......

go4it
昨天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部