发一次版就冒一串 502,别急着怪 K8s 探针,先看你的关闭顺序
发一次版,监控上就冒出一串 502。
不多,十几个,几秒钟就没了。下一次发版又来一串。你先怀疑的大概是 K8s:是不是 readiness 探针没配好?是不是 terminationGracePeriod 太短?是不是 LB 摘流慢了半拍? …
发一次版,监控上就冒出一串 502。
不多,十几个,几秒钟就没了。下一次发版又来一串。你先怀疑的大概是 K8s:是不是 readiness 探针没配好?是不是 terminationGracePeriod 太短?是不是 LB 摘流慢了半拍? …
值班群里有人贴了 Go 官方的截图:Green Tea 能把 GC 开销降 10–40%。
下一句往往是:那下周就升 1.26,服务应该能快不少。
我会先去看一条监控:GC CPU 在总 CPU 里占多少。没有这个数,截图再漂亮,也不能替你 …
这套对话,我在 code review 里见过很多次。
A 说:这个 map 读多写少,并发安全得处理一下。 B 说:那把 map + RWMutex 换成 sync.Map 吧,无锁的,更快。
第一句不完全错,第二句基本错了。 …
你在 HTTP 服务最外层加了 recovery middleware,以为兜底已经做完。后来某个 handler 里随手起了一个 goroutine;它空指针了,整个进程还是退出。
这不是 middleware 没挂上,也不是 …
上周重构一个核心模块。if-else 堆了六层,改成策略模式,干净多了。
改完跑测试。20 个用例红了 15 个。
不是因为逻辑错了。
是因为测试里 mock 了 ServiceA 调 ServiceB 的顺序,mock 了某个内部方法的调 …
很多后端团队有一个很顽固的默认值:能上 Postgres,就上 Postgres。
配置表放 Postgres,后台任务状态放 Postgres,内部仪表盘也查 Postgres。哪怕那张表一天写不了几次,哪怕服务只是读几个本地状态,大家还 …
线上报 connection pool exhausted,我见过不少人的第一反应是把 max_open 调大。
从 20 调到 100,报警没了,接口也稳了。看起来问题解决了。
直到另一个服务把它调到 200,还是超时;DBA 又在群里说 …
线上有一张设备状态表,用 sync.Map 存着。
读很多,写很少。平时接口很稳,一到定时刷新那几秒,读 p99 就会抖一下。你看代码,第一反应可能不是怀疑 sync.Map,而是怀疑刷新逻辑:是不是批量太大?是不是网络抖了?是不是 GC …
接口 p99 突然抖起来,pprof 里 mutex wait 很显眼。
会议上最容易出现的三个动作是:锁太粗,换成 RWMutex;分配太多,把 buffer 放进 sync.Pool;再不行,把临界区附近的代码拆一拆。
听起来都像优化。 …
产品说,订单列表加一个 coupon_code。
你以为就是 SQL 多 select 一个字段,response 多返回一个字段。结果打开项目以后,手开始慢慢凉下来:
OrderDTO 要加,OrderEntity 要加, …
WebSocket、SSE、gRPC Stream 都能做到"实时",但这三个词在工程上的体感天差地别。你的老板可能觉得"不就是把数据推给用户吗,哪个有现成的就上哪个",做过 …
跑通 tRPC-Agent-Go 的 Demo 只需 5 分钟,但上线生产,真正的工程问题才开始浮现:
Agent 跑了 5 分钟、调了十几个工具,网络抖了一下——整个流程要重来。审批类场景下,Agent 分析完需要人 …
如果你是一个 Go 后端开发者,过去两年一定被各种 AI Agent 框架刷过屏。LangChain、CrewAI、AutoGen、LangGraph…… 清一色 Python。而你手上的 Go 项目要 …
网上说 Go 后端转 AI Agent,动不动 40K、60K。
我第一反应不是激动,是怀疑。
这类说法太容易变成培训班招生,也容易被技术圈一句“套壳”打掉。我去翻了一圈公开招聘页,结论比这两种都别扭一点:
30K-60K 的样本确实存在, …
你已经有一堆 Go 服务了。
订单、库存、权限、工单、内部审批,全都跑在线上。现在老板说要接 Agent,第一反应往往是:是不是又要拉一套 Python 服务?是不是 Go 只能在旁边当业务 API?
这个问题在 2024 年还比较尴尬。那 …
很多团队升级 Go 版本,只做一件事:把 CI 跑绿。
跑绿以后就结束了。代码没动,测试没动,工具链没动。版本号从 1.22 变成 1.26,心里安慰自己:至少我们没落后太多。
但 Go 1.25 / 1.26 这两版,我不太建议这样看。 …
有一次线上接口返回了一个很诡异的 null。
调用方说:你们这个错误字段为什么一直有值?
我看代码,第一反应是不可能。分支里明明写着:
| |
这事最后拖了将近两个小时。
日志没有 panic,链路没有超时,数 …
你在 Go 项目里接一个新的模型供应商。
文档写着“OpenAI compatible”。你心里一松:那不就是改三个值吗?
| |
把 GOMAXPROCS 设成 1,再启动一个没有函数调用、没有 I/O、没有 sleep 的死循环。
按很多人对 goroutine 的理解,程序应该完了:唯一的 P 被这个 goroutine 一直占着,其他 goroutine 没机会 …
把 GOMAXPROCS 设成 1,再让一个 goroutine 卡在 syscall.Read 里。
按直觉,整个 Go 程序应该只剩一个执行名额。这个名额被卡住了,其他 goroutine 也该一起停。
但你真跑一下,会看到另一件事 …
goroutine 泄漏最讨厌的地方,不是它会炸,而是它不炸。
它先是悄悄多几个,再多几十个,最后把内存、延迟、CPU 一起拖下水。你回头看日志,发现代码没报错;你再看监控,发现曲线只是在往上爬。
很多人看到这种情况,第一反应是:是不是忘了 …
goroutine 从 200 慢慢涨到 2 万,线上服务还没挂,但你知道它不对劲。
监控图上那条线不陡,甚至有点温和。它不像事故,更像一个迟早会来的事故。
这时候很多人的第一反应是:把 channel buffer 调大一点。 …
代码评审里经常出现这种争论:一个 goroutine 写完结果,close(done);另一个 goroutine 等 <-done 再读结果。有人看着觉得多此一举:这不就是通知一下吗?换成一个 bool,或者 sleep 一小会儿, …
线上最烦的并发 bug,往往不是崩得轰轰烈烈,而是偶尔读到一个不该出现的旧值。
配置已经加载了,日志也打了“ready”,另一个 goroutine 却像没看见一样,读到的还是旧数据。你盯着代码看半天,越看越觉得冤:明明是先写数据,再把 …
服务又 OOM 了。
Pod 刚重启,群里已经开始出方案:GOMEMLIMIT 设低一点,GOGC 调一下,limit 先加 1Gi,GC 日志再翻一遍。
这些动作有时候能救火,但经常救不到根上。
因为很多 Go 服务所谓的“内存泄漏”,根 …
服务又被 OOMKilled 了。
Pod 刚重启,群里已经开始报菜名:把 GOGC 调低一点,给 GOMEMLIMIT 设个值,容器 limit 再加 1Gi,顺手翻一下 GC 日志。
这些动作不一定错,但顺序错了。
你现在看到的是“尸体 …
上篇讲完 GOGC、GOMEMLIMIT 和 STW,很多人真正卡住的地方才刚开始。
不是“不知道这些参数是什么”,而是线上真的出问题时,不知道下一步该干什么。
服务 p99 每隔几十秒抖一下,GC CPU 看起来有 5%。有人说把 …
服务 p99 每隔几十秒抖一下。
监控里 GC CPU 大概 5%,不算离谱,但曲线很准:GC 一来,延迟就往上冒。群里很快有人提议,把 GOGC 从 100 调到 200,先让 GC 少跑几次。
这个动作看起来很合理。
CPU 下来了 …
pprof 抓回来了,最容易发生的事不是你看不懂。
而是你看懂了一半:知道一批 goroutine 卡在 main.worker,知道等待状态是 [chan receive],知道创建点也在栈里,可下一步还是开始全局搜 context、搜 …
线上 goroutine 数开始往上爬,很多人的第一反应是猜。
是不是 context 泄漏?是不是超时没生效?是不是某个 channel 没人读?是不是锁没释放?
这些猜法都可能对。
但问题在于,你现在还没有证据。你只有一个数字 …
上篇讲了 Go 为什么宁可让你多传一个 ctx 参数。
这篇往下走一步:既然 context 已经显式传进来了,为什么它自己没有 Cancel()?
这件事看起来很别扭。Context 明明能被取消,Done() 明明会关闭,Err() 明 …
你写一个接口,ctx 从 handler 传到 service,再传到 repo。
中间两层明明不关心超时,也不读取 trace id,却还是要把 ctx 原样递下去。
代码看起来像这样:
|
服务内存从 800MB 慢慢涨到 2.6GB。
曲线不陡,不像雪崩。它只是每天往上爬一点,重启以后掉下来,过几天又回到老地方。告警一响,群里很快会出现两个动作:先怀疑泄漏,再问 GOGC 要不要调。
这两个反应都不离谱,但都太早。
在 Go …
很多人写 select,脑子里都有一个抽奖箱。
几个 case 往里一扔,runtime 随机摸一个。哪个 channel 先 ready,哪个就中;都没 ready,就看 default;没有 default,就等。
这个理解不能说全错, …
线上最烦的 channel 问题,往往不是“发不出去”。
而是你以为自己在等一个值,结果等来的是零值;你以为 close(ch) 只是关门,结果它把一批阻塞的 goroutine 全部叫醒;你以为 nil channel 和 closed …
goroutine dump 里刷出一排 [chan send],你知道它卡住了。
但更麻烦的问题是:它到底卡在哪?
是 channel 本身是 nil?是已经有人在等接收,只差一次交付?是 buffer 满了?还是它已经被 runtime …
函数签名很干净,代码却越来越难查。
你打开一个 Go 函数,只看到一个 ctx context.Context。再往里翻,发现 user id 从 ctx.Value 里取,tenant 从 ctx.Value 里取,trace id 从 …
接口已经超时了,goroutine 曲线还在往上爬。
上篇讲到这里时,我们先压住了一个误判:这通常不是 context 没生效,而是你的 goroutine 没有响应取消信号。
但还有一个更隐蔽的问题,很多 Go 代码每天都在写:
|
接口超时了,goroutine 曲线还在往上爬。
日志里已经打出 context deadline exceeded,调用方也早就不等了。你盯着监控看了十分钟,心里开始冒出那个判断:是不是 context.WithTimeout 没生效? …
上一篇讲到最后,其实只把问题说完了一半。
如果你面对的是任务、结果、信号和访问权交接,channel 确实比 mutex 更顺手。它能把 goroutine 之间的关系写出来,让代码不只是在“保护状态”,而是在表达一段通信。
但很多 …
上一篇说到一个很典型的场景:为了写得“更 Go”,有人把一个普通 cache 写成了小型 RPC。
锁确实没了。
但你多了 request、reply channel、owner goroutine、退出顺序、取消处理。读一个 map,本来 …
有些 Go 代码,一看就知道作者很努力。
为了让一个普通 cache 看起来“更 Go”,他把 sync.RWMutex 拿掉了,换成一个专门的 goroutine 来“拥有”这份状态。每次 Get,调用方先构造 request,发进 …
你读过的很多 Go map 文章,可能已经过时了。
它们还在讲 hmap、bmap、overflow bucket、load factor 6.5,还会画一张 bucket 后面挂着 overflow bucket 的图。那些内容不是没有价 …
很多人聊 Go 调度器,开口就能背:G 是 goroutine,M 是 machine,P 是 processor。
背完以后,问题来了:为什么已经有 M 这个 OS 线程,还要多一个 P?
如果这个问题答不上来,GMP 其实还没真的理解。 …
同样是复制,一个 [32]byte 的 hash 往往很安全,一个 struct 里的 [4096]int 却可能把性能拖得很难看。
这不是 Go 数组“好不好用”的问题,而是你有没有把它当成正确的东西。
很多 Go 开发者平时几乎不直接写 …
一次 append,把原来的数据改坏了。
代码看起来很普通:从一个 slice 里切一段出来,往这段里追加一个元素。你以为只是改了新变量,结果回头一看,原 slice 里的某个位置也变了。
这类问题很烦,因为它不像空指针那样直接炸给你看。它 …
线上偶现一个很烦的 bug:配置已经加载了,日志也打印了“ready”,但另一个 goroutine 读到的还是旧值。
代码看起来没什么毛病:先写数据,再把 ready 置成 true。读的一侧先等 ready,再读数据。人脑看这段逻辑,很 …
服务又被 OOMKilled 了。
Pod 刚重启,群里已经有人开始翻 GC 日志。有人说 GC CPU 到了 20%,有人说把 GOGC 调低一点,还有人建议直接上 GOMEMLIMIT。
这些动作不一定错,但顺序错了。 …
服务 p99 每隔几十秒抖一下。
监控里 GC CPU 大概 5%,不算夸张,但曲线很准:GC 一来,延迟就往上冒。群里很快有人提议:把 GOGC 从 100 调到 200,先让 GC 少跑几次。
这个动作看起来很合理。
CPU 下来了 …
服务内存从 800MB 慢慢涨到 2.6GB。
曲线不陡,不像雪崩。它只是每天往上爬一点,重启以后掉下来,过几天又回到老地方。告警响了,群里第一句话通常是:是不是内存泄漏?
这个判断不算错,但太急。
在 Go 服务里,内存持续上涨可能是真泄 …
goroutine 数量从 200 慢慢涨到 2 万。
监控图上那条线,不陡,但一直在爬。就像水龙头没拧紧——不喷,但也不会停。
你的第一反应是什么?
加大 buffer。
make(chan int, 100) 改成 make(chan …
线上 goroutine 数开始往上爬,最怕的不是它涨。
最怕的是你盯着监控看了十分钟,然后开始猜:是不是 context 泄漏了?是不是超时没生效?是不是某个 channel 卡住了?
这些猜法都有可能对,也都有可能错。 …
很多 Go 开发者第一次系统用 context,都会嫌它啰嗦。
一个请求从 handler 进来,service 要传 ctx,repo 要传 ctx,RPC client 要传 ctx,连中间那些根本不关心超时和取消的函数,也要机械地把 …
线上接口开始超时,goroutine 数一路往上涨。
你第一反应可能是:不是已经传了 context.WithTimeout 吗?超时到了,goroutine 不就该停了吗?
这就是 Go context 最容易被误用的地方。 …
TypeScript 的 10 倍提速,最容易被讲歪。
一歪成“Go 打赢 Rust”。
再歪成“以后 TypeScript 业务代码快 10 倍”。
这两个说法都抓人,也都危险。前者把工程决策讲成语言饭圈,后者直接把性能场景讲错了。
真正 …
内部工具越堆越多,每个新服务都要重新写一遍登录、鉴权、日志、错误处理。
新来的同事拿到一堆脚本,不知道哪个能跑、哪个已经废了。测试环境和生产环境的 token 混着用,一不小心就把测试数据写到生产。
这是我搭这套 CLI 基座的真实原因:不 …