Mysql proxy体系结构:多线程+libevent+glib2
从前面的内容我们已经确认了accept过程是由主线程完成的,并且它创建了一个con->client的与client连接的event;并且它接着创建一个con->server打算与后台mysql server进行连接。此时如果该fd不可写,则会在proxy_connect_server返回NETWORK_SOCKET_ERROR_RETRY,然后调用WAIT_FOR_EVENT(con->server, EV_WRITE, NULL);该宏完成一个非常重要的作用,部分代码如下:
#define WAIT_FOR_EVENT(ev_struct, ev_type, timeout) \
event_set(&(ev_struct->event), ev_struct->fd, ev_type, network_mysqld_con_handle, user_data); \
chassis_event_add(srv, &(ev_struct->event));
case CON_STATE_CONNECT_SERVER:
switch ((retval = plugin_call(srv, con, con->state))) {
case NETWORK_SOCKET_SUCCESS:
/**
* hmm, if this is success and we have something in the clients send-queue
* we just send it out ... who needs a server ? */
if ((con->client != NULL && con->client->send_queue->chunks->length > 0) && con->server == NULL) {
/* we want to send something to the client */
con->state = CON_STATE_SEND_HANDSHAKE;
} else {
g_assert(con->server);
}
break;
case NETWORK_SOCKET_ERROR_RETRY:
if (con->server) {
/**
* we have a server connection waiting to begin writable
*/
WAIT_FOR_EVENT(con->server, EV_WRITE, NULL);
NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::connect_server");
return;
} else {
/* try to get a connection to another backend,
*
* setting ostate = CON_STATE_INIT is a hack to make sure
* the loop is coming back to this function again */
ostate = CON_STATE_INIT;
}
break;
…
我们可以看到WAIT_FOR_EVENT修改了它之前的event事件,设置回调函数为network_mysqld_con_handle,然后调用chassis_event_add
void chassis_event_add(chassis *chas, struct event *ev) {
chassis_event_op_t *op = chassis_event_op_new();
op->type = CHASSIS_EVENT_OP_ADD;
op->ev = ev;
g_async_queue_push(chas->threads->event_queue, op);
send(chas->threads->event_notify_fds[1], C("."), 0); /* ping the event handler */
}
可以看到这里将刚才修改的event通过glib2的g_async_queue_push接口push到chas->threads->event_queue这个队列里,然后向chas->threads->event_notify_fds[1]这个文件描述符写一个字符(还记得这会引起什么效果吗?——chassis_event_threads_init_thread),前面我们在初始化的时候创建了一对描述符[0]可读,[1]可写;并且可读的回调函数为:chassis_event_handle。而且所有的线程都监听该fd对应的事件,所以当当前线程send(chas->threads->event_notify_fds[1], C("."), 0)时,显然将唤醒所有其它线程(包括自己),并进入chassis_event_handle,现在我们也可以猜到该回调函数就要完成从chas->threads->event_queue取出刚才push的event,然后由它(现在运行的线程)来监听该event,这样下一刻当这个event可用时,将由该线程调用network_mysqld_con_handle进行当前状态的处理。代码如下:
void chassis_event_handle(int G_GNUC_UNUSED event_fd, short G_GNUC_UNUSED events, void *user_data) {
chassis_event_thread_t *event_thread = user_data;
struct event_base *event_base = event_thread->event_base;
chassis *chas = event_thread->chas;
chassis_event_op_t *op;
char ping[1024];
guint received = 0;
gssize removed;
while ((op = g_async_queue_try_pop(chas->threads->event_queue))) {
chassis_event_op_apply(op, event_base);
chassis_event_op_free(op);
received++;
}
/* the pipe has one . per event, remove as many as we received */
while (received > 0 &&
(removed = recv(event_thread->notify_fd, ping, MIN(received, sizeof(ping)), 0)) > 0) {
received -= removed;
}
}
这里看起来有点像“惊群”的现象,但其实这是proxy故意这样的,因为当有多个client con并发的时候,此时会有多个不同的con的不同状态,它们分别等待进入各自的下一个状态,此时惊群就可以使得所有的线程分别去取当前队列的第一个event,也就是说这样可能使每个线程都同时去处理不同的con。【有点儿晕了,其实就是想让每个线程都跑起来(有说等于没说)】,整个mysql proxy的框架及主流程如下图所示:
图2 mysql proxy体系结构
从图2,我们可以看到mysql proxy的大致启动流程,以及系统中最重要的三个回调函数:network_mysqld_con_accept、chassis_event_handle、network_mysqld_con_handle;以及它们调用的时机。