转载 Memcached BinaryProtocol incr指令内存泄露的bug

缘起

最近有个分布式限速的需求。支付宝的接口双11只允许每秒调用10次。

单机的限速,自然是用google guava的RateLimiter。

http://docs.guava-libraries.googlecode.com/git-history/master/javadoc/com/google/common/util/concurrent/RateLimiter.html

分布式的ReteLimiter,貌似没有现在的实现方案。不过用memcached或者Redis来实现一个简单的也很快。

比如上面的要求,每秒钟只允许调用10次,则按下面的流程来执行,以memcached为例:

[plain] view plaincopy

  1. incr alipay_ratelimiter  1 1
  2. 如果返回NOT_FOUND,则
  3. add alipay_ratelimiter  0  1 1
  4. 1
  5. 即如果alipay_ratelimiter不存在,则设置alipay_ratelimiter的值为1,过期时间为1秒。
  6. 如果incr返回不是具体的数值,则判断是否大于10,
  7. 如果大于10则要sleep等待。

上面是Memcached 文本协议的做法。因为文本协议不允许incr 设置不存在的key。

如果是二进制协议,则可以直接用incr命令设置初始值,过期时间。

memcached二进制协议的bug

上面扯远了,下面来说下memcached incr指令的bug。

在测试的时间,用XMemcached做客户端,来测试,发现有的时候,incr函数返回两个1。

于是,在命令行,用telnet来测试,结果发现有时候返回很奇怪的数据:

[plain] view plaincopy

  1. get alipay_ratelimiter
  2. VALUE alipay_ratelimiter 0 22
  3. END2446744073709551608

明显END后面跟了一些很奇怪的数据。而且返回数据的长度是22,而正确的长度应该是1。

正常的返回应该是这样的:

[plain] view plaincopy

  1. get alipay_ratelimiter
  2. VALUE alipay_ratelimiter 0 4
  3. 1
  4. END

抓包分析

开始以为是XMemcached客户端的bug,也有可能是序列化方式有问题。于是调试了下代码,没发现什么可疑的地方。

于是祭出wireshakr来抓包。发现XMemcached发出来的数据包是正常的。而且服务器的确返回了22字节的数据。

那么这个可能是Memcached本身的bug了。这个令人比较惊奇,因为Memcached本身已经开发多年,很稳定了,怎么会有这么明显的bug?

查找有问题的Memcached的版本

检查下当前的Memcahcd版本,是memcached 1.4.14。

于是去下载了最新的1.4.21版,编绎安装之后,再次测试。发现正常了。

于是到release log里查看是哪个版本修复了:

https://code.google.com/p/memcached/wiki/ReleaseNotes

发现1417版的release note里有incr相关的信息:

https://code.google.com/p/memcached/wiki/ReleaseNotes1417

Fix for incorrect length of initial value set via binary increment protocol.

查找bug发生的原因:

于是再到github上查看修改了哪些内容:

https://github.com/memcached/memcached/commit/8818bb698ea0abd5199b2792964bbc7fbe4cd845?diff=split

对比下修改内容,和查看下源代码,可以发现,其实是开发人员在为incr指令存储的数据分配内存时,没有注意边界,一下子分配了INCR_MAX_STORAGE_LEN,即24字节的内存,却没有正常地设置‘\r\n‘到真实数据的最后。所以当Get请求拿到数据是,会把22字节 + "\r\n"的数据返回给用户,造成了内存数据泄露。

修复前的代码:

[cpp] view plaincopy

  1. it = item_alloc(key, nkey, 0, realtime(req->message.body.expiration),
  2. INCR_MAX_STORAGE_LEN);
  3. if (it != NULL) {
  4. snprintf(ITEM_data(it), INCR_MAX_STORAGE_LEN, "%llu",
  5. (unsigned long long)req->message.body.initial);

修复后的代码:

[cpp] view plaincopy

  1. snprintf(tmpbuf, INCR_MAX_STORAGE_LEN, "%llu",
  2. (unsigned long long)req->message.body.initial);
  3. int res = strlen(tmpbuf);
  4. it = item_alloc(key, nkey, 0, realtime(req->message.body.expiration),
  5. res + 2);
  6. if (it != NULL) {
  7. memcpy(ITEM_data(it), tmpbuf, res);
  8. memcpy(ITEM_data(it) + res, "\r\n", 2);

为什么这个bug隐藏了这么久

从测试的版本可以看到,至少从12年这个bug就存在了,从github上的代码来看,09年之前就存在了。直到13年12月才被修复。

为什么这个bug隐藏了这个久?可能是因为返回的22字节数据里中间有正确的加了\r\n,后面的才是多余的泄露数据,可能大部分解析库都以"\r\n"为分隔,从而跳过了解析到的多余的数据。比如XMemcached就能解析到。

另外,只有混用二进制协议和文本协议才可能会发现。

估计有不少服务器上运行的memcached版本都是比1.4.17要老的,比如ubuntu14默认的就是1.4.14。

这个bug的危害

不过这个bug的危害比较小,因为泄露的只有20个字节的数据。对于一些云服务指供的cache服务,即使后面是memcached做支持,也会有一个中转的路由。

窃取到有效信息的可能性很小。

其它的一些东东

wireshark设置解析memcached协议:

wireshark默认是支持解析memcached文本和二进制协议的,不过默认解析端口是11211,所以如果想要解析其它端口的包,要设置下。

在"Edit", ”Preferences“ 里,找到Memcached协议,就可以看到端口的配置了。

参考:https://ask.wireshark.org/questions/24495/memcache-and-tcp

XMemcached的文本协议incr指令的实现:

上面说到Memcached的文本协议是不支持incr设置不存在的key的,但是XMemcached却提供了相关的函数,而且能正常运行,是为什么呢?

[java] view plaincopy

  1. /**
  2. * "incr" are used to change data for some item in-place, incrementing it.
  3. * The data for the item is treated as decimal representation of a 64-bit
  4. * unsigned integer. If the current data value does not conform to such a
  5. * representation, the commands behave as if the value were 0. Also, the
  6. * item must already exist for incr to work; these commands won‘t pretend
  7. * that a non-existent key exists with value 0; instead, it will fail.This
  8. * method doesn‘t wait for reply.
  9. *
  10. * @param key
  11. *            key
  12. * @param delta
  13. *            increment delta
  14. * @param initValue
  15. *            the initial value to be added when value is not found
  16. * @param timeout
  17. *            operation timeout
  18. * @param exp
  19. *            the initial vlaue expire time, in seconds. Can be up to 30
  20. *            days. After 30 days, is treated as a unix timestamp of an
  21. *            exact date.
  22. * @return
  23. * @throws TimeoutException
  24. * @throws InterruptedException
  25. * @throws MemcachedException
  26. */
  27. long incr(String key, long delta, long initValue, long timeout, int exp)
  28. throws TimeoutException, InterruptedException, MemcachedException;

实际上,XMemcached内部包装了incr和add指令,表明上是调用了incr但实际上是两条指令:

[plain] view plaincopy

  1. incr alipay_ratelimiter 1
  2. NOT_FOUND
  3. add alipay_ratelimiter 0 0 1
  4. 1
  5. STORED

另外,要注意XMemcached默认是文本协议的,只有手动配置,才会使用二进制协议。

Memcached二进制协议比文本协议要快多少?

据这个演示的结果,二进制协议比文本协议要略快,第14页:

http://www.slideshare.net/tmaesaka/memcached-binary-protocol-in-a-nutshell-presentation/

参考:

https://code.google.com/p/memcached/wiki/MemcacheBinaryProtocol

二进制协议介绍的ppt:
http://www.slideshare.net/tmaesaka/memcached-binary-protocol-in-a-nutshell-presentation/

转载:http://blog.csdn.net/hengyunabc/article/details/40897421

时间: 2024-11-06 07:08:08

转载 Memcached BinaryProtocol incr指令内存泄露的bug的相关文章

Java String.substring内存泄露?

String可以说是最常用的Java类型之一了,但是最近听说JDK6里面String.substring存在内存泄露的bug,伙惊呆!一起来看看到底是啥情况吧. 这个是可以导致Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 的代码: public class TestGC {     private String largeString = new String(new byte[100000

Memcached 二进制协议(BinaryProtocol) incr指令泄露内存数据的bug

缘起 最近有个分布式限速的需求.支付宝的接口双11只允许每秒调用10次. 单机的限速,自然是用google guava的RateLimiter. http://docs.guava-libraries.googlecode.com/git-history/master/javadoc/com/google/common/util/concurrent/RateLimiter.html 分布式的ReteLimiter,貌似没有现在的实现方案.不过用memcached或者Redis来实现一个简单的也

[转载] 从Handler.post(Runnable r) ,Handler.sendEmptyMessage()梳理Android的消息机制(以及handler的内存泄露)

Handler 每个初学Android开发的都绕不开Handler这个"坎",为什么说是个坎呢,首先这是Android架构的精髓之一,其次大部分人都是知其然却不知其所以然.今天看到Handler.post这个方法之后决定再去翻翻源代码梳理一下Handler的实现机制. 异步更新UI 先来一个必背口诀"主线程不做耗时操作,子线程不更新UI",这个规定应该是初学必知的,那要怎么来解决口诀里的问题呢,这时候Handler就出现在我们面前了(AsyncTask也行,不过本质

【转载】 Android App 内存泄露之Thread

转载地址http://blog.csdn.net/zhuanglonghai/article/details/37909553 Thread 内存泄露 线程也是造成内存泄露的一个重要的源头.线程产生内存泄露的主要原因在于线程生命周期的不可控. 1.看一下下面是否存在问题 <span style="white-space:pre"> </span>/** * * @version 1.0.0 * @author Abay Zhuang <br/> *

使用SDWebImage加载大量图片后造成内存泄露的解决办法 转载

使用SDWebImage加载大量图片后造成内存泄露的解决办法 时间:2015-07-21 14:26:47      阅读:5885      评论:0      收藏:0      [点我收藏+] SDWebImage的知名度就不用说了,github上近10k的star,国内外太多的App使用其进行图片加载. 但是最近在使用过程中发现,在UITableView中不断加载更多的内容,使用SDWebImage会造成内存占用越来越大,导致memory warning最终terminate,稍微找了下

[转载] js内存泄露的几种情况——javascript系列

想解决内存泄露问题,必须知道什么是内存泄露,什么情况下出现内存泄露,才能在遇到问题时,逐个排除.这里只讨论那些不经意间的内存泄露. 一.什么是内存泄露 内存泄露是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束.在C++中,因为是手动管理内存,内存泄露是经常出现的事情.而现在流行的C#和Java等语言采用了自动垃圾回收方法管理内存,正常使用的情况下几乎不会发生内存泄露.浏览器中也是采用自动垃圾回收方法管理内存,但由于浏览器垃圾回收方法有bug,会产生内存泄露. 二.内存泄露的几种情况

转载:内存泄露与内存溢出的区别

内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory:比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出. 内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光. memory leak会最终会导致out of memory! 内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产

查看w3wp进程占用的内存及.NET内存泄露,死锁分析--转载

一 基础知识 在分析之前,先上一张图: 从上面可以看到,这个w3wp进程占用了376M内存,启动了54个线程. 在使用windbg查看之前,看到的进程含有 *32 字样,意思是在64位机器上已32位方式运行w3wp进程.这个可以通过查看IIS Application Pool 的高级选项进行设置: 好了,接下打开Windbg看看这个w3wp进程占用了376M内存,启动的54个线程. 1. 加载 WinDbg SOS 扩展命令 .load C:\Windows\Microsoft.NET\Fram

最新版 使用Xcode6.4 和Instruments、Leaks调试解决iOS内存泄露

分析内存泄露 最近用到内存泄露,查看以前的博客,方法不错,但操作时,步骤好多都找不到啦,不知道怎么操作.所以下面做了一个简单的例子 实验的开发环境:XCode 6.4 一.Analyze (shift+command+b) app不crash了,那看看有没有内存泄露.用XCode的Analyze就能分析到哪里有内存泄露 分析之后可以看到: 这里提示alertView没被释放,有内存泄露,那我们释放 [alertView release]; 再分析,这个问题解决了. 二.使用Instruments