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

推荐订阅源

The Last Watchdog
The Last Watchdog
K
Kaspersky official blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Spread Privacy
Spread Privacy
T
Threatpost
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
MongoDB | Blog
MongoDB | Blog
V
Vulnerabilities – Threatpost
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Help Net Security
Help Net Security
Microsoft Azure Blog
Microsoft Azure Blog
GbyAI
GbyAI
小众软件
小众软件
Cloudbric
Cloudbric
The Hacker News
The Hacker News
阮一峰的网络日志
阮一峰的网络日志
Vercel News
Vercel News
人人都是产品经理
人人都是产品经理
Forbes - Security
Forbes - Security
Martin Fowler
Martin Fowler
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
AWS News Blog
AWS News Blog
Stack Overflow Blog
Stack Overflow Blog
N
News | PayPal Newsroom
P
Privacy & Cybersecurity Law Blog
TaoSecurity Blog
TaoSecurity Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
博客园_首页
A
Arctic Wolf
www.infosecurity-magazine.com
www.infosecurity-magazine.com
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Scott Helme
Scott Helme
T
Tor Project blog
S
Secure Thoughts
Know Your Adversary
Know Your Adversary
P
Proofpoint News Feed
M
MIT News - Artificial intelligence
博客园 - 司徒正美
T
Threat Research - Cisco Blogs
C
Cyber Attacks, Cyber Crime and Cyber Security
Schneier on Security
Schneier on Security
B
Blog RSS Feed
AI
AI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Attack and Defense Labs
Attack and Defense Labs
Webroot Blog
Webroot Blog
Google DeepMind News
Google DeepMind News
Project Zero
Project Zero
Hacker News: Ask HN
Hacker News: Ask HN

郑文峰的博客

使用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. 善于利用工具来辅助排查问题。