从Memcached看锁竞争对服务器性能的巨大影响

原文见于http://shiningray.cn/scaling-memcached-at-facebook.html,不过此文对翻译进行了一些自认为的修改和内容的概括总结。这里有英文原文:http://guojuanjun.blog.51cto.com/277646/735854 html

http://www.facebook.com/notes/facebook-engineering/scaling-memcached-at-facebook/39391378919 git


Memcached是一个高性能、分布式的内存对象缓存系统。Facebook利用Memcached来减轻数据库的负担,多是世界上最大的Memcached用户了(使用了超过800台服务器,提供超过28TB的内存来服务于用户)。

Memcached确实很快了,可是Facebook仍是对Memcached进行了4处大的修改来进一步提高Memcached的性能。浏览下面的4个修改,不难发现其中2个都和“严重的锁竞争”有关。修改1主要是节省了大量内存,因此减小“锁竞争”对性能提高的贡献估计能达到70%左右,因而可知“锁竞争”的可怕!!!。修改后的代码在这里:https://github.com/fbmarc/facebook-memcached-old(根据文章中的连接找到的) github

固然Facebook对Memcached的优化也不能说明Memcached不好,正是由于它足够优秀,Facebook这种牛人云集的公司才会用它。并且Memcached自身的文档也提到了在多线程状况下性能会由于锁竞争降低,见最下面一段。 算法

1. 内存优化:

原状:memcached为“每一个TCP连接”使用单独的缓存(a per-connection buffer)进行数据的读写。 数据库

问题:当达到几十万连接的时候,这些累计起来达好几个G——这些内存其实能够更好地用于存储用户数据。

方案:实现了一个针对TCP和UDP套接字的“每线程共享”的连接缓存池(a per-thread shared connection buffer pool for TCP and UDP sockets)。 后端

效果:这个改变使每一个服务器能够收回几个G的内存。

这个让我想起了云风大哥的“Ring Buffer 的应用”这篇博客。http://blog.codingnow.com/2012/02/ring_buffer.html

2. 网络流量优化(及由此引发的UDP套接字锁竞争)

优化目的:使用UDP代替TCP,让get(获取)操做能下降网络流量、让multi-get(同时并行地获取几百个键值)能实现应用程序级别的流量控制。

新问题:咱们发现Linux上到了必定负载以后,UDP的性能降低地很厉害。 缓存

新问题缘由:当从多个线程经过单个套接字传递数据时,在UDP套接字锁上产生的大量锁竞争(considerable lock contention)致使的。

新问题方案:要经过分离锁来修复代码核心不太容易。因此,咱们使用了分离的UDP套接字来“传递回复”(每一个线程用一个回复套接字)。 服务器

新问题解决效果:这样改动以后,咱们就能够部署UDP同时后端性能不打折。

3. 网络IO优化:

问题:(1)Linux中的问题是到了必定负载后,某个核心可能因进行网络软终端处理会饱和而限制了网络IO。(2)特定的网卡有特别高的中断频率。 网络

问题(1)的缘由:在Linux中,网络中断只会老是传递给某个核心,所以全部接收到的软中断网络处理都发生在该核心上。 多线程

解决:咱们经过引入网络接口的“投机”轮询(“opportunistic” polling of the network interfaces)解决了这两个问题。在该模型中,咱们综合了中断驱动和轮询驱动的网络IO。一旦进入网络驱动(一般是传输一个数据包时)以及在进程调度器的空闲循环的时候,对网络接口进行轮询。另外,咱们仍是用到了中断(来控制延迟),不过用到的网络中断数量相比大大减小了(通常经过大幅度提高中断联结阈值interrupt coalescing thresholds)。

效果:因为咱们在每一个核心(core)上进行网络传输,同时因为在调度器的空闲循环中对网络IO进行轮询,咱们将网络处理均匀地分散到每一个核心(core)上。

4. 8核机器优化:

(1) stat收集

问题:memcached的stat收集依赖于一个全局锁。这在4核上已经很使人讨厌了,在8核上,这个锁能够占用20-30%的CPU使用率。

方案:咱们经过将stat收集移入每一个线程,而且须要的时候将结果聚合起来。

(2) UDP线程不能scale

问题:随着传输UDP数据包的线程数量的增长,性能却在下降。

缘由:保护每一个网络设备的传送队列的锁上发现了严重的争用(significant contention)。传输时将数据包入队,而后设备驱动进行出队操做。该队列由Linux的“netdevice”层来管理,它位于IP和设备驱动之间。每次只能有一个数据包加入或移出队列,这形成了严重的争用(causing significant contention)。

方案:一位工程师修改了出队算法以达到传输时候的批量出队,去掉了队列锁(drop the queue lock)。这样就能够批量传送数据包了。

效果:将锁请求(the lock acquisition)的开销平摊到了许多个数据包上,显著地减小了锁争用(reduces lock contention significantly),这样咱们就能在8核系统上将memcached伸展至8线程。

最终优化效果:
“作了这些修改以后,咱们能够将Memcached提高到每秒处理20万个UDP请求,平均延迟下降为173微秒。能够达到的总吞吐量为30万UDP请求/s,不过在这个请求速度上的延迟过高,所以在咱们的系统中用处不大。对于普通版本的Linux和Memcached上的50,000 UDP请求/s而言,这是个了不得的提高。”

20万/s相对于5万,这个性能提高太吓人了!!!

不过Memcached源码doc目录下的thread.txt也有这样的一句话:

Due to memcached's nonblocking architecture, there is no real advantage to using more threads than the number of CPUs on the machine; doing so will increase lock contention and is likely to degrade performance.

而后还提到了当前的锁的粒度比较粗,对于在大规模并行的机器上运行仍是有不少优化空间的。也提到了全部客户端都共享一个UDP套接字。

因此使用开源软件以前仍是最好先阅读下它的相关文档,ChangeNodes,TODO等。好比这里就有一个教训:http://blog.codingnow.com/2009/06/tcc_bug.html  花了不少时间确认了Tcc的一个bug,可是后来才发现,这个bug早就在TODO文件里面了。

相关文章
相关标签/搜索