<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>性能排查 on Zampo Blog</title><link>https://blog.cpdd.fyi/tags/%E6%80%A7%E8%83%BD%E6%8E%92%E6%9F%A5/</link><description>Recent content in 性能排查 on Zampo Blog</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Thu, 28 May 2026 14:20:00 +0800</lastBuildDate><atom:link href="https://blog.cpdd.fyi/tags/%E6%80%A7%E8%83%BD%E6%8E%92%E6%9F%A5/index.xml" rel="self" type="application/rss+xml"/><item><title>Go 内存为什么不能都怪 GC：goroutine 泄漏、无界缓存和高分配速率修法完全不同</title><link>https://blog.cpdd.fyi/posts/go-gc-leak-three-types/</link><pubDate>Thu, 28 May 2026 14:20:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-gc-leak-three-types/</guid><description>&lt;p&gt;服务又 OOM 了。&lt;/p&gt;
&lt;p&gt;Pod 刚重启，群里已经开始出方案：&lt;code&gt;GOMEMLIMIT&lt;/code&gt; 设低一点，&lt;code&gt;GOGC&lt;/code&gt; 调一下，limit 先加 1Gi，GC 日志再翻一遍。&lt;/p&gt;
&lt;p&gt;这些动作有时候能救火，但经常救不到根上。&lt;/p&gt;
&lt;p&gt;因为很多 Go 服务所谓的“内存泄漏”，根本不是一种病。goroutine 卡住、全局 map 只增不删、热路径疯狂分配，看起来都像内存曲线往上走，修法却完全不同。&lt;/p&gt;
&lt;p&gt;你把它们都叫 GC 问题，排查就会变成调参赌博。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GC 不是清洁工，它不能删除还被引用的对象。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这篇不再重复讲 OOMKilled 第一现场怎么确认。上篇已经讲过：先看谁被杀、撞到什么 limit、heap 和 RSS 是不是同一本账。这篇往下走一步：当你确认 Go 服务确实有内存压力，怎么把“泄漏”拆成三类。&lt;/p&gt;
&lt;p&gt;拆清楚以后，很多争论会少一半。&lt;/p&gt;
&lt;h2 id="第一类goroutine-泄漏真正漏的是生命周期"&gt;第一类：goroutine 泄漏，真正漏的是生命周期&lt;/h2&gt;
&lt;p&gt;goroutine 泄漏最容易被低估。&lt;/p&gt;
&lt;p&gt;很多人听到 goroutine，会觉得它很轻：几 KB 栈，问题不大。但线上事故里，麻烦通常不只是 goroutine 自己占多少内存，而是它还挂着什么。&lt;/p&gt;
&lt;p&gt;请求已经超时了，调用方走了，下游 channel 还在等发送；&lt;code&gt;context&lt;/code&gt; 已经 cancel 了，后台 worker 还在跑；连接断了，读循环没有退出；定时任务里起了 goroutine，却没有任何关闭协议。&lt;/p&gt;
&lt;p&gt;这些 goroutine 还活着，栈上可能就挂着 request、response、session、buffer、logger field、trace span。&lt;/p&gt;
&lt;p&gt;对象还被引用着，GC 就不会删。&lt;/p&gt;
&lt;p&gt;排查信号通常很直接：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;goroutine 数持续上涨，业务回落后不回落；
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;goroutine profile 里同一类栈越来越多；
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;heap diff 里能看到被这些 goroutine 间接持有的对象；
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GC CPU 跟着变高，但 live heap 不一定夸张到一眼能看出来。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;事故现场不要只抓 heap。goroutine profile 要一起留：&lt;/p&gt;</description></item><item><title>OOMKilled 到底是谁动的手：Go 为什么要先看 kubectl describe 再做 pprof diff</title><link>https://blog.cpdd.fyi/posts/go-gc-oom-first-step/</link><pubDate>Thu, 28 May 2026 10:37:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-gc-oom-first-step/</guid><description>&lt;p&gt;服务又被 OOMKilled 了。&lt;/p&gt;
&lt;p&gt;Pod 刚重启，群里已经开始报菜名：把 &lt;code&gt;GOGC&lt;/code&gt; 调低一点，给 &lt;code&gt;GOMEMLIMIT&lt;/code&gt; 设个值，容器 limit 再加 1Gi，顺手翻一下 GC 日志。&lt;/p&gt;
&lt;p&gt;这些动作不一定错，但顺序错了。&lt;/p&gt;
&lt;p&gt;你现在看到的是“尸体”，不是“凶手”。OOMKilled 只说明容器被内核杀掉了，不说明是谁把内存账花爆了。可能是 Go heap 真的在涨，可能是 goroutine 泄漏把对象挂住了，可能是 RSS 里有 cgo、mmap、tmpfs，也可能只是容器 limit 给得太紧。&lt;/p&gt;
&lt;p&gt;很多 Go 内存事故，第一步不是调 GC，而是先把账本拿出来。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OOMKilled 是结果，不是根因。先查谁杀了你，再谈怎么救。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="第一步先确认谁杀了你"&gt;第一步：先确认谁杀了你&lt;/h2&gt;
&lt;p&gt;线上内存告警一来，最危险的动作不是慢，而是快。&lt;/p&gt;
&lt;p&gt;你一快，就会把“容器被杀”理解成“Go heap 有问题”；把“GC CPU 变高”理解成“GC 参数不合适”；把“RSS 比 heap 大”理解成“Go 没把内存还给系统”。&lt;/p&gt;
&lt;p&gt;这些判断都太早。&lt;/p&gt;
&lt;p&gt;先看 Kubernetes 给你的第一现场：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl describe pod &amp;lt;pod&amp;gt; -n &amp;lt;ns&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl logs &amp;lt;pod&amp;gt; -n &amp;lt;ns&amp;gt; --previous
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl top pod &amp;lt;pod&amp;gt; -n &amp;lt;ns&amp;gt; --containers
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get pod &amp;lt;pod&amp;gt; -n &amp;lt;ns&amp;gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{.status.containerStatuses[*].lastState.terminated.reason}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;你要先回答几件事：&lt;/p&gt;</description></item></channel></rss>