<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Graceful Shutdown on Zampo Blog</title><link>https://blog.cpdd.fyi/tags/graceful-shutdown/</link><description>Recent content in Graceful Shutdown on Zampo Blog</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Tue, 21 Jul 2026 09:00:00 +0800</lastBuildDate><atom:link href="https://blog.cpdd.fyi/tags/graceful-shutdown/index.xml" rel="self" type="application/rss+xml"/><item><title>发一次版就冒一串 502，别急着怪 K8s 探针，先看你的关闭顺序</title><link>https://blog.cpdd.fyi/posts/go-graceful-shutdown/</link><pubDate>Tue, 21 Jul 2026 09:00:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-graceful-shutdown/</guid><description>&lt;p&gt;发一次版，监控上就冒出一串 502。&lt;/p&gt;
&lt;p&gt;不多，十几个，几秒钟就没了。下一次发版又来一串。你先怀疑的大概是 K8s：是不是 readiness 探针没配好？是不是 terminationGracePeriod 太短？是不是 LB 摘流慢了半拍？&lt;/p&gt;
&lt;p&gt;这些方向都不算错，但很多时候真正的问题根本不在编排层。它在你 &lt;code&gt;main&lt;/code&gt; 函数最后那几行——收到信号之后，你按什么顺序关掉了这个进程。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/go-graceful-shutdown/cover.svg" alt="Go 服务优雅关闭顺序封面"&gt;&lt;/p&gt;
&lt;h2 id="graceful-shutdown-不是一个函数是一个顺序"&gt;graceful shutdown 不是一个函数，是一个顺序&lt;/h2&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-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;chan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SIGTERM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;srv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Shutdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;
&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;code&gt;signal.Notify&lt;/code&gt;，加了 &lt;code&gt;srv.Shutdown()&lt;/code&gt;，看起来该做的都做了。&lt;/p&gt;
&lt;p&gt;但优雅关闭从来不是一个函数调用。它是一整套关闭的先后次序：先停止接收新请求，再等已经进来的请求做完，最后才去关它们依赖的东西——数据库、消息队列、连接池、下游客户端。&lt;/p&gt;
&lt;p&gt;这三步的顺序不能乱。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;srv.Shutdown()&lt;/code&gt; 只负责前两步：它先关掉监听端口，不再 accept 新连接，然后等已经在处理的请求跑完。它管不着你的 DB 连接、你的 Kafka producer、你后台那几个还在写数据的 goroutine。那些是你自己拉起来的，也得你自己按顺序收。&lt;/p&gt;
&lt;p&gt;顺序错了，&lt;code&gt;Shutdown&lt;/code&gt; 做得再对也白搭。&lt;/p&gt;
&lt;h2 id="收到信号就-osexit就关-db为什么它平时看着能跑"&gt;收到信号就 os.Exit、就关 DB，为什么它平时看着能跑&lt;/h2&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-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 先把 DB 关了&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;srv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Shutdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 再来收 HTTP&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&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;code&gt;&amp;lt;-c&lt;/code&gt; 之后直接 &lt;code&gt;os.Exit(0)&lt;/code&gt;，连 &lt;code&gt;Shutdown&lt;/code&gt; 都没有。&lt;/p&gt;</description></item></channel></rss>