Memcached按之前的分析可以知道,其是典型的Master-Worker线程模型,这种模型很典型,其工作模型是Master绑定端口,监听网络连接,接受网络连接之后,通过线程间通信来唤醒Worker线程,Worker线程已经连接的描述符执行读写操作,这种模型简化了整个通信模型,下面分析下这个过程。
case conn_listening: addrlen = sizeof(addr); //Master线程(main)进入状态机之后执行accept操作,这个操作也是非阻塞的。 if ((sfd = accept(c->sfd, (struct sockaddr *) &addr, &addrlen)) == -1) { //非阻塞模型,这个错误码继续等待 if (errno == EAGAIN || errno == EWOULDBLOCK) { stop = true; } //连接超载 else if (errno == EMFILE) { if (settings.verbose > 0) fprintf(stderr, "Too many open connections\n"); accept_new_conns(false); stop = true; } else { perror("accept()"); stop = true; } break; } //已经accept成功,将accept之后的描述符设置为非阻塞的 if ((flags = fcntl(sfd, F_GETFL, 0)) < 0 || fcntl(sfd, F_SETFL, flags | O_NONBLOCK) < 0) { perror("setting O_NONBLOCK"); close(sfd); break; } //判断是否超过最大连接数 if (settings.maxconns_fast && stats.curr_conns + stats.reserved_fds >= settings.maxconns - 1) { str = "ERROR Too many open connections\r\n"; res = write(sfd, str, strlen(str)); close(sfd); STATS_LOCK(); stats.rejected_conns++; STATS_UNLOCK(); } else { //直线连接分发 dispatch_conn_new(sfd, conn_new_cmd, EV_READ | EV_PERSIST, DATA_BUFFER_SIZE, tcp_transport); } stop = true; break;这个是TCP的连接建立过程,由于UDP不需要建立连接,所以直接分发给Worker线程,让Worker线程进行读写操作,而TCP在建立连接之后,也执行连接分发(和UDP的一样),下面看看dispatch_conn_new内部是如何进行链接分发的。
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;//通过round-robin算法选择一个线程 LIBEVENT_THREAD *thread = threads + tid;//thread数组存储了所有的工作线程 last_thread = tid;//缓存这次的线程编号,下次待用 item->sfd = sfd;//sfd表示accept之后的描述符 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);//投递item信息到Worker线程的工作队列中 MEMCACHED_CONN_DISPATCH(sfd, thread->thread_id); buf[0] = ‘c‘; //在Worker线程的notify_send_fd写入字符c,表示有连接 if (write(thread->notify_send_fd, buf, 1) != 1) { perror("Writing to thread notify pipe"); } }
投递到子线程的连接队列之后,同时,通过忘子线程的PIPE管道写入字符c来,下面我们看看子线程是如何处理的?
//子线程会在PIPE管道读上面建立libevent事件,事件回调函数是thread_libevent_process event_set(&me->notify_event, me->notify_receive_fd, EV_READ | EV_PERSIST, thread_libevent_process, me); static void thread_libevent_process(int fd, short which, void *arg) { LIBEVENT_THREAD *me = arg; CQ_ITEM *item; char buf[1]; if (read(fd, buf, 1) != 1)//PIPE管道读取一个字节的数据 if (settings.verbose > 0) fprintf(stderr, "Can‘t read from libevent pipe\n"); switch (buf[0]) { case ‘c‘://如果是c,则处理网络连接 item = cq_pop(me->new_conn_queue);//从连接队列读出Master线程投递的消息 if (NULL != item) { conn *c = conn_new(item->sfd, item->init_state, item->event_flags, item->read_buffer_size, item->transport, me->base);//创建连接 if (c == NULL) { if (IS_UDP(item->transport)) { fprintf(stderr, "Can‘t listen for events on UDP socket\n"); exit(1); } else { if (settings.verbose > 0) { fprintf(stderr, "Can‘t listen for events on fd %d\n", item->sfd); } close(item->sfd); } } else { c->thread = me; } cqi_free(item); } break; } }之前分析过conn_new的执行流程,conn_new里面会建立sfd的网络监听libevent事件,事件回调函数为event_handler。
event_set(&c->event, sfd, event_flags, event_handler, (void *) c); event_base_set(base, &c->event);而event_handler的执行流程最终会进入到业务处理的状态机中,关于状态机,后续分析。