转载 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为例:
- incr alipay_ratelimiter 1 1
- 如果返回NOT_FOUND,则
- add alipay_ratelimiter 0 1 1
- 1
- 即如果alipay_ratelimiter不存在,则设置alipay_ratelimiter的值为1,过期时间为1秒。
- 如果incr返回不是具体的数值,则判断是否大于10,
- 如果大于10则要sleep等待。
上面是Memcached 文本协议的做法。因为文本协议不允许incr 设置不存在的key。
如果是二进制协议,则可以直接用incr命令设置初始值,过期时间。
memcached二进制协议的bug
上面扯远了,下面来说下memcached incr指令的bug。
在测试的时间,用XMemcached做客户端,来测试,发现有的时候,incr函数返回两个1。
于是,在命令行,用telnet来测试,结果发现有时候返回很奇怪的数据:
- get alipay_ratelimiter
- VALUE alipay_ratelimiter 0 22
- END2446744073709551608
明显END后面跟了一些很奇怪的数据。而且返回数据的长度是22,而正确的长度应该是1。
正常的返回应该是这样的:
- get alipay_ratelimiter
- VALUE alipay_ratelimiter 0 4
- 1
- 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"的数据返回给用户,造成了内存数据泄露。
修复前的代码:
- it = item_alloc(key, nkey, 0, realtime(req->message.body.expiration),
- INCR_MAX_STORAGE_LEN);
- if (it != NULL) {
- snprintf(ITEM_data(it), INCR_MAX_STORAGE_LEN, "%llu",
- (unsigned long long)req->message.body.initial);
修复后的代码:
- snprintf(tmpbuf, INCR_MAX_STORAGE_LEN, "%llu",
- (unsigned long long)req->message.body.initial);
- int res = strlen(tmpbuf);
- it = item_alloc(key, nkey, 0, realtime(req->message.body.expiration),
- res + 2);
- if (it != NULL) {
- memcpy(ITEM_data(it), tmpbuf, res);
- 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却提供了相关的函数,而且能正常运行,是为什么呢?
- /**
- * "incr" are used to change data for some item in-place, incrementing it.
- * The data for the item is treated as decimal representation of a 64-bit
- * unsigned integer. If the current data value does not conform to such a
- * representation, the commands behave as if the value were 0. Also, the
- * item must already exist for incr to work; these commands won't pretend
- * that a non-existent key exists with value 0; instead, it will fail.This
- * method doesn't wait for reply.
- *
- * @param key
- * key
- * @param delta
- * increment delta
- * @param initValue
- * the initial value to be added when value is not found
- * @param timeout
- * operation timeout
- * @param exp
- * the initial vlaue expire time, in seconds. Can be up to 30
- * days. After 30 days, is treated as a unix timestamp of an
- * exact date.
- * @return
- * @throws TimeoutException
- * @throws InterruptedException
- * @throws MemcachedException
- */
- long incr(String key, long delta, long initValue, long timeout, int exp)
- throws TimeoutException, InterruptedException, MemcachedException;
实际上,XMemcached内部包装了incr和add指令,表明上是调用了incr但实际上是两条指令:
- incr alipay_ratelimiter 1
- NOT_FOUND
- add alipay_ratelimiter 0 0 1
- 1
- 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
转载 Memcached BinaryProtocol incr指令内存泄露的bug的更多相关文章
- jQuery在updatepanel中使用造成内存泄露
wijmo用户反馈了一个RadialGauge控件内存泄露的bug,采用chrome监控内存使用情况,发现明显的内存泄露,在前面的文章中我就发现了jQuery内存泄露的问题,这次再次发现此问题,自然就 ...
- Netty堆外内存泄露排查与总结
导读 Netty 是一个异步事件驱动的网络通信层框架,用于快速开发高可用高性能的服务端网络框架与客户端程序,它极大地简化了 TCP 和 UDP 套接字服务器等网络编程. Netty 底层基于 JDK ...
- Vue.js表单校验;动画指令;避免内存泄露。
Vue.js表单校验: 动画指令:创建自定义的滚动指令. 避免内存泄露. 避免内存泄露 在单页面应用开发时SPA,用户无需刷新浏览器.所以javascript应用需要自行清理组件来防止内存占用不断增长 ...
- 查看w3wp进程占用的内存及.NET内存泄露,死锁分析--转载
一 基础知识 在分析之前,先上一张图: 从上面可以看到,这个w3wp进程占用了376M内存,启动了54个线程. 在使用windbg查看之前,看到的进程含有 *32 字样,意思是在64位机器上已32位方 ...
- 【转载】Java中如何写一段内存泄露的程序 & ThreadLocal 介绍和使用
可以参考这段文章: link A1:通过以下步骤可以很容易产生内存泄露(程序代码不能访问到某些对象,但是它们仍然保存在内存中): 上文中提到了使用ThreadLocal造成了内存泄露,但是写的不清不楚 ...
- Android为TV端助力 转载:内存泄露与内存溢出的区别
内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory:比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出. ...
- (转载--修改)使用Xcode9的Instruments检测解决iOS内存泄露
作为一名iOS开发攻城狮,在苹果没有出ARC(自动内存管理机制)时,我们几乎有一半的开发时间都耗费在这么管理内存上.后来苹果很人性的出了ARC,虽然在很大程度上,帮助我们开发者节省了精力和时间.但是我 ...
- 【微信小程序项目实践总结】30分钟从陌生到熟悉 web app 、native app、hybrid app比较 30分钟ES6从陌生到熟悉 【原创】浅谈内存泄露 HTML5 五子棋 - JS/Canvas 游戏 meta 详解,html5 meta 标签日常设置 C#中回滚TransactionScope的使用方法和原理
[微信小程序项目实践总结]30分钟从陌生到熟悉 前言 我们之前对小程序做了基本学习: 1. 微信小程序开发07-列表页面怎么做 2. 微信小程序开发06-一个业务页面的完成 3. 微信小程序开发05- ...
- 内存泄露检测之mtrace
————————————————版权声明:本文为CSDN博主「知耻而后勇的蜗牛」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明.原文链接:https://blog ...
随机推荐
- 使用Android Studio开发J2SE项目方法
0.前言 最近因为要为项目开发一个底层的Java应用,所以非常偶然的遇到了这样一个问题,过去Eclipse有Java Project而现在手头使用Android Studio并不能直接建立Java应用 ...
- Node.js学习笔记 02 Implementing flow control
What is flow control? 和其它语言一样,Node.js 在代码编写时,如何组织代码,如何写出clean code都是不可避免的难点. 同时,由于Node.js的天然特性(异步,事件 ...
- bzoj 1012 [JSOI2008]最大数maxnumber
原题链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1012 线段树,单点更新.. #include<algorithm> #incl ...
- android开发系列之代码整洁之道
说起代码整洁之道,想必大家想到更多的是那本经典重构书籍.没错,记得当时自己读那本书的时候,一边结合项目实战,一边结合书中的讲解,确实学到了很多东西,对我自己的编码风格影响极深.随着时间的流逝,书中很多 ...
- Windows Phone Listbox虚拟化的问题
最近在项目里面碰到最头疼的一个难题就是Listbox虚拟化的问题,查阅很多文档其实Listbox本身是支持虚拟化的,那么在什么情况下会破坏Listbox的虚拟化呢?目前我接触到的主要有两个原因:一.是 ...
- Objective-C-实例变量与属性的关系
当在一个类创建一个属性,Xcode编译器就会自动产生一个带下划线的同名实例变量: 一般来说,如果getter这个属性采用下划线的方式获取效率更高,而setter采用self.属性名更加合理. 读取实例 ...
- 使用Git和远程代码库
git是个了不起但却复杂的源代码管理系统.它能支持复杂的任务,却因此经常被认为太过复杂而不适用于简单的日常工作.让我们诚实一记吧:Git是复杂的,我们不要装作它不是.但我仍然会试图教会你用(我的)基本 ...
- Spark 3000门徒第二课scala面向对象总结
昨晚听了王家林老师3000门徒spark系列课程的第二课,讲述了scala面向对象知识,并且带着过了一遍Spark核心类:SparkContent,RDD的代码,下面写一下心得: RDD是抽象类,实现 ...
- 005--VS C++ 加载位图
//全局变量 HDC mdc; //--------------------------------------------InitInstance() 函数--------------------- ...
- Thread系列——Thread.Sleep(0)
转载自:http://www.cnblogs.com/ATually/archive/2010/10/21/1857261.html 线程这一概念,可以理解成进程中的一个小单元.这个单元是一个独立的执 ...