关于 memcached,虽然可以找到一些基本
介绍,DK大神也有点到一些进阶议题,不过最近看了 Using memcached PDF 之后才真正学到了不少实战技巧跟如何设计快取的概念。以下是一些零散的笔记:

装好之后,基本的启动方式是

* `memcached -l 127.0.0.1 -P 11211 -m 128 -d` for deamon
* `memcached -l 127.0.0.1 -P 11211 -m 128 -vv` for development debug

memcached 是一套 Name-Value Pair(NVP) 分散式记忆体快取系统,Key 的长度被限制在 250 characters,储存的资料不能超过 1 megabyte。如果资料会超过 1mb,可以考虑使用压缩工具,例如在 Rails 2.1 里就内建了 ActiveSupport::Cache::CompressedMemCacheStore。

除了 memcached server,不同程式语言都有自己的 memcache client library 工具提供更方便的介面。一个基本的任务就是使用 Hashing algorithm 根据 Key 来决定该去存取一台 memcached server (如果有超过一台的 memcached server 的话)。Ruby 的 client 目前台面上有几套 1. memcache-client 2. fiveruns fork 版 memcache-client,针对 Hasing 的部份用C改写了 3. 使用 libmemcached 的超快 memcached

有趣的是,不需要 client library,memcached 是可以直接 telnet 127.0.0.1 11211 的。telnet 之后打 stats 可以得到一些统计资料,除了目前共有多少笔资料跟共用多少空间之外,重要的有 cmd_get 跟 cmd_hits,就可以得出 cache hit ratio,这个数字应该努力到九成以上。另外还有你的 cmd_set 应该超过 cmd_get,

其他的 memcached 标准操作有 SET (新增或是更新一个值)、ADD(只有在该key不存在时,才会新增快取资料成功)、REPLACE(只有在该key已经存在时,才会更新资料成功)、GET(拿快取资料)。

在考虑使用 memcached 前,要知道它不是你系统中唯一的 cache,HTML 的整页快取应该用 Web server、纯 SQL query result 可以用 MySQL 内建 Query Cache,设定很简单,效果很不错 (Cache Performance Comparison。我自己的心得是很多时候你想要快取的物件其实并不是一个 SQL query 就可以搞定的,而是多个 SQL query 才计算出来,这时候去做纯快取 SQL query result 我个人觉得也许不是很有意义,Rails 这部份就有人实做了query_memcached。。

另外要注意的是 memcached 并不是 persistent data store,只要一关掉 memcached server,里面的资料就会通通不见,如果要拿来储存 session authentication 资料要特别小心。

写 memcached 程式的第一个问题是找出什么资料需要快取? 一个常见的问题是我该快取 HTML fragment 还是纯资料结构? 如果你操作介面只有一处用到,我们可以只快取 HTML fragment 即可,不然其实规模稍大的网站其实两者都可以快取起来。

撰写使用 memcached 程式的基本模式就是,先查看有没有 key-value,有就把快取资料读出来,没有就运算结果后存到 memcached sever。这部份算是简单的。真正困难的事情有两件:一是清除过期的快取资料(expire),二是Key的命名。

命名的一个惯用的格式是 ObjectName:ObjectType:Key 或是该 SQL statement,但为了 Security 避免被人猜到 Key 和避免超过 255 bytes 的 key 长度限制,建议你将 Key hash 过。当然 Security 的最好解决方式是要有防火墙,因为只要连的上 Port 11211,有 Key 就可以读的到快取资料,memcached server 本身是没有任何认证机制的。

有两种方式清除快取资料:一是在新增/删除资料时,顺便删掉这个快取 key-value,这样下次 request 来时便会重新 快取。二是在有更新的时候直接重设快取资料(Reset)。要注意的是如果您有不同的程式会直接更新资料库(也就是不只是透过主应用程式,还有别的背景程式),就会有可能 memcachd 里面的资料没有被更新到,解法有 1. 清空所有资料 2. 有一只程式可以重建 memcached 里面的资料 3. 统一用一套知道 memcached 机制的介面操作

如果站很大,race condition 就会是个效能问题了,同时有多个 request 同时去更新同一份快取资料。虽然改用上述的方法二在有更新的时候直接重设(Reset)快取资料可以改善这个问题,不过如果资料根本还不在快取里的话问题还是存在。也因此我们需要一个 lock 机制,概念是先检查有没有 lock entry,有的话先等一下,然后再抓。没有的话就更新,然后删除 lock entry。

实做招数有二,如果你的 client library 支援 ADD 操作(也就是如果该 key 已经存在的话,操作会失败),就可以先用 ADD key:lock 决定是不是有别的 process 在用,没有的话就 SET key,最后 DELETE delete key:lock。

如果 client library 只支援 GET 操作,只好先 GET key:lock,没有值就 SET key:lock (有的话表示已经被锁住,就先稍等),然后再设定真正的快取资料 SET key,最后 DELETE key:lock。这样会比方法一多一个操作就是了。这的方法的范例程式码可以去PDF的 Source Code下载,在 /LinuxBasics/memcachedUpdateCalendarOfEvents-3.pl。(well, it’s perl code)

如果不采用主动更新快取资料的方式,也可以直接设定过期时间(expire time),这取决于你有多常读取/更新。越常更新 expire time 就越短。这种方式特别适合例如首页每隔几分钟才换,而且没有要求快取资料一定要跟资料库里面完全一致,例如在五分钟内虽有很多更新资料,但前台只需要每隔五分钟重组页面即可。

为了彻底解决使用 expire time 方式仍会有的 race condition 问题,有一招是 proactive cache refill:我们另外纪录一个比 expire 周期还短的 refresh time,如果 refresh time 到了,我们就先更新快取资料,这样无论何时都不会有 client 要不到资料的情形,自然也就不会发生 rece condition。这招大绝的范例在 /LinuxBasics/memcachedRollingCalendarOfEvents.pl。

在更新跟查询都很极端频繁的情况下,也可以考虑用另外的程式专门去捞资料库执行更新快取,这样主应用程式只需要处理拿快取资料即可(唯一会去资料库捞可能只有第一次资料不存在时)。

另一个议题是:如果群集跟个别资料都快取会有的重复现象,例如我们如果同时快取了 People.find(:all) 跟 Person.find(1),这时就重复存了 Person.find(1)。作者是建议群集的部份只当作指标(pointer)来使用,也就是群集只快取了所有 Person 的 ID 而不是完整资料。毕竟单项比较好重复使用(reuse),而且在处理更新快取资料时也比较方便,不会发生个别资料更新了,但是群集忘了没有处理。

话说虽然 memcached 有提供 ADD/REPLACE 操作(根据Key存在或不存在,会导致储存快取不一定成功的操作),但是这样你就必须处理 error code 的情况,作者认为“快取”的基本原理是不回应(noreply):你不应该关心为什么你的资料不在快取里 (事实上就算你的 memcached server 都关掉,你的程式应该也要可以正常执行,只是比较慢而已),也许可能是 expire time 到了? 记忆体用完了? 被删除了? 或是根本不存在? 这些都不重要。虽然有些例外,但基本上应该 SET 操作就可以应付绝大多数的情况。

ihower

相关文章