Gin获取Response Body引发的OOM
有轮子尽量用轮子 😭 😭 😭 😭 😭 😭
我们在开发中基于Gin开发了一个Api网关,但上线后发现内存会在短时间内暴涨,然后被OOM kill掉。具体内存走势如下图:

放大其中一次

在图二中可以看到内存的增长是很快的,在一分半的时间内,内存增长了近2G。
对于这种内存短时间暴涨的问题,pprof不好管用,除非写个脚本定时去pprof
经过再次review代码,找到了原因了
package serverimport ("bytes""fmt""github.com/gin-gonic/gin"jsoniter "github.com/json-iterator/go"
)var json = jsoniter.ConfigCompatibleWithStandardLibrarytype BodyDumpResponseWriter struct {gin.ResponseWriterbody *bytes.Buffer
}func (w *BodyDumpResponseWriter) Write(b []byte) (int, error) {w.body.Write(b) // 注意这一行return w.ResponseWriter.Write(b)
}func ReadResponseBody(ctx *gin.Context) {rbw := &BodyDumpResponseWriter{body: &bytes.Buffer{}, ResponseWriter: ctx.Writer}ctx.Writer = rbwctx.Next()rawResp := rbw.body.String()if len(rawResp) == 0 {AbnormalPrint(ctx, "resp-empty", rawResp)return}ctx.Set(ctx_raw_response_body, rawResp)// 序列化Body,并放到ctx中// 读取响应Body的目的是记录审计日志用
}// AbnormalPrint 异常情况,打印信息到日志
func AbnormalPrint(ctx *gin.Context, typ string, rawResp string) {
// 具体代码忽略
}
简单一看,这不就是Gin获取响应体一种标准的方式吗?毕竟GitHub及Stack Overflow上都是这么写的
https://github.com/gin-gonic/gin/issues/1363
https://stackoverflow.com/questions/38501325/how-to-log-response-body-in-gin
那么问题出在哪呢?
再看下代码,可以看到这个代码的逻辑是每一个请求都会将响应的Body完整的缓存在内存一份,对于响应体很大的请求,在这里就会造成内存暴涨,比如:像日志下载。
找到了原因修改起来就比较简单了,根据请求响应的Header跳过文件下载类的请求;同时根据请求的Header跳过SSE及Websocket请求,因为这两类流的请求记录到审计日志中意义不大,而且在json序列化的时候也会有问题。
package serverimport ("bytes""fmt""net/http""strings""github.com/gin-gonic/gin"jsoniter "github.com/json-iterator/go"
)var json = jsoniter.ConfigCompatibleWithStandardLibrarytype BodyDumpResponseWriter struct {gin.ResponseWriterbody *bytes.Buffer
}func (w *BodyDumpResponseWriter) Write(b []byte) (int, error) {// 文件下载类请求,不再缓存相应结果if !isFileDownLoad(w.Header()) {w.body.Write(b)}return w.ResponseWriter.Write(b)
}func isNoNeedToReadResponse(req *http.Request) bool {if isSSE(req) || isWebsocket(req) {return true}return false
}func isSSE(req *http.Request) bool {contentType := req.Header.Get("Accept")if contentType == "" {contentType = req.Header.Get("accept")}contentType = strings.ToLower(contentType)// sseif !strings.Contains(contentType, "text/event-stream") {return false}return true
}// https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
func isFileDownLoad(responseHeader http.Header) bool {contentType := strings.ToLower(responseHeader.Get("Content-Type"))if strings.Contains(contentType, "application/octet-stream") {return true}contentDisposition := responseHeader.Get("Content-Disposition")if contentDisposition != "" {return true}return false
}func isWebsocket(req *http.Request) bool {conntype := strings.ToLower(req.Header.Get("Connection"))upgrade := strings.ToLower(req.Header.Get("Upgrade"))if conntype == "upgrade" && upgrade == "websocket" {return true}return false
}func ReadResponseBody(ctx *gin.Context) {if isNoNeedToReadResponse(ctx.Request) {return}rbw := &BodyDumpResponseWriter{body: &bytes.Buffer{}, ResponseWriter: ctx.Writer}ctx.Writer = rbwctx.Next()contentType := ctx.Writer.Header().Get("content-type")if !strings.Contains(contentType, "application/json") {return}rawResp := rbw.body.String()if len(rawResp) == 0 {AbnormalPrint(ctx, "resp-empty", rawResp)return}ctx.Set(ctx_raw_response_body, rawResp)// 序列化Body,并放到ctx中// 读取响应Body的目的是记录审计日志用
}// AbnormalPrint 异常情况,打印信息到日志
func AbnormalPrint(ctx *gin.Context, typ string, rawResp string) {
// 具体代码忽略
}
其实,写这篇文章的目的并不是为了阐述这个问题如何解决,而是想说:
- Copy 代码的时候留意下自己的场景
- 尽量用轮子,而不是自己去造轮子
在我们手写API网关的时候,还遇到过以下问题
- 第一版的网络处理也是手写的,导致对于各种Content-Type处理不好;
- 因为要解析Body,也没有精力去适配各种压缩协议,所以在网关这里会强制关闭压缩;
- 手写网络处理,会一些情况会出现一些诡异的问题
- 比如:我们支持页面终端连接到K8S集群,而这个终端连接走的是Websocket,假设支持该连接操作的服务是A(就是:页面< - - - - - - >服务A< - - - - - - >K8S集群),那么后面过网关的请求部分请求会直接请求到服务A上(此时根本没有走网关的API router,
直接就复用Websocket这个连接了),即使这些API不是服务A的。
- 比如:我们支持页面终端连接到K8S集群,而这个终端连接走的是Websocket,假设支持该连接操作的服务是A(就是:页面< - - - - - - >服务A< - - - - - - >K8S集群),那么后面过网关的请求部分请求会直接请求到服务A上(此时根本没有走网关的API router,
第一版手写网络请求处理的代码示意如下:
func proxyHttp(ctx context.Context, proxy_req *http.Request, domain string) {// origin requestreq := ctx.Request()response, err := HttpClient.Do(proxy_req)if err != nil {// 打印异常return}defer response.Body.Close()//copy response headerif response != nil && response.Header != nil {for k, values := range response.Header {for _, value := range values {ctx.ResponseWriter().Header().Set(k, value)}}}// status codectx.StatusCode(response.StatusCode)buf := make([]byte, 1024)for {len, err := response.Body.Read(buf)if err != nil && err != io.EOF {// 打印异常break}if len == 0 {break}ctx.ResponseWriter().Write(buf[:len])ctx.ResponseWriter().Flush()continue}ctx.Next()
}func proxyWebSocket(ctx context.Context, request *http.Request, target string) {var logger = ctx.Application().Logger()responseWriter := http.ResponseWriter(ctx.ResponseWriter())conn, err := net.Dial("tcp", target)if err != nil {// 打印异常return}hijacker, ok := responseWriter.(http.Hijacker)if !ok {http.Error(responseWriter, "Not a hijacker?", 500)return}nc, _, err := hijacker.Hijack()if err != nil {// 打印异常return}defer nc.Close()defer conn.Close()err = request.Write(conn)if err != nil {// 打印异常return}errc := make(chan error, 2)cp := func(dst io.Writer, src io.Reader) {_, err := io.Copy(dst, src)errc <- err}go cp(conn, nc)go cp(nc, conn)// wait over<-errcctx.Application().Logger().Infof("websocket proxy to %s over", target)
}
后来换成了基础类库的httputil.ReverseProxy来处理网络连接,问题解决。
相关文章:
Gin获取Response Body引发的OOM
有轮子尽量用轮子 😭 😭 😭 😭 😭 😭 我们在开发中基于Gin开发了一个Api网关,但上线后发现内存会在短时间内暴涨,然后被OOM kill掉。具体内存走势如下图: 放大其中一次 在…...
不同方案特性对比
特性对比项 2.4G 蓝牙 868M WIFI 通信速率 低 低 低 高 距离(实用可靠) 20米 10米 30米 15米 确定性 高 低 高 高 可靠性(距离内) 高 低 高 高 刷新一个标签时间(通常) 0.5-1s …...
线性数据结构:链表 LinkList
一、前言 链表的历史 于1955-1956年,由兰德公司的Allen Newell、Cliff Shaw和Herbert A. Simon开发了链表,作为他们的信息处理语言的主要数据结构。链表的另一个早期出现是由 Hans Peter Luhn 在 1953 年 1 月编写的IBM内部备忘录建议在链式哈希表中使…...
对restful的支持 rust-grpc-proxy
目录前言快速体验说明1. 启动目标服务2. 启动代理3. 测试4. example.sh尾语前言 继上一篇博文的展望,这个月rust-grpc-proxy提供了对restful的简单支持。 并且提供了完成的用例,见地址如下, https://github.com/woshihaoren4/grpc-proxy/tre…...
【模拟集成电路】环路滤波器(LPF)设计
环路滤波器 LPF 设计 前言环路滤波器设计仿真结果各部分链接链接:前言 本文主要内容是对环路滤波器 模块设计设计进行阐述,LPF在电荷泵频率综合器中,主要作用是进行滤波,消除毛刺,因此一个简单的RC就可以起到很好的效果…...
adb及cmd部分常用命令
adb及cmd部分常用命令cmd常用命令adb常用命令内存/cpu相关此文章日常记录,有可能存在不准确的地方,仅供参考即可。 cmd常用命令 返回上一级: cd… 进入指定盘: D: 进入指定路径: cd 文件路径 查看子文件列表…...
ProtoBuf介绍
1 编码和解码编写网络应用程序时,因为数据在网络传输的都是二进制字节码数据,在发送数据时进行编码,在接受数据时进行解码codec(编码器)的组成部分有2个:decoder(解码器)和encoder&a…...
数据结构:完全二叉树开胃菜小练习
目录 一.前言 二.完全二叉树的重要结构特点 三.完全二叉树开胃菜小练习 1.一个重要的数学结论 2.简单的小练习 一.前言 关于树及完全二叉树的基础概念(及树结点编号规则)参见:http://t.csdn.cn/imdrahttp://t.csdn.cn/imdra 完全二叉树是一种非常重要的数据结构: n个结点的…...
mybatis与jpa
1、官方文档 mybatis:mybatis-spring – jpa:https://springdoc.cn/spring-data-jpa/ 应用文档 jpa详解_java菜鸟1的博客-CSDN博客 JPA简介及其使用详解_Tourist-xl的博客-CSDN博客_jpa的作用 2、使用比较 mybatis一般用于互联网性质的项目&#x…...
js 求解《初级算法》66. 加一
一、题目描述 给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。你可以假设除了整数 0 之外,这个整数不会以零开头。 示例 1: 输入:…...
力扣-游戏玩法分析
大家好,我是空空star,本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目:511. 游戏玩法分析二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.其他总结…...
ZZNUOJ_用C语言编写程序实现1186 : 奖学金(结构体专题)(附完整源码)
题目描述 某校发放奖学金共5种,获取条件各不同: 1.阳明奖学金,每人8000,期末平均成绩>80,且在本学期发表论文大于等于1篇; 2.梨洲奖学金,每人4000,期末平均成绩>85,且班级评议成绩>80; 3.成绩优秀奖,每人2000,期末平均成绩>90; 4.西部奖学金,…...
加油站ai系统视频监测 yolov5
加油站ai系统视频监测通过yolov5网络模型深度学习边缘计算技术,加油站ai系统视频监测对现场卸油过程中人员违规离岗、现场灭火器没有按要求正确摆放、以及卸油前需要遵守静电释放15分钟、打电话、明火烟雾情况、抽烟行为进行自动识别。YOLO系列算法是一类典型的one-…...
【JDK8新特性之Stream流-Stream结果收集案例实操】
一.JDK8新特性之Stream流-Stream结果收集以及案例实操 二.Stream结果收集(collect函数)-实例实操 2.1 结果收集到集合中 /*** Stream将结果收集到集合中以及具体的实现 collect*/Testpublic void test01(){// 收集到List中 接口List<Integer> list Stream.of(1, 2, 3…...
Fiddler 抓包工具
HTTP代理所谓的http代理,其实就是代理客户机的http访问,主要代理浏览器访问页面。代理服务器是介于浏览器和web服务器之间的一台服务器,有了它之后,浏览器不是直接到Web服务器去取回网页而是向代理服务器发出请求,Requ…...
2023最新版网络安全保姆级指南,手把手带你从零基础进阶渗透攻防工程师
前言 一份网络攻防渗透测试的学习路线,不藏私了! 1、学习编程语言(phpmysqljshtml) 原因: phpmysql可以帮助你快速的理解B/S架构是怎样运行的,只有理解了他的运行原理才能够真正的找到问题/漏洞所在。所以对于国内那些上来就说…...
排序基础之选择排序法
目录 前言 一、什么是选择排序 二、实现选择排序 三、使用泛型扩展 四、使用自定义类型测试 前言 今天天气不错,这么好的天气不干点啥实在是有点可惜了,于是乎,拿出键盘撸一把! 来,今天来学习一下排序算法中的选…...
2.24测试用例
一.测试模型1.V模型特点:1.明确标注了测试的类型2.明确标注了测试阶段和开发阶段的对应关系缺点:测试后置2.W模型也叫双v模型,测试阶段全流程介入缺点:1.上一阶段完成.下一个阶段才能开始2.开发模型和测试模型也保持着一种线性的前后关系3.重文档,重过程,不支持敏捷模式二.设计…...
面试必刷101 Java题解 -- part 1
练习地址 面试必刷101-牛客1、链表反转2、链表内指定区间反转**3. 链表中的节点每k个一组翻转**4、**合并两个排序的链表**5、**合并k个已排序的链表**6、**判断链表中是否有环****7、链表中环的入口结点**8、链表中倒数最后k个结点**9、删除链表的倒数第n个节点****10、两个链…...
Python---关联与继承
专栏:python 个人主页:HaiFan. 专栏简介:Python在学,希望能够得到各位的支持!!! 关联与继承前言has a关联关系is a继承关系子类不添加__init__子类添加__init__前言 has a关联关系 has - a 是在…...
RPA+AI市场进入精细化竞争阶段,企业选型逻辑正在改变
IDC最新数据显示,中国RPAAI解决方案市场规模已达31.5亿元,竞争格局呈现“头部集中、市场分散”特征:金智维以10.1%份额位居第一,艺赛旗(9.1%)、来也科技(8.4%)紧随其后,前…...
OpenRouter最新免费额度调整:如何用微信支付宝充值解锁1000次/天API调用
OpenRouter API调用新规解析:微信支付宝充值实战指南 最近OpenRouter平台对免费API调用额度进行了重要调整,这一变化直接影响着国内开发者和AI爱好者的日常使用体验。作为聚合了300多个主流AI模型的统一接口平台,OpenRouter一直以友好的免费政…...
技术揭秘:QtScrcpy如何实现跨平台Android投屏与低延迟控制
技术揭秘:QtScrcpy如何实现跨平台Android投屏与低延迟控制 【免费下载链接】QtScrcpy Android实时投屏软件,此应用程序提供USB(或通过TCP/IP)连接的Android设备的显示和控制。它不需要任何root访问权限 项目地址: https://gitcode.com/barry-ran/QtScr…...
郭老师-我们为什么要爱国?
我们为什么要爱国? ——因为家在,根在,魂在“你可以不爱你的管家, 但必须爱你家的房子。”🌿 国家如屋,人民为主, 执政者不过管家—— 而这屋,是我们的命脉所系。🏠 一、…...
仅限前500位开发者获取:20年MCP协议老兵手写《Python服务器模板源码认知地图》PDF+可执行调试镜像
第一章:MCP协议核心原理与Python服务器模板设计哲学MCP(Model Control Protocol)是一种轻量级、面向模型交互的双向通信协议,专为AI代理系统与外部工具服务之间的结构化指令交换而设计。其核心在于以JSON-RPC 2.0为传输语义基础&a…...
KEPServerEX与SQLServer数据库的无缝集成指南
1. KEPServerEX与SQLServer集成的核心价值 在工业自动化和数据采集领域,KEPServerEX作为领先的通信平台,与SQLServer数据库的集成能够实现设备数据到关系型数据库的高效流转。这种组合特别适合需要长期存储设备运行数据、生成生产报表或进行数据分析的场…...
【ROS2 基础】ROS2与Colcon核心指令速查手册与避坑指南
为了在 ROS2 的日常开发中提升效率,本文为您整理了一份结构化的核心指令速查清单。去除了冗长的理论,直击实战痛点,并附带了多平台差异、性能优化数据以及常见报错的修复方案。 文章目录[TOC]一、 快速入门:3步跑通基础流程二、 版…...
CentOS 7.9 上部署 ELK 9.2.0 踩坑实录:从系统优化到证书配置的完整避坑指南
CentOS 7.9 上部署 ELK 9.2.0 实战指南:系统调优与安全配置全解析 在当今数据驱动的时代,企业日志管理已成为运维工作的核心环节。ELK Stack(Elasticsearch、Logstash、Kibana)作为开源日志分析解决方案的标杆,其9.2.0…...
Wan2.2-I2V-A14B部署教程:解决OOM/驱动报错/端口冲突三大常见问题
Wan2.2-I2V-A14B部署教程:解决OOM/驱动报错/端口冲突三大常见问题 1. 镜像概述与核心优势 Wan2.2-I2V-A14B是一款专为文生视频任务优化的私有部署镜像,特别针对RTX 4090D 24GB显存配置进行了深度优化。这个镜像最大的特点是解决了AI视频生成领域常见的…...
小白也能玩转GLM-4V-9B:免费开源多模态模型部署全流程
小白也能玩转GLM-4V-9B:免费开源多模态模型部署全流程 1. 环境准备与快速部署 1.1 硬件要求与系统配置 GLM-4V-9B作为90亿参数的多模态模型,对硬件有一定要求: GPU推荐:至少24GB显存的显卡(如RTX 4090)…...
