开启 Keep-Alive 可能会导致http 请求偶发失败
大家好,我是蓝胖子,说起提高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-Key
和Idempotency-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ÿ…...
通过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月的副业规划目标ÿ…...
学习java第三十二天
Spring 会利用AutowiredAnnotationBeanPostProcessor.postProcessMergedBeanDefinition() 找出注入点并缓存, 找注入点的流程为: 遍历当前类的所有的属性字段 Field 查看字段上是否存在 Autowired、Value、Inject 中的其中任意一个,存在则认…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...

Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
代码随想录刷题day30
1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...
Bean 作用域有哪些?如何答出技术深度?
导语: Spring 面试绕不开 Bean 的作用域问题,这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开,结合典型面试题及实战场景,帮你厘清重点,打破模板式回答,…...
掌握 HTTP 请求:理解 cURL GET 语法
cURL 是一个强大的命令行工具,用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中,cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...