一、功能介绍
Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。
它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态、数据库驱动网站的速度。
Memcached基于一个存储键/值对的hashmap。其守护进程(daemon)是用C写的,但是客户端可以用任何语言来编写,
并通过memcached协议与守护进程通信。
memcached缺乏认证以及安全管制,这代表应该将memcached服务器放置在防火墙后。
memcached的API使用32位元的循环冗余校验(CRC-32)计算键值后,将资料分散在不同的机器上。
当表格满了以后,接下来新增的资料会以LRU机制替换掉。
memcached是一款非常普及的服务器端缓存软件,memcached主要是基于Libevent库进行开发的
memcached服务器端并没有分布式功能、各个memcached不会互相通信以共享信息、完全取决于客户端的实现达到分布式。
研究版本:memcached-1.4.31
二、网络线程模型
dispatch_conn_new函数@thread.c
总结一下主线程工作:
1、memcached首先在主线程中会创建main_base,memcached的主线程的主要工作就是监听和接收listen和accpet新进入的连接。
2、当用户有连接进来的时候,main thread主线程会通过libevent驱动在状态机中accept新的连接。
3、main thread主线程会通过求余的方式选择一个worker thread工作线程。
主线程处程流程图:
在些注册回调函数 thread_libevent_process 用于数据读事件监控。create_worker@thread.c 就是调用 pthread_create 创建线程、执行worker_libevent函数实体。
创建工作线程的工作结束后、那么工作线程如何配合主线程工作的。主线程接收到accept新的连接后,
调用 dispatch_conn_new 分配一个工作线程并往队列中写入数据,利用管道唤醒工作线程函数 thread_libevent_process。
又通过conn_new函数介入到event_handler->drive_machine状态机中,如此就形成由数据事件驱动的完整运行态。
总结一下工作线程工作:
1、memcached启动的时候会初始化N个worker thread工作线程、默认是8个可配置。
2、worker thread工作线程和main thread主线程之间主要通过pipe来进行通信。
3、main thread会将当前用户的连接信息放入一个CQ_ITEM,并且将CQ_ITEM放入这个线程的conn_queue处理队列,然后主线程会通过写入pipe的方式来通知worker thread工作线程。
4、工作线程得到主线程通知,从自已的conn_queue队列中取得一条连接信息进行处理,创建libevent的socket读写事件
5、工作线程会监听用户的socket,如果用户有消息传递过来,则会进行消息解析和处理,返回相应的结果。
工作线程数据处理流程:
Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。
它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态、数据库驱动网站的速度。
Memcached基于一个存储键/值对的hashmap。其守护进程(daemon)是用C写的,但是客户端可以用任何语言来编写,
并通过memcached协议与守护进程通信。
memcached缺乏认证以及安全管制,这代表应该将memcached服务器放置在防火墙后。
memcached的API使用32位元的循环冗余校验(CRC-32)计算键值后,将资料分散在不同的机器上。
当表格满了以后,接下来新增的资料会以LRU机制替换掉。
memcached是一款非常普及的服务器端缓存软件,memcached主要是基于Libevent库进行开发的
memcached服务器端并没有分布式功能、各个memcached不会互相通信以共享信息、完全取决于客户端的实现达到分布式。
研究版本:memcached-1.4.31
二、网络线程模型
主要涉及两个主要文件:memcached.c 和thread.c文件
主线程主要用于监听accpet客户端的Socket连接,而工作线程主要用于接管具体的客户端连接。
主线程和工作线程之间主要通过基于Libevent的pipe的读写事件来监听,当有连接连接上来的时候,主线程会将连接交个某一个工作线程去接管,后期客户端和服务端的读写工作都会在这个工作线程中进行。
工作线程也是基于Libevent的事件的,当有读或者写的事件进来的时候,就会触发事件的回调函数。
分析后的整体流程图解:
主线程负责客户端的套接字监听、当有新的连接到来时则均衡选择工作线程、工作线程负责读写并处理相应的命令。
main函数@memcached.c 文件了解整个构建逻辑
int main (int argc, char **argv) {
/* handle SIGINT and SIGTERM */
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
/* init settings */
settings_init();
/* Run regardless of initializing it later */
init_lru_crawler();
init_lru_maintainer();
hash_init(hash_type);
/* initialize main thread libevent instance */
main_base = event_init();
/* start up worker threads if MT mode */
// 这个方法主要用来创建工作线程,默认会创建8个工作线程
memcached_thread_init(settings.num_threads);
// 根据启动配置可以创建TCP/UDP协议套接字
server_sockets(settings.port, tcp_transport, portnumber_file);
server_sockets(settings.udpport, udp_transport, portnumber_file);
/* enter the event loop */
// 进入主线程的事件循环
event_base_loop(main_base, 0);
}
先看主线程的监听工作:
<pre name="code" class="cpp">server_sockets(settings.port, tcp_transport, portnumber_file);
-> server_socket(settings.inter, port, transport, portnumber_file);
/**
* Create a socket and bind it to a specific port number
*/
static int server_socket(const char *interface,
int port,
enum network_transport transport,
FILE *portnumber_file) {
// socket的bind、listen、setopt 等操作创建socket server
socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
fcntl(sfd, F_SETFL, flags | O_NONBLOCK);
setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags));
setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags));
setsockopt(sfd, SOL_SOCKET, SO_LINGER, (void *)&ling, sizeof(ling));
setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags));
bind(sfd, next->ai_addr, next->ai_addrlen);
// state的类型为:conn_listening 监听类型套接字、那么在哪里进行accept.
listen_conn_add = conn_new(sfd, conn_listening, EV_READ | EV_PERSIST, 1,transport, main_base);
}
conn *conn_new(const int sfd, enum conn_states init_state,...){
// 这里首先会将监听套接字加入到libevent体系中,并设定event_handler事件回调函数
event_set(&c->event, sfd, event_flags, event_handler, (void *)c);
event_base_set(base, &c->event);
c->ev_flags = event_flags;
event_add(&c->event, 0);
}
void event_handler(const int fd, const short which, void *arg) {
drive_machine(c); // 异步fd编程最重要的状态机
}
//
static void drive_machine(conn *c) {
while (!stop) {
case conn_listening: // 调用accept接受客户端的socket连接代码
sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen);
// 客户端用socket连接上来,则会调用这个分发逻辑的函数、分发到工作线程接管具体的读写操作
dispatch_conn_new(sfd, conn_new_cmd, EV_READ | EV_PERSIST, DATA_BUFFER_SIZE, c->transport);
break;
case conn_read:
IS_UDP(c->transport) ? try_read_udp(c) : try_read_network(c);
break;
case conn_parse_cmd :
try_read_command(c);
break;
case conn_write:
add_iov(c, c->wcurr, c->wbytes);
break;
...
}
}
dispatch_conn_new函数@thread.c
void dispatch_conn_new(int sfd, enum conn_states init_state, int event_flags...){
// 申请一块CQ_ITEM的内存块,用于存储连接的基本信息
CQ_ITEM *item = cqi_new();
// 通过求余数的方法来得到当前的连接需要哪个工作线程接管、且记录每次最后一次使用的工作线程
// 通过最后记录就可以让工作线程进入一个轮询,保证了每个工作线程处理的连接数的平衡-相同连接数
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_ITEM
cq_push(thread->new_conn_queue, item);
// 向工作线程的pipe中写入数据,只为唤醒工作线程
buf[0] = 'c';
write(thread->notify_send_fd, buf, 1);
}
总结一下主线程工作:
1、memcached首先在主线程中会创建main_base,memcached的主线程的主要工作就是监听和接收listen和accpet新进入的连接。
2、当用户有连接进来的时候,main thread主线程会通过libevent驱动在状态机中accept新的连接。
3、main thread主线程会通过求余的方式选择一个worker thread工作线程。
主线程处程流程图:
再看工作线程的工作:
在main中调用 memcached_thread_init 创建工作线程的函数:
<pre name="code" class="cpp">/*
* Initializes the thread subsystem, creating various worker threads.
*
* nthreads Number of worker event handler threads to spawn
*/
void memcached_thread_init(int nthreads) {
for (i = 0; i < nthreads; i++) {
int fds[2];
// 创建pipe,主要用于主线程和工作线程之间的通信
if (pipe(fds)) {
perror("Can't create notify pipe");
exit(1);
}
// 每个线程的LIBEVENT_THREAD基本数据结构
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_state.reserved_fds += 5;
}
/* Create threads after we've done all the libevent setup. */
for (i = 0; i < nthreads; i++) {
create_worker(worker_libevent, &threads[i]);
}
}
/*
* Set up a thread's information.
*/
static void setup_thread(LIBEVENT_THREAD *me) {
/* Listen for notifications from other threads */
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);
event_add(&me->notify_event, 0) ;
// 初始化一个工作队列
me->new_conn_queue = malloc(sizeof(struct conn_queue));
cq_init(me->new_conn_queue);
}
在些注册回调函数 thread_libevent_process 用于数据读事件监控。create_worker@thread.c 就是调用 pthread_create 创建线程、执行worker_libevent函数实体。
/*
* Worker thread: main event loop
*/
static void *worker_libevent(void *arg) {
register_thread_initialized();
// 每个线程中都会有自己独立的event_base和事件的循环机制、独立处理自己接管的连接。
event_base_loop(me->base, 0);
}
创建工作线程的工作结束后、那么工作线程如何配合主线程工作的。主线程接收到accept新的连接后,
调用 dispatch_conn_new 分配一个工作线程并往队列中写入数据,利用管道唤醒工作线程函数 thread_libevent_process。
<pre name="code" class="cpp">/*
* 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) {
// 主线程中如果有新的连接,会向其中一个线程的pipe中写入1
read(fd, buf, 1);
//从工作线程的队列中获取一个CQ_ITEM连接信息、如果item不为空,则需要进行连接后的接管。
item = cq_pop(me->new_conn_queue);
if(item != NULL){
//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);
...
}
}
又通过conn_new函数介入到event_handler->drive_machine状态机中,如此就形成由数据事件驱动的完整运行态。
总结一下工作线程工作:
1、memcached启动的时候会初始化N个worker thread工作线程、默认是8个可配置。
2、worker thread工作线程和main thread主线程之间主要通过pipe来进行通信。
3、main thread会将当前用户的连接信息放入一个CQ_ITEM,并且将CQ_ITEM放入这个线程的conn_queue处理队列,然后主线程会通过写入pipe的方式来通知worker thread工作线程。
4、工作线程得到主线程通知,从自已的conn_queue队列中取得一条连接信息进行处理,创建libevent的socket读写事件
5、工作线程会监听用户的socket,如果用户有消息传递过来,则会进行消息解析和处理,返回相应的结果。
工作线程数据处理流程: