云计算之路-阿里云上:原来“黑色0.1秒”发生在socket读取数据时
在昨天的博文(云计算之路-阿里云上:读取缓存时的“黑色0.1秒”)中我们犯了一个很低级的错误——把13ms算成了130ms(感谢陈硕发现这个错误!),从而对问题的原因作出了错误的推断,望大家谅解!
从中我们吸取到了一个教训:趁热打铁要小心,容易失去冷静,作出错误的判断。
今天我们痛定思痛,用了一个下午的时间重新分析了“黑色0.1秒”问题,这次从EnyimMemcached的源代码下手(https://github.com/enyim/EnyimMemcached)。
怎么下手呢?我们用了最粗鲁、最原始的方法,在EnyimMemcached源代码中反复找点写日志,运行程序,观察执行时间,逐步缩小范围。
。。。
经过一次又一次的努力,终于锁定了“黑色0.1秒”的引发点:
2014-05-10 15:29:29,903 Enyim.Caching.Memcached.Protocol.Binary.BinaryResponse
MemcachedBinaryResponse-header_socket_read: 103.5174ms 2014-05-10 15:29:29,904 Enyim.Caching.Memcached.MemcachedNode
$MemcachedExecuteOperation-InitPool: 104.4935ms
MemcachedBinaryResponse-header_socket_read的日志记录代码是添加在BinaryResponse的Read()方法中的:
public unsafe bool Read(PooledSocket socket)
{
this.StatusCode = -; if (!socket.IsAlive)
return false; var header = new byte[HeaderLength]; var startTime = DateTime.Now;
socket.Read(header, , header.Length);
LogExecutionTime("header_socket_read", startTime, ); int dataLength, extraLength;
DeserializeHeader(header, out dataLength, out extraLength); if (dataLength > )
{
var data = new byte[dataLength];
socket.Read(data, , dataLength);
} return this.StatusCode == ;
} private void LogExecutionTime(string title, DateTime startTime, int thresholdMs)
{
var duration = (DateTime.Now - startTime).TotalMilliseconds;
if (duration > thresholdMs)
{
log.ErrorFormat("MemcachedBinaryResponse-{0}: {1}ms", title, duration);
}
}
原来“黑色0.1秒”发生在执行socket.Read(header, 0, header.Length);的时候,而且从日志中发现EnyimMemcached读取缓存数据时,超过10ms的操作绝大多数都发生在这个地方。
我们来看一下socket.Read的实现代码:
public void Read(byte[] buffer, int offset, int count)
{
this.CheckDisposed(); int read = ;
int shouldRead = count; while (read < count)
{
try
{
int currentRead = this.inputStream.Read(buffer, offset, shouldRead);
if (currentRead < )
continue; read += currentRead;
offset += currentRead;
shouldRead -= currentRead;
}
catch (IOException)
{
this.isAlive = false;
throw;
}
}
}
inputStream的类型是BufferedStream,socket.Read的操作很简单,就是从BufferedStream中读取指定长度的Byte数据。
这个操作为什么会超过100ms?以我们有限的知识目前无法作出进一步的推断。
为了排除BufferedStream读取方式的原因,我们采用一种更简洁的读取方式——BinaryReader。
用下面的代码取代了原先的socket.Read:
private BinaryReader binReader;
public PooledSocket(...)
{
//...
this.inputStream = new BufferedStream(new BasicNetworkStream(socket));
binReader = new BinaryReader(this.inputStream);
} public byte[] ReadBytes(int count)
{
this.CheckDisposed(); try
{
return binReader.ReadBytes(count);
}
catch (IOException)
{
this.isAlive = false;
throw;
}
}
调用代码变成了这样:
public unsafe bool Read(PooledSocket socket)
{
this.StatusCode = -; if (!socket.IsAlive)
return false; var startTime = DateTime.Now;
var header = socket.ReadBytes(HeaderLength);
LogExecutionTime("header_socket_read", startTime, ); int dataLength, extraLength;
DeserializeHeader(header, out dataLength, out extraLength); if (dataLength > )
{
var data = socket.ReadBytes(dataLength);
} return this.StatusCode == ;
}
采用BinaryReader之后,“黑色0.1秒”问题依旧。
这次我们再三确认,没有犯低级错误, “黑色0.1秒”的确是发生在socket读取数据时。
这次我们依然发出和上次一样的疑问:究竟是微软Windows的原因,还是阿里云虚拟机的原因?
期待有经验的朋友的指点!
【补充】
1. 前面所说的socket.Read中只是读取了前24个字节,而后面真正读取数据时(剩下的字节),超过10ms的情况却少很多。日志中对比了一下,在16:38-18:10期间,超过10ms的header_socket_read出现了2254次,data_socket_read只出现了129次(超过100ms的只出现了1次)。
var data = new byte[dataLength];
socket.Read(data, , dataLength);
2. 连执行System.Net.Sockets.Socket.Send(IList<ArraySegment<byte>> buffers, SocketFlags socketFlags, out SocketError errorCode)都会出现超过50ms的情况。
【附】
修改后的EnyimMemcached代码:https://github.com/cnblogs/EnyimMemcached
【推荐学习材料】
1. BufferedStream Improves .NET Sockets Performance
2. 《TCP/IP Sockets in C#: Practical Guide for Programmers》中的内容:

云计算之路-阿里云上:原来“黑色0.1秒”发生在socket读取数据时的更多相关文章
- 云计算之路-阿里云上:“黑色1秒”问题与2009年Xen一个补丁的故事
在之前对“黑色1秒”问题的分析博文中,我们将最大嫌疑对象锁定在了Xen,在这篇博文我们将从Xen的角度进行分析.也许有人会问,为什么不知道天多高地多厚地去研究不属于自己范围的问题?只因我们对一个问题的 ...
- 云计算之路-阿里云上:“黑色1秒”最新线索——w3tp与w3dt
向大家分享一下最近排查“黑色1秒”问题的进展,“黑色1秒”的问题表现详见什么是黑色1秒. 1. 发生在w3wp进程内 判断依据:“黑色1秒”期间,http.sys的HTTP Service Reque ...
- 云计算之路-阿里云上:从ASP.NET线程角度对“黑色30秒”问题的全新分析
在这篇博文中,我们抛开对阿里云的怀疑,完全从ASP.NET的角度进行分析,看能不能找到针对问题现象的更合理的解释. “黑色30秒”问题现象的主要特征是:排队的请求(Requests Queued)突增 ...
- 云计算之路-阿里云上:Web服务器遭遇奇怪的“黑色30秒”问题
今天下午访问高峰的时候,主站的Web服务器出现奇怪的问题,开始是2台8核8G的云服务器(ECS),后来又加了1台8核8G的云服务器,问题依旧. 而且3台服务器特地使用了不同的配置:1台是禁用了虚拟内存 ...
- 云计算之路-阿里云上:基于Xen的IO模型进一步分析“黑色0.1秒”问题
在发现云服务器读取OCS缓存的“黑色0.1秒”是发生在socket读取数据时,而且是发生在读取开始的字节,甚至在socket写数据时(比如写入缓存key)也会出现超过50ms的情况,我们的好奇心被激发 ...
- 云计算之路-阿里云上:SLB会话保持的一个坑
冒着被大家厌烦的风险,今天再发一篇“云计算之路-阿里云上”.这是在前一篇发过之后真实发生的事情,我们觉得定位问题的过程值得分享.而且估计园子里不少朋友被这个问题骚扰过,我们有责任让大家知道问题的真正原 ...
- 云计算之路-阿里云上-容器难容:容器服务故障以及自建 docker swarm 集群故障
3月21日,由于使用阿里云服务器自建 docker swarm 集群的不稳定,我们将自建 docker swarm 集群上的所有应用切换阿里云容器服务 swarm 版(非swarm mode). 3月 ...
- 云计算之路-阿里云上-新发现:又一种与虚拟内存有关的CPU波动情况
在云上真是无奇不有,昨天偶然间发现在IIS的应用程序池回收设置中,仅仅设置了一下基于虚拟内存限制的回收,就引发了CPU有规律的波动.在这篇博文中,我们将向大家汇报一下云计算之路上的这个小发现. 在之前 ...
- 云计算之路-阿里云上:启用Windows虚拟内存引发的CPU 100%故障
今天上午11:35~11:40左右,由于负载均衡中的两台云服务器CPU占用突然飚至100%,造成网站5分钟左右不能正常访问,请大家带来了麻烦,请谅解! (上图中红色曲线表示CPU占用) 经过分析,我们 ...
随机推荐
- 【[ZJOI2014]力】
题目 好神仙啊 \[F_{j}=\sum_{i<j}\frac{q_iq_j}{(i-j)^2}-\sum_{j<i}\frac{q_iq_j}{(i-j)^2}\] 求\(\frac{F ...
- bzoj 2434 [Noi2011]阿狸的打字机——AC自动机
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2434 dfs AC自动机,走过的点权值+1,回溯的时候权值-1:走到询问的 y 串的节点,看 ...
- [18/11/22] 将点分十进制的IP地址化成二进制输出
#include <stdio.h> void binary(int d){ ,j,n,b[]={}; ){ n=d%; d=d/; b[i++]=n; //不停的除2,余数保存在b[8] ...
- 2018.10.2 Eclipse中如何测地修改一个we项目步骤
找到项目的web.xml文件 大概的路径如下: 修改xml文件中的display-name 节点的值 下一步就是切换工作目录 显示的效果 打开最后一个文件修改 接下来找到这个文件 是部署的时候用的 运 ...
- SpringMVC学习记录六——异常定义和上传图片
19 异常处理 19.1 异常处理思路 系统中异常包括两类:预期异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发.测试 ...
- Android学习笔记_24_多媒体MediaPlayer对象之音乐播放器与SoundPool声音池
一.MediaPlayer对象常用方法介绍: MediaPlayer mediaPlayer = new MediaPlayer(); if (mediaPlayer.isPlaying()) { m ...
- SpringBoot非官方教程 | 第十五篇:Springboot整合RabbitMQ
转载请标明出处: 原文首发于:https://www.fangzhipeng.com/springboot/2017/07/11/springboot15-rabbitmq/ 本文出自方志朋的博客 这 ...
- Python 学习笔记(八)Python列表(三)
序列 序列:数学上,序列是被排成一列的对象(或事件):这样,每个元素不是在器他元素之前,就是在其他元素之后.这里元素之间的顺序非常重要.<维基百科> 序列是Python中最基本的数据结构. ...
- flask中的CBV和FBV
flask中CBV使用 from flask import Flask, views app = Flask(__name__) class Login(views.MethodView): meth ...
- zTree的核心处理逻辑
zTree 是一个前端树形结构的插件. 使用起来很简单,我们重点关注一下插件的核心代码. 首先,zTree需要如下的数据结构: let areaData = [ { "id": & ...