memcached是一个分布式的内存cache系统,目前被大量地运用于各种各样的站点中,以不断提高站点的总体访问性能,而另外一方面,memcached的使用是非常简单的,可以说,使用门槛很低,这也许是造成memcached目前非常流行的原因之一。
我们可以看到,网上分析memcached的文章也比较多,本文是笔者结合memcached源代码的基础上对其线程接入模型进行深入的分析,通过学习和借鉴memcached设计和实现上 的一些思想,为我们的系统架构设计增添新的思路。
简单的说,memcached的实现并没有使用多进程模型,而是选择了多线程的模型,可以说,这种实现的方案确实是比较轻量级的。而多线程是基于libevent的事件机制以及基于管道pipe的信令周知机制来实现的。
主线程main_thread完成了系统的初始化和生成指定数量的worker_thread后,自身转化为一个dispatch_thread,负责系统的监听和转发,main_thread有自身的event_base 实例,该实例负责监听socket是否有连接到来,如果有,则获取一个新的连接socket并且封装成一个CQ_ITEM对象放入其中某个worker_thread的CQ队列中,以此同时,往该worker_thread的notify pipe写入一个字节以周知该worker_thread已经有新的CQ_ITME到,可以进行处理了。
每个worker_thread都有自己的一个CQ队列,该队列存放已经建立连接的各个连接对象,worker_thread正是不断地从各自的CQ队列里面取得各个连接对象进行处理的。同时,worker_thread也有各自的event_base实例用于出来各自的socket读写等操作以及有一对用于信令周知的读写pipe。
下面是简单的main_thread和worker_thread示意图:
(http://user.qzone.qq.com/270362985?ptlang=2052,这里查看图片)
下面结合源代码(memcached-1.4.4)进行分析,首页看一下关键的数据结构定义。
一,存放到每个worker_thread连接队列和连接item的定义
view plaincopy to clipboardprint?
/*
An item in the connection queue.
封装已经连接的socket file description
*/
typedef struct conn_queue_item CQ_ITEM;
struct conn_queue_item {
int sfd; //建立连接的socket file description
enum conn_states init_state; //连接的状态枚举
int event_flags; //事件的flags
int read_buffer_size; //读buffer大小
enum network_transport transport; //该连接对应的传输协议:TCP或者UDP
CQ_ITEM *next; //指向下一个CQ_ITEM的指针
};
/*
An item in the connection queue.
封装已经连接的socket file description
*/
typedef struct conn_queue_item CQ_ITEM;
struct conn_queue_item {
int sfd; //建立连接的socket file description
enum conn_states init_state; //连接的状态枚举
int event_flags; //事件的flags
int read_buffer_size; //读buffer大小
enum network_transport transport; //该连接对应的传输协议:TCP或者UDP
CQ_ITEM *next; //指向下一个CQ_ITEM的指针
};
该CQ_ITEM实例就是main_thread accept一个连接socket后的封装。
view plaincopy to clipboardprint?
/* A connection queue. */
typedef struct conn_queue CQ;
struct conn_queue {
CQ_ITEM *head;
CQ_ITEM *tail;
pthread_mutex_t lock;
pthread_cond_t cond;
};
/* A connection queue. */
typedef struct conn_queue CQ;
struct conn_queue {
CQ_ITEM *head;
CQ_ITEM *tail;
pthread_mutex_t lock;
pthread_cond_t cond;
};
CQ是每个worker_thread存放的已经建立的连接队列,每个worker_thread正是不断的通过读取自己的连接队列的CQ_ITEM进行处理的。
二,其实memcached对每个worker_thread也是进行封装成相应的结构体的
为了便于对worker_thread进行处理,memcache对worker_thread进行重新封装,结构体如下:
view plaincopy to clipboardprint?
/*
* tenfyguo: Memcached 对每个线程的封装,每个thread都有自己独立的event_base obj和对应的conn_queue队列
*/
typedef struct {
pthread_t thread_id; /* unique ID of this thread */
struct event_base *base; /* libevent handle this thread uses */
struct event notify_event; /* listen event for notify pipe */
int notify_receive_fd; /* receiving end of notify pipe */
int notify_send_fd; /* sending end of notify pipe */
struct thread_stats stats; /* Stats generated by this thread */
struct conn_queue *new_conn_queue; /* queue of new connections to handle */
cache_t *suffix_cache; /* suffix cache */
} LIBEVENT_THREAD;
/*
* tenfyguo: Memcached 对每个线程的封装,每个thread都有自己独立的event_base obj和对应的conn_queue队列
*/
typedef struct {
pthread_t thread_id; /* unique ID of this thread */
struct event_base *base; /* libevent handle this thread uses */
struct event notify_event; /* listen event for notify pipe */
int notify_receive_fd; /* receiving end of notify pipe */
int notify_send_fd; /* sending end of notify pipe */
struct thread_stats stats; /* Stats generated by this thread */
struct conn_queue *new_conn_queue; /* queue of new connections to handle */
cache_t *suffix_cache; /* suffix cache */
} LIBEVENT_THREAD;
下面是对main_thread作为一个dispatch_thread的封装:
view plaincopy to clipboardprint?
typedef struct {
pthread_t thread_id; /* unique ID of this thread */
struct event_base *base; /* libevent handle this thread uses */
} LIBEVENT_DISPATCHER_THREAD;
typedef struct {
pthread_t thread_id; /* unique ID of this thread */
struct event_base *base; /* libevent handle this thread uses */
} LIBEVENT_DISPATCHER_THREAD;
介绍完主要的数据结构后(当然还有其他重要的数据结构,但跟我们本文要介绍的线程部分联系不是特别紧密,这里暂不扩展介绍了),下面重点介绍各个关键流程。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/tenfyguo/archive/2010/01/31/5273828.aspx