开启 Keep-Alive 可能会导致http 请求偶发失败

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

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

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

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

过程如下图所示,

image.png

如何避免此类问题

上述问题在理论上的确是一直存在的,但是我们可以针对发送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,最后一点,千万不要抱着侥幸心理去看待网络请求,正如第四点说的那样,不管你的网络情况是否好坏,都是存在失败的可能。嗯,面对异常编程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/791779.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++Date类的实现

目录 前言: 1.显示日期 2.构造函数与获取某年某月的日期的函数 3.日期比较 4.日期加减天数 5.日期减日期 6.前置后置与-- 7.完整代码 8.测试 总结: 感谢支持! 前言: 结合了前面的内容的学习,本篇来对之前的…

基于php医院预约挂号系统

摘 要 随着信息时代的来临,过去的管理方式缺点逐渐暴露,对过去的医院预约挂号管理方式的缺点进行分析,采取计算机方式构建医院预约挂号系统。本文通过阅读相关文献,研究国内外相关技术,开发并设计一款医院预约挂号系统…

语义分割交互式智能标注工具 | 澳鹏数据标注平台

随着人工智能应用的大规模落地,数据标注市场在高速增长的同时,也面临着标注成本的挑战。据IDC报告显示:数据标注在AI应用开发过程中所耗费的时间占到了25%,部分医学类应用一条数据的标注成本甚至高达20元。数据精度的高要求、强人…

【Flink技术原理构造及特性】

1、Flink简介 Flink是一个批处理和流处理结合的统一计算框架,其核心是一个提供了数据分发以及并行化计算的流数据处理引擎。它的最大亮点是流处理,是业界最顶级的开源流处理引擎。 Flink最适合的应用场景是低时延的数据处理(Data Processin…

爬虫实战三、PyCharm搭建Scrapy开发调试环境

#一、环境准备 Python开发环境以及Scrapy框架安装,参考:爬虫实战一、Scrapy开发环境(Win10Anaconda)搭建 PyCharm安装和破解,参考:爬虫实战二、2019年PyCharm安装(激活到2100年) …

音视频过滤器实战--音频混音

1、添加并初始化音频输入、输出流 2、打开输入、输出音频文件 3、添加音频帧数据&#xff0c;然后循环获取输出的音频帧&#xff0c;将音频数据写文件保存 4、代码实例 audiomixer.h #ifndef AUDIOMIXER_H #define AUDIOMIXER_H#include <map> #include <mutex&g…

0基础如何进入IT行业?

0基础如何进入IT行业&#xff1f; 简介&#xff1a;对于没有任何相关背景知识的人来说&#xff0c;如何才能成功进入IT行业&#xff1f;是否有一些特定的方法或技巧可以帮助他们实现这一目标&#xff1f;我不知道&#xff0c;我的行业算不算是IT&#xff0c;或者最多是半个IT行…

重磅!openGauss6.0创新版本,带着新特性正式发布了!

&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&#x1f61c;&#x1f61c; 中国DBA联盟(ACD…

Tokio强大的Rust异步框架

一、简介 异步是现在编写高性能应用的核心要素之一&#xff0c;很多语言都已经纷纷加入异步的特效。C#应该是最先做出响应的语言。连微软这位软件大佬都非常重视异步&#xff0c;可见异步编程重要性。 在Rust中同样支持异步编程&#xff0c;只不过一般我们不会使用Rust官方&a…

从“量子”到分子:探索计算的无限可能 | 综述荐读

在2023年年末&#xff0c;两篇划时代的研究报告在《科学》&#xff08;Science&#xff09;杂志上引发了广泛关注。这两篇论文分别来自两个研究小组&#xff0c;它们共同揭示了单氟化钙分子间相互作用的研究成果&#xff0c;成功地在这些分子间创造出了分子量子比特。这一成就不…

算法练习—day1

title: 算法练习—day1 date: 2024-04-03 21:49:55 tags: 算法 categories:LeetCode typora-root-url: 算法练习—day1 网址&#xff1a;https://red568.github.io 704. 二分查找 题目&#xff1a; 题目分析&#xff1a; 左右指针分别为[left,right]&#xff0c;每次都取中…

【机器学习】K-近邻算法(KNN)介绍、应用及文本分类实现

一、引言 1.1 K-近邻算法&#xff08;KNN&#xff09;的基本概念 K-近邻算法&#xff08;K-Nearest Neighbors&#xff0c;简称KNN&#xff09;是一种基于实例的学习算法&#xff0c;它利用训练数据集中与待分类样本最相似的K个样本的类别来判断待分类样本所属的类别。KNN算法…

ArcGIS Pro导出布局时去除在线地图水印

目录 一、背景 二、解决方法 一、背景 在ArcGIS Pro中经常会用到软件自带的在线地图&#xff0c;但是在导出布局时&#xff0c;图片右下方会自带地图的水印 二、解决方法 解决方法&#xff1a;添加动态文本--服务图层制作者名单&#xff0c;然后在布局中选定位置添加 在状…

FPGA_mipi

1 mipi接口 mipi(移动行业处理器接口&#xff0c;是为高速数据传输量身定做的&#xff0c;旨在解决日益增长的高清图像(视频)传输的高带宽要求与传统接口低速率之间的矛盾。 采用差分信号传输&#xff0c;在设计时需要按照差分设计的一般规则进行严格的设计。 mipi协议提出之际…

dhcp中继代理

不同过路由器分配ip了&#xff0c;通过一台服务器来代替&#xff0c;路由器充当中继代理功能&#xff0c;如下图 服务器地址&#xff1a;172.10.1.1/24 配置流程&#xff1a; 1.使能dhcp功能 2.各个接口网关地址&#xff0c;配置dhcp中继功能 dhcp select relay &#xff0…

Qt | 发布程序(以 minGW 编译器为例)

1、注意:修改 pro 文件后,最好执行“构建”>“重新构建项目”,否则 pro 文件的更改将不会反应到程序上。 2、发布程序的目的:就是让编译后生成的可执行文件(如 exe 文件),能在其他计算机上运行。 一、编译后生成的各种文件简介 Qt Creator 构建项目后产生的文件及目录…

实时渲染 -- 材质(Materials)

一、自然界中的材质 首先了解下自然界中的材质 如上这幅图&#xff0c;不同的物体、场景、组合&#xff0c;会让我们看到不同的效果。 我们通常认为物体由其表面定义&#xff0c;表面是物体和其他物体或周围介质之间的边界面。但是物体内部的材质也会影响光照效果。我们目前只…

C++ 程序自动重启(windows 有源码)

初级代码游戏的专栏介绍与文章目录-CSDN博客 程序长时间运行&#xff0c;内存泄漏&#xff0c;最后崩溃&#xff0c;怎么办&#xff1f; 程序24小时运行&#xff0c;偶发随机崩溃&#xff0c;怎么办&#xff1f; 啃代码、内存泄漏检查工具、分析线程交互……没人敢承诺解决问题…

续二叉搜索树递归玩法

文章目录 一、插入递归二、寻找递归&#xff08;非常简单&#xff0c;走流程就行&#xff09;三、插入递归&#xff08;理解起来比较麻烦&#xff09; 先赞后看&#xff0c;养成习惯&#xff01;&#xff01;&#xff01;^ _ ^<3 ❤️ ❤️ ❤️ 码字不易&#xff0c;大家的…

第六题:标题统计

题目描述 凯凯刚写了一篇美妙的作文&#xff0c;请问这篇作文的标题中有多少个字符&#xff1f; 注意&#xff1a;标题中可能包含大、小写英文字母、数字字符、空格和换行符。统计标题字符数时&#xff0c;空格和换行符不计算在内。 输入描述 输入文件只有一行&#xff0c;…