memcached通过epoll(也是使用libevent)进行实现的异步服务器,在其中担任主要任务的线程有两种,一个是主线程,一个是worker线程。
一、libevent
memcached使用libevent实现事件监听。在这简单介绍一下libevent的使用,一般有以下几步:
1)event_base = event_init(); 初始化事件基地。
2)event_set(event, fd, event_flags, event_handler, args); 创建事件event,fd为要监听的fd,event_flags为监听的事件类型,event_handler为事件发生后的处理函数,args为调用处理函数时传递的参数。
3)event_base_set(event_base, event); 为创建的事件event指定事件基地。
4)event_add(event, timeval); 把事件加入到事件基地进行监听
5)event_base_loop(event_base, flag); 进入事件循环,即epoll_wait。
memcached主线程和worker线程各有自己的监听队列,故有主线程和每个worker线程都有一个独立的event_base。
简单介绍了libevent,现在来看下memcached。
二、memcached线程基本结构
在memcached线程中有三种结构需要注意下:
2.1 CQ_ITEM
主要存储的是用户链接Socket的链接的基本信息,主要的作用是主线程和工作线程沟通的媒介。
1 2 3 4 5 6 7 8 9 |
typedef struct conn_queue_item CQ_ITEM; struct conn_queue_item { int sfd; //socket的fd enum conn_states init_state; //事件类型 int event_flags; //libevent的flags int read_buffer_size; //读取的buffer的大小 enum network_transport transport; CQ_ITEM *next; }; |
2.2 CQ
CQ是CQ_ITEM的集合信息,是每个线程的处理队列。
1 2 3 4 5 6 7 |
/* A connection queue. */ typedef struct conn_queue CQ; struct conn_queue { CQ_ITEM *head; CQ_ITEM *tail; pthread_mutex_t lock; }; |
2.3 LIBEVENT_THREAD
这个结构是工作线程的结构,每一个工作线程都具有的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
typedef struct { //线程Id pthread_t thread_id; /* unique ID of this thread */ //libevent句柄 struct event_base *base; /* libevent handle this thread uses */ //异步event事件 struct event notify_event; /* listen event for notify pipe */ //pipe管道接收端 int notify_receive_fd; /* receiving end of notify pipe */ //pipe管道发送端 int notify_send_fd; /* sending end of notify pipe */ //线程状态 struct thread_stats stats; /* Stats generated by this thread */ //也就是上面的结构CQ,CQ_ITEM队列 struct conn_queue *new_conn_queue; /* queue of new connections to handle */ cache_t *suffix_cache; /* suffix cache */ uint8_t item_lock_type; /* use fine-grained or global item lock */ } LIBEVENT_THREAD; |
三、memcached的网络处理流程
在了解了上面三种简单的结构以后,首先来看下memcached的网络处理流程。
1)主线程的主要工作就是监听和接收listen和accpet进入新的链接,主线程为自己分配一个event_base句柄,用于监听连接,即listen fd。
2)主线程启动的时候会创建n个worker线程(默认情况下是4个,可根据配置进行修改),同时每个worker线程也分配了独立的event_base句柄。
3)每个worker线程通过管道方式与其它线程(主要是主线程)进行通信,调用pipe函数创建匿名管道。worker线程把管道读取fd加到自己的event_base,监听管道读取fd的可读事件,即当主线程往某个线程的管道写入fd写数据时,触发事件。
4)主线程监听到有一个连接到达时,accept连接,产生一个client fd,然后通过求余的方式选择一个worker线程,把这个client fd包装成一个CQ_ITEM对象,然后压到worker线程的CQ_ITEM队列里面去,同时主线程往选中的worker线程的管道写入fd中写入一个1,来通知woker线程(触发worker线程)。
5)当worker线程监听到自己的管道读取fd可读,触发事件处理,而此是的事件处理是:从自己的CQ_ITEM队列中取出CQ_ITEM对象(相当于收信,看看主线程给了自己什么东西),worker线程把此client fd加入到自己的event_base(创建libevent的读写事件),从此负责该连接的读写工作。
如上图所示,整个流程还是相对比较简单的,下面我们来看下Memcache的网络模型部分源码。
四、源码
这里的代码看的版本是1.4.21。
其中网络模型部分的源码主要在两个文件上,一个是Memcached.c,一个是Thread.c两个文件。其中关于工作线程的数据结构部分是在Memcached.h文件中。
4.1 main函数代码
main函数的代码存放在Memcached.c文件中,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
int main ( int argc, char **argv) { //省略。。。 //前面代码主要是根据输入调整配置等。 /* initialize main thread libevent instance */ //初始化,得到句柄 main_base = event_init(); /* initialize other stuff */ stats_init(); assoc_init(settings.hashpower_init); conn_init(); slabs_init(settings.maxbytes, settings.factor, preallocate); //...... //这里是用来创建工作线程的代码。 /* start up worker threads if MT mode */ thread_init(settings.num_threads, main_base); //....... errno = 0 ; //这边的server_sockets方法主要是socket的bind、listen、accept等操作 //主线程主要用于接收客户端的socket连接,并且将连接交给工作线程接管。 if (settings.port && server_sockets(settings.port, tcp_transport, portnumber_file)) { vperror( "failed to listen on TCP port %d" , settings.port); exit(EX_OSERR); } /* enter the event loop */ //主事件循环 if (event_base_loop(main_base, 0 ) != 0 ) { retval = EXIT_FAILURE; } //... } |
所以我们可以看到,在main函数中主要是以下几个函数比较重要:
4.2 thread_init创建工作线程代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
/* * Initializes the thread subsystem, creating various worker threads. * * nthreads Number of worker event handler threads to spawn * main_base Event base for main thread */ void thread_init( int nthreads, struct event_base *main_base) { //加锁代码删除.... //省略... for (i = 0 ; i < nthreads; i++) { int fds[ 2 ]; if (pipe(fds)) { perror( "Can't create notify pipe" ); exit( 1 ); } //此处的threads是工作线程的结构 //接收端 threads[i].notify_receive_fd = fds[ 0 ]; //写入端 threads[i].notify_send_fd = fds[ 1 ]; //创建现线程自己的libevent的 event_base setup_thread(&threads[i]); /* Reserve three fds for the libevent base, and two for the pipe */ stats.reserved_fds += 5 ; } /* Create threads after we've done all the libevent setup. */ //这里循环创建线程,设置回调函数为worker_libevent for (i = 0 ; i < nthreads; i++) { create_worker(worker_libevent, &threads[i]); } //省略...... } |
如上创建工作线程代码主要有两部分:
- 一个setup_thread是给每个工作线程创建属于自己的event_base句柄。
- 一个是create_worker真正的创建工作线程以及设置每个线程的回调函数。
4.2.1 setup_thread设置句柄
下面这段代码主要做了工作线程设置event句柄以及填写了工作线程结构LIBEVENT_THREAD里面的变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
/* * Set up a thread's information. */ static void setup_thread(LIBEVENT_THREAD *me) { //创建一个event_base //根据libevent的使用文档,我们可以知道一般情况下每个独立的线程都应该有自己独立的event_base me->base = event_init(); if (! me->base) { fprintf(stderr, "Can't allocate event base\n" ); exit( 1 ); } /* Listen for notifications from other threads */ //读事件EV_READ的监听,如果pipe中有写事件的时候,libevent就会调用thread_libevent_process方法 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 ) { fprintf(stderr, "Can't monitor libevent notify pipe\n" ); exit( 1 ); } //初始化一个工作队列 me->new_conn_queue = malloc(sizeof(struct conn_queue)); if (me->new_conn_queue == NULL) { perror( "Failed to allocate memory for connection queue" ); exit(EXIT_FAILURE); } cq_init(me->new_conn_queue); if (pthread_mutex_init(&me->stats.mutex, NULL) != 0 ) { perror( "Failed to initialize mutex" ); exit(EXIT_FAILURE); } me->suffix_cache = cache_create( "suffix" , SUFFIX_SIZE, sizeof( char *), NULL, NULL); if (me->suffix_cache == NULL) { fprintf(stderr, "Failed to create suffix cache\n" ); exit(EXIT_FAILURE); } } |
4.2.2 create_worker创建工作线程
下面这段代码主要做了创建工作线程以及设置线程回掉函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/* * Creates a worker thread. */ static void create_worker( void *(*func)( void *), void *arg) { pthread_t thread; pthread_attr_t attr; int ret; pthread_attr_init(&attr); //pthread_create来创建线程 if ((ret = pthread_create(&thread, &attr, func, arg)) != 0 ) { fprintf(stderr, "Can't create thread: %s\n" , strerror(ret)); exit( 1 ); } } |
其中回调函数的内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/* * Worker thread: main event loop */ static void *worker_libevent( void *arg) { LIBEVENT_THREAD *me = arg; /* Any per-thread setup can happen here; thread_init() will block until * all threads have finished initializing. */ /* set an indexable thread-specific memory item for the lock type. * this could be unnecessary if we pass the conn *c struct through * all item_lock calls... */ me->item_lock_type = ITEM_LOCK_GRANULAR; pthread_setspecific(item_lock_type_key, &me->item_lock_type); register_thread_initialized(); //主要是开启事件循环,因此每个工作线程都具有事件循环 //memcache的每个工作线程都会独立处理自己接管的连接 event_base_loop(me->base, 0 ); return NULL; } |
4.2.3 工作线程pipe监听发生后调用thread_libevent_process
这段代码是setup_thread设置句柄函数中的管道pipe监听事件发生后所调用的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
/* * Processes an incoming "handle a new connection" item. This is called when * input arrives on the libevent wakeup pipe. */ static void thread_libevent_process( int fd, short which, void *arg) { LIBEVENT_THREAD *me = arg; CQ_ITEM *item; char buf[ 1 ]; //回调函数中回去读取pipe中的信息 //主线程中如果有新的连接,会向其中一个线程的pipe中写入1 //这边读取pipe中的数据,如果为1,则说明从pipe中获取的数据是正确的 if (read(fd, buf, 1 ) != 1 ) if (settings.verbose > 0 ) fprintf(stderr, "Can't read from libevent pipe\n" ); switch (buf[ 0 ]) { case 'c' : //从工作线程的队列中获取一个CQ_ITEM连接信息 item = cq_pop(me->new_conn_queue); if (NULL != item) { //conn_new这个方法非常重要,主要是创建socket的读写等监听事件。 //init_state 为初始化的类型,主要在drive_machine中通过这个状态类判断处理类型 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 ; /* we were told to flip the lock type and report in */ case 'l' : me->item_lock_type = ITEM_LOCK_GRANULAR; register_thread_initialized(); break ; case 'g' : me->item_lock_type = ITEM_LOCK_GLOBAL; register_thread_initialized(); break ; } } |
4.2.4 conn_new创建socket的读写等监听事件
在4.2.3中有一个conn_new方法。
1 2 3 4 5 6 7 8 9 10 11 |
//我们发现这个方法中又在创建event了,这边实际上是监听socket的读写等事件 //主线程主要是监听用户的socket连接事件;工作线程主要监听socket的读写事件 //当用户socket的连接有数据传递过来的时候,就会调用event_handler这个回调函数 event_set(&c->event, sfd, event_flags, event_handler, ( void *)c); event_base_set(base, &c->event); c->ev_flags = event_flags; //将事件添加到libevent的loop循环中 if (event_add(&c->event, 0 ) == - 1 ) { perror( "event_add" ); return NULL; } |