上周在公司里面跟同事一起交流了一些memcache的个人看法,后来整理了一下。主要分为:网络线程模型、内存基本结构、LRU结构、hash冲突以及hash乾坤大挪移。
memcache为单进程多线程结构,通信框架使用libevent(http://monkey.org/~provos/libevent/,对定时器、管道、socket、中断都进行了统一的管理,功能丰富肯定带来一些性能上面的副作用,代码值得去阅读学习)。
1、线程分为两种类型:主线程(接受线程)和工作线程:
1)主线程负责完成memcache的一些初始化工作之后,启动event_base_loop循环,开始监控接受new connection,接收到new connection之后,主线程通过轮训的方式,将new connection分配到对应的线程中(当然工作线程也是由主线程启动,不然谁来启动工作线程);
2)工作线程从主线程接受new connection后,将new connection加入到监听队列中,完成后续的所有数据通信工作,例如根据客户端命令set、get、del数据均需要在工作线程完成(工作线程的监听方式也是一个event_base_loop循环);
2、主线程与工作线程socket传递
主线程分配new connect的方式为:循环法;
关键代码为:int tid = (last_thread + 1) % settings.num_threads;根据计算值选择对应的工作线程
主线程与工作线程在传递socket句柄的时候采用一个conn_queue_item,每个工作线程都对应一个conn_queue来接受主线程分配的conn_queue_item。主线程首先从conn_queue_item空闲列表中获取一个conn_queue_item元素,填充相应的信息存放到工作线程的消息队列中,然后用管道工作线程去处理新的连接。
关键代码为:
1)从空闲链表中获取空元素
if (cqi_freelist) { item = cqi_freelist; cqi_freelist = item->next; }else{
new cqi_freelist
}
2)添加到工作线程队列、管道通知
if (NULL == cq->tail) cq->head = item; else cq->tail->next = item;
write(pipefd)
3)工作线程处理新连接
item = cq->head; if (NULL != item) { cq->head = item->next; if (NULL == cq->head) cq->tail = NULL; }
free(item)
新连接分配处理流程图