<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Tim[后端技术] &#187; memcached</title>
	<atom:link href="http://timyang.net/tag/memcached/feed/" rel="self" type="application/rss+xml" />
	<link>http://timyang.net</link>
	<description>Tim&#039;s blog, 关于后端架构、互联网技术、分布式、大型网络应用、NoSQL、Key Value等</description>
	<lastBuildDate>Mon, 26 Jul 2010 15:32:05 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.1</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Memcache mutex设计模式</title>
		<link>http://timyang.net/programming/memcache-mutex/</link>
		<comments>http://timyang.net/programming/memcache-mutex/#comments</comments>
		<pubDate>Mon, 26 Jul 2010 15:08:00 +0000</pubDate>
		<dc:creator>Tim</dc:creator>
				<category><![CDATA[编程]]></category>
		<category><![CDATA[cache]]></category>
		<category><![CDATA[memcache]]></category>
		<category><![CDATA[memcached]]></category>
		<category><![CDATA[mutex]]></category>

		<guid isPermaLink="false">http://timyang.net/?p=639</guid>
		<description><![CDATA[周六的S2 Web 2.0技术沙龙上介绍了memcache中使用mutex场景(文后要演讲稿)，有网友对详情感兴趣，简单介绍如下。
场景
Mutex主要用于有大量并发访问并存在cache过期的场合，如

首页top 10, 由数据库加载到memcache缓存n分钟
微博中名人的content cache, 一旦不存在会大量请求不能命中并加载数据库
需要执行多个IO操作生成的数据存在cache中, 比如查询db多次

问题
在大并发的场合，当cache失效时，大量并发同时取不到cache，会同一瞬间去访问db并回设cache，可能会给系统带来潜在的超负荷风险。我们曾经在线上系统出现过类似故障。
解决方法
方法一
在load db之前先add一个mutex key, mutex key add成功之后再去做加载db, 如果add失败则sleep之后重试读取原cache数据。为了防止死锁，mutex key也需要设置过期时间。伪代码如下
(注：下文伪代码仅供了解思路，可能存在bug，欢迎随时指出。)
if (memcache.get(key) == null) {
    // 3 min timeout to avoid mutex holder crash
    if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {
        value = db.get(key);
  [...]]]></description>
			<content:encoded><![CDATA[<p>周六的S2 <a href="http://www.s2forum.org/1/topic/">Web 2.0技术沙龙</a>上介绍了memcache中使用mutex场景(文后要演讲稿)，有网友对详情感兴趣，简单介绍如下。</p>
<h3>场景</h3>
<p>Mutex主要用于有大量并发访问并存在cache过期的场合，如</p>
<ul>
<li>首页top 10, 由数据库加载到memcache缓存n分钟</li>
<li>微博中名人的content cache, 一旦不存在会大量请求不能命中并加载数据库</li>
<li>需要执行多个IO操作生成的数据存在cache中, 比如查询db多次</li>
</ul>
<h3>问题</h3>
<p>在大并发的场合，当cache失效时，大量并发同时取不到cache，会同一瞬间去访问db并回设cache，可能会给系统带来潜在的超负荷风险。<strong>我们曾经在线上系统出现过类似故障</strong>。</p>
<h3>解决方法</h3>
<p><strong>方法一</strong><br />
在load db之前先add一个mutex key, mutex key add成功之后再去做加载db, 如果add失败则sleep之后重试读取原cache数据。为了防止死锁，mutex key也需要设置过期时间。伪代码如下<br />
(<em>注：下文伪代码仅供了解思路，可能存在bug，欢迎随时指出。</em>)</p>
<pre>if (memcache.get(key) == null) {
    // 3 min timeout to avoid mutex holder crash
    if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {
        value = db.get(key);
        memcache.set(key, value);
        memcache.delete(key_mutex);
    } else {
        sleep(50);
        retry();
    }
}</pre>
<p><strong>方法二</strong><br />
在value内部设置1个超时值(timeout1), timeout1比实际的memcache timeout(timeout2)小。当从cache读取到timeout1发现它已经过期时候，马上延长timeout1并重新设置到cache。然后再从数据库加载数据并设置到cache中。伪代码如下</p>
<pre>v = memcache.get(key);
if (v == null) {
    if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {
        value = db.get(key);
        memcache.set(key, value);
        memcache.delete(key_mutex);
    } else {
        sleep(50);
        retry();
    }
} else {
    if (v.timeout &lt;= now()) {
        if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {
            // extend the timeout for other threads
            v.timeout += 3 * 60 * 1000;
            memcache.set(key, v, KEY_TIMEOUT * 2);

            // load the latest value from db
            v = db.get(key);
            v.timeout = KEY_TIMEOUT;
            memcache.set(key, value, KEY_TIMEOUT * 2);
            memcache.delete(key_mutex);
        } else {
            sleep(50);
            retry();
        }
    }
}</pre>
<p>相对于方案一<br />
优点：避免cache失效时刻大量请求获取不到mutex并进行sleep<br />
缺点：代码复杂性增大，因此一般场合用方案一也已经足够。</p>
<p>方案二在Memcached FAQ中也有详细介绍 <a href="http://code.google.com/p/memcached/wiki/FAQ#How_to_prevent_clobbering_updates,_stampeding_requests">How to prevent clobbering updates, stampeding requests</a>，并且Brad还介绍了用他另外一个得意的工具 Gearman 来实现单实例设置cache的方法，见 <a href="http://lists.danga.com/pipermail/memcached/2007-July/004858.html">Cache miss stampedes</a>，不过用Gearman来解决就感觉就有点奇技淫巧了。</p>
<p>附：本次Web2.0技术沙龙演讲主题：微博Cache设计谈，需下载请点击演讲稿下menu/download (需登录slideshare)。</p>
<div id="__ss_4842490" style="width: 425px;"><strong><a title="微博cache设计谈" href="http://www.slideshare.net/iso1600/cache-4842490">微博cache设计谈</a></strong><object id="__sse4842490" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="425" height="355" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"><param name="allowFullScreen" value="true" /><param name="allowScriptAccess" value="always" /><param name="src" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=s2weibocachearch-100726101949-phpapp02&amp;stripped_title=cache-4842490" /><param name="name" value="__sse4842490" /><param name="allowfullscreen" value="true" /><embed id="__sse4842490" type="application/x-shockwave-flash" width="425" height="355" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=s2weibocachearch-100726101949-phpapp02&amp;stripped_title=cache-4842490" name="__sse4842490" allowscriptaccess="always" allowfullscreen="true"></embed></object></p>
<div style="padding: 5px 0 12px;">View more <a href="http://www.slideshare.net/">presentations</a> from <a href="http://www.slideshare.net/iso1600">Tim Y</a>.</div>
</div>
Similar Posts:<ul><li><a href="http://timyang.net/data/memcached-lru-evictions/" rel="bookmark" title="September 7, 2009">Memcached数据被踢(evictions>0)现象分析</a></li>

<li><a href="http://timyang.net/sns/web20-forum/" rel="bookmark" title="June 6, 2010">Web 2.0技术沙龙设想</a></li>

<li><a href="http://timyang.net/architecture/twitter-cache-architecture/" rel="bookmark" title="October 28, 2009">Twitter架构图(cache篇)</a></li>

<li><a href="http://timyang.net/architecture/farmville/" rel="bookmark" title="March 8, 2010">FarmVille(美版开心农场)谈架构:所有模块都是一个可降级的服务</a></li>

<li><a href="http://timyang.net/data/mcdb-tt-redis/" rel="bookmark" title="August 11, 2009">MemcacheDB, Tokyo Tyrant, Redis performance test</a></li>
</ul><!-- Similar Posts took 10.054 ms -->]]></content:encoded>
			<wfw:commentRss>http://timyang.net/programming/memcache-mutex/feed/</wfw:commentRss>
		<slash:comments>14</slash:comments>
		</item>
		<item>
		<title>Twitter“鲸鱼”故障技术剖析</title>
		<link>http://timyang.net/tech/twitter-whale/</link>
		<comments>http://timyang.net/tech/twitter-whale/#comments</comments>
		<pubDate>Mon, 08 Mar 2010 03:56:53 +0000</pubDate>
		<dc:creator>Tim</dc:creator>
				<category><![CDATA[tech]]></category>
		<category><![CDATA[memcached]]></category>
		<category><![CDATA[twitter]]></category>

		<guid isPermaLink="false">http://timyang.net/?p=564</guid>
		<description><![CDATA[很多人都熟悉Twitter访问故障时候那条白色的鲸鱼。今年新推出的Twitter Engineering Blog讲述了Twitter白鲸技术故障的原因及解决思路。这是到目前为止Twitter公开的最底层的一篇技术资料。
http://engineering.twitter.com/2010/02/anatomy-of-whale.html
当Web Server发生503错误后，Twitter配置了一个前端鲸鱼的显示页面。Twitter对鲸鱼页面有监控体系，当每秒超过100个鲸鱼就会引起报警。

为什么在单位时间内会有大量的&#8221;fail whale&#8221;呢？Twitter成立了一个小组来专门分析此原因。
1. 分析背景资料
“分析性能问题不是一门科学，而是一门艺术”。
鲸鱼页面实际上是对HTTP 503错误的前端展示，503错误通常是调用后台请求超时产生，为了避免用户长时间等待，Twitter的前端(Tim: 也可能是HTTP反向代理)给请求加了超时，避免用户无限制的等待。超时通常是由于单位时间内访问的用户数过大，也有可能是后台某个服务突然变慢造成。
由于Twitter网站每个时刻都有海量的数据流过，因此要简单的定位并解决此问题并不容易。
2. Web page请求分解
Twitter的页面请求后端分成2个阶段，在Twitter内部称为IO phase及CPU phase。IO phase指通过网络服务获取用户的关注关系及相关的Tweets。第2阶段为CPU phase，指将数据聚合、排序及按用户请求的条件输出。IO及CPU各自在1天内消耗的时间如下。

从图上看到，latency增大时IO是主要瓶颈。IO对应于Network service，因此可以判断是某个网络服务性能降级造成。
3. 深度分析
理想情况是网络服务在应答相同参数的请求消耗时间应该基本相同。但实际情况并非如此，我们大胆假设某一网络服务性能下降厉害，于是我们就从统计分析中去寻找这个服务，我们看到Memcached的统计图表如下

4. Memcached 竟然是鲸鱼故障的直接原因
可提高的空间及解决思路

 从上图看，Memcached在 latency高峰的性能比低谷相差一倍，因此最简单的判断是增加硬件即可提高50%的性能。
另外一种思路就是优化Memcached程序，判断程序热点和瓶颈并进行优化。

分析

通过 Google perf-tools project 工具来分析, http://code.google.com/p/google-perftools/ http://github.com/tmm1/perftools.rb
通过自己些的一段分析代码来监控 http://github.com/eaceaser/ruby-call-graph
通过上面工具的call graph来分析热点和瓶颈

最后分析数据Memcached请求分布比例如下
get         0.003s
get_multi   0.008s
add         0.003s
delete      0.003s
set         0.003s
incr        0.003s
prepend     0.002s

get         71.44%
get_multi    8.98%
set          8.69%
delete       5.26%
incr         3.71%
add          1.62%
prepend      0.30%

结论：从上面数据来看，调用热点和瓶颈主要集中在Get操作
因此回头取看Twitter页面执行流程代码，找出优化方法见注释。
get(["User:auth:missionhipster",              # 将昵称转换成uid
get(["User:15460619",                         # 获取user object(用于检查密码)
get(["limit:count:login_attempts:...",        # 防止密码字典攻击
set(["limit:count:login_attempts:...",        # 大部分情况不需要, bug
set(["limit:timestamp:login_attempts:...",    # 大部分情况不需要, bug
get(["limit:timestamp:login_attempts:...",
get(["limit:count:login_attempts:...",        [...]]]></description>
			<content:encoded><![CDATA[<p>很多人都熟悉Twitter访问故障时候那条白色的鲸鱼。今年新推出的<a href="http://engineering.twitter.com/">Twitter Engineering Blog</a>讲述了Twitter白鲸技术故障的原因及解决思路。这是到目前为止Twitter公开的最底层的一篇技术资料。<br />
<a href="http://engineering.twitter.com/2010/02/anatomy-of-whale.html">http://engineering.twitter.com/2010/02/anatomy-of-whale.html</a></p>
<p>当Web Server发生503错误后，Twitter配置了一个前端鲸鱼的显示页面。Twitter对鲸鱼页面有监控体系，当每秒超过100个鲸鱼就会引起报警。<br />
<a href="http://timyang.net/blog/wp-content/uploads/2010/03/whale.png"><img class="alignnone size-full wp-image-565" title="whale" src="http://timyang.net/blog/wp-content/uploads/2010/03/whale.png" alt="" width="391" height="292" /></a></p>
<p>为什么在单位时间内会有大量的&#8221;fail whale&#8221;呢？Twitter成立了一个小组来专门分析此原因。</p>
<h3>1. 分析背景资料</h3>
<blockquote><p>“分析性能问题不是一门科学，而是一门艺术”。</p></blockquote>
<p>鲸鱼页面实际上是对HTTP 503错误的前端展示，503错误通常是调用后台请求超时产生，为了避免用户长时间等待，Twitter的前端(Tim: 也可能是HTTP反向代理)给请求加了超时，避免用户无限制的等待。超时通常是由于单位时间内访问的用户数过大，也有可能是后台某个服务突然变慢造成。<br />
由于Twitter网站每个时刻都有海量的数据流过，因此要简单的定位并解决此问题并不容易。</p>
<h3>2. Web page请求分解</h3>
<p>Twitter的页面请求后端分成2个阶段，在Twitter内部称为IO phase及CPU phase。IO phase指通过网络服务获取用户的关注关系及相关的Tweets。第2阶段为CPU phase，指将数据聚合、排序及按用户请求的条件输出。IO及CPU各自在1天内消耗的时间如下。<br />
<a href="http://timyang.net/blog/wp-content/uploads/2010/03/cpulatency.png"><img class="alignnone size-full wp-image-566" title="cpulatency" src="http://timyang.net/blog/wp-content/uploads/2010/03/cpulatency.png" alt="" width="400" height="285" /></a></p>
<p>从图上看到，latency增大时IO是主要瓶颈。IO对应于Network service，因此可以判断是某个网络服务性能降级造成。</p>
<h3>3. 深度分析</h3>
<p>理想情况是网络服务在应答相同参数的请求消耗时间应该基本相同。但实际情况并非如此，我们大胆假设某一网络服务性能下降厉害，于是我们就从统计分析中去寻找这个服务，我们看到Memcached的统计图表如下<br />
<a href="http://timyang.net/blog/wp-content/uploads/2010/03/networkservice.png"><img class="alignnone size-full wp-image-567" title="networkservice" src="http://timyang.net/blog/wp-content/uploads/2010/03/networkservice.png" alt="" width="400" height="253" /></a></p>
<h3>4. Memcached 竟然是鲸鱼故障的直接原因</h3>
<p>可提高的空间及解决思路</p>
<ol>
<li> 从上图看，Memcached在 latency高峰的性能比低谷相差一倍，因此最简单的判断是增加硬件即可提高50%的性能。</li>
<li>另外一种思路就是优化Memcached程序，判断程序热点和瓶颈并进行优化。</li>
</ol>
<p>分析</p>
<ol>
<li>通过 Google perf-tools project 工具来分析, <a href="http://code.google.com/p/google-perftools/">http://code.google.com/p/google-perftools/</a> <a href="http://github.com/tmm1/perftools.rb">http://github.com/tmm1/perftools.rb</a></li>
<li>通过自己些的一段分析代码来监控 <a href="http://github.com/eaceaser/ruby-call-graph">http://github.com/eaceaser/ruby-call-graph</a></li>
<li>通过上面工具的call graph来分析热点和瓶颈</li>
</ol>
<p>最后分析数据Memcached请求分布比例如下</p>
<pre>get         0.003s
get_multi   0.008s
add         0.003s
delete      0.003s
set         0.003s
incr        0.003s
prepend     0.002s

get         71.44%
get_multi    8.98%
set          8.69%
delete       5.26%
incr         3.71%
add          1.62%
prepend      0.30%
</pre>
<p>结论：从上面数据来看，调用热点和瓶颈主要集中在Get操作</p>
<p>因此回头取看Twitter页面执行流程代码，找出优化方法见注释。</p>
<pre>get(["User:auth:missionhipster",              # 将昵称转换成uid
get(["User:15460619",                         # 获取user object(用于检查密码)
get(["limit:count:login_attempts:...",        # 防止密码字典攻击
set(["limit:count:login_attempts:...",        # 大部分情况不需要, bug
set(["limit:timestamp:login_attempts:...",    # 大部分情况不需要, bug
get(["limit:timestamp:login_attempts:...",
get(["limit:count:login_attempts:...",        # 重复调用，可记住
get(["limit:count:login_attempts:...",        # 重复调用
get(["user:basicauth:...",                    # 防止解密的优化
get(["limit:count:api:...",                   # 请求数限制
set(["limit:count:api:...",                   # 设置请求数，大部分情况不需要，为什么？
set(["limit:timestamp:api:...",               # 大部分情况不需要, bug
get(["limit:timestamp:api:...",
get(["limit:count:api:...",                   # 重复调用
get(["home_timeline:15460619",                # home_timeline业务调用
get(["favorites_timeline:15460619",           # favorites_timeline业务调用
get_multi([["Status:fragment:json:74736693",  # multi_get所有tweets内容
</pre>
<p>上面这段代码将17个请求优化成10个，部分重复调用通过本地cache避免，另外一些没必要的调用直接删除。通过一个简单的优化性能就提高了42%。</p>
<h3>结论</h3>
<ol>
<li>在前文<a href="http://timyang.net/architecture/2010-tech-predictions/">2010年的技术架构建议</a>中提过Cache已经是Web 2.0系统核心元素。从Twitter的故障案例来看Memcached竟然成为了瓶颈并导致了Twitter服务的不稳定。由于在social应用中cache核心化的设计，“RAM is the new disk”，在cache广泛使用后也变得调用成本增加，需要考虑进行系统的规划减少不必要的调用。避免开发人员在代码中随意使用cache</li>
<li>如何定位瓶颈，可以借鉴Google perf-tools项目及上面其他分析工具的思路。</li>
<li>Twitter页面执行流程值得参考</li>
<li>整个故障流程分析图如下</li>
</ol>
<p><a href="http://timyang.net/blog/wp-content/uploads/2010/03/twitter_whale_error.png"><img class="alignnone size-full wp-image-569" title="twitter_whale_error" src="http://timyang.net/blog/wp-content/uploads/2010/03/twitter_whale_error.png" alt="" width="412" height="701" /></a></p>
Similar Posts:<ul><li><a href="http://timyang.net/architecture/twitter-cache-architecture/" rel="bookmark" title="October 28, 2009">Twitter架构图(cache篇)</a></li>

<li><a href="http://timyang.net/web/pagination/" rel="bookmark" title="January 19, 2010">用Twitter的cursor方式进行Web数据分页</a></li>

<li><a href="http://timyang.net/data/mcdb-tt-redis/" rel="bookmark" title="August 11, 2009">MemcacheDB, Tokyo Tyrant, Redis performance test</a></li>

<li><a href="http://timyang.net/sns/twitter-api-changes/" rel="bookmark" title="December 30, 2009">Twitter API最近的一些飞跃</a></li>

<li><a href="http://timyang.net/tech/twitter-operations/" rel="bookmark" title="November 2, 2009">Twitter系统运维经验</a></li>
</ul><!-- Similar Posts took 11.874 ms -->]]></content:encoded>
			<wfw:commentRss>http://timyang.net/tech/twitter-whale/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Twitter系统运维经验</title>
		<link>http://timyang.net/tech/twitter-operations/</link>
		<comments>http://timyang.net/tech/twitter-operations/#comments</comments>
		<pubDate>Mon, 02 Nov 2009 12:46:49 +0000</pubDate>
		<dc:creator>Tim</dc:creator>
				<category><![CDATA[tech]]></category>
		<category><![CDATA[cache]]></category>
		<category><![CDATA[memcached]]></category>
		<category><![CDATA[twitter]]></category>
		<category><![CDATA[velocity]]></category>

		<guid isPermaLink="false">http://timyang.net/?p=463</guid>
		<description><![CDATA[最近看到的另外一个介绍Twitter技术的视频[Slides] [Video (GFWed)]，这是Twitter的John Adams在Velocity 2009的一个演讲，主要介绍了Twitter在系统运维方面一些经验。 本文大部分整理的观点都在Twitter(@xmpp)上发过，这里全部整理出来并补充完整。
Twitter没有自己的硬件，都是由NTTA来提供，同时NTTA负责硬件相关的网络、带宽、负载均衡等业务，Twitter operations team只关注核心的业务，包括Performance，Availability，Capacity Planning容量规划，配置管理等，这个可能跟国内一般的互联网公司有所区别。
1. 运维经验
* Metrics
Twitter的监控后台几乎都是图表(critical metrics)，类似驾驶室的转速表，时速表，让操作者可以迅速的了解系统当前的运作状态。联想到我们做的类似监控后台，数据很多，但往往还需要浏览者做二次分析判断，像这样满屏都是图表的方法做得还不够，可以学习下这方面经验。 据John介绍可以从图表上看到系统的瓶颈-系统最弱的环节(web, mq, cache, db?)
根据图表可以科学的制定系统容量规划，而不是事后救火。
* 配置管理
每个系统都需要一个自动配置管理系统，越早越好，这条一整理发到Twitter上去之后引起很多回应。
* Darkmode
配置界面可以enable/disable 高计算消耗或高I/O的功能，也相当于优雅降级，系统压力过大时取消一些非核心但消耗资源大的功能。
* 进程管理
Twitter做了一个&#8221;Seppaku&#8221; patch, 就是将Daemon在完成了n个requests之后主动kill掉，以保持健康的low memory状态，这种做法据了解国内也有不少公司是这样做。
* 硬件
Twitter将CPU由AMD换成Xeon之后，获得30%性能提升，将CPU由双核/4核换成8核之后，减少了40%的CPU, 不过John也说，这种升级不适合自己购买硬件的公司。
2. 代码协同经验
* Review制度
Twitter有上百个模块，如果没有一个好的制度，容易引起代码修改冲突，并把问题带给最终用户。所以Twitter有一强制的source code review制度, 如果提交的代码的svn comment没有&#8221;reviewed by xxx&#8221;, 则pre-commit脚本会让提交失败, review过的代码提交后会通过自动配置管理系统应用到上百台服务器上。 有@xiaomics同学在Twitter上马上就问，时间成本能否接受？如果有紧急功能怎么办？个人认为紧急修改时有两人在场，一人修改一人review也不是什么难事。
* 部署管理
从部署图表可以看到每个发布版本的CPU及latency变化，如果某个新版本latency图表有明显的向上跳跃，则说明该发布版本存在问题。另外在监控首页列出各个模块最后deploy版本的时间，可以清楚的看到代码库的现状。
* 团队沟通
Campfire来协同工作，campfire有点像群，但是更适合协同工作。对于Campfire就不做更多介绍，可参考Campfire官方说明。
3. cache

Memcache key hash, 使用FNV hash 代替 MD5 hash，因为FNV更快。
开发了Cache Money plugin(Ruby), 给应用程序提供read-through, write-through cache, 就像一个db访问的钩子，当读写数据库的时候会自动更新cache, 避免了繁琐的cache更新代码。
“Evictions make the [...]]]></description>
			<content:encoded><![CDATA[<p>最近看到的另外一个介绍Twitter技术的视频[<a href="http://assets.en.oreilly.com/1/event/29/Fixing%20Twitter_%20Improving%20the%20Performance%20and%20Scalability%20of%20the%20World%27s%20Most%20Popular%20Micro-blogging%20Site%20Presentation.pdf">Slides</a>] [<a href="http://blip.tv/file/2300327">Video</a> (GFWed)]，这是Twitter的John Adams在<a href="http://en.oreilly.com/velocity2009">Velocity 2009</a>的一个演讲，主要介绍了Twitter在系统运维方面一些经验。 本文大部分整理的观点都在Twitter(@<a href="http://twitter.com/xmpp">xmpp</a>)上发过，这里全部整理出来并补充完整。</p>
<p>Twitter没有自己的硬件，都是由NTTA来提供，同时NTTA负责硬件相关的网络、带宽、负载均衡等业务，Twitter operations team<strong>只关注核心的业务，包括Performance，Availability，Capacity Planning容量规划，配置管理</strong>等，这个可能跟国内一般的互联网公司有所区别。</p>
<h3>1. 运维经验</h3>
<h4>* Metrics</h4>
<p>Twitter的监控后台几乎都是图表(critical metrics)，类似驾驶室的转速表，时速表，让操作者可以迅速的了解系统当前的运作状态。联想到我们做的类似监控后台，数据很多，但往往还需要浏览者做二次分析判断，像这样满屏都是图表的方法做得还不够，可以学习下这方面经验。 据John介绍可以从图表上看到系统的瓶颈-系统最弱的环节(web, mq, cache, db?)<br />
根据图表可以科学的制定系统容量规划，而不是事后救火。<img class="alignnone size-full wp-image-464" title="Twitter operation dashboard" src="http://timyang.net/blog/wp-content/uploads/2009/11/dashboard.jpg" alt="Twitter operation dashboard" width="543" height="488" /></p>
<h4>* 配置管理</h4>
<p>每个系统都需要一个自动配置管理系统，越早越好，这条一整理发到Twitter上去之后引起很多回应。</p>
<h4>* Darkmode</h4>
<p>配置界面可以enable/disable 高计算消耗或高I/O的功能，也相当于优雅降级，系统压力过大时取消一些非核心但消耗资源大的功能。</p>
<h4>* 进程管理</h4>
<p>Twitter做了一个&#8221;Seppaku&#8221; patch, 就是将Daemon在完成了n个requests之后主动kill掉，以保持健康的low memory状态，这种做法据了解国内也有不少公司是这样做。</p>
<h4>* 硬件</h4>
<p>Twitter将CPU由AMD换成Xeon之后，获得30%性能提升，将CPU由双核/4核换成8核之后，减少了40%的CPU, 不过John也说，这种升级不适合自己购买硬件的公司。</p>
<h3>2. 代码协同经验</h3>
<h4>* Review制度</h4>
<p>Twitter有上百个模块，如果没有一个好的制度，容易引起代码修改冲突，并把问题带给最终用户。所以Twitter有一强制的source code review制度, 如果提交的代码的svn comment没有&#8221;reviewed by xxx&#8221;, 则pre-commit脚本会让提交失败, review过的代码提交后会通过自动配置管理系统应用到上百台服务器上。 有@xiaomics同学在Twitter上马上就问，时间成本能否接受？如果有紧急功能怎么办？个人认为紧急修改时有两人在场，一人修改一人review也不是什么难事。</p>
<h4>* 部署管理</h4>
<p>从部署图表可以看到每个发布版本的CPU及latency变化，如果某个新版本latency图表有明显的向上跳跃，则说明该发布版本存在问题。另外在监控首页列出各个模块最后deploy版本的时间，可以清楚的看到代码库的现状。</p>
<h4>* 团队沟通</h4>
<p>Campfire来协同工作，campfire有点像群，但是更适合协同工作。对于Campfire就不做更多介绍，可参考<a href="http://campfirenow.com/">Campfire</a>官方说明。</p>
<h3>3. cache</h3>
<ul>
<li>Memcache key hash, 使用FNV hash 代替 MD5 hash，因为FNV更快。</li>
<li>开发了Cache Money plugin(Ruby), 给应用程序提供<strong>read-through, write-through cache</strong>, 就像一个db访问的钩子，当读写数据库的时候会自动更新cache, 避免了繁琐的cache更新代码。</li>
<li>“Evictions make the cache unreliable for important configuration data”，Twitter使用memcache的一条经验是，不同类型的数据需放在不同的mc,避免eviction，跟作者前文<a href="http://timyang.net/data/memcached-lru-evictions/">Memcached数据被踢(evictions&gt;0)现象分析</a>中的一些经验一致。</li>
<li>Memcached SEGVs, Memcached崩溃(cold cache problem)据称会给这种高度依赖Cache的Web 2.0系统带来灾难，不知道Twitter具体怎么解决。</li>
<li>在Web层Twitter使用了Varnish作为反向代理，并对其评价较高。</li>
</ul>
Similar Posts:<ul><li><a href="http://timyang.net/architecture/twitter-cache-architecture/" rel="bookmark" title="October 28, 2009">Twitter架构图(cache篇)</a></li>

<li><a href="http://timyang.net/tech/twitter-whale/" rel="bookmark" title="March 8, 2010">Twitter“鲸鱼”故障技术剖析</a></li>

<li><a href="http://timyang.net/programming/memcache-mutex/" rel="bookmark" title="July 26, 2010">Memcache mutex设计模式</a></li>

<li><a href="http://timyang.net/architecture/warehouse-scale-computer/" rel="bookmark" title="May 24, 2009">Google说，一个Datacenter就是一台计算机</a></li>

<li><a href="http://timyang.net/data/twitter-cassandra/" rel="bookmark" title="July 12, 2010">Twitter停用Cassandra原因分析</a></li>
</ul><!-- Similar Posts took 10.924 ms -->]]></content:encoded>
			<wfw:commentRss>http://timyang.net/tech/twitter-operations/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Memcached数据被踢(evictions&gt;0)现象分析</title>
		<link>http://timyang.net/data/memcached-lru-evictions/</link>
		<comments>http://timyang.net/data/memcached-lru-evictions/#comments</comments>
		<pubDate>Mon, 07 Sep 2009 14:17:36 +0000</pubDate>
		<dc:creator>Tim</dc:creator>
				<category><![CDATA[data]]></category>
		<category><![CDATA[evictions]]></category>
		<category><![CDATA[LRU]]></category>
		<category><![CDATA[memcached]]></category>

		<guid isPermaLink="false">http://timyang.net/?p=418</guid>
		<description><![CDATA[很多同学可能熟知Memcached的LRU淘汰算法，它是在slab内部进行的，如果所有空间都被slabs分配，即使另外一个slab里面有空位，仍然存在踢数据可能。你可以把slab理解为教室，如果你的教室满了，即使别的教室有空位你的教室也只能踢人才能进人。

本文介绍的却是另外一种现象。今天监控发现线上一memcached发生数据被踢现象，用stats命令看evictions&#62;0,因为以前也出现过此问题，后来对这个参数增加了一个监控，所以这次主动就发现了。由于给memcached分配的内存远大于业务存储数据所需内存，因此初步判断是“灵异现象”。
第一步，netstat查看所有连接，排除是否被一些未规划的client使用，经排查后断定无此可能。
第二步，用tcpdump抽样检查set的指令，排除是否有忘记设cache过期时间的client，初步检查所有典型的业务都有expire time。
第三步，Google，未果
第四步，看源代码，了解evictions计数器增加时的具体细节，oh, no&#8230;
in items.c, memcached-1.2.8,
125         for (search = tails[id]; tries &#62; 0 &#38;&#38; search != NULL; tries--, search=search-&#62;prev) {
126             if (search-&#62;refcount == 0) {
127            [...]]]></description>
			<content:encoded><![CDATA[<p>很多同学可能熟知Memcached的LRU淘汰算法，它是在slab内部进行的，如果所有空间都被slabs分配，即使另外一个slab里面有空位，仍然存在踢数据可能。你可以把slab理解为教室，如果你的教室满了，即使别的教室有空位你的教室也只能踢人才能进人。</p>
<p><img class="alignnone size-full wp-image-422" title="mc" src="http://timyang.net/blog/wp-content/uploads/2009/09/mc.png" alt="mc" width="541" height="196" /></p>
<p>本文介绍的却是另外一种现象。今天监控发现线上一memcached发生数据被踢现象，用stats命令看evictions&gt;0,因为以前也出现过此问题，后来对这个参数增加了一个监控，所以这次主动就发现了。由于给memcached分配的内存远大于业务存储数据所需内存，因此初步判断是“灵异现象”。</p>
<p>第一步，netstat查看所有连接，排除是否被一些未规划的client使用，经排查后断定无此可能。</p>
<p>第二步，用tcpdump抽样检查set的指令，排除是否有忘记设cache过期时间的client，初步检查所有典型的业务都有expire time。</p>
<p>第三步，Google，未果</p>
<p>第四步，看源代码，了解evictions计数器增加时的具体细节，oh, no&#8230;</p>
<p>in items.c, memcached-1.2.8,</p>
<pre>125         for (search = tails[id]; tries &gt; 0 &amp;&amp; search != NULL; tries--, search=search-&gt;prev) {
126             if (search-&gt;refcount == 0) {
127                 if (search-&gt;exptime == 0 || search-&gt;exptime &gt; current_time) {
128                     itemstats[id].evicted++;
129                     itemstats[id].evicted_time = current_time - search-&gt;time;
130                     STATS_LOCK();
131                     stats.evictions++;
132                     STATS_UNLOCK();
133                 }
134                 do_item_unlink(search);
135                 break;
136             }
137         }</pre>
<p>从源代码发现踢数据只判断一个条件，if (search-&gt;refcount == 0)，这个refcount是多线程版本计数用，在当前服务器未启用多线程情况下，refcount应该始终为0,因此初步判断memcached是从访问队列尾部直接踢数据。</p>
<p>为了证实想法，设计以下场景：</p>
<ol>
<li>部署一个memcached测试环境，分配比较小的内存，比如8M</li>
<li>设置1条永远不过期的数据到memcached中，然后再get一次，这条数据后续应该存在LRU队尾。</li>
<li>每隔1S向memcached set(并get一次) 1,000条数据，过期时间设为3秒。</li>
<li>一段时间后，stats命令显示evictions=1</li>
</ol>
<p>按我以前的理解，第2步的数据是永远不会被踢的，因为有足够过期的数据空间可以给新来的数据用，LRU淘汰算法应该跳过没过期的数据，但结果证实这种判断是错误的。以上业务的服务器发生被踢的现象是由于保存了大量存活期短的key/value,且key是不重复的。另外又有一业务保存了小量不过期的数据，因此导致不过期的数据惨遭被挤到队列踢出。</p>
<p>本来这个问题就告一段落了，但在写完这篇文章后，顺便又看了新一代memcached 1.4.1的源代码，很惊喜发现以下代码被增加。</p>
<p>items.c, memcached 1.4.1</p>
<pre>107     /* do a quick check if we have any expired items in the tail.. */
108     int tries = 50;
109     item *search;
110
111     for (search = tails[id];
112          tries &gt; 0 &amp;&amp; search != NULL;
113          tries--, search=search-&gt;prev) {
114         if (search-&gt;refcount == 0 &amp;&amp;
115             (search-&gt;exptime != 0 &amp;&amp; search-&gt;exptime &lt; current_time)) {
116             it = search;
117             /* I don't want to actually free the object, just steal
118              * the item to avoid to grab the slab mutex twice <img src='http://timyang.net/blog/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' />
119              */
120             it-&gt;refcount = 1;
121             do_item_unlink(it);
122             /* Initialize the item block: */
123             it-&gt;slabs_clsid = 0;
124             it-&gt;refcount = 0;
125             break;
126         }
127     }</pre>
<p>重复进行上述测试，未发生evictions。</p>
<p>9/8 Update: 注意到L108的tries=50没有？试想把测试第2步设置51条不过期数据到cache中，情况会怎样？因此新版的Memcached也同样存在本文描述问题。</p>
<p>几条总结：</p>
<ul>
<li> 过期的数据如果没被显式调用get，则也要占用空间。</li>
<li>过期的不要和不过期的数据存在一起，否则不过期的可能被踢。</li>
<li>从节约内存的角度考虑，即使数据会过期，也不要轻易使用随机字符串作为key，尽量使用定值如uid，这样占用空间的大小相对固定。</li>
<li>估算空间大小时候请用slab size计算，不要按value长度去计算。</li>
<li>不要把cache当作更快的key value store来用, cache不是storage。</li>
</ul>
Similar Posts:<ul><li><a href="http://timyang.net/programming/memcache-mutex/" rel="bookmark" title="July 26, 2010">Memcache mutex设计模式</a></li>

<li><a href="http://timyang.net/architecture/twitter-cache-architecture/" rel="bookmark" title="October 28, 2009">Twitter架构图(cache篇)</a></li>

<li><a href="http://timyang.net/tech/twitter-operations/" rel="bookmark" title="November 2, 2009">Twitter系统运维经验</a></li>

<li><a href="http://timyang.net/tech/twitter-whale/" rel="bookmark" title="March 8, 2010">Twitter“鲸鱼”故障技术剖析</a></li>

<li><a href="http://timyang.net/data/mcdb-tt-redis/" rel="bookmark" title="August 11, 2009">MemcacheDB, Tokyo Tyrant, Redis performance test</a></li>
</ul><!-- Similar Posts took 13.703 ms -->]]></content:encoded>
			<wfw:commentRss>http://timyang.net/data/memcached-lru-evictions/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
	</channel>
</rss>
