在昨天的博文(云计算之路-阿里云上:读取缓存时的“黑色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. (第三场) A PACM Team 【dp,五维背包】

    链接:https://www.nowcoder.com/acm/contest/141/A来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 262144K,其他语言5242 ...

  2. java实现按拼音排序

    List<WaPayFileVO> list =(List<WaPayFileVO>) dao.execQueryBeanList(pagesql, params.toArra ...

  3. Java中Synchronized的用法(简单介绍)

    简单介绍 synchronized是Java中的关键字,是一种同步锁.它修饰的对象有以下几种: 1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调 ...

  4. json 二进制传输方案

    json 传输二进制数组方案 json 是一种很简洁的协议,但可惜的是,它只能传递基本的数型(int,long,string等),但不能传递byte类型.如果想要传输图片等二进制文件的话,是没办法直接 ...

  5. lucene&solr学习——solr学习(一)

    1.什么是solr solr是Apache下的一个顶级开源项目,采用Java开发,它是基于Lucene的全文检索服务器.Solr提供了比lucene风味丰富的查询语言,同时实现了可配置,可扩展,并对索 ...

  6. 微信小程序分包加载

    分包加载 某些情况下,开发者需要将小程序划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载. 在构建小程序分包项目时,构建会输出一个或多个功能的分包,其中每个分包小程序必定含有一个主 ...

  7. js获取站点根目录

    function getRootPath(){        var strFullPath=window.document.location.href;        var strPath=win ...

  8. EventBus 基础篇

    最近在研究RxJava ,突然想起了事件分发另一个强大的框架Eventbus ,并且项目经常用到,特意整理了下. what is Eventbus? 官方的解释为: EventBus is a pub ...

  9. 菜鸟笔记 -- Chapter 6.1 面向对象概述

    6.1 面向对象概述 在程序开发的初期人们使用结构化开发语言,但是随着时间的流逝,软件的规模越来越庞大,结构化语言的弊端逐渐暴露出来,开发周期被无限制拖延,产品质量也不尽如人意,人们终于发现结构化语言 ...

  10. Oracle 差异性增量 和 累计增量 原理(转)

    RMAN一个强大的功能是支持增量备份,增量备份中心思想就是减少备份的数据量,我们不需要在从头开始备份了,只需要备份自上次已备份之后的数据块即可.   Oracle 9i 共有五种级别 0 1 2 3 ...