在昨天的博文(云计算之路-阿里云上:读取缓存时的“黑色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. 云计算之路-阿里云上:“黑色1秒”问题与2009年Xen一个补丁的故事

    在之前对“黑色1秒”问题的分析博文中,我们将最大嫌疑对象锁定在了Xen,在这篇博文我们将从Xen的角度进行分析.也许有人会问,为什么不知道天多高地多厚地去研究不属于自己范围的问题?只因我们对一个问题的 ...

  2. 云计算之路-阿里云上:“黑色1秒”最新线索——w3tp与w3dt

    向大家分享一下最近排查“黑色1秒”问题的进展,“黑色1秒”的问题表现详见什么是黑色1秒. 1. 发生在w3wp进程内 判断依据:“黑色1秒”期间,http.sys的HTTP Service Reque ...

  3. 云计算之路-阿里云上:从ASP.NET线程角度对“黑色30秒”问题的全新分析

    在这篇博文中,我们抛开对阿里云的怀疑,完全从ASP.NET的角度进行分析,看能不能找到针对问题现象的更合理的解释. “黑色30秒”问题现象的主要特征是:排队的请求(Requests Queued)突增 ...

  4. 云计算之路-阿里云上:Web服务器遭遇奇怪的“黑色30秒”问题

    今天下午访问高峰的时候,主站的Web服务器出现奇怪的问题,开始是2台8核8G的云服务器(ECS),后来又加了1台8核8G的云服务器,问题依旧. 而且3台服务器特地使用了不同的配置:1台是禁用了虚拟内存 ...

  5. 云计算之路-阿里云上:基于Xen的IO模型进一步分析“黑色0.1秒”问题

    在发现云服务器读取OCS缓存的“黑色0.1秒”是发生在socket读取数据时,而且是发生在读取开始的字节,甚至在socket写数据时(比如写入缓存key)也会出现超过50ms的情况,我们的好奇心被激发 ...

  6. 云计算之路-阿里云上:SLB会话保持的一个坑

    冒着被大家厌烦的风险,今天再发一篇“云计算之路-阿里云上”.这是在前一篇发过之后真实发生的事情,我们觉得定位问题的过程值得分享.而且估计园子里不少朋友被这个问题骚扰过,我们有责任让大家知道问题的真正原 ...

  7. 云计算之路-阿里云上-容器难容:容器服务故障以及自建 docker swarm 集群故障

    3月21日,由于使用阿里云服务器自建 docker swarm 集群的不稳定,我们将自建 docker swarm 集群上的所有应用切换阿里云容器服务 swarm 版(非swarm mode). 3月 ...

  8. 云计算之路-阿里云上-新发现:又一种与虚拟内存有关的CPU波动情况

    在云上真是无奇不有,昨天偶然间发现在IIS的应用程序池回收设置中,仅仅设置了一下基于虚拟内存限制的回收,就引发了CPU有规律的波动.在这篇博文中,我们将向大家汇报一下云计算之路上的这个小发现. 在之前 ...

  9. 云计算之路-阿里云上:启用Windows虚拟内存引发的CPU 100%故障

    今天上午11:35~11:40左右,由于负载均衡中的两台云服务器CPU占用突然飚至100%,造成网站5分钟左右不能正常访问,请大家带来了麻烦,请谅解! (上图中红色曲线表示CPU占用) 经过分析,我们 ...

随机推荐

  1. CF449C Jzzhu and Apples

    嘟嘟嘟 这道题正解是怎么对的其实我也不清楚,总之靠感性理解吧. 首先当然要把1到n / 2的素数都筛出来,因为两两能配对的数一定都是这些素数的倍数.这也就说明对于(n / 2, n]的素数,他们一定不 ...

  2. spring 跨域 CORS (Cross Origin Resources Share) 跨域

    Spring提供了三种方式跨域 1.CorsFilter 过滤器 2.<mvc:cors> Bean(全局,推荐使用) 3.@CrossOrigin注解 以上三种方式本质都是用来配置Cor ...

  3. Java中关于String的比较

    关于String的各种==和equals,有的人搞不懂 简而言之 String  s1 = "Hello"                     声明的是一个常量,会在常量池里. ...

  4. LeetCode6.Z字形变换 JavaScript

    将一个给定字符串根据给定的行数,以从上往下.从左到右进行 Z 字形排列. 比如输入字符串为 "LEETCODEISHIRING" 行数为 3 时,排列如下: L C I R E T ...

  5. Django-rest-framework(八)使用技巧(持续补充中)

    当viewset中,多个action对应了不同的serializers的时候 class UserView(viewsets.GenericViewSet): queryset = User.obje ...

  6. Oracle EM12c 安装

    EM12c系统由下列组件构成: 1.Oracle Management Agent(管理代理) 2.Oracle Management Service(管理服务) 3.Oracle Managemen ...

  7. 面试遇到的select into 但是在PL/SQL developer ORA-00905:缺失关键字"错误。

    select into 是什么意思. 1.INSERT INTO SELECT语句 语句形式为:Insert into Table2(field1,field2,...) select value1, ...

  8. Redis工具之Jedis

    //jedis的连接池 public void test1(){ //创建连接池配置对象 JedisPoolConfig poolConfig = new JedisPoolConfig(); poo ...

  9. 构建vue零散笔记

    # vue项目(用webpack构建)的前提是已安装了node.js,vue,vue-cli,webpack # 主要命令构建:vue init webpack 项目名(纯英文,且不可驼峰)运行:np ...

  10. P1330 封锁阳光大学 DFS+染色

    题目链接:https://www.luogu.org/problemnew/show/P1330 这个题有意思,如果能想到染色,就会很简单,但若想不到就很麻烦 要想把一条边封锁,就必须且只能占据这条边 ...