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

推荐订阅源

Hacker News - Newest:
Hacker News - Newest: "LLM"
AI
AI
T
Troy Hunt's Blog
GbyAI
GbyAI
H
Hacker News: Front Page
SecWiki News
SecWiki News
V2EX - 技术
V2EX - 技术
A
About on SuperTechFans
人人都是产品经理
人人都是产品经理
Hacker News: Ask HN
Hacker News: Ask HN
S
Secure Thoughts
Last Week in AI
Last Week in AI
MyScale Blog
MyScale Blog
L
LINUX DO - 最新话题
C
CERT Recently Published Vulnerability Notes
C
Cyber Attacks, Cyber Crime and Cyber Security
O
OpenAI News
S
SegmentFault 最新的问题
Y
Y Combinator Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
The Cloudflare Blog
P
Proofpoint News Feed
Engineering at Meta
Engineering at Meta
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
美团技术团队
Google DeepMind News
Google DeepMind News
Simon Willison's Weblog
Simon Willison's Weblog
Know Your Adversary
Know Your Adversary
K
Kaspersky official blog
T
The Exploit Database - CXSecurity.com
S
Securelist
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
B
Blog RSS Feed
L
Lohrmann on Cybersecurity
Spread Privacy
Spread Privacy
博客园 - 司徒正美
Stack Overflow Blog
Stack Overflow Blog
博客园 - Franky
The GitHub Blog
The GitHub Blog
B
Blog
F
Fortinet All Blogs
I
InfoQ
C
Check Point Blog
Webroot Blog
Webroot Blog
博客园 - 叶小钗
P
Privacy International News Feed
Latest news
Latest news
Forbes - Security
Forbes - Security
博客园 - 三生石上(FineUI控件)
N
News | PayPal Newsroom

郑文峰的博客

使用dify对接飞书多维表格 使用n8n对接飞书多维表格 一次服务升级时pg表DDL执行超时失败 Go语言高效IO缓冲技术详解 Go语言延迟初始化(Lazy Initialization)最佳实践 Go语言字符串拼接性能对比与优化指南 Go语言结构体内存对齐完全指南 Go语言空结构体:零内存消耗的高效编程 Go语言堆栈分配与逃逸分析深度解析 Go语言原子操作完全指南 Go语言内存预分配完全指南 Go语言不可变数据共享:无锁并发编程实践 Go语言零拷贝技术完全指南 Go语言遍历性能深度解析:从原理到优化实践 Go语言Interface Boxing原理与性能优化指南 Go协程池深度解析:原理、实现与最佳实践 使用etcd分布式锁导致的协程泄露与死锁问题 基于pre-commit的Python代码规范落地实践 初识 MCP Server pulsar阻塞导致logstash无法接入日志 django-prometheus使用及源码分析 kube-proxy源码分析 kubernetes service如何通过iptables转发 tcp缓存引起的日志丢失 django-apschedule定时任务异常停止 理解calico容器网络通信方案原理 理解flannel的三种容器网络方案原理 理解Linux IPIP隧道 理解VXLAN网络 理解Linux TunTap设备 快速了解iptables kafka中listener和advertised.listeners的作用 django rest_framework 分页 django后端服务、logstash和flink接入VictoriaMetrics指标监控 python中import原理 docker容器单机网络 手动实现docker容器bridge网络模型 mysql之MVCC原理 mysql之日志 使用java开发logstash的filter插件 使用python实现单例模式的三种方式 redis之缓存 redis之分片集群 redis之哨兵机制 redis之主从库同步 redis之持久化 redis之五种基本数据类型 go中如何处理error pod中将代码与运行环境分离 ddt源码分析 python装饰器的使用方法 读书笔记:如何阅读一本书 使用ddt实现unittest的参数化测试 使用kubeadm安装k8s 优化gin表单的错误提示信息 gin中validator模块的源码分析 go简单使用grpc python简单使用grpc k8s之PV、PVC和StorageClass k8s之StatefulSet k8s之DaemonSet k8s之Job和CronJob k8s之ConfigMap和Secret k8s之Service k8s之Pod k8s之Deployment 容器的本质 docker容器 python迭代器与生成器 python元编程 python垃圾回收机制 python上下文管理器 django rest_framework使用jwt django rest_framework异常处理 django rest_framework 自定义文档 django压缩文件下载 django rest_framework使用pytest单元测试 django restframework choice 自定义输出数据 django Filtering 使用 django viewset 和 Router 配合使用时报的错 django model的序列化 django中使用AbStractUser django.core.exceptions.ImproperlyConfigured Application labels aren't unique, duplicates users django 中 media配置 django 外键引用自身和on_delete参数 django 警告 while time zone support is active Flask使用flask_socketio实现websocket flask结合mongo tornado 文件上传 tornado 使用jwt完成用户异步认证 tornado 用户密码 bcrypt加密 tornado 结合wtforms使用表单操作 tornado finish和write区别 tornado 使用peewee-async 完成异步orm数据库操作 pyspark streaming简介 和 消费 kafka示例 使用hue创建ozzie的pyspark action workflow count的性能优化 django rest_framework Authentication django celery 结合使用 网站
服务启动时出现 OOM
zhengwenfeng · 2025-09-22 · via 郑文峰的博客

# 前言

本文详细记录了一次在Kubernetes环境中Golang服务启动时出现OOM(Out of Memory)问题的排查和解决过程。服务在启动约2分钟后出现内存溢出,通过pprof工具分析发现主要问题源于bytes.Buffer对象的频繁扩容。

# 现象

有一个golang后台服务跑在了k8s集群上,在启动过程中,大约 2 分钟后,该服务所在的pod出现了 OOM,也就是超过了部署该 POD 的 limit 内存大小。

# 分析

该现象是可复现的,所以我通过重启该pod,在其还没出现 OOM 时使用 pprof 将服务的 profile 文件给获取到。

curl http://localhost:8080/debug/pprof/heap -o profile

1

然后使用下面的命令在网页上查看服务的内存使用情况。

go tool pprof -http=:8081 profile

1

在网页上,在顶部的导航栏中,选择 View -> Top,SAMPLES -> alloc_space,来查看从服务启动时,申请内存Top。

17585287225681758528722158.png

首先可以看到第一名申请的内存是 bytes.makeSlice,看名字大胆的猜测,是因为切片内存申请过多导致的,接下来需要验证。

选中该行,然后选择 View -> Graph,就会进入进入到一个函数调用链。

17585288355611758528835189.png

我们先找到调用链的底端,可以看到 bytes.makeSlice 占用了1.12G 内存,调用者是 bytes(*Buffer).grow (opens new window),通过源码分析是 Buffer 对象扩容导致的。

17585294490741758529449060.png

通过调用链往上找到在业务代码上创建并使用 Buffer 对象的地方,最终找到了 Serialize 函数。

17585295595181758529558760.png

我们看想该函数,该函数的作用是,将value对象序列化成byte数组。然后看到其创建了 Buffer 对象,并将 value 对象序列化存储到其中。

func Serialize(value interface{}) ([]byte, error) {
	// gob.Register(value)
	buf := bytes.Buffer{}
	if err := gob.NewEncoder(&buf).Encode(&value); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

1
2
3
4
5
6
7
8

但我们立马发现了问题,我们创建 Buffer{} 并没有指定其大小,可以看到其结构体也没有默认大小,当写入数据时,则会进行调用 grow 进行扩容。

type Buffer struct {
	buf      []byte // contents are the bytes buf[off : len(buf)]
	off      int    // read at &buf[off], write at &buf[len(buf)]
	lastRead readOp // last read operation, so that Unread* can work correctly.
}

func (b *Buffer) Write(p []byte) (n int, err error) {
	b.lastRead = opInvalid
	m, ok := b.tryGrowByReslice(len(p))
	if !ok {
		m = b.grow(len(p))
	}
	return copy(b.buf[m:], p), nil
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14

再结合业务进行分析,服务启动时会有大量的数据需要进行序列化,每次调用该函数都是创建一个未指定大小的 Buffer 对象,然后序列化时进行不断地扩容,从而导致内存迅速增加,从而导致 OOM。

# 解决办法

使用 sync.Pool 来复用 Buffer 对象,并且在创建 Buffer 对象时指定一个合适的大小,这样就可以减少内存的申请从而避免了 OOM。

// 缓冲区池,复用 bytes.Buffer 对象
var bufferPool = sync.Pool{
	New: func() interface{} {
		// 预分配 1KB 容量,减少扩容次数
		buf := make([]byte, 0, 1024)
		return bytes.NewBuffer(buf)
	},
}

func Serialize(value interface{}) ([]byte, error) {
	if value == nil {
		return nil, fmt.Errorf("input nil value")
	}

	// 从池中获取缓冲区
	buf := bufferPool.Get().(*bytes.Buffer)
	defer func() {
		buf.Reset()
		bufferPool.Put(buf)
	}()

	encoder := gob.NewEncoder(buf)
	if err := encoder.Encode(&value); err != nil {
		return nil, err
	}

	// 高效的字节切片克隆返回数据
	return bytesClone(buf.Bytes()), nil
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 总结

  1. 在编码方面需要注意内存管理和对象复用对系统稳定性的重要性。
  2. 善于利用工具来辅助排查问题。