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;
}
|