大家好,我是蓝胖子,说起提高http的传输效率,很多人会开启http的Keep-Alive选项,这会http请求能够复用tcp连接,节省了握手的开销。但开启Keep-Alive真的没有问题吗?我们来细细分析下。

最大空闲时间造成请求失败

通常我们开启Keep-Alive后 ,服务端还会设置连接的最大空闲时间,这样能保证在没有请求发生时,及时释放连接,不会让过多的tcp连接白白占用机器资源。

问题就出现在服务端主动关闭空闲连接这个地方,试想一下这个场景,客户端复用了一个空闲连接发送http请求,但此时服务端正好检测到这个连接超过了配置的连接最大空闲时间,在请求到达前,提前关闭了空闲连接,这样就会导致客户端此次的请求失败。

过程如下图所示,

如何避免此类问题

上述问题在理论上的确是一直存在的,但是我们可以针对发送http请求的代码做一些加强,来尽量避免此类问题。来看看在Golang中,http client客户端是如何尽量做到安全的http重试的。

go http client 是如何做到安全重试请求的?

在golang中,在发送一次http请求后,如果发现请求失败,会通过shouldRetryRequest 函数判断此次请求是否应该被重试,代码如下,

func (pc *persistConn) shouldRetryRequest(req *Request, err error) bool {
if http2isNoCachedConnError(err) {
// Issue 16582: if the user started a bunch of
// requests at once, they can all pick the same conn // and violate the server's max concurrent streams. // Instead, match the HTTP/1 behavior for now and dial // again to get a new TCP connection, rather than failing // this request.
return true
}
if err == errMissingHost {
// User error.
return false
}
if !pc.isReused() {
// This was a fresh connection. There's no reason the server
// should've hung up on us. // // Also, if we retried now, we could loop forever // creating new connections and retrying if the server // is just hanging up on us because it doesn't like // our request (as opposed to sending an error).
return false
}
if _, ok := err.(nothingWrittenError); ok {
// We never wrote anything, so it's safe to retry, if there's no body or we
// can "rewind" the body with GetBody.
return req.outgoingLength() == 0 || req.GetBody != nil
}
if !req.isReplayable() {
// Don't retry non-idempotent requests.
return false
}
if _, ok := err.(transportReadFromServerError); ok {
// We got some non-EOF net.Conn.Read failure reading
// the 1st response byte from the server.
return true
}
if err == errServerClosedIdle {
// The server replied with io.EOF while we were trying to
// read the response. Probably an unfortunately keep-alive // timeout, just as the client was writing a request.
return true
}
return false // conservatively
}

我们来挨个看看每个判断逻辑,

http2isNoCachedConnError 是关于http2的判断逻辑,这部分逻辑我们先不管。

err == errMissingHost 这是由于请求路径中缺少请求的域名或ip信息,这种情况不需要重试。

pc.isReused() 这个是在判断此次请求的连接是不是属于连接复用情况,因为如果是新创建的连接,服务器正常情况下是没有理由拒绝我们的请求,此时如果请求失败了,则新建连接就好,不需要重试。

if _, ok := err.(nothingWrittenError); ok 这是在判断此次的请求失败的时候是不是还没有向对端服务器写入任何字节,如果没有写入任何字节,并且请求的body是空的,或者有body但是能通过req.GetBody 恢复body就能进行重试。

注意,因为在真正向连接写入请求头和body时,golang其实是构建了一个bufio.Writer 去封装了连接对象,数据是先写到了bufio.Writer 缓冲区中,所以有可能出现请求体Request已经读取了部分body,写入到缓冲区中,但实际真正向连接写入数据时失败的场景,这种情况重试就需要恢复原先的body,重试请求时,从头读取body数据。

req.isReplayable() 则是从请求体中判断这个请求是否能够被重试,如果不满足重试要求,则直接不重试,满足重试要求则会继续进行下面的重试判断。 其代码如下,如果http的请求body为空,或者有GetBody 方法能为其恢复body,并且是"GET", "HEAD", "OPTIONS", "TRACE" 方法之一则认为该请求重试是安全的。

还有种情况是如果http请求头中有Idempotency-Key 或者X-Idempotency-Key 也认为重试是安全的。

X-Idempotency-KeyIdempotency-Key 其实是为了给post请求的重试给了一个后门,对应的key是由业务方自己定义的具有幂等性质的key,服务端可以拿到它做幂等性校验,所以重试是安全的。

func (r *Request) isReplayable() bool {
if r.Body == nil || r.Body == NoBody || r.GetBody != nil {
switch valueOrDefault(r.Method, "GET") {
case "GET", "HEAD", "OPTIONS", "TRACE":
return true
}
// The Idempotency-Key, while non-standard, is widely used to
// mean a POST or other request is idempotent. See // https://golang.org/issue/19943#issuecomment-421092421
if r.Header.has("Idempotency-Key") || r.Header.has("X-Idempotency-Key") {
return true
}
}
return false
}

只有认为请求重试是安全后,才会进一步判断请求失败 是不是由于服务端关闭空闲连接造成的 _, ok := err.(transportReadFromServerError)errServerClosedIdle都是由于服务端关闭空闲连接造成的错误码,如果产生的错误码是其中之一,则都是允许被重试的。

所以,综上你可以看出,如果你发的请求是一个不带有Idempotency-Key或者X-Idempotency-Keypost请求头的post请求,那么即使是由于服务器关闭空闲连接造成请求失败,该post请求是不会被重试的。不过在其他请求方法比如GET方法下,由服务器关闭空闲连接造成的请求错误,Golang 能自动重试。

最佳实践

针对上述场景,我们应该如何设计我们的请求发送来保证安全可靠的发送http请求呢?针对于Golang开发环境,我总结几点经验,

1,GET请求可以自动重试,如果你的接口没有完全准寻restful 风格,GET请求的处理方法仍然有修改数据的操作,那么你应该保证你的接口是幂等的。

2,POST请求不会自动重试,但是如果你需要让你的操作百分百的成功,请添加失败重试逻辑,同样,服务端最好做好幂等操作。

3,如果对性能要求不是那么高,那么直接关闭掉http的长链接,将请求头的Connection 字段设置为close 这样每次发送发送http请求时都是用的新的连接,不会存在潜在的服务端关闭空闲连接造成请求失败的问题。

4,第四点,其实你可以发现,网络请求,不管你的网络情况是否好坏,都是存在失败的可能,即使将http长连接关掉,在网络坏的情况下,请求还是会失败,失败了要想保证成功,就得重试,重试就一定得保证服务端接口幂等了,所以,你的接口如果是幂等的,你的请求如果具有重试逻辑,那么恭喜你,你的系统十分可靠。

5,最后一点,千万不要抱着侥幸心理去看待网络请求,正如第四点说的那样,不管你的网络情况是否好坏,都是存在失败的可能。嗯,面对异常编程。

开启 Keep-Alive 可能会导致http 请求偶发失败的更多相关文章

  1. IE浏览器缓存导致Ajax请求失败

    在IE浏览器中通过Ajax请求后台的数据,如果Page请求是postback类型的,可能会导致Ajax请求失败的问题 我们都知道ajax能提高页面载入的速度主要的原因是通过ajax减少了重复数据的载入 ...

  2. servlet请求编码与响应编码问题(编码不一致可能会导致乱码)

    html中的编码 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"&g ...

  3. nginx开启gzip压缩后导致apk包下载不能正常安装

    最后更新时间:2019/4/27 nginx一般都会开启gzip压缩,以提升传输性能. 配置如下: gzip on; gzip_comp_level 2; gzip_min_length 1k; gz ...

  4. 痞子衡嵌入式:IAR在线调试时设不同复位类型可能会导致i.MXRT下调试现象不一致(J-Link / CMSIS-DAP)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是IAR在线调试时设不同复位类型可能会导致i.MXRT下调试现象不一致. 做Cortex-M内核MCU嵌入式软件开发,可用的集成开发环境( ...

  5. 对“demo!demo.Index+HookProc::Invoke”垃圾收集的类型已委托回调。这可能会导致应用程序崩溃、损坏和数据丢失。当传递委托给非托管代码,托管应用程序必须让这些委托保持活着

    对"demo!demo.Index+HookProc::Invoke"垃圾收集的类型已委托回调.这可能会导致应用程序崩溃.损坏和数据丢失.当传递委托给非托管代码,托管应用程序必须承 ...

  6. EF Core 遇到“可能会导致循环或多重级联路径”

    在ef core中你可能会设计这样一个实体: public class Customer : Entity,IMustHaveTenant, IHasCreationTime { public Cus ...

  7. 360或者金山毒霸可能会导致HP网络打印机驱动安装失败“数据无效”的解决办法

    360或者金山毒霸可能会导致HP网络打印机驱动安装失败“数据无效”的解决办法     同事办公室的打印机是网线接口的那种网络打印机,不是直接连到电脑的那种,他电脑安装了360和金山毒霸,WIN10下安 ...

  8. 可能会导致循环或多重级联路径。请指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。

    错误提示:可能会导致循环或多重级联路径.请指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束. 原因:自表连接(同一张表 ...

  9. 所生成项目的处理器架构“MSIL”与引用“***”的处理器架构“x86”不匹配。这种不匹配可能会导致运行时失败。请考虑通过配置管理器...

    警告:所生成项目的处理器架构“MSIL”与引用“***”的处理器架构“x86”不匹配.这种不匹配可能会导致运行时失败.请考虑通过配置管理器更改您的项目的目标处理器架构,以使您的项目与引用间的处理器架构 ...

  10. C# json反序列化 对象中嵌套数组 (转载) 可能会导致循环或多重级联路径。请指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。

    C# json反序列化 对象中嵌套数组 (转载)   看图: 这里可以看到是二层嵌套!!使用C#如何实现?? 思路:使用list集合实现 → 建立类 → list集合 → 微软的   Newtonso ...

随机推荐

  1. 开源大语言模型作为 LangChain 智能体

    概要 开源大型语言模型 (LLMs) 现已达到一种性能水平,使它们适合作为推动智能体工作流的推理引擎: Mixtral 甚至在我们的基准测试中 超过了 GPT-3.5,并且通过微调,其性能可以轻易的得 ...

  2. 【LeetCode二叉树#19】有序数组转换为二叉搜索树(构造二叉树)

    将有序数组转换为二叉搜索树 力扣题目链接(opens new window) 将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树. 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个 ...

  3. Linux查看文件大小、磁盘使用情况

    1.显示磁盘的可用情况: df -h 2.显示文件夹大小 du -ka folder | sort -rnk 1 | head -n 10

  4. BeanShell 如何加密加签?

    一 首先我们要搞清楚接口签名步骤: **第一步:初步实现接口****第二步:找开发拿到算法和key** * key:sAHDRNJg0ZevmEn7HwBfbw== * 算法:HmacMD5 咱们就找 ...

  5. 出海业务如何搭建国内也能快速访问的https网站与接口(无需备案)

    背景信息 由于最近在搭建我的出海网站 https://www.idatariver.com/zh-cn , 感兴趣的可以看看. 其中一个环节便是给后端API接口加上ssl,毕竟http看着不如http ...

  6. vue3使用路由keep-alive和监听路由实现transition

    随着vue3.0的发布,vue-router发布了4.0版本,文档 很明了,提供了vue2路由到vue3的变化和写法指导. vue2: // transition <transition nam ...

  7. 深入解析:AntSK 0.1.7版本的技术革新与多模型管理策略

    在信息技术快速迭代的当下,.Net生态中的AntSK项目凭借其前沿的AI知识库和智能体技术,已经吸引了广大开发者的关注和参与.今天,我要给大家介绍的主角,AntSK 0.1.7版本,无疑将是这个开源项 ...

  8. web项目开发写接口时,为什么需要在关键位置打印日志-2022新项目

    一.业务场景 最近在开发新功能,新功能主要就是写app的首页查询接口,接口比较多有十几个,首页会有各种查询,新增操作比较少.由于用户量 比较大,据说并发量不小,所以首页的很多查询都做了缓存处理,用来提 ...

  9. PDF/Excel文件预览功能完整实现-java版本

    新需求 最近接到一个新的需求,说是之前直接下载的PDF文件或者是Excel文件,现在不能直接下载,需要实现在线预览功能. 前端人员拿到这个需求后,去看了一下以前的代码,以前调用的下载接口和PDF文件预 ...

  10. KTL 一个支持C++14编辑公式的K线技术工具平台 - 第九版,数据分析工具。支持通达信日线数据。增强即时插件编程体现。

    K,K线,Candle蜡烛图. T,技术分析,工具平台 L,公式Language语言使用c++14,Lite小巧简易. 项目仓库:https://github.com/bbqz007/KTL 国内仓库 ...