<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title><![CDATA[向东博客 专注WEB应用 构架之美 --- 构架之美，在于尽态极妍 | 应用之美，在于药到病除]]></title> 
<link>http://jackxiang.com/index.php</link> 
<description><![CDATA[赢在IT，Playin' with IT,Focus on Killer Application,Marketing Meets Technology.]]></description> 
<language>zh-cn</language> 
<copyright><![CDATA[向东博客 专注WEB应用 构架之美 --- 构架之美，在于尽态极妍 | 应用之美，在于药到病除]]></copyright>
<item>
<link>http://jackxiang.com/post//</link>
<title><![CDATA[Memcached深度分析(原创)]]></title> 
<author>jack &lt;xdy108@126.com&gt;</author>
<category><![CDATA[WEB2.0]]></category>
<pubDate>Mon, 27 Sep 2010 16:11:33 +0000</pubDate> 
<guid>http://jackxiang.com/post//</guid> 
<description>
<![CDATA[ 
	<br/>Memcached是danga.com（运营LiveJournal的技术团队）开发的一套分布式内存对象缓存系统，用于在动态系统中减少数据库负载，提升性能。关于这个东西，相信很多人都用过，本文意在通过对memcached的实现及代码分析，获得对这个出色的开源软件更深入的了解，并可以根据我们的需要对其进行更进一步的优化。末了将通过对BSM_Memcache扩展的分析，加深对memcached的使用方式理解。<br/><br/>本文的部分内容可能需要比较好的数学基础作为辅助。<br/><br/>◎Memcached是什么<br/><br/>在阐述这个问题之前，我们首先要清楚它“不是什么”。很多人把它当作和SharedMemory那种形式的存储载体来使用，虽然memcached使用了同样的“Key=&gt;Value”方式组织数据，但是它和共享内存、APC等本地缓存有非常大的区别。Memcached是分布式的，也就是说它不是本地的。它基于网络连接（当然它也可以使用localhost）方式完成服务，本身它是一个独立于应用的程序或守护进程（Daemon方式）。<br/><br/>Memcached使用libevent库实现网络连接服务，理论上可以处理无限多的连接，但是它和Apache不同，它更多的时候是面向稳定的持续连接的，所以它实际的并发能力是有限制的。在保守情况下memcached的最大同时连接数为200，这和Linux线程能力有关系，这个数值是可以调整的。关于libevent可以参考相关文档。 Memcached内存使用方式也和APC不同。APC是基于共享内存和MMAP的，memcachd有自己的内存分配算法和管理方式，它和共享内存没有关系，也没有共享内存的限制，通常情况下，每个memcached进程可以管理2GB的内存空间，如果需要更多的空间，可以增加进程数。 <br/><br/>◎Memcached适合什么场合<br/><br/>在很多时候，memcached都被滥用了，这当然少不了对它的抱怨。我经常在论坛上看见有人发贴，类似于“如何提高效率”，回复是“用memcached”，至于怎么用，用在哪里，用来干什么一句没有。memcached不是万能的，它也不是适用在所有场合。<br/><br/>Memcached是“分布式”的内存对象缓存系统，那么就是说，那些不需要“分布”的，不需要共享的，或者干脆规模小到只有一台服务器的应用，memcached不会带来任何好处，相反还会拖慢系统效率，因为网络连接同样需要资源，即使是UNIX本地连接也一样。 在我之前的测试数据中显示，memcached本地读写速度要比直接PHP内存数组慢几十倍，而APC、共享内存方式都和直接数组差不多。可见，如果只是本地级缓存，使用memcached是非常不划算的。<br/><br/>Memcached在很多时候都是作为数据库前端cache使用的。因为它比数据库少了很多SQL解析、磁盘操作等开销，而且它是使用内存来管理数据的，所以它可以提供比直接读取数据库更好的性能，在大型系统中，访问同样的数据是很频繁的，memcached可以大大降低数据库压力，使系统执行效率提升。另外，memcached也经常作为服务器之间数据共享的存储媒介，例如在SSO系统中保存系统单点登陆状态的数据就可以保存在memcached中，被多个应用共享。<br/><br/>需要注意的是，memcached使用内存管理数据，所以它是易失的，当服务器重启，或者memcached进程中止，数据便会丢失，所以memcached不能用来持久保存数据。很多人的错误理解，memcached的性能非常好，好到了内存和硬盘的对比程度，其实memcached使用内存并不会得到成百上千的读写速度提高，它的实际瓶颈在于网络连接，它和使用磁盘的数据库系统相比，好处在于它本身非常“轻”，因为没有过多的开销和直接的读写方式，它可以轻松应付非常大的数据交换量，所以经常会出现两条千兆网络带宽都满负荷了，memcached进程本身并不占用多少CPU资源的情况。<br/><br/>◎Memcached的工作方式<br/><br/>以下的部分中，读者最好能准备一份memcached的源代码。<br/><br/>Memcached是传统的网络服务程序，如果启动的时候使用了-d参数，它会以守护进程的方式执行。创建守护进程由daemon.c完成，这个程序只有一个daemon函数，这个函数很简单（如无特殊说明，代码以1.2.1为准）：<br/><br/>CODE:<br/>#include &lt;fcntl.h&gt;<br/>#include &lt;stdlib.h&gt;<br/>#include &lt;unistd.h&gt;<br/><br/>int<br/>daemon(nochdir, noclose)<br/>&nbsp;&nbsp;&nbsp;&nbsp;int nochdir, noclose;<br/>&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;int fd; <br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;switch (fork()) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;case -1:<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return (-1);<br/>&nbsp;&nbsp;&nbsp;&nbsp;case 0: <br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break;&nbsp;&nbsp;<br/>&nbsp;&nbsp;&nbsp;&nbsp;default:<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_exit(0);<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;if (setsid() == -1)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return (-1);<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;if (!nochdir)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(void)chdir(”/”);<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;if (!noclose &amp;&amp; (fd = open(”/dev/null”, O_RDWR, 0)) != -1) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(void)dup2(fd, STDIN_FILENO);<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(void)dup2(fd, STDOUT_FILENO);<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(void)dup2(fd, STDERR_FILENO);<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (fd &gt; STDERR_FILENO)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(void)close(fd);<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/>&nbsp;&nbsp;&nbsp;&nbsp;return (0);<br/>&#125;<br/><br/>这个函数 fork 了整个进程之后，父进程就退出，接着重新定位 STDIN 、 STDOUT 、 STDERR 到空设备， daemon 就建立成功了。 <br/><br/>Memcached 本身的启动过程，在 memcached.c 的 main 函数中顺序如下： <br/><br/>1 、调用 settings_init() 设定初始化参数<br/>2 、从启动命令中读取参数来设置 setting 值<br/>3 、设定 LIMIT 参数<br/>4 、开始网络 socket 监听（如果非 socketpath 存在）（ 1.2 之后支持 UDP 方式）<br/>5 、检查用户身份（ Memcached 不允许 root 身份启动）<br/>6 、如果有 socketpath 存在，开启 UNIX 本地连接（Sock 管道）<br/>7 、如果以 -d 方式启动，创建守护进程（如上调用 daemon 函数）<br/>8 、初始化 item 、 event 、状态信息、 hash 、连接、 slab<br/>9 、如设置中 managed 生效，创建 bucket 数组<br/>10 、检查是否需要锁定内存页<br/>11 、初始化信号、连接、删除队列<br/>12 、如果 daemon 方式，处理进程 ID<br/>13 、event 开始，启动过程结束， main 函数进入循环。 <br/><br/>在 daemon 方式中，因为 stderr 已经被定向到黑洞，所以不会反馈执行中的可见错误信息。 <br/><br/>memcached.c 的主循环函数是 drive_machine ，传入参数是指向当前的连接的结构指针，根据 state 成员的状态来决定动作。 <br/><br/>Memcached 使用一套自定义的协议完成数据交换，它的 protocol 文档可以参考： http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt<br/><br/>在API中，换行符号统一为&#92;r&#92;n<br/><br/>◎Memcached的内存管理方式<br/><br/>Memcached有一个很有特色的内存管理方式，为了提高效率，它使用预申请和分组的方式管理内存空间，而并不是每次需要写入数据的时候去malloc，删除数据的时候free一个指针。Memcached使用slab-&gt;chunk的组织方式管理内存。<br/><br/>1.1和1.2的slabs.c中的slab空间划分算法有一些不同，后面会分别介绍。<br/><br/>Slab可以理解为一个内存块，一个slab是memcached一次申请内存的最小单位，在memcached中，一个slab的大小默认为1048576字节（1MB），所以memcached都是整MB的使用内存。每一个slab被划分为若干个chunk，每个chunk里保存一个item，每个item同时包含了item结构体、key和value（注意在memcached中的value是只有字符串的）。slab按照自己的id分别组成链表，这些链表又按id挂在一个slabclass数组上，整个结构看起来有点像二维数组。slabclass的长度在1.1中是21，在1.2中是200。<br/><br/>slab有一个初始chunk大小，1.1中是1字节，1.2中是80字节，1.2中有一个factor值，默认为1.25<br/><br/>在1.1中，chunk大小表示为初始大小*2^n，n为classid，即：id为0的slab，每chunk大小1字节，id为1的slab，每chunk大小2字节，id为2的slab，每chunk大小4字节……id为20的slab，每chunk大小为1MB，就是说id为20的slab里只有一个chunk：<br/><br/>CODE:<br/>void slabs_init(size_t limit) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;int i;<br/>&nbsp;&nbsp;&nbsp;&nbsp;int size=1;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;mem_limit = limit;<br/>&nbsp;&nbsp;&nbsp;&nbsp;for(i=0; i&lt;=POWER_LARGEST; i++, size*=2) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;slabclass[i].size = size;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;slabclass[i].perslab = POWER_BLOCK / size;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;slabclass[i].slots = 0;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;slabclass[i].sl_curr = slabclass[i].sl_total = slabclass[i].slabs = 0;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;slabclass[i].end_page_ptr = 0;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;slabclass[i].end_page_free = 0;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;slabclass[i].slab_list = 0;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;slabclass[i].list_size = 0;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;slabclass[i].killing = 0;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;/* for the test suite:&nbsp;&nbsp;faking of how much we’ve already malloc’d */<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;char *t_initial_malloc = getenv(”T_MEMD_INITIAL_MALLOC”);<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (t_initial_malloc) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mem_malloced = atol(getenv(”T_MEMD_INITIAL_MALLOC”));<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;/* pre-allocate slabs by default, unless the environment variable<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for testing is set to something non-zero */<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;char *pre_alloc = getenv(”T_MEMD_SLABS_ALLOC”);<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (!pre_alloc &#124;&#124; atoi(pre_alloc)) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;slabs_preallocate(limit / POWER_BLOCK);<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/>&#125;<br/><br/>在1.2中，chunk大小表示为初始大小*f^n，f为factor，在memcached.c中定义，n为classid，同时，201个头不是全部都要初始化的，因为factor可变，初始化只循环到计算出的大小达到slab大小的一半为止，而且它是从id1开始的，即：id为1的slab，每chunk大小80字节，id为2的slab，每chunk大小80*f，id为3的slab，每chunk大小80*f^2，初始化大小有一个修正值CHUNK_ALIGN_BYTES，用来保证n-byte排列 （保证结果是CHUNK_ALIGN_BYTES的整倍数）。这样，在标准情况下，memcached1.2会初始化到id40，这个slab中每个chunk大小为504692，每个slab中有两个chunk。最后，slab_init函数会在最后补足一个id41，它是整块的，也就是这个slab中只有一个1MB大的chunk：<br/><br/>CODE:<br/>void slabs_init(size_t limit, double factor) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;int i = POWER_SMALLEST – 1;<br/>&nbsp;&nbsp;&nbsp;&nbsp;unsigned int size = sizeof(item) + settings.chunk_size;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;/* Factor of 2.0 means use the default memcached behavior */<br/>&nbsp;&nbsp;&nbsp;&nbsp;if (factor == 2.0 &amp;&amp; size &lt; 128)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;size = 128;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;mem_limit = limit;<br/>&nbsp;&nbsp;&nbsp;&nbsp;memset(slabclass, 0, sizeof(slabclass));<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;while (++i &lt; POWER_LARGEST &amp;&amp; size &lt;= POWER_BLOCK / 2) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/* Make sure items are always n-byte aligned */<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (size % CHUNK_ALIGN_BYTES)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;size += CHUNK_ALIGN_BYTES – (size % CHUNK_ALIGN_BYTES);<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;slabclass[i].size = size; <br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;slabclass[i].perslab = POWER_BLOCK / slabclass[i].size;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;size *= factor; <br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (settings.verbose &gt; 1) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fprintf(stderr, “slab class %3d: chunk size %6d perslab %5d&#92;n”,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i, slabclass[i].size, slabclass[i].perslab);<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#125;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;power_largest = i;<br/>&nbsp;&nbsp;&nbsp;&nbsp;slabclass[power_largest].size = POWER_BLOCK;<br/>&nbsp;&nbsp;&nbsp;&nbsp;slabclass[power_largest].perslab = 1;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;/* for the test suite:&nbsp;&nbsp;faking of how much we’ve already malloc’d */<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;char *t_initial_malloc = getenv(”T_MEMD_INITIAL_MALLOC”);<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (t_initial_malloc) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mem_malloced = atol(getenv(”T_MEMD_INITIAL_MALLOC”));<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#125;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/><br/>#ifndef DONT_PREALLOC_SLABS<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;char *pre_alloc = getenv(”T_MEMD_SLABS_ALLOC”);<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (!pre_alloc &#124;&#124; atoi(pre_alloc)) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;slabs_preallocate(limit / POWER_BLOCK);<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/>#endif<br/>&#125;<br/><br/>由上可以看出，memcached的内存分配是有冗余的，当一个slab不能被它所拥有的chunk大小整除时，slab尾部剩余的空间就被丢弃了，如id40中，两个chunk占用了1009384字节，这个slab一共有1MB，那么就有39192字节被浪费了。<br/><br/>Memcached使用这种方式来分配内存，是为了可以快速的通过item长度定位出slab的classid，有一点类似hash，因为item的长度是可以计算的，比如一个item的长度是300字节，在1.2中就可以得到它应该保存在id7的slab中，因为按照上面的计算方法，id6的chunk大小是252字节，id7的chunk大小是316字节，id8的chunk大小是396字节，表示所有252到316字节的item都应该保存在id7中。同理，在1.1中，也可以计算得到它出于256和512之间，应该放在chunk_size为512的id9中(32位系统）。<br/><br/>Memcached初始化的时候，会初始化slab（前面可以看到，在main函数中调用了slabs_init()）。它会在slabs_init()中检查一个常量DONT_PREALLOC_SLABS，如果这个没有被定义，说明使用预分配内存方式初始化slab，这样在所有已经定义过的slabclass中，每一个id创建一个slab。这样就表示，1.2在默认的环境中启动进程后要分配41MB的slab空间，在这个过程里，memcached的第二个内存冗余发生了，因为有可能一个id根本没有被使用过，但是它也默认申请了一个slab，每个slab会用掉1MB内存<br/><br/>当一个slab用光后，又有新的item要插入这个id，那么它就会重新申请新的slab，申请新的slab时，对应id的slab链表就要增长，这个链表是成倍增长的，在函数grow_slab_list函数中，这个链的长度从1变成2，从2变成4，从4变成8……：<br/><br/>CODE:<br/>static int grow_slab_list (unsigned int id) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;slabclass_t *p = &amp;slabclass[id];<br/>&nbsp;&nbsp;&nbsp;&nbsp;if (p-&gt;slabs == p-&gt;list_size) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;size_t new_size =&nbsp;&nbsp;p-&gt;list_size ? p-&gt;list_size * 2 : 16; <br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;void *new_list = realloc(p-&gt;slab_list, new_size*sizeof(void*));<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (new_list == 0) return 0;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;p-&gt;list_size = new_size;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;p-&gt;slab_list = new_list;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/>&nbsp;&nbsp;&nbsp;&nbsp;return 1;<br/>&#125;<br/>在定位item时，都是使用slabs_clsid函数，传入参数为item大小，返回值为classid，由这个过程可以看出，memcached的第三个内存冗余发生在保存item的过程中，item总是小于或等于chunk大小的，当item小于chunk大小时，就又发生了空间浪费。<br/><br/>◎Memcached的NewHash算法<br/><br/>Memcached的item保存基于一个大的hash表，它的实际地址就是slab中的chunk偏移，但是它的定位是依靠对key做hash的结果，在primary_hashtable中找到的。在assoc.c和items.c中定义了所有的hash和item操作。<br/><br/>Memcached使用了一个叫做NewHash的算法，它的效果很好，效率也很高。1.1和1.2的NewHash有一些不同，主要的实现方式还是一样的，1.2的hash函数是经过整理优化的，适应性更好一些。<br/><br/>NewHash的原型参考：http://burtleburtle.net/bob/hash/evahash.html。数学家总是有点奇怪，呵呵～<br/><br/>为了变换方便，定义了u4和u1两种数据类型，u4就是无符号的长整形，u1就是无符号char(0-255)。<br/><br/>具体代码可以参考1.1和1.2源码包。<br/><br/>注意这里的hashtable长度，1.1和1.2也是有区别的，1.1中定义了HASHPOWER常量为20，hashtable表长为hashsize(HASHPOWER)，就是4MB（hashsize是一个宏，表示1右移n位），1.2中是变量16，即hashtable表长65536：<br/><br/>CODE:<br/>typedef&nbsp;&nbsp;unsigned long&nbsp;&nbsp;int&nbsp;&nbsp;ub4;&nbsp;&nbsp; /* unsigned 4-byte quantities */<br/>typedef&nbsp;&nbsp;unsigned&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char ub1;&nbsp;&nbsp; /* unsigned 1-byte quantities */<br/><br/>#define hashsize(n) ((ub4)1&lt;&lt;(n))<br/>#define hashmask(n) (hashsize(n)-1)<br/><br/>在assoc_init()中，会对primary_hashtable做初始化，对应的hash操作包括：assoc_find()、assoc_expand()、assoc_move_next_bucket()、assoc_insert()、assoc_delete()，对应于item的读写操作。其中assoc_find()是根据key和key长寻找对应的item地址的函数（注意在C中，很多时候都是同时直接传入字符串和字符串长度，而不是在函数内部做strlen），返回的是item结构指针，它的数据地址在slab中的某个chunk上。<br/><br/>items.c是数据项的操作程序，每一个完整的item包括几个部分，在item_make_header()中定义为：<br/><br/>key：键<br/>nkey：键长<br/>flags：用户定义的flag（其实这个flag在memcached中没有启用）<br/>nbytes：值长（包括换行符号&#92;r&#92;n）<br/>suffix：后缀Buffer<br/>nsuffix：后缀长<br/><br/>一个完整的item长度是键长＋值长＋后缀长＋item结构大小（32字节），item操作就是根据这个长度来计算slab的classid的。<br/><br/>hashtable中的每一个桶上挂着一个双链表，item_init()的时候已经初始化了heads、tails、sizes三个数组为0，这三个数组的大小都为常量LARGEST_ID（默认为255，这个值需要配合factor来修改），在每次item_assoc()的时候，它会首先尝试从slab中获取一块空闲的chunk，如果没有可用的chunk，会在链表中扫描50次，以得到一个被LRU踢掉的item，将它unlink，然后将需要插入的item插入链表中。<br/><br/>注意item的refcount成员。item被unlink之后只是从链表上摘掉，不是立刻就被free的，只是将它放到删除队列中（item_unlink_q()函数）。<br/><br/>item对应一些读写操作，包括remove、update、replace，当然最重要的就是alloc操作。<br/><br/>item还有一个特性就是它有过期时间，这是memcached的一个很有用的特性，很多应用都是依赖于memcached的item过期，比如session存储、操作锁等。item_flush_expired()函数就是扫描表中的item，对过期的item执行unlink操作，当然这只是一个回收动作，实际上在get的时候还要进行时间判断：<br/><br/>CODE:<br/>/* expires items that are more recent than the oldest_live setting. */<br/>void item_flush_expired() &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;int i;&nbsp;&nbsp;<br/>&nbsp;&nbsp;&nbsp;&nbsp;item *iter, *next;<br/>&nbsp;&nbsp;&nbsp;&nbsp;if (! settings.oldest_live)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return; <br/>&nbsp;&nbsp;&nbsp;&nbsp;for (i = 0; i &lt; LARGEST_ID; i++) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/* The LRU is sorted in decreasing time order, and an item’s timestamp<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * is never newer than its last access time, so we only need to walk<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * back until we hit an item older than the oldest_live time.<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * The oldest_live checking will auto-expire the remaining items.<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; */<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for (iter = heads[i]; iter != NULL; iter = next) &#123; <br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (iter-&gt;time &gt;= settings.oldest_live) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;next = iter-&gt;next;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ((iter-&gt;it_flags &amp; ITEM_SLABBED) == 0) &#123; <br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;item_unlink(iter);<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#125;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#125; else &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/* We’ve hit the first old item. Continue to the next queue. */<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break;&nbsp;&nbsp;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#125;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#125;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/>&#125;<br/> <br/><br/>CODE:<br/>/* wrapper around assoc_find which does the lazy expiration/deletion logic */<br/>item *get_item_notedeleted(char *key, size_t nkey, int *delete_locked) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;item *it = assoc_find(key, nkey);<br/>&nbsp;&nbsp;&nbsp;&nbsp;if (delete_locked) *delete_locked = 0;<br/>&nbsp;&nbsp;&nbsp;&nbsp;if (it &amp;&amp; (it-&gt;it_flags &amp; ITEM_DELETED)) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/* it’s flagged as delete-locked.&nbsp;&nbsp;let’s see if that condition<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; is past due, and the 5-second delete_timer just hasn’t<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gotten to it yet… */<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (! item_delete_lock_over(it)) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (delete_locked) *delete_locked = 1;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;it = 0; <br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#125;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/>&nbsp;&nbsp;&nbsp;&nbsp;if (it &amp;&amp; settings.oldest_live &amp;&amp; settings.oldest_live &lt;= current_time &amp;&amp;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;it-&gt;time &lt;= settings.oldest_live) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;item_unlink(it);<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;it = 0; <br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/>&nbsp;&nbsp;&nbsp;&nbsp;if (it &amp;&amp; it-&gt;exptime &amp;&amp; it-&gt;exptime &lt;= current_time) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;item_unlink(it);<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;it = 0; <br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/>&nbsp;&nbsp;&nbsp;&nbsp;return it;<br/>&#125;<br/>Memcached的内存管理方式是非常精巧和高效的，它很大程度上减少了直接alloc系统内存的次数，降低函数开销和内存碎片产生几率，虽然这种方式会造成一些冗余浪费，但是这种浪费在大型系统应用中是微不足道的。<br/><br/><br/>◎Memcached的理论参数计算方式<br/><br/>影响 memcached 工作的几个参数有： <br/><br/>常量REALTIME_MAXDELTA 60*60*24*30<br/>最大30天的过期时间<br/><br/>conn_init()中的freetotal（=200）<br/>最大同时连接数<br/><br/>常量KEY_MAX_LENGTH 250<br/>最大键长<br/><br/>settings.factor（=1.25）<br/>factor将影响chunk的步进大小<br/><br/>settings.maxconns（=1024）<br/>最大软连接<br/><br/>settings.chunk_size（=48）<br/>一个保守估计的key+value长度，用来生成id1中的chunk长度（1.2）。id1的chunk长度等于这个数值加上item结构体的长度（32），即默认的80字节。<br/><br/>常量POWER_SMALLEST 1<br/>最小classid（1.2）<br/><br/>常量POWER_LARGEST 200<br/>最大classid（1.2）<br/><br/>常量POWER_BLOCK 1048576<br/>默认slab大小<br/><br/>常量CHUNK_ALIGN_BYTES (sizeof(void *))<br/>保证chunk大小是这个数值的整数倍，防止越界（void *的长度在不同系统上不一样，在标准32位系统上是4）<br/><br/>常量ITEM_UPDATE_INTERVAL 60<br/>队列刷新间隔<br/><br/>常量LARGEST_ID 255<br/>最大item链表数（这个值不能比最大的classid小）<br/><br/>变量hashpower（在1.1中是常量HASHPOWER）<br/>决定hashtable的大小<br/><br/>根据上面介绍的内容及参数设定，可以计算出的一些结果：<br/><br/>1、在memcached中可以保存的item个数是没有软件上限的，之前我的100万的说法是错误的。<br/>2、假设NewHash算法碰撞均匀，查找item的循环次数是item总数除以hashtable大小（由hashpower决定），是线性的。<br/>3、Memcached限制了可以接受的最大item是1MB，大于1MB的数据不予理会。<br/>4、Memcached的空间利用率和数据特性有很大的关系，又与DONT_PREALLOC_SLABS常量有关。 在最差情况下，有198个slab会被浪费（所有item都集中在一个slab中，199个id全部分配满）。 <br/><br/>◎Memcached的定长优化<br/><br/>根据上面几节的描述，多少对memcached有了一个比较深入的认识。在深入认识的基础上才好对它进行优化。<br/><br/>Memcached本身是为变长数据设计的，根据数据特性，可以说它是“面向大众”的设计，但是很多时候，我们的数据并不是这样的“普遍”，典型的情况中，一种是非均匀分布，即数据长度集中在几个区域内（如保存用户 Session）；另一种更极端的状态是等长数据（如定长键值，定长数据，多见于访问、在线统计或执行锁）。<br/><br/>这里主要研究一下定长数据的优化方案（1.2），集中分布的变长数据仅供参考，实现起来也很容易。<br/><br/>解决定长数据，首先需要解决的是slab的分配问题，第一个需要确认的是我们不需要那么多不同chunk长度的slab，为了最大限度地利用资源，最好chunk和item等长，所以首先要计算item长度。<br/><br/>在之前已经有了计算item长度的算法，需要注意的是，除了字符串长度外，还要加上item结构的长度32字节。<br/><br/>假设我们已经计算出需要保存200字节的等长数据。<br/><br/>接下来是要修改slab的classid和chunk长度的关系。在原始版本中，chunk长度和classid是有对应关系的，现在如果把所有的chunk都定为200个字节，那么这个关系就不存在了，我们需要重新确定这二者的关系。一种方法是，整个存储结构只使用一个固定的id，即只使用199个槽中的1个，在这种条件下，就一定要定义DONT_PREALLOC_SLABS来避免另外的预分配浪费。另一种方法是建立一个hash关系，来从item确定classid，不能使用长度来做键，可以使用key的NewHash结果等不定数据，或者直接根据key来做hash（定长数据的key也一定等长）。这里简单起见，选择第一种方法，这种方法的不足之处在于只使用一个id，在数据量非常大的情况下，slab链会很长（因为所有数据都挤在一条链上了），遍历起来的代价比较高。<br/><br/>前面介绍了三种空间冗余，设置chunk长度等于item长度，解决了第一种空间浪费问题，不预申请空间解决了第二种空间浪费问题，那么对于第一种问题（slab内剩余）如何解决呢，这就需要修改POWER_BLOCK常量，使得每一个slab大小正好等于chunk长度的整数倍，这样一个slab就可以正好划分成n个chunk。这个数值应该比较接近1MB，过大的话同样会造成冗余，过小的话会造成次数过多的alloc，根据chunk长度为200，选择1000000作为POWER_BLOCK的值，这样一个slab就是100万字节，不是1048576。三个冗余问题都解决了，空间利用率会大大提升。<br/><br/>修改 slabs_clsid 函数，让它直接返回一个定值（比如 1 ）：<br/><br/>CODE:<br/>unsigned int slabs_clsid(size_t size) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 1;<br/>&#125;<br/>修改slabs_init函数，去掉循环创建所有classid属性的部分，直接添加slabclass[1]：<br/><br/>CODE:<br/>slabclass[1].size = 200;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//每chunk200字节<br/>slabclass[1].perslab = 5000;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//1000000/200<br/>◎Memcached客户端<br/><br/>Memcached是一个服务程序，使用的时候可以根据它的协议，连接到memcached服务器上，发送命令给服务进程，就可以操作上面的数据。为了方便使用，memcached有很多个客户端程序可以使用，对应于各种语言，有各种语言的客户端。基于C语言的有libmemcache、APR_Memcache；基于Perl的有Cache::Memcached；另外还有Python、Ruby、Java、C#等语言的支持。PHP的客户端是最多的，不光有mcache和PECL memcache两个扩展，还有大把的由PHP编写的封装类，下面介绍一下在PHP中使用memcached的方法：<br/><br/>mcache扩展是基于libmemcache再封装的。libmemcache一直没有发布stable版本，目前版本是1.4.0-rc2，可以在这里找到。libmemcache有一个很不好的特性，就是会向stderr写很多错误信息，一般的，作为lib使用的时候，stderr一般都会被定向到其它地方，比如Apache的错误日志，而且libmemcache会自杀，可能会导致异常，不过它的性能还是很好的。<br/><br/>mcache扩展最后更新到1.2.0-beta10，作者大概是离职了，不光停止更新，连网站也打不开了（~_~），只能到其它地方去获取这个不负责的扩展了。解压后安装方法如常：phpize &amp; configure &amp; make &amp; make install，一定要先安装libmemcache。使用这个扩展很简单：<br/><br/>CODE:<br/>&lt;?php<br/>$mc = memcache();&nbsp;&nbsp;&nbsp;&nbsp;// 创建一个memcache连接对象，注意这里不是用new！<br/>$mc-&gt;add_server(‘localhost’, 11211);&nbsp;&nbsp;&nbsp;&nbsp;// 添加一个服务进程<br/>$mc-&gt;add_server(‘localhost’, 11212);&nbsp;&nbsp;&nbsp;&nbsp;// 添加第二个服务进程<br/>$mc-&gt;set(‘key1′, ‘Hello’);&nbsp;&nbsp;&nbsp;&nbsp;// 写入key1 =&gt; Hello<br/>$mc-&gt;set(‘key2′, ‘World’, 10);&nbsp;&nbsp;&nbsp;&nbsp;// 写入key2 =&gt; World，10秒过期<br/>$mc-&gt;set(‘arr1′, array(‘Hello’, ‘World’));&nbsp;&nbsp;&nbsp;&nbsp;// 写入一个数组<br/>$key1 = $mc-&gt;get(‘key1′);&nbsp;&nbsp;&nbsp;&nbsp;// 获取’key1′的值，赋给$key1<br/>$key2 = $mc-&gt;get(‘key2′);&nbsp;&nbsp;&nbsp;&nbsp;// 获取’key2′的值，赋给$key2，如果超过10秒，就取不到了<br/>$arr1 = $mc-&gt;get(‘arr1′);&nbsp;&nbsp;&nbsp;&nbsp;// 获取’arr1′数组<br/>$mc-&gt;delete(‘arr1′);&nbsp;&nbsp;&nbsp;&nbsp;// 删除’arr1′<br/>$mc-&gt;flush_all();&nbsp;&nbsp;&nbsp;&nbsp;// 删掉所有数据<br/>$stats = $mc-&gt;stats();&nbsp;&nbsp;&nbsp;&nbsp;// 获取服务器信息<br/>var_dump($stats);&nbsp;&nbsp;&nbsp;&nbsp;// 服务器信息是一个数组<br/>?&gt;<br/>这个扩展的好处是可以很方便地实现分布式存储和负载均衡，因为它可以添加多个服务地址，数据在保存的时候是会根据hash结果定位到某台服务器上的，这也是libmemcache的特性。libmemcache支持集中hash方式，包括CRC32、ELF和Perl hash。<br/><br/>PECL memcache是PECL发布的扩展，目前最新版本是2.1.0，可以在pecl网站得到。memcache扩展的使用方法可以在新一些的PHP手册中找到，它和mcache很像，真的很像：<br/><br/>CODE:<br/>&lt;?php<br/><br/>$memcache = new Memcache;<br/>$memcache-&gt;connect(‘localhost’, 11211) or die (“Could not connect”);<br/><br/>$version = $memcache-&gt;getVersion();<br/>echo “Server’s version: ”.$version.“n”;<br/><br/>$tmp_object = new stdClass;<br/>$tmp_object-&gt;str_attr = ‘test’;<br/>$tmp_object-&gt;int_attr = 123;<br/><br/>$memcache-&gt;set(‘key’, $tmp_object, false, 10) or die (“Failed to save data at the server”);<br/>echo “Store data in the cache (data will expire in 10 seconds)n”;<br/><br/>$get_result = $memcache-&gt;get(‘key’);<br/>echo “Data from the cache:n”;<br/><br/>var_dump($get_result);<br/><br/>?&gt;<br/><br/>这个扩展是使用php的stream直接连接memcached服务器并通过socket发送命令的。它不像libmemcache那样完善，也不支持add_server这种分布操作，但是因为它不依赖其它的外界程序，兼容性要好一些，也比较稳定。至于效率，差别不是很大。<br/><br/>另外，有很多的PHP class可以使用，比如MemcacheClient.inc.php，phpclasses.org上可以找到很多，一般都是对perl client API的再封装，使用方式很像。<br/><br/>◎BSM_Memcache<br/><br/>从C client来说，APR_Memcache是一个很成熟很稳定的client程序，支持线程锁和原子级操作，保证运行的稳定性。不过它是基于APR的（APR将在最后一节介绍），没有libmemcache的应用范围广，目前也没有很多基于它开发的程序，现有的多是一些Apache Module，因为它不能脱离APR环境运行。但是APR倒是可以脱离Apache单独安装的，在APR网站上可以下载APR和APR-util，不需要有Apache，可以直接安装，而且它是跨平台的。<br/><br/>BSM_Memcache是我在BS.Magic项目中开发的一个基于APR_Memcache的PHP扩展，说起来有点拗口，至少它把APR扯进了PHP扩展中。这个程序很简单，也没做太多的功能，只是一种形式的尝试，它支持服务器分组。<br/><br/>和mcache扩展支持多服务器分布存储不同，BSM_Memcache支持多组服务器，每一组内的服务器还是按照hash方式来分布保存数据，但是两个组中保存的数据是一样的，也就是实现了热备，它不会因为一台服务器发生单点故障导致数据无法获取，除非所有的服务器组都损坏（例如机房停电）。当然实现这个功能的代价就是性能上的牺牲，在每次添加删除数据的时候都要扫描所有的组，在get数据的时候会随机选择一组服务器开始轮询，一直到找到数据为止，正常情况下一次就可以获取得到。<br/><br/>BSM_Memcache只支持这几个函数：<br/><br/>CODE:<br/>zend_function_entry bsm_memcache_functions[] =<br/>&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;PHP_FE(mc_get,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;NULL)<br/>&nbsp;&nbsp;&nbsp;&nbsp;PHP_FE(mc_set,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;NULL)<br/>&nbsp;&nbsp;&nbsp;&nbsp;PHP_FE(mc_del,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;NULL)<br/>&nbsp;&nbsp;&nbsp;&nbsp;PHP_FE(mc_add_group,&nbsp;&nbsp;&nbsp;&nbsp;NULL)<br/>&nbsp;&nbsp;&nbsp;&nbsp;PHP_FE(mc_add_server,&nbsp;&nbsp; NULL)<br/>&nbsp;&nbsp;&nbsp;&nbsp;PHP_FE(mc_shutdown,&nbsp;&nbsp;&nbsp;&nbsp; NULL)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#123;NULL, NULL, NULL&#125;<br/>&#125;;<br/>mc_add_group函数返回一个整形（其实应该是一个object，我偷懒了~_~）作为组ID，mc_add_server的时候要提供两个参数，一个是组ID，一个是服务器地址（ADDRORT）。<br/><br/>CODE:<br/>/**<br/>* Add a server group<br/>*/<br/>PHP_FUNCTION(mc_add_group)<br/>&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;apr_int32_t group_id;<br/>&nbsp;&nbsp;&nbsp;&nbsp;apr_status_t rv;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;if (0 != ZEND_NUM_ARGS())<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WRONG_PARAM_COUNT;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RETURN_NULL();<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;group_id = free_group_id();<br/>&nbsp;&nbsp;&nbsp;&nbsp;if (-1 == group_id)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RETURN_FALSE;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;apr_memcache_t *mc;<br/>&nbsp;&nbsp;&nbsp;&nbsp;rv = apr_memcache_create(p, MAX_G_SERVER, 0, &amp;mc);<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;add_group(group_id, mc);<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;RETURN_DOUBLE(group_id);<br/>&#125;<br/><br/> <br/><br/>CODE:<br/>/**<br/>* Add a server into group<br/>*/<br/>PHP_FUNCTION(mc_add_server)<br/>&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;apr_status_t rv;<br/>&nbsp;&nbsp;&nbsp;&nbsp;apr_int32_t group_id;<br/>&nbsp;&nbsp;&nbsp;&nbsp;double g;<br/>&nbsp;&nbsp;&nbsp;&nbsp;char *srv_str;<br/>&nbsp;&nbsp;&nbsp;&nbsp;int srv_str_l;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;if (2 != ZEND_NUM_ARGS())<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WRONG_PARAM_COUNT;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “ds”, &amp;g, &amp;srv_str, &amp;srv_str_l) == FAILURE)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RETURN_FALSE;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;group_id = (apr_int32_t) g;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;if (-1 == is_validate_group(group_id))<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RETURN_FALSE;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;char *host, *scope;<br/>&nbsp;&nbsp;&nbsp;&nbsp;apr_port_t port;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;rv = apr_parse_addr_port(&amp;host, &amp;scope, &amp;port, srv_str, p);<br/>&nbsp;&nbsp;&nbsp;&nbsp;if (APR_SUCCESS == rv)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Create this server object<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;apr_memcache_server_t *st;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rv = apr_memcache_server_create(p, host, port, 0, 64, 1024, 600, &amp;st);<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (APR_SUCCESS == rv)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (NULL == mc_groups[group_id])<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RETURN_FALSE;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Add server<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rv = apr_memcache_add_server(mc_groups[group_id], st);<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (APR_SUCCESS == rv)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RETURN_TRUE;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;RETURN_FALSE;<br/>&#125;<br/><br/>在set和del数据的时候，要循环所有的组：<br/><br/>CODE:<br/>/**<br/>* Store item into all groups<br/>*/<br/>PHP_FUNCTION(mc_set)<br/>&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;char *key, *value;<br/>&nbsp;&nbsp;&nbsp;&nbsp;int key_l, value_l;<br/>&nbsp;&nbsp;&nbsp;&nbsp;double ttl = 0;<br/>&nbsp;&nbsp;&nbsp;&nbsp;double set_ct = 0;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;if (2 != ZEND_NUM_ARGS())<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WRONG_PARAM_COUNT;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “ss&#124;d”, &amp;key, &amp;key_l, &amp;value, &amp;value_l, ttl) == FAILURE)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RETURN_FALSE;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;// Write data into every object<br/>&nbsp;&nbsp;&nbsp;&nbsp;apr_int32_t i = 0;<br/>&nbsp;&nbsp;&nbsp;&nbsp;if (ttl &lt; 0)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ttl = 0;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;apr_status_t rv;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;for (i = 0; i &lt; MAX_GROUP; i++)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (0 == is_validate_group(i))<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Write it!<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rv = apr_memcache_add(mc_groups[i], key, value, value_l, (apr_uint32_t) ttl, 0);<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (APR_SUCCESS == rv)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;set_ct++;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;RETURN_DOUBLE(set_ct);<br/>&#125;<br/><br/>在mc_get中，首先要随机选择一个组，然后从这个组开始轮询：<br/><br/>CODE:<br/>/**<br/>* Fetch a item from a random group<br/>*/<br/>PHP_FUNCTION(mc_get)<br/>&#123;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br/>&nbsp;&nbsp;&nbsp;&nbsp;char *key, *value = NULL;<br/>&nbsp;&nbsp;&nbsp;&nbsp;int key_l;<br/>&nbsp;&nbsp;&nbsp;&nbsp;apr_size_t value_l;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;if (1 != ZEND_NUM_ARGS())<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WRONG_PARAM_COUNT;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “s”, &amp;key, &amp;key_l) == FAILURE)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RETURN_MULL();<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/>&nbsp;&nbsp;&nbsp;&nbsp;<br/>&nbsp;&nbsp;&nbsp;&nbsp;// I will try …<br/>&nbsp;&nbsp;&nbsp;&nbsp;// Random read<br/>&nbsp;&nbsp;&nbsp;&nbsp;apr_int32_t curr_group_id = random_group();<br/>&nbsp;&nbsp;&nbsp;&nbsp;apr_int32_t i = 0;<br/>&nbsp;&nbsp;&nbsp;&nbsp;apr_int32_t try = 0;<br/>&nbsp;&nbsp;&nbsp;&nbsp;apr_uint32_t flag;<br/>&nbsp;&nbsp;&nbsp;&nbsp;apr_memcache_t *oper;<br/>&nbsp;&nbsp;&nbsp;&nbsp;apr_status_t rv;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;for (i = 0; i &lt; MAX_GROUP; i++)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try = i + curr_group_id;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try = try % MAX_GROUP;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (0 == is_validate_group(try))<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Get a value<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;oper = mc_groups[try];<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rv = apr_memcache_getp(mc_groups[try], p, (const char *) key, &amp;value, &amp;value_l, 0);<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (APR_SUCCESS == rv)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RETURN_STRING(value, 1);<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#125;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;RETURN_FALSE;<br/>&#125;<br/><br/> <br/><br/>CODE:<br/>/**<br/>* Random group id<br/>* For mc_get()<br/>*/<br/>apr_int32_t random_group()<br/>&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;struct timeval tv;<br/>&nbsp;&nbsp;&nbsp;&nbsp;struct timezone tz;<br/>&nbsp;&nbsp;&nbsp;&nbsp;int usec;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;gettimeofday(&amp;tv, &amp;tz);<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;usec = tv.tv_usec;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;int curr = usec % count_group();<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;return (apr_int32_t) curr;<br/>&#125;<br/><br/>BSM_Memcache的使用方式和其它的client类似：<br/><br/>CODE:<br/>&lt;?php<br/>$g1 = mc_add_group();&nbsp;&nbsp;&nbsp;&nbsp;// 添加第一个组<br/>$g2 = mc_add_group();&nbsp;&nbsp;&nbsp;&nbsp;// 添加第二个组<br/>mc_add_server($g1, ‘localhost:11211′);&nbsp;&nbsp;&nbsp;&nbsp;// 在第一个组中添加第一台服务器<br/>mc_add_server($g1, ‘localhost:11212′);&nbsp;&nbsp;&nbsp;&nbsp;// 在第一个组中添加第二台服务器<br/>mc_add_server($g2, ‘10.0.0.16:11211′);&nbsp;&nbsp;&nbsp;&nbsp;// 在第二个组中添加第一台服务器<br/>mc_add_server($g2, ‘10.0.0.17:11211′);&nbsp;&nbsp;&nbsp;&nbsp;// 在第二个组中添加第二台服务器<br/><br/>mc_set(‘key’, ‘Hello’);&nbsp;&nbsp;&nbsp;&nbsp;// 写入数据<br/>$key = mc_get(‘key’);&nbsp;&nbsp;&nbsp;&nbsp;// 读出数据<br/>mc_del(‘key’);&nbsp;&nbsp;&nbsp;&nbsp;// 删除数据<br/>mc_shutdown();&nbsp;&nbsp;&nbsp;&nbsp;// 关闭所有组<br/>?&gt;<br/><br/>APR_Memcache的相关资料可以在这里找到，BSM_Memcache可以在本站下载。<br/><br/>◎APR环境介绍<br/><br/>APR的全称：Apache Portable Runtime。它是Apache软件基金会创建并维持的一套跨平台的C语言库。它从Apache httpd1.x中抽取出来并独立于httpd之外，Apache httpd2.x就是建立在APR上。APR提供了很多方便的API接口可供使用，包括如内存池、字符串操作、网络、数组、hash表等实用的功能。开发Apache2 Module要接触很多APR函数，当然APR可以独立安装独立使用，可以用来写自己的应用程序，不一定是Apache httpd的相关开发。<br/><br/>◎后记<br/><br/>这是我在农历丙戌年（我的本命年）的最后一篇文章，由于Memcached的内涵很多，仓促整理一定有很多遗漏和错误。感谢新浪网提供的研究机会，感谢部门同事的帮助。<br/><br/>来源：http://blog.developers.api.sina.com.cn/?p=124<br/>
]]>
</description>
</item><item>
<link>http://jackxiang.com/post//#blogcomment</link>
<title><![CDATA[[评论] Memcached深度分析(原创)]]></title> 
<author> &lt;user@domain.com&gt;</author>
<category><![CDATA[评论]]></category>
<pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate> 
<guid>http://jackxiang.com/post//#blogcomment</guid> 
<description>
<![CDATA[ 
	
]]>
</description>
</item>
</channel>
</rss>