故障现场

本人负责的主备集群,发出的 HttpClient 请求有 30%概率超时, 报context deadline exceeded (Client.Timeout or context cancellation while reading body)

异常

Kibana 显示 Nginx 处理请求的耗时request_time在[5s-1min]区间波动, upstream_response_time在 2s 级别。

所以我们认定是 Nginx 向客户端回传 50M 的数据,发生了网络延迟。

于是将 HttpClient Timeout 从 30s 调整到 60s, 上线之后明显改善。

But 昨天又出现了一次同样的超时异常

time="2022-01-05T22:28:59+08:00" ....
time="2022-01-05T22:30:02+08:00" level=error msg="service Run error" error="region: sz,first load self allIns error:context deadline exceeded (Client.Timeout or context cancellation while reading body)"

线下复盘

Kibana 显示 Nginx 处理请求的耗时request_time才 32s, 远不到我们对于 Http 客户端设置的 60s 超时阈值。

这里有必要穿插 Nginx Access Log 的几个背景点

  1. Nginx 写日志的时间

    根据nginx access log官方,NGINX writes information about client requests in the access log right after the request is processed. 也就是说 Nginx 是在端到端请求被处理完之后才写入日志。
  2. Nginx Request_Time upstream_response_time
  • $upstream_response_time – The time between establishing a connection and receiving the last byte of the response body from the upstream server

    从 Nginx 向后端建立连接开始到接受完数据然后关闭连接为止的时间
  • $request_time – The total time spent processing a request

    Nginx 从接受用户请求的第一个字节到发送完响应数据的时间

从目前的信息看,Nginx 从接受请求到发送完响应流,总共耗时 32s。

那剩下的 28s,是在哪里消耗的。

三省吾身

这是我抽离的 HttpClient 的实践, 常规的不能再常规。

package main

import (
"bytes"
"encoding/json"
"io/ioutil"
"log"
"net/http"
"time"
) func main() {
c := &http.Client{Timeout: 10 * time.Second}
body := sendRequest(c, http.MethodPost)
log.Println("response body length:", len(body))
} func sendRequest(client *http.Client, method string) []byte {
endpoint := "http://mdb.qa.17usoft.com/table/instance?method=batch_query"
expr := "idc in (logicidc_hd1,logicidc_hd2,officeidc_hd1)"
jsonData, err := json.Marshal([]string{expr})
response, err := client.Post(endpoint, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
log.Fatalf("Error sending request to api endpoint, %+v", err)
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Fatalf("Couldn't parse response body, %+v", err)
}
return body
}

核心就两个动作

  • 调用Get、Post、Do方法发起 Http 请求, 如果无报错,则表示服务端已经处理了请求
  • iotil.ReadAll表示客户端准备从网卡读取 Response Body (流式数据), 超时异常正是从这里爆出来的

go 的 HttpClient Timeout 定义

Timeout specifies a time limit for requests made by this Client. The timeout includes connection time, any redirects, and reading the response body. The timer remains running after Get, Head, Post, or Do return and will interrupt reading of the Response.Body.

HttpClient Timeout包括连接、重定向(如果有)、从Response Body读取的时间,内置定时器会在Get,Head、Post、Do 方法之后继续运行,直到读取完Response.Body.

报错内容:"Client.Timeout or context cancellation while reading body" 读取 Response Body 超时,

潜台词是:服务器已经处理了请求,并且开始向客户端网卡写入数据。

根据我有限的网络原理/计算机原理,与此同时,客户端会异步从网卡读取 Response Body。

写入和读取互不干扰,但是时空有重叠

所以[读取 Body 超时]位于图中的红框区域,这就有点意思了。

  • 之前我们有 30%概率[读取 Body 超时],确实是因为 Nginx 回传 50M 数据超时,这在 Nginx request_time 上能体现。

  • 本次 Nginx 显示 request_time=32s, 却再次超时,推断 Nginx 已经写完数据,而客户端还没有读取完 Body

至于为什么没读取完,这就得吐槽iotil.ReadAll的性能了。

客户端使用 iotil.ReadAll 读取大的响应体,会不断申请内存(源码显示会从 512B->50M),耗时较长,性能较差、并且有内存泄漏的风险, 下一篇我会讲解针对大的响应体替换iotil.ReadAll的方案


为了模拟这个偶发的情况,我们可在Postiotil.ReadAll前后加入时间日志。

$ go run main.go
2022/01/07 20:21:46 开始请求: 2022-01-07 20:21:46.010
2022/01/07 20:21:47 服务端处理结束: 2022-01-07 20:21:47.010
2022/01/07 20:21:52 客户端读取结束: 2022-01-07 20:21:52.010
2022/01/07 20:21:52 response body length: 50575756

可以看出,当读取大的响应体时候,客户端iotil.ReadAll的耗时并不算小,这块需要开发人员重视。

我们甚至可以iotil.ReadAll之前time.Sleep(xxx), 就能轻松模拟出生产环境的读取 Body 超时。

我的收获

  1. Nginx Access Log 的时间含义
  2. go 的 HttpClient Timeout 包含了连接、请求、读取 Body 的耗时
  3. 通过对[读取 Body 超时异常]的分析,我梳理了端到端的请求耗时、客户端的行为耗时的时空关系, 这个至关重要。

深度分析 [go的HttpClient读取Body超时]的更多相关文章

  1. MapReduce深度分析(二)

    MapReduce深度分析(二) 五.JobTracker分析 JobTracker是hadoop的重要的后台守护进程之一,主要的功能是管理任务调度.管理TaskTracker.监控作业执行.运行作业 ...

  2. [转帖]深度分析HBase架构

    深度分析HBase架构 https://zhuanlan.zhihu.com/p/30414252   原文链接(https://mapr.com/blog/in-depth-look-hbase-a ...

  3. MapReduce深度分析(一)

    MapReduce深度分析(一) 一.数据流向分析 图为MapReduce数据流向示意图 步骤1.输入文件从HDFS流向到Mapper节点.在一般情况下,存储数据的节点就是Mapper运行的节点,不需 ...

  4. HttpClient设置连接超时时间

    https://www.cnblogs.com/winner-0715/p/7087591.html 使用HttpClient,一般都需要设置连接超时时间和获取数据超时时间.这两个参数很重要,目的是为 ...

  5. (转)Memcached深度分析

    转自:http://jwen.iteye.com/blog/1123991 memcached是高性能的分布式内存缓存服务器.一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态W ...

  6. HttpClient库设置超时

    HttpClient库API跟Lucene一样,每个版本的API都变化很大,这有点让人头疼.就好比创建一个HttpClient对象吧,每一个版本的都不一样. 3.X是正常的Java语法 HttpCli ...

  7. Flink 源码解析 —— Standalone Session Cluster 启动流程深度分析之 Job Manager 启动

    Job Manager 启动 https://t.zsxq.com/AurR3rN 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0到1学习 -- Mac ...

  8. Flink 源码解析 —— Standalone Session Cluster 启动流程深度分析之 Task Manager 启动

    Task Manager 启动 https://t.zsxq.com/qjEUFau 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0到1学习 -- Ma ...

  9. 从Wannacry到WannaRen:螣龙安科带你深度分析勒索病毒原理

    从Wannacry到WannaRen:螣龙安科2020年4月7日,360CERT监测发现网络上出现一款新型勒索病毒wannaRen,该勒索病毒会加密windows系统中几乎所有的文件,并且以.Wann ...

随机推荐

  1. 【CF1591】【数组数组】【逆序对】#759(div2)D. Yet Another Sorting Problem

    题目:Problem - D - Codeforces 题解 此题是给数组排序的题,操作是选取任意三个数,然后交换他们,确保他们的位置会发生改变. 可以交换无限次,最终可以形成一个不下降序列就输出&q ...

  2. 解决用creact-react-app新建React项目不支持 mobx装饰器模式导致报错问题 。

    创建react项目 create-react-app mobx-demo cd my-app npm run start 使用react-app-rewired npm install customi ...

  3. 计算机网络-4-8-外部网关协议BGP

    外部网关协议BGP 1989年,公布了新的外部网关协议BGP(边界网关协议),我们目前使用最多的版本是BGP-4(但仍然是起草方案[RFC 4271]),简写为BGP. 在不同的自治系统AS中之间的路 ...

  4. java 数据类型:集合接口Collection之队列Queue:PriorityQueue ;Dequeue接口和ArrayDeque实现类:

    什么是Queue集合: Queue用于模拟队列这种数据结构,队列通常是"先进先出"(FIFO)的容器.队列的头部保存在队列中存放时间最长的元素,尾部保存存放时间最短的元素.    ...

  5. Dapr项目应用探索

    背景介绍 前面文章对Dapr的基本信息进行了学习,接下来尝试将Dapr应用相关应用中. 接下来一步步实现应用dapr功能. 一.预期效果 如上图应用Dapr点包含: a) 报表服务绑定统一数据源服务: ...

  6. mac OSX使用spdlog1.7

    !!版权声明:本文为博主原创文章,版权归原文作者和博客园共有,谢绝任何形式的 转载!! 作者:mohist 注意️ 请选择对c++11支持较为完善的编译器,因为spdlog一直更新.   本机演示环境 ...

  7. c(++)变长参数之整形(非字符串类型类似)

    0.序言 变长参数,接触的第一个可变长参数函数是 printf   , 然后是 scanf   .他们的原型如下: printf: _Check_return_opt_ _CRT_STDIO_INLI ...

  8. 【LeetCode】8. String to Integer (atoi) 字符串转换整数

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 公众号:负雪明烛 本文关键词:字符串转整数,atoi,题解,Leetcode, 力扣,P ...

  9. java源码——计算立体图形的表面积和体积

    计算球,圆柱,圆锥的表面积和体积. 利用接口实现. 上代码. Contants.java 常量存储类 package com.fuxuemingzhu.solidgraphics.contants; ...

  10. 【LeetCode】408. Valid Word Abbreviation 解题报告(C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 双指针 日期 题目地址:https://leetcod ...