memcached简介:
memcached是一套分布式的快取系统,当初是Danga Interactive为了LiveJournal所发展的,但目前被许多软件(如MediaWiki)所使用。这是一套开放源代码软件,以BSD license授权释出。
作者:
Anatoly Vorobey <mellon@pobox.com>
Brad Fitzpatrick <brad@danga.com>
源码地址:
http://github.com/memcached/memcached
模块分解:
个人感觉memcached的源码可以分为三个部分:
1. 协议,即通信模块,包括如何监听socket,创建连接,解析命令等等;
2. 内存管理,主要负责内存分配,回收
3. 散列管理,负责对存储的内容进行散列,维护散列表
接下来从每个模块来分析memcached的源码:
协议模块:
主要代码逻辑位于memcached.c文件中。主要的逻辑就是监听某个端口,发起的连接有多个线程进行处理(round-robin),每个线程处理的连接会去解析socket流中的数据,从中剥离命令类型、数据等等内容。这块主要会用到libevent这个框架进行事件的监听并回调自己定义好的函数。
之后就是解析socket流中的数据,这块代码很庞大,但是没有仔细阅读的必要,大体的函数调用顺序如下(以get操作为例):
event_handler --> drive_machine --> try_read_command --> process_command --> process_get_command -->item_get
在取得item数据后写回socket。
内存管理:
memcached的内存分配策略会把内存分成一页一页的内容,然后每个页里面会放置很多个slab,而存储单元item就是放入这些slab中,主要的源码文件在slabs.c和item.c。可以先看看slabclass的数据结构:
slabclass主要用来描述slab的信息,每个slabclass里面有一个slablist的指针数组,用来记录当前已经分配的该大小的slab,还会有个slots的指针数组,用来记录存储的item的列表。
item的数据结构:
很显然item是一个双向链表的节点,会包括自己的一个时间戳、过期时间以及数据。
那么内存是如何进行分配的呢?首先可以看看初始化时候的代码:
如果没有设置prealloc的话,memcached会根据用户的配置:页大小、增长因子等等来初始化slabclass,这里只需初始化slabclass,至于如何分配一个slab,再看看这段代码:
如果slots中的freelist中有空余内存或者当前页中还有空余内存,直接返回这块内存地址即可,否则就分配一个新的内存页。
对于一个set请求,会创建一个新的item,然后根据item的大小,找到一个最小的可以放下item的slab,然后将这个item放入这个slab里面,如果找不到这样的slab,则需要根据LRU策略回收掉一部分的内存。
散列管理:
assoc.c主要维护了两个散列表,当一个散列表的item个数超过一定范围后,会用一个线程在后台重建一个新的更大的散列表。主要的代码:
扩大散列表的后台线程代码:
主要就是将旧的散列表中的item通过新的散列mask重新散列后放入新的散列表中。这种做法是为了保证开散列过程中,一个散列值的链表长度不至于太长,否则会影响性能。
总结:
给我印象最为深刻的还是内存管理及散列管理这块,体现了一定的数据结构和算法知识,实现的页非常漂亮。从读代码中还发现了很多自己以前不了解的东西,比如页大小可以设置成很大,但是官方推荐不要超过1M,否则性能会很差。