Memcached是开源的分布式cache系统,现在很多的大型web应用程序包括facebook,youtube,wikipedia,yahoo 等等都在使用memcached来支持他们每天数亿级的页面访问。通过把cache层与他们的web架构集成,他们的应用程序在提高了性能的同时,还大大降低了数据库的负载。
如果您还对memcached还不是很了解,请先阅读:
利用memcached构建高性能的Web应用程序
本文中我们将集中探讨如何对memcached中存储的cache进行高效管理。在我们实际项目中,由于网站经常需要更新,而cache难免会发生与数据库不同步却仍然还有效的情况,那网站管理员就有需求对某个或某些cache进行手工清理。
比如,大众点评的每个商户页面都利用了cache,并存成键值为Shop.[ShopID]的cache对象。如果一个商户ID为1234的页面cache有问题,我们就可以通过Remove(”Shop.1234”)方法通知memcached清掉对应的cache对象。
但问题又随之而来了,有时候我们发现有一批cache都存在问题而没有及时过期。此时我们会面临一个两难的选择:
通过flush_all清掉所有的cache
这种做法很黄很暴力…把有问题和没问题都一起kill掉了…
找出这一批cache中所有的key值并逐一进行清理
这种做法看似不错,但没有好的办法实现。因为memcached是hash表结构,无法提供像sql里select from where的操作,所以你不知道这批数据到底有多少已经存进了cache
暴力法我们肯定是不能主动考虑的,否则也不用这篇文章了,呵呵。所以我们看看有没有可行的方案来实现方法2。
LogDB方案
这个方案说起来也很简单。既然memcached没有select操作,我们就用db去记录所有的Cache SET操作。只要memcached存了一个cache对象进instance,就同时往logdb上写一条记录。
等到要删除的时候,我们就通过select操作,找出所有Shop.xxxx的商户,并逐一进行remove操作。
这种办法虽然可以达到精确删除的目的,但是所消耗的代价也未免有些过高,为了清除key还要另外拉个数据库进来做辅助。而且cache的存取量是很大的,说不定数据库频繁的insert操作还会成为另一个潜在的性能瓶颈。此方案在硬件条件不是很充裕的条件下谨慎使用。
自定义keylocator方案
了解memcached的读者都知道,cache对象分配到哪个instance是由memcached客户端决定的。而memcached客户端默认的分配算法是SHA-1散列算法。所以默认情况下,商户类的cache对象(Shop.xxxx)是被散列到不同的memcached instance上的。
(X:User100,Shop202 Y:User101,Shop200 Z:User102,Shop201)
假如我们可以自定义这个分配算法,将Shop.xxx类的cache对象都同一存到某一个instance或某几个instance下,这样我们就通过清掉这些Shop.xxx专用的instance来达到批量清楚的目的。
(X:User100 Y:Shop200,Shop201,Shop202 Z:User101,User102)
这个方案看似不错,其实还是有不少衍生问题的:
使用率不均问题:
从上面两个例子就可以看到,前面通过散列算法存放,每个instance都放了两个对象,但是采用自定义的办法,instance Y就存了3个,X才存了1个。如果自定义的locator在实现之前不能够通盘考虑各类key的使用率的话,很容易造成instance使用不均的问题。
负载率不均问题:
有些热门类的key读取率很高,因为原本是散列在各个服务器上,读取负载也就自然地分解到各个服务器上。现在采用专用服务器模式的话,所有的读取操作就限制在某一台服务器上,无疑是加大了这台服务器的负担。
Key flag 方案
这个方案是眼下笔者认为最简单有效的一个办法,通过在key上做标记来实现懒清理。其实这种做法也是很合memcached的胃口的,之前的文章就介绍过,memcached server的清理策略就是懒清理。
让我们看一下实际的例子:
我们在所有的key后面都带一个版本标记,“Shop.200_1”代表商户ID为200,版本号为1的缓存。一旦有需要对所有的商户类缓存进行清理的话,我们只需要升级一下Shop类缓存的版本就可以做到。
上例中,商户类缓存版本都升级到2了,读取的key值也发生了变化。所以原来的Shop.xxx_1的缓存就永远不会再被读取到,也就等同于失效了。
这样做还有个好处就是,不需要进行批量删除的操作,而耗费大量的维护时间。那些过时版本的cache会根据LRU原则,只要cache一满,他们就会自动被剔除,不需要我们还劳神劳力地去伺候他们。
所以,在memcached这个圈子里也有个原则就是,不用的话就别去管它…它自己会消失的。
说道这个方案不足的话,我想也就是key的长度不得不进行加长来包含版本号,不过这方面会带来的性能影响是微乎其微的。
memcached的批量清理一直是个比较麻烦的问题,希望以上几种方案的探讨能给您带来一些启发。