找回密码
 用户注册

QQ登录

只需一步,快速开始

查看: 3908|回复: 0

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

[复制链接]
发表于 2012-2-7 10:09:42 | 显示全部楼层 |阅读模式
这里有英文原文:http://www.acejoy.com/thread-3761-1-1.html
Memcached是一个高性能、分布式的内存对象缓存系统。Facebook利用Memcached来减轻数据库的负担,可能是世界上最大的Memcached用户了(使用了超过800台服务器,提供超过28TB的内存来服务于用户)。


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


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文件里面了。:D



作者:archimedes_zht 发表于2012-2-6 23:36:47 原文链接


您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

Archiver|手机版|小黑屋|ACE Developer ( 京ICP备06055248号 )

GMT+8, 2024-5-2 06:14 , Processed in 0.016864 second(s), 6 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表