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;以及它们调用的时机。