memcached是基于多路复用实现事件驱动的服务器,在事件处理方面,应用了master+works的线程模型。
master线程负责监听端口,当有新的连接,master线程负责接收连接,将连接加入到指定的work线程的队列,然后通过管道通知该线程处理该连接。
master线程和work线程之间通过一个管道进行信息交互。
work线程监听管道读端,当master线程发送'c',work线程从队列获取客户端连接进行监听,负责客户端所有请求的处理。
总的来说,master线程负责对listen fd的监听,work线程负责对管道fd和client fd的监听。
memcached的线程处理逻辑图如下所示:
总的来说,master和work都需要实现事件监听,master监听listen fd,work监听client fd。memcached实现事件监听是基于libevent网络库。主要的实现流程:
1)main_base = event_init();初始化事件基地
2)event_set(event, fd, event_flags, event_handler, args); 设置事件的监听的fd,事件类型,事件处理函数
3)event_base_set(event_base, event);设置事件指定的事件基地
4)event_add(event, timeval);将事件加入事件基地的监听队列
5)event_base_loop(event_base, flag); 进入事件循环
一、main函数
int main (int argc, char **argv) {
……
main_base = event_init();//初始化主线程的事件基地
……
memcached_thread_init(settings.num_threads, main_base);//创建工作线程
……
if (settings.port && server_sockets(settings.port, tcp_transport,
portnumber_file)) {
exit(EX_OSERR);//监听tcp端口
}
if (settings.udpport && server_sockets(settings.udpport, udp_transport,
portnumber_file)) {
exit(EX_OSERR);//监听udp端口
}
……
if (event_base_loop(main_base, 0) != 0) {
retval = EXIT_FAILURE;//进入主线的的事件循环
}
}
二、创建工作线程
1、工作线程结构体
typedef struct {
pthread_t thread_id; /* 线程id*/
struct event_base *base; /* 每个线程自有的事件基地 */
struct event notify_event; /* 监听与master线程的管道的事件 */
int notify_receive_fd; /* 交互管道的读端 */
int notify_send_fd; /* 交互管道的写端*/
struct thread_stats stats; /* Stats generated by this thread */
struct conn_queue *new_conn_queue; /* 主线程分发过来未处理的新连接的队列*/
cache_t *suffix_cache; /* suffix cache */
} LIBEVENT_THREAD;//每个工作线程对应的结构体
2、创建工作线程池的主要流程
1)创建LIBEVENT_THREAD数组
2)创建工作线程与主线程的通信管道
3)创建工作线程的事件基地,添加监听管道的事件
4)启动工作线程
3、创建工作线程的主函数
void memcached_thread_init(int nthreads, struct event_base *main_base) {
……//初始化工作线程——LIBEVENT_THREAD数组
threads = calloc(nthreads, sizeof(LIBEVENT_THREAD));
dispatcher_thread.base = main_base;//设置主线程的event_base
dispatcher_thread.thread_id = pthread_self();//设置主线程的线程ID
for (i = 0; i < nthreads; i++) {
……
//设置工作线程与主线通信的管道
threads[i].notify_receive_fd = fds[0];
threads[i].notify_send_fd = fds[1];
setup_thread(&threads[i]);//设置工作线程的配置
stats.reserved_fds += 5;
}//启动工作线程
for (i = 0; i < nthreads; i++) {
create_worker(worker_libevent, &threads[i]);
}
pthread_mutex_lock(&init_lock);
wait_for_thread_registration(nthreads);//等待所有work线程创建完毕
pthread_mutex_unlock(&init_lock);
}
4、设置工作线程的事件模型的函数
static void setup_thread(LIBEVENT_THREAD *me) {
me->base = event_init();//创建工作线程的event_base;
//设置与主线程管道读端的读事件
event_set(&me->notify_event, me->notify_receive_fd,
EV_READ | EV_PERSIST, thread_libevent_process, me);
event_base_set(me->base, &me->notify_event);
if (event_add(&me->notify_event, 0) == -1) {
exit(1);//添加读事件到线程的event_base
}//初始化线程的接收新连接的队列
me->new_conn_queue = malloc(sizeof(struct conn_queue));
cq_init(me->new_conn_queue);
……
}//工作线程notify_send_fd的可读事件的事件处理函数
static void thread_libevent_process(int fd, short which, void *arg) {
LIBEVENT_THREAD *me = arg;
……
if (read(fd, buf, 1) != 1)
……
switch (buf[0]) {
case 'c'://从线程的新连接队列中获取client fd进行监听
item = cq_pop(me->new_conn_queue);
if (NULL != item) {
//监听新的client fd
conn *c = conn_new(item->sfd, item->init_state, item->event_flags,
item->read_buffer_size, item->transport, me->base);
……
c->thread = me;
cqi_free(item);
}
break;
/* we were told to pause and report in */
case 'p':
register_thread_initialized();
break;
}
}
5、工作线程的逻辑函数
static void *worker_libevent(void *arg) {
LIBEVENT_THREAD *me = arg;
register_thread_initialized();
event_base_loop(me->base, 0);//进入事件循环,等待与主线程和client的通信
return NULL;
}
三、网络通信处理
1、创建socket,监听listen fd
static int server_sockets(int port, enum network_transport transport,
FILE *portnumber_file) {
if (settings.inter == NULL) {
return server_socket(settings.inter, port, transport, portnumber_file);
}
……
}
static int server_socket(const char *interface,
int port,
enum network_transport transport,
FILE *portnumber_file) {
//套接字类型设置
hints.ai_socktype = IS_UDP(transport) ? SOCK_DGRAM : SOCK_STREAM;
……
for (next= ai; next; next= next->ai_next) {
conn *listen_conn_add;
if ((sfd = new_socket(next)) == -1) {
……
continue;
}
……//设置socket的选项
if (bind(sfd, next->ai_addr, next->ai_addrlen) == -1) {
continue;
} else {
success++;
if (!IS_UDP(transport) && listen(sfd, settings.backlog) == -1) {
……
return 1;
}
……
}
if (IS_UDP(transport)) {
int c;//udp连接
for (c = 0; c < settings.num_threads_per_udp; c++) {
int per_thread_fd = c ? dup(sfd) : sfd;
dispatch_conn_new(per_thread_fd, conn_read,
EV_READ | EV_PERSIST,
UDP_READ_BUFFER_SIZE, transport);
}
} else {//主线程进行端口监听,conn的状态为conn_listening
if (!(listen_conn_add = conn_new(sfd, conn_listening,
EV_READ | EV_PERSIST, 1,
transport, main_base))) {
exit(EXIT_FAILURE);
}//监听的端口的fd对应的conn结构体
listen_conn_add->next = listen_conn;
listen_conn = listen_conn_add;
}
}
return success == 0;
}
2、监听fd和事件处理
conn_new主要是添加fd到事件基地,主线程监听listen fd和工作线程监听client fd
//添加sfd到事件基地,主要用于监听listen fd和client fd
conn *conn_new(const int sfd, enum conn_states init_state,
const int event_flags,
const int read_buffer_size, enum network_transport transport,
struct event_base *base) {
conn *c;
c = conns[sfd];
//conn结构体的属性初始化
……
//注册sfd的可读事件
event_set(&c->event, sfd, event_flags, event_handler, (void *)c);
event_base_set(base, &c->event);
c->ev_flags = event_flags;
if (event_add(&c->event, 0) == -1) {
perror("event_add");
return NULL;
}
return c;
}
事件处理函数主要是根据c->state的状态执行状态机
/*监听listen fd时,c->state为conn_listening
监听client fd时,c->states比较多样化*/
void event_handler(const int fd, const short which, void *arg) {
conn *c;
c = (conn *)arg;
……
drive_machine(c);//关于c->state的状态机处理io事件
return;
}
状态机函数,此处只展开关于conn_listening的处理,工作线程中的状态事件的处理会在后续memcached请求处理中介绍。
static void drive_machine(conn *c) {
……
while (!stop) {
switch(c->state) {
case conn_listening:
addrlen = sizeof(addr);
……
sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen);
……
if (settings.maxconns_fast &&
stats.curr_conns + stats.reserved_fds >= settings.maxconns - 1) {
……
} else {
//连接创建成功,分配连接给工作线程
dispatch_conn_new(sfd, conn_new_cmd, EV_READ | EV_PERSIST,
DATA_BUFFER_SIZE, tcp_transport);
}
stop = true;
break;
……
}
}
return;
}
主线程进行新连接的分发,选择一个工作线程,将新连接封装成CQ_ITEM添加到线程的new_conn_queue,发送‘c’给工作线程,工作线程接收到‘c’会从new_conn_queue获取CQ_ITEM,调用conn_new进行client fd的监听。
void dispatch_conn_new(int sfd, enum conn_states init_state, int event_flags,
int read_buffer_size, enum network_transport transport) {
CQ_ITEM *item = cqi_new();
char buf[1];
//选择工作线程来管理新连接
int tid = (last_thread + 1) % settings.num_threads;
LIBEVENT_THREAD *thread = threads + tid;
last_thread = tid;
item->sfd = sfd;
item->init_state = init_state;
item->event_flags = event_flags;
item->read_buffer_size = read_buffer_size;
item->transport = transport;
//将新连接加入到工作线程的新连接队列
cq_push(thread->new_conn_queue, item);
MEMCACHED_CONN_DISPATCH(sfd, thread->thread_id);
buf[0] = 'c';
//发送消息给工作线程,notify_send_fd可读调用thread_libevent_process
if (write(thread->notify_send_fd, buf, 1) != 1) {
perror("Writing to thread notify pipe");
}
}