Tag Archives: memcached

Facebook对memcached的提升

原文:Scaling memcached at Facebook
作者:Paul Saab
翻译:ShiningRay

如果你翻阅过一些关于大型网站扩展(Scaling)的资料,那么你可能听说过一个叫memcached的东西。memcached是一个高性能、分布式的内存对象缓存系统。我们Facebook可能是世界上最大的memcached用户了。我们利用memcached来减轻数据库的负担。memcached确实很快,但是我们还要让他更快、更高效。我们使用了超过800台服务器,提供超过28TB的内存来服务于用户。在过去的一年里,随着Facebook的用户量直线上升,我们遇到了一系列的扩展问题。日益增长的需求使得我们必须对操作系统和memcached进行一些修改,以获得足够的性能来为我们的用户提供最好的体验。
因为我们有好几千台机器,每个都运行了几百个Apache进程甚至更多,最终导致到memcached进程的TCP链接有几十万个。这些链接本身并不是什么大问题,但是memcached为每个TCP链接分配内存的方法却很成问题。memcached为每个链接使用单独的缓存进行数据的读写。当达到几十万链接的时候,这些累计起来达好几个G——这些内存其实可以更好地用于存储用户数据。为了收复这些内存,我们实现了一个针对TCP和UDP套接字的每线程共享的链接缓存池。这个改变使每个服务器可以收回几个G的内存。
虽然TCP上我们改进了内存的使用效率,但我们还是转向了UDP,目的是让get(获取)操作能降低网络流量、让multi-get(同时并行地获取几百个键值)能实现应用程序级别的流量控制。我们发现Linux上到了一定负载之后,UDP的性能下降地很厉害。这是由于,当从多个线程通过单个套接字传递数据时,在UDP套接字锁上产生的大量锁竞争导致的。要通过分离锁来修复内核恐怕不太容易。所以,我们使用了分离的UDP套接字来传递回复(每个线程用一个答复套接字)。这样改动之后,我们就可以部署UDP同时后端性能不打折。
另一个Linux中的问题是到了一定负载后,某个核心可能因进行网络软终端处理会饱和而限制了网络IO。在Linux中,网络中断只会总是传递给某个核心,因此所有的接受软终端的网络处理都发生在该内核上。另外,我们还发现某些网卡有过高的中断频率。我们通过引入网络接口的“投机”轮询解决了这两个问题。在该模型中,我们组合了中断驱动和轮询驱动的网络IO。一旦进入网络驱动(通常是传输一个数据包时)以及在进程调度器的空闲循环的时候,对网络接口进行轮询。另外,我们也用到了中断(来控制延迟),不过网络中断用到的数量大大减少(一般通过大幅度提升中断联结阈值interrupt coalescing thresholds)。由于我们在每个核心上进行网络传输,同时由于在调度器的空闲循环中对网络IO进行轮询,我们将网络处理均匀地分散到每个核心上。
最后,当开始部署8核机器的时候,我们在测试中发现了新的瓶颈。首先,memcached的stat工具集依赖于一个全局锁。这在4核上已经很令人讨厌了,在8核上,这个锁可以占用20-30%的CPU使用率。我们通过将stats工具集移入每个线程,并且需要的时候将结果聚合起来。其次,我们发现随着传递UDP数据包的线程数量的增加,性能却在降低。最后在保护每个网络设备的传送队列的锁上发现了严重的争用。数据包是由设备驱动进行入队传输和出队。该队列由Linux的“netdevice”层来管理,它位于IP和设备驱动之间。每次只能有一个数据包加入或移出队列,这造成了严重的争用。我们当中的一位工程师修改了出队算法,实现了传输的批量出队,去掉了队列锁,然后批量传送数据包。这个更正将请求锁的开销平摊到了多个数据包,显著地减少了锁争用,这样我们就能在8核系统上将memcached伸展至8线程。
做了这些修改之后,我们可以将memcached提升到每秒处理20万个UDP请求,平均延迟降低为173微秒。可以达到的总吞吐量为30万UDP请求/s,不过在这个请求速度上的延迟太高,因此在我们的系统中用处不大。对于普通版本的Linux和memcached上的50,000 UDP请求/s而言,这是个了不起的提升。
我们希望尽快将我们的修改集成到官方的memcached仓库中去,我们决定在这之前,先将我们对memcached的修改发布到github上。

Rails内置缓存引擎与线程安全

升级到Rails 2.1之后,使用Rails内置的缓存之后,发现使用mem_cache_store总是报错,于是干脆研究了Rails内置缓存引擎的代码。阅读的结果发现,Rails的内置引擎只应该应用于进程模式,尤其是mem_cache_store,因为mem_cache_store是每进程建立一个到memcached的链接,即便是读取,也需要写入套接字,所以为了同步,必须使用锁。因此即使在读取缓存的时候,也会出现争用的情况。mem_cache_store也必须像Rails2.2中的ActiveRecord一样实现一个到memcached的线程池,或者使用异步链接,否则是发挥不出memcached的效率的。
我研究了另外几个cache_store,

memory_store则根本连锁机制都没有,但对应有个多线程的synchronized_memory_store,使用了Monitor;
drb_store没有用过,可能Drb本身有一些同步机制,但估计也可能会出现与mem_cache_store一样的问题;
file_store应该是在多线程中应该比较理想的,因为使用了文件系统自身的同步机制,使用了File.atomic_write,无论是多线程还是多进程都能共享同一个file_store。

所以,Rails的线程安全还有很长一段路要走。
PS:前面的我说的我遇到的mem_cache_store的错误是Rails.cache.fetch误加了:raw => true 参数。

Rails + Memcached = Undefined Class/Module?

问题描述:
当使用memcached并将Model对象保存在其中时,若要取出这个缓存对象时,可能会找不到Model的类,并抛出“Undefined Class/Module SomeClass”的错误。
例如:
if not (genres = Cache.get(key))
genres = Genre.find(:all, :condition => “platform_id = 1″)
Cache.put(key, genres, 60*60*24) # cache for 1 day
end
解决方案
要解决这个问题,可以在引用到该对象之间,先引用其类。比如,在前面的代码前面加入要引用的类Genre:

Genre
if not (genres = Cache.get(key))
genres = Genre.find(:all, :condition => “platform_id = 1″)
Cache.put(key, genres, 60*60*24) # cache for 1 day
end
还有更好地方法是在Controller的before_filter中加载所依赖的所有Model:
before_filter :preload_models
def preload_models
Model1
Model2
[...]