21、为什么单个item的大小被限制在1M byte之内?
简单的回答:因为内存分配器的算法就是这样的。
详细的回答:
1)Memcached的内存存储引擎,使用slabs来管理内存。内存被分成大小不等的slabs chunks(先分成大小相等的slabs,然后每个slab被分成大小相等chunks,不同slab的chunk大小
是不相等的)。chunk的大小依次从一个最小数开始,按某个因子增长,直到达到最大的可能值。如果最小值为400B,最大值是1MB,因子是1.20,各个slab的chunk的大小依次是:
slab1 – 400B;slab2 – 480B;slab3 – 576B …slab中chunk越大,它和前面的slab之间的间隙就越大。因此,最大值越大,内存利用率越低。Memcached必须为每个slab预先分配内
存,因此如果设置了较小的因子和较大的最大值,会需要为Memcached提供更多的内存。
2)不要尝试向memcached中存取很大的数据,例如把巨大的网页放到mencached中。因为将大数据load和unpack到内存中需要花费很长的时间,从而导致系统的性能反而不好。如果
确实需要存储大于1MB的数据,可以修改slabs.c:POWER_BLOCK的值,然后重新编译memcached;或者使用低效的malloc/free。另外,可以使用数据库、MogileFS等方案代替
Memcached系统。
22、可以在不同的memcached节点上使用大小不等的缓存空间吗?如果这么做之后,memcached能够更有效地使用内存吗?
Memcache客户端仅根据哈希算法来决定将某个key存储在哪个节点上,而不考虑节点的内存大小。因此,可以在不同的节点上使用大小不等的内存作为缓存空间。但是一般可以这样做
:拥有较多内存的节点上可以运行多个memcached实例,每个实例使用的内存跟其他节点上的实例相同。
23、什么是二进制协议,是否需要关注?
二进制协议尝试为端提供一个更有效的、可靠的协议,减少客户端/服务器端因处理协议而产生的CPU时间。根据Facebook的测试,解析ASCII协议是memcached中消耗CPU时间最多的
环节。
24、memcached的内存分配器是如何工作的?为什么不适用malloc/free!?为何要使用slabs?
实际上,这是一个编译时选项。默认会使用内部的slab分配器,而且确实应该使用内建的slab分配器。最早的时候,memcached只使用malloc/free来管理内存。然而,这种方式不能与
OS的内存管理以前很好地工作。反复地malloc/free造成了内存碎片,OS最终花费大量的时间去查找连续的内存块来满足malloc的请求,而不是运行memcached进程。slab分配器就是
为了解决这个问题而生的。内存被分配并划分成chunks,一直被重复使用。因为内存被划分成大小不等的slabs,如果item的大小与被选择存放它的slab不是很合适的话,就会浪费一些内存。
25、memcached是原子的吗?
所有的被发送到memcached的单个命令是完全原子的。如果您针对同一份数据同时发送了一个set命令和一个get命令,它们不会影响对方。它们将被串行化、先后执行。即使在多线程模
式,所有的命令都是原子的。然是,命令序列不是原子的。如果首先通过get命令获取了一个item,修改了它,然后再把它set回memcached,系统不保证这个item没有被其他进程
(process,未必是操作系统中的进程)操作过。memcached 1.2.5以及更高版本,提供了gets和cas命令,它们可以解决上面的问题。如果使用gets命令查询某个key的item,
memcached会返回该item当前值的唯一标识。如果客户端程序覆写了这个item并想把它写回到memcached中,可以通过cas命令把那个唯一标识一起发送给memcached。如果该item
存放在memcached中的唯一标识与您提供的一致,写操作将会成功。如果另一个进程在这期间也修改了这个item,那么该item存放在memcached中的唯一标识将会改变,写操作就会
失败。
性能和客户端库方面的问题
26、memcached没有我的database快,为什么?
在一对一比较中,memcached可能没有SQL查询快。但是,这不是memcached的设计目标。Memcached的目标是可伸缩性。当连接和请求增加的时候,memcached的性能将比
大多数数据库查询好。可以先在高负载的环境(并发的连接和请求)中测试您的代码,然后再决定memcached是否适合您。
27、使用不同的客户端库,可以访问到memcached中相同的数据吗?
从技术上说,是可以的。但是可能会遇到下面三个问题:
1)不同的库采用不同的方式序列化数据。举个例子,perl的Cache::Memcached使用Storable来序列化结构复杂的数据(比如hash references, objects, 等)。其他语言的客户端库很
可能不能读取这种格式的数据。如果您要存储复杂的数据并且想被多种客户端库读取,那么您应该以简单的string格式来存储,并且这种格式可以被JSON、XML等外部库解析。
2)从某个客户端来的数据被压缩了,从另一个客户端来的却没被压缩。
3)各个客户端库可能使用不同的哈希算法(阶段一哈希)。在连接到多个memcached服务器端的情况下,客户端库根据自身实现的哈希算法把key映射到某台memcached上。正是因为
不同的客户端库使用不同的哈希算法,所以被Perl客户端库映射到memcached A的key,可能又会被Python客户端库映射到memcached B,等等。Perl客户端库还允许为每台
memcached指定不同的权重(weight),这也是导致这个问题的一个因素。
28、什么是一致性哈希的客户端?
这里有一篇文章很好地解释了它的用处:http://www.last.fm/user/RJ/journal/2007/04/10/392555 。
客户端可以通过”前缀”来给key设置一个域(命名空间)。例如,在一个共享主机的环境中,可以将客户姓名作为”前缀”,为key创建一个特定的域。在存储数据的时候,”前缀”可以用在
key上,但是不应该参与哈希计算。目前,memcached自己还没有实现针对复杂结构数据的序列化方法,JSON则是一种被广泛使用的对象序列化格式。
哈希 / 键分布
29、什么时候失效的数据项会从缓存中删除?
memcached 使用懒失效,当客户端请求数据项时, memcached 在返回数据前会检查失效时间来确定数据项是否已经失效。同样地,当添加一个新的数据项时,如果缓存已经满了, memcached 就会先替换失效的数据项,然后才是缓存中最少使用的数据项。
命名空间
30、memcached 不支持命名空间。以下提供几种模仿命名空间的方式:
1)用键的前缀模仿命名空间:在真实的键之前加入有意义的前缀。
2)用命名空间删除数据项:尽管 memcached 不支持使用任何类型的通配符或命名空间来完成删除操作,但是可以采用一些技巧来替代:
在 PHP 中使用一个叫 foo 的命名空间:$ns_key = $memcache->get(“foo_namespace_key”);
// if not set, initialize it
if($ns_key=false) $memcache->set(“foo_namespace_key”, rand(1, 10000));
$my_key = “foo_”.$ns_key.”_12345″;
清除命名空间:$memcache->increment(“foo_namespace_key”);
应用设计
31、在设计应用时,可以通过Memcached缓存那些内容?
1)缓存简单的查询结果:查询缓存存储了给定查询语句对应的整个结果集,最合适缓存那些经常被用到,但不会改变的 SQL 语句对查询到的结果集,比如载入特定的过滤内容。
$key = md5(‘SELECT * FROM rest_of_sql_statement_goes_here’);
if ($memcache->get($key)) {
` return $memcache->get($key);`
}else {
` // Run the query and transform the result data into your final dataset form`
` $result = $query_results_mangled_into_most_likely_an_array`
` $memcache->set($key, $result, TRUE, 86400); // Store the result of the query for a day`
` return $result;`
}
记住,如果查询语句对应的结果集改变,该结果集不会展现出来。这种方法不总是有用,但它确实让工作变得比较快。
2)缓存简单的基于行的查询结果:基于行的缓存会检查缓存数据key的列表,那些在缓存中的行可以直接被取出,不在缓存中的行将会从数据库中取出并以唯一的键为标识缓存起来,最
后加入到最终的数据集中返回。随着时间的推移,大多数数据都会被缓存,这也意味着相比与数据库,查询语句会更多地从 memcached 中得到数据行。如果数据是相当静态的,我们可
以设置一个较长的缓存时间。
基于行的缓存模式对下面这种搜索情况特别有用:数据集本身很大或是数据集是从多张表中得到,而数据集取决于查询的输入参数但是查询的结果集之间的有重复部分。
比如,如果你有用户 A , B , C , D , E 的数据集。你去点击一张显示用户 A , B , E 信息的页面。首先, memcached 得到 3 个不同的键,每个对应一个用户去缓存中查找,全部未
命中。然后就到数据库中用 SQL 查询得到 3 个用户的数据行,并缓存他们。
现在,你又去点击另一张显示显示 C , D , E 信息的页面。当你去查找 memcached 时, C , D 的数据并没有被命中,但我们命中了 E 的数据。然后从数据库得到 C , D 的行数据,缓
存在 memcached 中。至此以后,无论这些用户信息怎样地排列组合,任何关于 A , B , C , D , E 信息的页面都可以从 memcached 得到数据了。
3)缓存的不只是 SQL 数据,可以缓存最终完成的部分显示页面,以节省CPU计算时间
例如正在制作一张显示用户信息的页面,你可能得到一段关于用户的信息(姓名,生日,家庭住址,简介),然后你可能会将 XML 格式的简介信息转化为 HTML 格式或做其他的一些工
作。相比单独存储这些属性,你可能更愿意存储经过渲染的数据块。那时你就可以简单地取出被预处理后的 HTML 直接填充在页面中,这样节省了宝贵的 CPU 时间。
32、使用分层的缓存
memcached 可以高速处理大量的缓存数据,但是还是要根据系统的情况考虑维护多层的缓存结构。例如除了memcached缓存之外,还可以通过本地缓存(如ehcache、oscache等)建
立起多级缓存。例如,可以采用本地缓存缓存一些基本数据,例如少量但访问频繁的数据(如产品分类,连接信息,服务器状态变量,应用配置变量等),缓存这些数据并让他们尽可能的
接近处理器是有意义的 , 这样可以帮助减少生成页面的时间,并且在 memcached 失效的情况下可以增加可靠性。
33、当数据更新时需要更新缓存
用户编辑了自己的信息,当保存信息到数据库时,需要更新缓存中的数据或是简单地删除老的数据。如果马上更新数据,要防止从数据库读取那些刚刚更新过的数据。当用户习惯性地重新
载入自己的用户信息来确认是否修改成功时,数据将从缓存中直接取出,这时他们获得了最新的数据。
34、模拟带锁的添加命令
如果你实在需要锁,你可以通过“添加”命令模仿锁的功能。尽管在未命中的情况下它不是那么有用,但如果你用它缓存平常的数据(应用服务器池的元数据)那还是有用的。
比如,你要更新键 A 。
1. 添加一个 “lock:A” 的键,这个键有一个持续几秒的过期时间(足够长以使你能完成计算和更新,也不要很长,因为如果锁进程挂了,这个键不会立即释放)
2. 如果添加操作成功了,你就拥有了锁:从缓存获取键 A 的数据;利用客户端程序更改数据;更新缓存键 A 的数据;删除键 “lock:A” 。如果你不需要立即再次更新,就让它存活直到失效。
3. 如果添加操作失败,说明有人获取了锁。这时让应用做些合适的事,比如返回老数据,等待后重试,或是其他的。
以上这些操作类似 MySQL 将 GET_LOCK 的 timeout 值设置成 0 。没有办法在 memcached 中通过互斥锁模拟 GET_LOCK() 的 timeout 操作。
35、预热你的缓存
如果你有一个很高访问率的站点,并且你正想加入故障恢复功能或是其他全新的功能,你最终可能会碰到空缓存的问题。一开始缓存是空的,然后一大群人点击你的站点,在填充缓存的过
程中,你的数据库可能会承受不住压力。为了解决这一问题,你可以试试任何可行的方法来 ” 温暖 ” 你的Memcached。方法:可以写一些脚本来缓存通用的页面;也可以写一个命令行工
具来填充缓存。你可以在高峰时刻在缓存里填充一些内容。