惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Security Archives - TechRepublic
Security Archives - TechRepublic
爱范儿
爱范儿
Recent Announcements
Recent Announcements
AI
AI
V
Visual Studio Blog
H
Heimdal Security Blog
L
LINUX DO - 最新话题
Attack and Defense Labs
Attack and Defense Labs
宝玉的分享
宝玉的分享
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
W
WeLiveSecurity
人人都是产品经理
人人都是产品经理
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
WordPress大学
WordPress大学
S
Secure Thoughts
S
Security Affairs
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
N
News and Events Feed by Topic
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 聂微东
博客园 - Franky
阮一峰的网络日志
阮一峰的网络日志
Schneier on Security
Schneier on Security
Hugging Face - Blog
Hugging Face - Blog
Apple Machine Learning Research
Apple Machine Learning Research
Forbes - Security
Forbes - Security
The Cloudflare Blog
博客园 - 【当耐特】
酷 壳 – CoolShell
酷 壳 – CoolShell
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
月光博客
月光博客
有赞技术团队
有赞技术团队
博客园 - 司徒正美
博客园_首页
Recent Commits to openclaw:main
Recent Commits to openclaw:main
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
NISL@THU
NISL@THU
C
Cybersecurity and Infrastructure Security Agency CISA
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
P
Proofpoint News Feed
罗磊的独立博客
V
Vulnerabilities – Threatpost
S
Securelist
N
News and Events Feed by Topic
Cloudbric
Cloudbric
P
Proofpoint News Feed
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
V2EX - 技术
V2EX - 技术
小众软件
小众软件

希仁之拥

领克900半年使用体验 | 希仁之拥的博客 Ubuntu 26.04 Desktop使用体验 | 希仁之拥的博客 【转载】谈谈不受欢迎的博客技术特征 | 希仁之拥的博客 【转载】ClaudeCode 你想知道的所有秘密,源码深度研究报告 | 希仁之拥的博客 2025年年终总结 | 希仁之拥的博客 集成和使用Openclaw后的思考 | 希仁之拥的博客 我买了领克900 | 希仁之拥的博客 Go-Sail导航站上线啦 | 希仁之拥的博客 今年国庆的一些感受 [2025] | 希仁之拥的博客 在Deepin 25上配置forticlient | 希仁之拥的博客 分享一些酷酷的站点 [20250908] | 希仁之拥的博客 Go-Sail发布v3.0.6版本了 | 希仁之拥的博客 我对V2EX发布$V2EX讨论的一些感受 | 希仁之拥的博客 如何让Stripe支持支付宝和微信支付 | 希仁之拥的博客 2025上半年里程碑 | 希仁之拥的博客 GitLab+Drone使用体验 | 希仁之拥的博客 四姑娘山之旅 | 希仁之拥的博客 近来帮同事做性能优化的过程回顾 | 希仁之拥的博客 聊聊接口的返回数据结构 | 希仁之拥的博客 由GORM的Updates语法糖 我把 Go-Sail 的文档站更新了 | 希仁之拥的博客 这就是我为什么讨厌拼多多 | 希仁之拥的博客 元旦快乐~ | 希仁之拥的博客 致敬还在写博客的我们 | 希仁之拥的博客 逐步的把图片资源迁移到星光图床上 | 希仁之拥的博客 帮弟弟配了一台mini主机 | 希仁之拥的博客 国庆的一些碎碎念 | 希仁之拥的博客 就这一刻而言,我觉得科技冷冰冰的。 | 希仁之拥的博客 如何使用acme.sh自动续签证书 | 希仁之拥的博客 Go-Sail的文档站点搭建好了 | 希仁之拥的博客 我的第一个谷歌浏览器插件发布成功啦! | 希仁之拥的博客 我开源了一个golang的web框架 | 希仁之拥的博客 流芳 | 希仁之拥的博客 第一次开发微信小程序 | 希仁之拥的博客 春节快乐 | 希仁之拥的博客 我把博客站点重写了 | 希仁之拥的博客 阆中古城 | 希仁之拥的博客 入了把Keydous NJ80 | 希仁之拥的博客 Mac上有用的知识 | 希仁之拥的博客 记一次服务器被攻击 | 希仁之拥的博客 Ubuntu server不停机扩容系统盘 | 希仁之拥的博客 端午安康 | 希仁之拥的博客 搭建hadoop伪分布式集群总结 | 希仁之拥的博客 在goa框架中,如何访问原始请求数据 | 希仁之拥的博客 postgresql相关操作笔记 | 希仁之拥的博客 golang解析带命名空间的xml数据(带冒号的节点或属性名) | 希仁之拥的博客 【转载】解决deepin环境下wine QQ无法接收图片问题 | 希仁之拥的博客 杂项笔记 | 希仁之拥的博客 Mac如何修改通知横幅消失时间 | 希仁之拥的博客 Golang校验结构体是否实现了接口 | 希仁之拥的博客 关于deepin os的大半年使用体验 | 希仁之拥的博客 有态度的翻唱 | 希仁之拥的博客 【转载】Golang 在 Mac、Linux、Windows 下如何交叉编译 | 希仁之拥的博客 和幺幺一起成长的半年时光 | 希仁之拥的博客 Element UI中message消息提示实现原理 | 希仁之拥的博客 从predis组件开始了解如何与redis服务交互 | 希仁之拥的博客 【转载】 尤雨溪谈Vue.js :缔造自由与真我 | 希仁之拥的博客 【转载】百万 Go TCP 连接的思考: epoll方式减少资源占用 | 希仁之拥的博客 【分享】移除git中的commit历史(保留文件,仅移除提交历史信息) | 希仁之拥的博客 shell学习笔记 | 希仁之拥的博客 【分享】Ruby 之父松本行弘:程序员的乐趣、存在感与平衡 | 希仁之拥的博客 安利一下我用过的一些sublime text 3工具、插件及配置 | 希仁之拥的博客 PHP处理苹果内购数据验证 | 希仁之拥的博客 PHP获取XML所有节点名称 | 希仁之拥的博客 Sentinel::check() 在Laravel5.3版本之后总是返回false的解决方案 | 希仁之拥的博客 吃货的美食集,持续更新~ | 希仁之拥的博客 第二十一届成都汽车博览会 | 希仁之拥的博客 马云宣布1年后卸任集团董事局主席 由CEO张勇接任 | 希仁之拥的博客 nginx配置非443端口的https连接 | 希仁之拥的博客 PHP7 异常类型列表 | 希仁之拥的博客 【分享】《燃点》:不被嘲笑的愿望是不值得去实现的|一部属于创业者的电影 | 希仁之拥的博客 【分享】前端资源加载优先级[译] | 希仁之拥的博客 【小技巧】Laravel动态配置多个数据库连接 | 希仁之拥的博客 php使用ftp上传、下载文件 | 希仁之拥的博客 php使用mysqlnd扩展进行mysql异步并行查询 | 希仁之拥的博客 基于以太坊区块链,使用web3.js在geth服务器上执行合约方法以及事件监听 | 希仁之拥的博客 Smartisan OS | 希仁之拥的博客 母校 | 希仁之拥的博客 php后期静态绑定 | 希仁之拥的博客 [mysql优化]子查询与连接查询 | 希仁之拥的博客 PHP常见运行模式及原理 | 希仁之拥的博客 mysql用户与授权 | 希仁之拥的博客 如何在ubuntu14.04上安装composer | 希仁之拥的博客 关于mews/captcha for laravel存在method [validateCaptcha] doesn't exists解决方法 | 希仁之拥的博客
服务器性能优化之io拷贝 | 希仁之拥的博客
希仁之拥 · 2025-12-19 · via 希仁之拥

前言

记得我刚接触Go这门语言的时候,用到它的网络请求库的时候还不太理解,为什么函数的返回值不直接是请求的响应内容,而是一个http Response,并且,Body字段也并不是实际的响应内容,而是一个io.ReadCloser接口。想要拿到响应内容,需要手动做一次读取操作。刚开始的时候,我觉得这样十分的不便,不像诸如PHP语言的 curl_exec 函数那样来的直接。

Go语言读取一个网络请求的结果简化版大致需要这样:

//1.执行一个请求
response, err := http.Get(...)

//2.使用io包读取body
io.ReadAll(response.Body)

//3.关闭响应
response.Body.Close()

当时我不太明白为什么Go语言要这样设计,直到后来在一次业务场景中,在对性能化时才初见端倪,并不禁感叹这样的设计真是精妙!

这样的设计并不只有Go语言一家。

实际业务场景

在我为StarDots实现图像处理的过程中,我走了与以往相同的常规路线:将原图像获取到本地内存,读取元数据及必要信息,加工处理,然后响应给下游服务。

这一条线路看起来是再正常不过的,然而在实际的生产环境下,我发现服务器内存占用居高不下,且与请求量成正相关。这样一来,显然是一个差劲的设计,并且费用成本高昂。

于是,我开始反思设计的不合理之处。就在我review代码的时候,看到了io.ReadAll(response.Body)这句代码。这让我重新回想起了初遇Go语言的疑惑。接着,我开始思考,read到底操作了一些什么,它的行为是怎样的。

从实战回到理论

从现有代码来看,我是将响应全部读出,也就是使用io.ReadAll,这样一来,响应体有多大,那么就将占用多大的内存空间,并且,还有OOM的风险。
在即需要获取到响应内容,又要保证内存可控这样的既要又要的场景下,能不能保证两者都能满足呢。顺理成章地,我想到了,能不能“一点一点地读取响应内容”。

回到文章最前面提到的代码:

//1.执行一个请求
response, err := http.Get(...)

//2.使用io包读取body
io.ReadAll(response.Body)

//3.关闭响应
response.Body.Close()

这里不再进行io.ReadAll()操作了。反观http Response的定义设计:

type Response struct {
    Status           string
    StatusCode       int
    Proto            string
    ProtoMajor       int
    ProtoMinor       int
    Header           Header
    Body             io.ReadCloser
    ContentLength    int64
    TransferEncoding []string
    Close            bool
    Uncompressed     bool
    Trailer          Header
    Request          *Request
    TLS              *tls.ConnectionState
}

我们已经能从此结构中获取到很大一部分有价值的信息,例如响应头。此时,我想到了Go语言的io包提供的一个函数Copy,这是它的源码定义:

func Copy(dst Writer, src Reader) (written int64, err error)

参数见名知意,可以将源拷贝到目标。这个操作,让我想起了这张图:

golang-logo-water-pipes.png

小鼹鼠修理工整准备将两根水管进行对接。

阅读了io.Copy源码之后,发现它调用了一个名叫copyBuffer的方法,下面是copyBuffer的具体实现:

func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
	// If the reader has a WriteTo method, use it to do the copy.
	// Avoids an allocation and a copy.
	if wt, ok := src.(WriterTo); ok {
		return wt.WriteTo(dst)
	}
	// Similarly, if the writer has a ReadFrom method, use it to do the copy.
	if rf, ok := dst.(ReaderFrom); ok {
		return rf.ReadFrom(src)
	}
	if buf == nil {
		size := 32 * 1024
		if l, ok := src.(*LimitedReader); ok && int64(size) > l.N {
			if l.N < 1 {
				size = 1
			} else {
				size = int(l.N)
			}
		}
		buf = make([]byte, size)
	}
	for {
		nr, er := src.Read(buf)
		if nr > 0 {
			nw, ew := dst.Write(buf[0:nr])
			if nw < 0 || nr < nw {
				nw = 0
				if ew == nil {
					ew = errInvalidWrite
				}
			}
			written += int64(nw)
			if ew != nil {
				err = ew
				break
			}
			if nr != nw {
				err = ErrShortWrite
				break
			}
		}
		if er != nil {
			if er != EOF {
				err = er
			}
			break
		}
	}
	return written, err
}

其中,指定buf的大小,就能实现我想要的“一点一点地读”的需求。而默认的buf大小是32*1024,也是适用于我的需求,那么在此场景下使用io.Copy能够有效抑制内存疯涨,让转发操作变得简单清晰。

优化效果

在优化前,当有一定量的图像请求时,观察服务器监控就能发现内存上涨很快且十分不稳定。优化之后,内存占用变得平稳可控。

优化后其中一个pod内容占用情况:

mem-usage-pod-stardots.png

优化后周期性的内存使用情况:

mem-metrics-pod-stardots.png

实际业务跑了一段时间,观察到这样的表现十分稳定。

觉得博主写的不错?给他一个赞赏:

keepchen的赞赏码-支付宝 keepchen的赞赏码-微信