当前位置: 首页 > news >正文

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

相关文章:

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

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

【leetcode面试经典150题】4.删除有序数组中的重复项 II(C++)

【leetcode面试经典150题】专栏系列将为准备暑期实习生以及秋招的同学们提高在面试时的经典面试算法题的思路和想法。本专栏将以一题多解和精简算法思路为主,题解使用C语言。(若有使用其他语言的同学也可了解题解思路,本质上语法内容一致&…...

【LeetCode热题100】【普通数组】合并区间

题目链接:56. 合并区间 - 力扣(LeetCode) 先排序,按左区排序,装第一个区间进入答案容器,判断答案容器钟最后一个区间的右区是否小于区间的左区,是则不能合并是新区间,否则可以合并 …...

自我认识的方法模型图

在漫长的人生旅途中,我们都在不断地探索、追寻,努力寻找那个最真实、最完整的自我。因为只有真正了解自己,才能战胜内心的种种困惑与恐惧,进而战胜外在的一切挑战与困难。自我认识,是每个人成长的必经之路,…...

dhcp和dhcp中继代理

简单说就是各个pc机的ip自动获取,不用手动设置 配置思路 1.使能dhcp功能 2.创建全局地址池 ip pool ,配置可用网络地址 network mask和网关地址刚刚忘记了,租约期 lease day hour 3.配置端口的网关地址(各个网络地址的第二位…...

【fastadmin】脚本模式下,日志钩子函数执行出现死循环,导致内存溢出奔溃

问题出现原因是想对项目中error级别的日志,接入钉钉告警,方便查看 于是使用钩子方法,日志写入完成后,自动调用自定义的告警方法中 1、在application/tags.php 中添加log_write_done > [app\\common\\behavior\\Common, ],2、在…...

gitlab代码迁移,包含历史提交记录、标签、分支

1、克隆现有的GitLab仓库(http://localhost:8888/aa/bb/cc.git)到本地,包括所有分支和标签 git clone --bare http://localhost:8888/aa/bb/cc.git 2、在gitlab上创建一个空的仓库(http://localhost:7777/aa/bb/cc.git&#xff…...

通过TCP或UDP向某个IP和端口发送数据

工具发送 如果您想要一个简单的方法来发送TCP或UDP数据,可以尝试使用nc(netcat)命令。这是一个功能强大的网络工具,可以用于读取和写入数据流。 发送TCP数据 在命令行中运行以下命令: echo "Hello, World\!&q…...

Go语言介绍及Go语言成功的项目列举

Go语言介绍: Go即 Golang ,是 Google 公司 2009 年 11 月正式对外公开的一门编程语言。 根据 Go 语言开发者自述,近 10 多年,从单机时代的 C 语言到现在互联网时代的 Java , 都没有令人满意的开发语言&a…...

CQI-17:2021 V2 英文 、中文版。特殊过程:电子组装制造-锡焊系统评审标准

锡焊作为一个特殊的工艺过程,由于其材料特性的差异性、工艺参数的复杂性和过程控制的不确定性,长期以来一直视为汽车零部件制造业的薄弱环节,并将很大程度上直接导致整车产品质量的下降和召回风险的上升。 美国汽车工业行动集团AIAG的特别工…...

普通Java工程可执行JAR两种打包方式探讨

文章目录 一、需求概述二、代码结构三、运行结果四、打包设置1. 一体化可执行包2. 带外部依赖lib的可执行包 五、打包运行1. 源码放送2. 打包执行3. 打包结果 一、需求概述 普通Java工程 docker-show 实现了定时打印docker应用信息,现在需要将其打包成可执行Jar部署…...

开源博客项目Blog .NET Core源码学习(13:App.Hosting项目结构分析-1)

开源博客项目Blog的App.Hosting项目为MVC架构的,主要定义或保存博客网站前台内容显示页面及后台数据管理页面相关的控制器类、页面、js/css/images文件,页面使用基于layui的Razor页面(最早学习本项目就是想学习layui的用法,不过最…...

Vue的双向绑定v-model详细介绍

使用: 比如用户在登录注册时需要提交账号密码; 比如用户创建,更新时,需要提交一些数据; v-model指令可以在表单 input、textarea以及select元素上创建双向绑定; 它会根据控件类型自动选取正确的方法来更…...

AWS入门实践-S3对象存储的基本用法

AWS S3(Simple Storage Service)是亚马逊云服务提供的一种高度可扩展、安全且经济高效的对象存储服务。它允许用户在任何位置存储和检索任意数量的数据,非常适合存储和分发静态文件、备份数据以及作为数据湖的存储层。 一、S3上传和下载文件(AWS门户) …...

el-tree-v2渲染树形大数据并设置默认展开

el-tree-v2无 el-tree中默认展开节点的属性,需要自行设置 default-expand-all是否默认展开所有节点 需求:首次默认展开全部节点 实现1尝试失败:增加设置了属性 :default-expand-keys"props.treeData.map(itemitem.id)"无效&…...

损失函数篇 | YOLOv8更换损失函数之MPDIoU(23年7月首发论文)

前言:Hello大家好,我是小哥谈。损失函数是机器学习中用来衡量模型预测值与真实值之间差异的函数。在训练模型时,我们希望通过不断调整模型参数,使得损失函数的值最小化,从而使得模型的预测值更加接近真实值。不同的损失函数适用于不同的问题,例如均方误差损失函数适用于回…...

【力扣】200.岛屿数量(染色法DFS深搜)

岛屿数量 题目描述 链接:力扣:200.岛屿数量 给你一个由 1(陆地)和 0(水)组成的的二维网格,请你计算网格中岛屿的数量。 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆…...

达梦配置ODBC连接

达梦配置ODBC连接 基础环境 操作系统:Red Hat Enterprise Linux Server release 7.9 (Maipo) 数据库版本:DM Database Server 64 V8 架构:单实例1 下载ODBC包 下载网址:https://www.unixodbc.org/ unixODBC-2.3.0.tar.gz2 编译并…...

独孤思维:高客单价项目,必须来一个

01 上次和水龙聊完以后,完成了图书电商项目小报童的梳理。 而且还让我规划后端低转高产品的设计。 目前独孤,准备以图书电商项目私教作为切入点,捆绑自己的合伙人。 设计高客单价项目。 所以,独孤4月的副业规划目标&#xff…...

学习java第三十二天

Spring 会利用AutowiredAnnotationBeanPostProcessor.postProcessMergedBeanDefinition() 找出注入点并缓存, 找注入点的流程为: 遍历当前类的所有的属性字段 Field 查看字段上是否存在 Autowired、Value、Inject 中的其中任意一个,存在则认…...

生成xcframework

打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

2024年赣州旅游投资集团社会招聘笔试真

2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...

BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践

6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf

FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...

虚拟电厂发展三大趋势:市场化、技术主导、车网互联

市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦&#xff0…...

Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换

目录 关键点 技术实现1 技术实现2 摘要: 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式(自动驾驶、人工驾驶、远程驾驶、主动安全),并通过实时消息推送更新车…...

Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成

一个面向 Java 开发者的 Sring-Ai 示例工程项目,该项目是一个 Spring AI 快速入门的样例工程项目,旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计,每个模块都专注于特定的功能领域,便于学习和…...

协议转换利器,profinet转ethercat网关的两大派系,各有千秋

随着工业以太网的发展,其高效、便捷、协议开放、易于冗余等诸多优点,被越来越多的工业现场所采用。西门子SIMATIC S7-1200/1500系列PLC集成有Profinet接口,具有实时性、开放性,使用TCP/IP和IT标准,符合基于工业以太网的…...

0x-3-Oracle 23 ai-sqlcl 25.1 集成安装-配置和优化

是不是受够了安装了oracle database之后sqlplus的简陋,无法删除无法上下翻页的苦恼。 可以安装readline和rlwrap插件的话,配置.bahs_profile后也能解决上下翻页这些,但是很多生产环境无法安装rpm包。 oracle提供了sqlcl免费许可&#xff0c…...