当前位置: 首页 > 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 中的其中任意一个,存在则认…...

XML Group端口详解

在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...

Chapter03-Authentication vulnerabilities

文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...

Python爬虫实战:研究feedparser库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

ESP32读取DHT11温湿度数据

芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...

Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器

第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...

spring:实例工厂方法获取bean

spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂&#xff…...

Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!

一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...