【转载】Memcached二三事儿

Memcached 绝对称得上是NoSQL老兵! 可惜随着时间的推移,Redis等后起之秀羽翼渐丰,Memcached相比之下已呈颓势。那我们还用不用学习它?答案是肯定的!毕竟仍然有很多项目依赖着它,如果忽视它,一旦出了问题就只有干瞪眼的份儿了。

网络上关于Memcached的资料可以说是浩如烟海,其中不乏一些精彩之作,比如说由爱好者翻译的「Memcached全面剖析」系列文章,在中文社区广为流传,虽然已经是几年前的文章了,但是即便现在读起来,依然感觉收获良多,推荐大家多看几遍:

当然, 官方Wiki 永远是最权威的资料,即便是里面的 ReleaseNotes 也不要放过。实际应用Memcached时,我们遇到的很多问题都是因为不了解其内存分配机制所致,下面就让我们以此为开端来开始Memcached之旅吧!

为了规避内存碎片问题,Memcached采用了名为SlabAllocator的内存分配机制。内存以Page为单位来分配,每个Page分给一个特定长度的Slab来使用,每个Slab包含若干个特定长度的Chunk。实际保存数据时,会根据数据的大小选择一个最贴切的Slab,并把数据保存在对应的Chunk中。如果某个Slab没有剩余的Chunk了,系统便会给这个Slab分配一个新的Page以供使用,如果没有Page可用,系统就会触发LRU机制,通过删除冷数据来为新数据腾出空间,这里有一点需要注意的是:LRU不是全局的,而是针对Slab而言的。

一个Slab可以有多个Page,这就好比在古代一个男人可以娶多个女人;一旦一个Page被分给某个Slab后,它便对Slab至死不渝,犹如古代那些贞洁的女人。但是女人的数量毕竟是有限的,所以一旦一些男人娶得多了,必然另一些男人就只剩下咽口水的份儿,这在很大程度上增加了社会的不稳定因素,于是乎我们要解放女性。

好在Memcached已经意识到解放女性的重要性,新版本中Page可以调配给其它的Slab:

1 shell> memcached -o slab_reassign,slab_automove

换句话说:女人可以改嫁了!这方面,其实Memcached的儿子Twemcache革命得更彻底,他甚至写了一篇大字报,以事实为依据,痛斥老子的无能,有兴趣的可以继续阅读:Random Eviciton vs Slab Automove

了解Memcached内存使用情况的最佳工具是:Memcached-tool。如果我们发现某个Slab的Evicted不为零,则说明这个Slab已经出现了LRU的情况,这通常是个危险的信号,但也不能一概而论,需要结合Evict_Time来做进一步判断。

在Memcached的使用过程中,除了会遇到内存分配机制相关的问题,还有很多稀奇古怪的问题等着你呢,下面我选出几个有代表性的问题来逐一说明:

Cache失效后的拥堵问题

通常我们会为两种数据做Cache,一种是热数据,也就是说短时间内有很多人访问的数据;另一种是高成本的数据,也就说查询很很耗时的数据。当这些数据过期的瞬间,如果大量请求同时到达,那么它们会一起请求后端重建Cache,造成拥堵问题,就好象在北京上班做地铁似的,英文称之为:stampeding herd,老外这里的用词还是很形象的。

一般有如下几种解决思路可供选择:

首先,我们可以主动更新Cache。前端程序里不涉及重建Cache的职责,所有相关逻辑都由后端独立的程序(比如CRON脚本)来完成,但此方法并不适应所有的需求。

其次,我们可以通过加锁来解决问题。以PHP为例,伪代码大致如下:

1 <!--?php function query() {     $data = $cache--->get($key);
2  
3     if ($cache->getResultCode() == Memcached::RES_NOTFOUND) {
4         if ($cache->add($lockKey, $lockData, $lockExpiration)) {
5             $data = $db->query();
6             $cache->add($key, $data, $expiration);
7             $cache->delete($lockKey);
8         } else {
9             sleep($interval);
10             $data = query();
11         }
12     }
13  
14     return $data;
15 }
16  
17 ?>

不过这里有一个问题,代码里用到了sleep,也就是说客户端会卡住一段时间,就拿PHP来说吧,即便这段时间非常短暂,
也有可能堵塞所有的FPM进程,从而使服务中断。于是又有人想出了柔性过期的解决方案,所谓柔性过期,指的是设置一个相对较长的过期时间,
或者干脆不再直接设置数据的过期时间,取而代之的是把真正的过期时间嵌入到数据中去,查询时再判断,如果数据过期就加锁重建,
如果加锁失败,不再sleep,而是直接返回旧数据,以PHP为例,伪代码大致如下:

1 <!--?php function query() {     $data = $cache--->get($key);
2  
3     if (isset($data['expiration']) && $data['expiration'] < $now) {        if ($cache->add($lockKey, $lockData, $lockExpiration)) {
4             $data = $db->query();
5             $data['expiration'] = $expiration;
6             $cache->add($key, $data);
7             $cache->delete($lockKey);
8         }
9     }
10  
11     return $data;
12 }
13  
14 ?>

问题到这里似乎已经圆满解决了,且慢!还有一些特殊情况没有考虑到:设想一下服务重启;
或者某个Cache里原本没有的冷数据因为某些情况突然转换成热数据;又或者由于LRU机制导致某些键被意外删除,等等,
这些情况都可能会让上面的方法失效,因为在这些情况里就不存在所谓的旧数据,等待用户的将是一个空页面。
好在我们还有Gearman这根救命稻草。当需要更新Cache的时候,我们不再直接查询数据库,而是把任务抛给Gearman来处理,
当并发量比较大的时候,Gearman内部的优化可以保证相同的请求只查询一次后端数据库,以PHP为例,伪代码大致如下:

1 <!--?php function query() {     $data = $cache--->get($key);
2  
3     if ($cache->getResultCode() == Memcached::RES_NOTFOUND) {
4         $data = $gearman->do($function, $workload, $unique);
5         $cache->add($key, $data, $expiration);
6     }
7  
8     return $data;
9 }
10  
11 ?>

说明:如果多个并发请求的$unique参数一样,那么实际上Gearman只会请求一次。

Multiget的无底洞问题

Facebook在Memcached的实际应用中,发现了Multiget无底洞问题,具体表现为:出于效率的考虑,很多Memcached应用都已Multiget操作为主,随着访问量的增加,系统负载捉襟见肘,遇到此类问题,直觉通常都是通过增加服务器来提升系统性能,但是在实际操作中却发现问题并不简单,新加的服务器好像被扔到了无底洞里一样毫无效果。

为什么会这样?让我们来模拟一下案发经过,看看到底发生了什么:

我们使用Multiget一次性获取100个键对应的数据,系统最初只有一台Memcached服务器,随着访问量的增加,系统负载捉襟见肘,于是我们又增加了一台Memcached服务器,数据散列到两台服务器上,开始那100个键在两台服务器上各有50个,问题就在这里:原本只要访问一台服务器就能获取的数据,现在要访问两台服务器才能获取,服务器加的越多,需要访问的服务器就越多,所以问题不会改善,甚至还会恶化。

不过,作为被告方,Memcached官方开发人员对此进行了辩护

请求多台服务器并不是问题的症结,真正的原因在于客户端在请求多台服务器时是并行的还是串行的!问题是很多客户端,包括Libmemcached在内,在处理Multiget多服务器请求时,使用的是串行的方式!也就是说,先请求一台服务器,然后等待响应结果,接着请求另一台,结果导致客户端操作时间累加,请求堆积,性能下降。

如何解决这个棘手的问题呢?只要保证Multiget中的键只出现在一台服务器上即可!比如说用户名字(user:foo:name),用户年龄(user:foo:age)等数据在散列到多台服务器上时,不应按照完整的键名(user:foo:name和user:foo:age)来散列的,而应按照特殊的键(foo)来散列的,这样就保证了相关的键只出现在一台服务器上。以PHP的 Memcached客户端为例,有getMultiByKeysetMultiByKey可供使用。

Nagle和DelayedAcknowledgment的延迟问题

老实说,这个问题和Memcached没有半毛钱关系,任何网络应用都有可能会碰到这个问题,但是鉴于很多人在用C或C++写Memcached程序的时候会遇到这个问题,所以还是拿出来聊一聊,在这之前我们先来看看NagleDelayedAcknowledgment的含义:

先看看Nagle:

假如需要频繁的发送一些小包数据,比如说1个字节,以IPv4为例的话,则每个包都要附带40字节的头,也就是说,总计41个字节的数据里,其中只有1个字节是我们需要的数据。为了解决这个问题,出现了Nagle算法。它规定:如果包的大小满足MSS,那么可以立即发送,否则数据会被放到缓冲区,等到已经发送的包被确认了之后才能继续发送。通过这样的规定,可以降低网络里小包的数量,从而提升网络性能。

再看看DelayedAcknowledgment:

假如需要确认每一个包的话,那么网络中将会充斥着数不胜数的ACK,从而降低了网络性能。为了解决这个问题,DelayedAcknowledgment规定:不再针对单个包发送ACK,而是一次确认两个包,或者在发送响应数据的同时捎带着发送ACK,又或者触发超时时间后再发送ACK。通过这样的规定,可以降低网络里ACK的数量,从而提升网络性能。

Nagle和DelayedAcknowledgment虽然都是好心,但是它们在一起的时候却会办坏事。下面我们看看Nagle和DelayedAcknowledgment是如何产生延迟问题的,如下图所示:

说明:Nagle和DelayedAcknowledgment的延迟问题中的图片源自「躲避性能暗礁」。

客户端需要向服务端传输数据,传输前数据被分为ABCD四个包,其中ABC三个包的大小都是MSS,而D的大小则小于MSS。客户端和服务端的交互过程如下所示:

首先,因为客户端的AB两个包的大小都是MSS,所以它们可以耗无障碍的发送,服务端因为DelayedAcknowledgment的缘故,会把这两个包放在一起来发送ACK。

接着,客户端发送C包,而D包由于小于MSS,所以不会立即发送,而被放到缓冲区里延迟发送,服务端因为DelayedAcknowledgment的缘故,不会单独确认C包,于是就傻傻的等啊等,等到花儿都谢了,最终触发了超时时间,发送了姗姗来迟的ACK。

最后,客户端收到ACK后,因为没有未被确认的包存在了,所以即便D包小于MSS,也总算熬出头了,可以发送了,服务端在收到了所有的包之后就可以发送响应数据了。

说到这里,假如你认为自己已经理解了这个问题的来龙去脉,那么我们尝试改变一下前提条件:传输前数据被分为ABCDE五个包,其中ABCD四个包的大小都是MSS,而E的大小则小于MSS。换句话说,满足MSS的完整包的个数是偶数个,而不是前面所说的奇数个,此时又会出现什么情况呢?答案我就不说了,留给大家自己思考。

知道了问题的原委,解决起来就简单了:我们只要设置socket选项为TCP_NODELAY即可,这样就可以禁用Nagle,如此一来,少了一个巴掌,问题自然就拍不响了。

如果大家意犹未尽,可以继续浏览:TCP Performance problems caused by interaction between Nagle’s Algorithm and Delayed ACK

希望本文能让大家在使用Memcached的过程中少走一些弯路。相对于Memcached,其实我更喜欢Redis,从功能上看,Redis可以说是Memcached的超集,不过Memcached自有它存在的价值,即便已呈颓势,但是:老兵永远不死,只是慢慢凋零。

原文地址:http://sevn7.com/archives/325

时间: 2024-09-19 08:54:28

【转载】Memcached二三事儿的相关文章

营销教程:新虫友必须知道的虫软二三事儿

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 很多新的虫虫营销助手的朋友 (简称虫友)拿到软件后,不知道要干些什么,总天真的以为软件嘛不就那回事嘛,肯定拿到就会用.然而在收到虫软加密狗的那一刻心情甭提有多兴奋了,插上加 密狗之后,打开软件,就开始直接发外链,然后在看到那叫人沮丧成功率,顿时就傻眼了.然后就开始在群里质问,这是什么软件,是人用的吗?成功率这么低的, 杯具,早知道不买了.本人

Spark UDF变长参数的二三事儿

在复杂业务逻辑中,我们经常会用到Spark的UDF,当一个UDF需要传入多列的内容并进行处理时,UDF的传参该怎么做呢? 下面通过变长参数引出,逐一介绍三种可行方法以及一些不可行的尝试... 引子 变长参数对于我们来说并不陌生,在Java里我们这么写 public void varArgs(String... args)  在Scala里我们这么写 def varArgs(cols: String*): String  而在Spark里,很多时候我们有自己的业务逻辑,现成的functions满足

Android面试二三事儿

最近开始接触Android方面的面试,收到Hr推荐过来的一份简历,看过之后大喜,工作技能完全符合要求,从事同样的产品开发(从竞品那里找来的).技能水平里的描述如下 个人技能: (1) 有良好的JAVA基础,熟练掌握面向对象思想. (2) 熟练使用集合.IO流及多线程. (3) 熟练掌握Android四大组件,常用的布局文件,自定义控件等. (4) 熟悉掌握ListView的优化及异步任务加载网络数据. (5) 熟悉XML/JSON解析数据,以及数据存储方式. (6) 精通Android下的Han

搜索引擎潜规则:精文转载与采集天壤地别

转载就一定会被降权吗?搜索引擎优化中,内容执掌网站命脉,因此能原创的原创,做不到原创的伪原创,甚至工具采集.然而姑且不论伪原创是否可以真正瞒天过海取得内容优化效果,文章转载与采集是否性质等同呢?大多人认为"转载"就是"采集",而区别主要在于转载是人为行动,"采集"则更多的被定性为人为编制程序代码所为.殊不知,在搜索引擎优化过程中,搜索引擎潜规则对于两者的定性却截然不同,本文就此分析: 一:转载与采集的区别在哪里 互联网每天的新闻有多少是重复信息?

文章转载与采集是否性质等同呢?

摘要: 转载就一定会被降权吗?搜索引擎优化中,内容执掌网站命脉,因此能原创的原创,做不到原创的伪原创,甚至工具采集.然而姑且不论伪原创是否可以真正瞒天过海取得内容优化效果, 转载就一定会被降权吗?搜索引擎优化中,内容执掌网站命脉,因此能原创的原创,做不到原创的伪原创,甚至工具采集.然而姑且不论伪原创是否可以真正瞒天过海取得内容优化效果,文章转载与采集是否性质等同呢?大多人认为"转载"就是"采集",而区别主要在于转载是人为行动,"采集"则更多的被定

云栖Android精华文章合集

云栖Android精彩文章整理自各位技术大咖们关于Android的精彩分享,本文将云栖Android精彩文章整理成为一个合集,以便于大家学习参考.Weex.apk瘦身.开发资源.应用维护.内存管理,一切尽在云栖Android精华文章合集. 云课堂: Android平台页面路由框架ARouter最佳实践 聚能聊: Android_Studio_那些年你常用的神奇快捷键及遇到的糗事儿 文章干货: 安全: APP漏洞扫描器之未使用地址空间随机化 [安全攻防挑战]Androidapp远程控制实战 你必须

游戏云游戏部署最佳实践之玉契OL

<玉契OL>与安全强防护的那些事儿 游戏介绍: <玉契OL>是国内首款全视角3D即时多人ARPG手机网游,全视角3D画面的多人在线.游戏具有炫丽画面,酷爽战斗,多人竞技,组队副本,特色塔防等特点.游戏讲述的是封印魔界的"镇魂神玉"被窃之后,玩家作为一名天界战士,受命下凡寻回神玉,以便能重新封印万千妖魔,化解三界危机.然而又有妖魔蛊惑世人:得神玉者得天下!只要和神玉定下生死契约,就可以拥有魔界强大的力量. 玉契安全防护二三事儿: 传统IDC服务商应对DDOS速度和

4.windows环境下如何安装memcached教程(转载+自己整理)

 Memcached 是一个开源免费高性能的分布式内存对象缓存系统,能够加快网站访问速度和减轻数据库压力,本文向大家介绍下windows环境下如何安装memcached.百度经验:jingyan.baidu.com 工具/原料 memcached1.4.13百度经验:jingyan.baidu.com 方法/步骤 软件的下载,好像从官网上只能下载未经编译的源码,需要自己编译后才能安装使用,不熟悉的用户还是直接百度搜索下载比较好,这里也提供一个下载地址给大家参考. www.newasp.net

我在兰亭这三年之大促的那些事儿

在说去年兰亭第一次搞这种大型促销活动之前,有必要跟大家说一下国外几个非常重要的购物节: 感恩节(Thanksgiving Day) 感恩节是每年11月的第四个星期四,由美国创立,原意是为了感谢上天赐予的好收成,是美国国定假日中最地道.最美国式的节日,和我国的春节一样重要.感恩节购物已经成为了美国人的习俗,从感恩节到圣诞节这一个月,总销售额能占到全年的1/3,是各个商家传统的打折促销旺季. 黑色星期五(Black Friday) 黑色星期五是感恩节的第二天,也就是11月的最后一个星期五,这一天是美