最早出现的问题情况是提供es的部门在es的外部封装了一个gateway做请求中转。

当我们转换到gateway上之后,发现了问题:

有的请求可以获取到数据,有的请求获取不到数据。

仔细分析了业务代码,抽取了一个出问题的业务请求,这个业务请求里面包含了多次对es的请求,只有最后一个es请求抛出异常,其他都正常。

ps: 我们的业务是使用php写的,使用了https://github.com/elastic/elasticsearch-php这个包进行es请求的。

初步分析

当然,我们没有把错误信息对外,首先看我们自己的日志,看到的错误信息是:

No alive nodes found in your cluster at elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/StaticNoPingConnectionPool.php:51

这个错误第一反应是是不是新的这个gateway节点有问题啊?但是想想这是不可能的,因为并不是所有请求都不可以,而且找了gateway部门的同事问了下,也不存在对请求单独处理的逻辑。

那么继续看到StaticNoPingConnectionPool.php

public function nextConnection($force = false)
{
$total = count($this->connections);
while ($total--) {
/** @var Connection $connection */
$connection = $this->selector->select($this->connections);
if ($connection->isAlive() === true) {
return $connection;
} if ($this->readyToRevive($connection) === true) {
return $connection;
}
} throw new NoNodesAvailableException("No alive nodes found in your cluster");
}

这里就说明了两个函数 isAlive和readyToRevive两个都是false。

仔细分析elasticsearch-php包的代码,大致逻辑是这个包帮忙做了重试机制,重试机制的次数为配置的hosts次数。当一个请求失败的时候,它会换一个hosts进行重试。比如你设置了2个hosts,它会用第一个host做请求,如果失败了,请求第二个host,如果还失败,根据isAlive和readyToRevive判断是不是距离第一个host的失败请求一定时间了,如果超过了,则再进行一次请求。(重试两次)。当所有重试都失败了,就出现错误“No alive nodes found in your cluster”。

好了,我这里其实只设置了一个host,那么它就是第一次请求之后,返回失败,本来准备进行一次请求,但是发现第一个host已经请求过了(isAlive() == false),并且距离第一个请求时长没超过60s(readyToRevive() == fasle)于是就抛出错误。

好了,问题转到判断为什么第一个请求会失败了。

error的真面目。

这个异常实际上是把curl的错误给覆盖了。我们看不到真相。需要揭开elasticsearch-php的面纱。

追到最后,在Connections/Connection.php里面的wrapHandler

private function wrapHandler(callable $handler, LoggerInterface $logger, LoggerInterface $tracer)
{
return function (array $request, Connection $connection, Transport $transport = null, $options) use ($handler, $logger, $tracer) {
// Send the request using the wrapped handler.
$response = Core::proxy($handler($request), function ($response) use ($connection, $transport, $logger, $tracer, $request, $options) { if (isset($response['error']) === true) {
// TODO:这里日志记录下有问题的response
if ($response['error'] instanceof ConnectException || $response['error'] instanceof RingException) {
$connection->markDead();
$transport->connectionPool->scheduleCheck(); $neverRetry = isset($request['client']['never_retry']) ? $request['client']['never_retry'] : false;
$shouldRetry = $transport->shouldRetry($request); if ($shouldRetry && !$neverRetry) {
return $transport->performRequest(
$request['http_method'],
$request['uri'],
[],
$request['body'],
$options
);
}

在代码TODO里面日志记录下$response信息。发现了response的error信息如下:

cURL: [P]roblem (2) in the Chunked-Encoded data

错误解决

好了,这里其实把error的真身拿出来了,就可以google了。也确实给我们google到了: https://github.com/jackalope/jackalope-jackrabbit/issues/89

按照里面的方法,强行设置HTTP的version头为1.0就能解决了。

我们尝试在Connection.php里面将version头设置上:

public function performRequest($method, $uri, $params = null, $body = null, $options = [], Transport $transport = null)
{
if (isset($body) === true) {
$body = $this->serializer->serialize($body);
} $request = [
'http_method' => $method,
'scheme' => $this->transportSchema,
'uri' => $this->getURI($uri, $params),
'body' => $body,
'headers' => [
'host' => [$this->host]
],
'version' => "1.0" ];
$request = array_merge_recursive($request, $this->connectionParams, $options);

确实解决了这个问题。

抓包

问题是可以这样解决,但是基本上来说,难道这个错误是客户端引起的么?根,还在提供es的gateway这端的。

先把version头去掉,我决定tcpdump抓包看看情况。

我使用HTTP过滤了下包显示,发现了一个奇怪的现象:

#107是response开始,我看了下实际的头:

有个Transfer-Encoding: chunked。这个就代表请求结果过长,所以我把这个请求结果分段返回给客户端。

wireshark把红框框了出来。想告诉我们的是,这个chunk返回的数据并不全。

好了,这个我们基本上找到了问题的根源:

服务端支持Transfer-Encoding:chunked,但是不知道什么原因,没有全部返回所有数据。

所以第二种解决方法也出现了:服务端关闭对Transfer-Encoding:chunked的支持。

深究

但是再问一下,为什么服务端会没有全部返回正常的chunked数据呢?

还要展开下,我在wireshark中按照tcp流方式显示了这个response,发现了一个很诡异的现象:

#107还是我们response的开始,然后#108,#109,#110,#111都是chunk数据,并且seq也按照如期地在顺序增长,但是到了#112,客户端这边莫名奇妙返回了一个RST。然后服务端返回了一个ACK。然后就是拒绝大戏了,服务端不断往这边送东西,客户端不断返回RST。到最后,这个连接就断开了。

所以现在的问题追结到为什么会发送#112这个请求。

看win窗口,到#114的时候win窗口已经为0了,而在后面的请求中,win窗口仍然一直为0,说明应用程序一直没有去读缓存区中获取数据。那这个问题估摸就在php-curl或者curl_lib上了。

最终章

最终也没有去追php-curl和curllib库的问题了,主要这个库公司统一部署的,不大可能由于这个问题进行修改了,最后的方法还是通过gateway服务端不使用chunk的头来解决了这个问题。

phpcurl 请求Chunked-Encoded data 遇到的一个问题的更多相关文章

  1. 17.1.1.6 Creating a Data Snapshot Using Raw Data Files 创建一个数据快照使用 Raw Data Files

    17.1.1.6 Creating a Data Snapshot Using Raw Data Files 创建一个数据快照使用 Raw Data Files 如果数据库是大的, 复制raw 数据文 ...

  2. 【Vue】定义组件 data 必须是一个函数返回的对象

    Vue 实例的数据对象.Vue 将会递归将 data 的属性转换为 getter/setter,从而让 data 的属性能够响应数据变化.对象必须是纯粹的对象 (含有零个或多个的 key/value ...

  3. ASP.NET Web API 记录请求响应数据到日志的一个方法

    原文:http://blog.bossma.cn/dotnet/asp-net-web-api-log-request-response/ ASP.NET Web API 记录请求响应数据到日志的一个 ...

  4. vue中组件的data为什么是一个函数

    1. 前言 在学习vue的时候,一直纳闷一件事:组件的data数据为什么必须要以函数返回的形式,为什么不是简单的对象形式呢?遂带着问题去翻官方文档,文档中自然也写明了这么做的原因,本篇博文以官方文档给 ...

  5. 如何实现 Https拦截进行 非常规“抓包” 珍惜Any 看雪学院 今天 前段时间在自己做开发的时候发现一个很好用的工具,OKHttp的拦截器(何为拦截器?就是在每次发送网络请求的时候都会走的一个回调)大概效果如下:

    如何实现 Https拦截进行 非常规“抓包” 珍惜Any 看雪学院 今天 前段时间在自己做开发的时候发现一个很好用的工具,OKHttp的拦截器(何为拦截器?就是在每次发送网络请求的时候都会走的一个回调 ...

  6. Vue之vue中的data为什么是一个函数+vue中路径别名alias设置

    问题描述 为什么在vue组件中,我们的data属性必须是一个函数,new Vue()中的data除外,因为new Vue中只有一个data属性. 原因 因为我们能抽离出来的组件,肯定是具有复用性的,它 ...

  7. vue中data必须是一个函数

    前端面试时经常被问到:“组建中data为什么是函数”? 答案就是:在组件中data必须是一个函数,这样的话,每个实例可以维护一份被返回对象的独立拷贝.

  8. iOS 如何解决并发请求时,只接受最后一个请求返回的结果

      大致意思是 虽然NSOperation 的cancel 并不能取消请求,但是可以对这个NSOperation进行标记. 当cancel 属性是YES时,表明 NSOperation虽然已经执行,并 ...

  9. Spring data redis的一个bug

    起因 前两天上线了一个新功能,导致线上业务的缓存总是无法更新,报错也是非常奇怪,redis.clients.jedis.exceptions.JedisConnectionException: Unk ...

随机推荐

  1. 利用sql注入

    INSERT查询中实现注入攻击 1. 思路就是在含有insert语句的页面插入目标值信息.经常包含的是一个子查询. 2. 注意在insert过程中,左边的注入点和右边的注入点会有不同 3. 在mysq ...

  2. JavaScript判断移动端及pc端访问不同的网站

    JavaScript判断移动端及pc端访问不同的网站 现在很多网站都是分为两个版本,一个pc端的一个移动端的(响应式除外),针对这两个版本,就需要对访问的设备进行判断,如果是pc,就直接访问pc网站, ...

  3. 天气预报API开发

    天气预报API开发 一.        寻觅篇 最近想要跟着视频练习一下利用API开发一个天气预报系统,就在网上找了一下可以用的API,结果好多都已经失效了... 1.       百度车联网天气预报 ...

  4. dev GridControl 根据鼠标坐标 选中行

    if (e.Button == System.Windows.Forms.MouseButtons.Right) { DevExpress.XtraGrid.Views.Grid.ViewInfo.G ...

  5. 学习笔记: Delphi之线程类TThread

    新的公司接手的第一份工作就是一个多线程计算的小系统.也幸亏最近对线程有了一些学习,这次一接手就起到了作用.但是在实际的开发过程中还是发现了许多的问题,比如挂起与终止的概念都没有弄明白,导致浪费许多的时 ...

  6. 如何在.NET上处理二维码

    在移动设备,网站以及应用程序间传送数据,而使用二维码真是一种较快捷的方法,也避免了蓝牙配对的混乱状况.ZXing.NET是一个开源,多格式1D/2D条码图像处理库的C#实现,ZXing.NET是个相当 ...

  7. ReactJs笔记

    中文教程:http://reactjs.cn/ 实例: http://www.ruanyifeng.com/blog/2015/03/react.html

  8. ReactJS入门(四)—— 组件API

    本篇将介绍 React 组件的API,其中主要的几个API我们在第一篇的时候便已介绍过,这里可以做个温故知新. 本篇的代码你也可以在我的Github上获取到. setState 参数: nextSta ...

  9. EF联合查询,如何设置条件过滤从表数据

    最近在使用EF进行联合查询过程中,遇到了一件不开心的事情. 已禁用懒加载 var post = await _repository.GetMyPostById(blogId, postId).AsNo ...

  10. .NET中操作IPicture、IPictureDisp的小随笔

    [题外话] 最近在做一个调用某实验仪器的程序,这个仪器提供了Windows上COM的接口.调用仪器的时候需要传输图片,提供的接口里使用了IPicture这个接口,由于以前没接触过,所以查找了一些资料, ...