-
我的 HTTP 请求明明已经超时返回了,可它在后台启动的 goroutine 还在埋头跑、下游调用也没停,goroutine 越积越多内存一路涨,因为我没把 context 的取消信号传下去也没人监听它的深度复盘
我有个接口处理请求时会启动一些 goroutine 做并行子任务,并给请求设了超时。线上现象诡异:有些请求超时返回了,可监控显示 goroutine 数量只增不减、内存一路缓慢上涨。排查发现那些超时的请求主流程返回了,但它启动的 goroutine 还在后台埋头跑(还在查早已没人要结果的下游)、跑完没人收、白白耗着然后泄漏。复盘才搞懂:Go 用 context 传播取消/超时,请求超时时 ctx …- 5
- 0
-
我在 Go 里用 == 判断一个错误是不是记录不存在,一直好好的,直到某层代码用 %w 把这个错误包装了一下再往上抛,我的 == 判断就突然失效了,把记录不存在当成了未知错误的深度复盘
我要区分记录不存在(返回 404)和真的出错了(返回 500),写成了 if err == sql.ErrNoRows。一直跑得好好的,直到某次重构、中间一层 DAO 在错误往上传时用 fmt.Errorf("query user failed: %w", err) 把原始错误包装了一层。从那以后我的 == 判断突然失效:明明底层就是 sql.ErrNoRows,== 却返回 …- 0
- 0
-
我用 WaitGroup 等一批 goroutine 全部干完,却把 wg.Add 写进了 goroutine 内部,结果主协程的 Wait 在 Add 之前就跑完直接返回,程序以为活儿都干完了其实大半还没开始:一次 WaitGroup 时序用错的深度复盘
我要启动一批 goroutine 并发处理任务、再用 WaitGroup 等它们全部干完汇总结果,写成了在循环里 go func(){ wg.Add(1); defer wg.Done(); doWork() }()——把 wg.Add(1) 放在了 goroutine 内部。线上现象诡异:不报错,但汇总结果经常不完整,明明启动 100 个任务却只有零星几个的结果。复盘才搞懂:WaitGroup …- 0
- 0
-
我用一个 channel 收集多个 worker 的结果,某个 worker 干完顺手把 channel 关了,其他还在干活的 worker 一发送就 panic:send on closed channel,一次 Go channel 关闭所有权的深度复盘
我用多个 worker goroutine 并发处理任务,每个 worker 把结果发到一个共享的 results channel,主 goroutine 收集。为了发完就通知结束,我让每个 worker 干完后 close(results)。功能偶尔能跑,可线上常崩 panic: send on closed channel。推演时序才明白:这个 channel 是多个 worker 共享发送的…- 0
- 0
-
我在一个循环里处理几千个文件、每个都顺手 defer 关闭,结果跑到一半报 too many open files,因为那些 defer 全攒到函数返回才执行:一次 Go defer 在循环里堆积的深度复盘
我有个函数要在 for 循环里依次处理几千个文件,每打开一个就顺手 defer file.Close() 确保关闭——这是 Go 管理资源的标准姿势。可线上跑到几百上千个文件时就报 too many open files 崩了。查清 defer 的执行时机才明白:defer 注册的函数是在当前函数返回时才执行,而不是当前循环迭代结束时;我在循环里 defer,这几千个 Close 并没有在每次迭代…- 3
- 0
-
我读那个 map 一直好好的,直到某次往里写了一个键,程序当场 panic:assignment to entry in nil map,一次 Go 里 nil map 读得写不得、未初始化被读操作掩盖的深度复盘
我有个 struct 里有个 map 字段 cache,某些地方读它一直没问题;直到某次代码走到一个分支往 cache 里写 m.cache[key]=value——当场 panic: assignment to entry in nil map,goroutine 崩了。这 map 前面明明读得好好的,怎么一写就说是 nil?捋一遍才明白:这个 cache 从来没 make 过、值是 nil(m…- 0
- 0
-
我在 for range 循环里起了一批 goroutine 并发处理任务,结果它们全都处理了同一个、也就是最后一个任务:一次 Go 循环变量被复用、闭包捕获到的全是最后一个值的深度复盘
我要并发处理一批任务,for range 遍历任务列表、每个任务起一个 goroutine 去处理。结果诡异:明明有 10 个不同任务,日志里 10 个 goroutine 处理的全是同一个、也就是最后一个任务,前 9 个一个都没处理。查清才发现:在 Go 1.22 之前,for range 的循环变量 task 是整个循环复用的同一个变量,每轮只是赋新值;而 goroutine 闭包捕获的是这个…- 2
- 0
-
一个每次请求都起一个 goroutine 却没人保证它能退出的服务,goroutine 越积越多、内存缓慢上涨,跑几天就 OOM:一次 goroutine 泄漏的深度复盘
服务内存缓慢上涨、跑几天就被 OOM,pprof 一看 goroutine 从几十个涨到几十万、只增不减,且全卡在同一行 channel 接收/发送上。根因是每请求起 goroutine 通过无缓冲 channel 返回结果,而主流程超时/取消后提前返回、不再接收,那个负责发送的 goroutine 就永远阻塞在 ch- 0
- 0
-
一个被多个 goroutine 同时读写的普通 map,把整个 Go 服务以 fatal error 直接干崩、连 recover 都拦不住:一次 map 并发不安全的深度复盘
高并发 Go 服务平时稳如磐石,一到高峰就毫无征兆整个进程挂掉,日志只留一行 fatal error: concurrent map read and map write,而且入口的 recover 居然拦不住。根因是用普通 map 做内存缓存、多个 goroutine 无同步地并发读写它——Go 内置 map 为性能不做并发同步,并发写会破坏其哈希结构,运行时检测到就抛不可 recover 的 …- 2
- 0
-
一次对 Go slice 做切片后 append,意外覆盖了原始 slice 里的数据,让一份订单列表凭空窜了值:一次共享底层数组的深度复盘
只往一段子切片里 append 了几个元素,原始订单 slice 紧挨其后的数据却被莫名其妙改掉了。根因是 Go 的 slice 切片(s[0:3])不复制数据、和原 slice 共享同一底层数组,且子切片的 cap 到底层数组末尾、有剩余容量,append 便原地写入、覆盖了原 slice 的元素;而 cap 不够时 append 会扩容分配新数组、不影响原数组——故行为时灵时不灵、极难复现。本…- 7
- 0
-
我只是往一个切片里 append 了个元素,另一个切片的数据却被我悄悄改了:Go 切片共享底层数组的坑,让我排查了一下午
我只对子切片 sub 做了 append(sub, 999),从没碰过原始切片 original,可 original[3] 却从 4 被悄悄改成了 999。排查一下午才搞懂:Go 切片是 (ptr, len, cap) 三件套、是底层数组的视图,sub := original[0:3] 时两者共享底层数组且 sub 的 cap 仍是 5;append 发现容量富余,就直接写进了共享数组、覆盖了 …- 7
- 0
-
明明成功了,err != nil 却永远为真:我在 Go 里被"带类型的 nil 指针塞进 error 接口"坑了整整一晚,才真正看懂接口的底层二元组
一个函数成功时返回 nil 错误,可调用方的 if err != nil 永远为真,打印出来还是个空的 。整晚排查后真凶浮出水面:我返回的是一个"值为 nil、但带着 *MyError 类型"的指针,它被装进 error 接口后就不再 == nil。这篇复盘从接口的 (类型,值) 二元组底层讲起,梳理正解、所有接口的通病、Go 里各种 nil 的不同面孔,以及一份接口 nil …- 0
- 0
-
返回 nil 却判定非 nil:Go nil 接口陷阱避坑
有个 Go 服务某接口偶发返回处理失败,可日志里找不到任何真正的错误——业务逻辑明明跑成功了数据也对,它就是固执地走进出错分支。我盯着那段再标准不过的代码看半天:err := doSomething(); if err != nil { return err },逻辑清清楚楚、doSomething 内部确认成功返回的是 nil,可那个 if err != nil 偏偏判定为真。一个等于 nil …- 4
- 0
-
改一段切片污染另一段:Go slice 底层数组避坑
有个 Go 服务处理一批数据时有这么个逻辑:从一个大切片里按条件切出几段子切片分别交给不同业务处理,其中一段会被追加新元素、另一段会被原地修改,代码写得清清爽爽测试也过了便上线。可线上偶尔冒出灵异现象:某一段数据会莫名其妙出现本不该属于它的内容,像是被另一段处理串味了,复现概率不高数据也对不上、排查好几天毫无头绪——两段切片在代码里明明独立处理八竿子打不着怎么会互相影响?直到把切片底层机制重新啃了…- 11
- 0
-
内存只涨不降、几万协程卡死:Go goroutine 泄漏避坑
有个 Go 后台服务跑了几周后运维找上门:进程内存像吹气球一样一天涨一点、从不回落,最后逼近上限被 OOM 杀掉,重启再涨周而复始。我先按内存泄漏的老套路查对象没揪出元凶,直到顺手看了眼 runtime.NumGoroutine 返回的协程数量当场倒吸一口凉气:这个数字也在只涨不降,从启动时几十个涨到几万个还在稳步往上爬。真相浮出水面——不是对象泄漏,是 goroutine 泄漏:每处理一个请求代…- 3
- 0
-
进程偶发猝死、recover 拦不住:Go 并发读写 map 避坑
一个 Go 写的网关服务,上线大半年一直省心,直到某天开始毫无征兆地整个进程崩掉:不是某个请求报错,而是整个服务"啪"地一下没了,被守护进程拉起来,过几小时再崩一次。本地怎么压都不崩,只在生产高并发下偶尔发作;到处写了 recover 兜底,却根本拦不住。崩溃日志最后一行 fatal error: concurrent map read and map write——这不是 p…- 2
- 0
-
从一套裸起 goroutine 不管生命周期加无缓冲 channel 发送方阻塞下游一慢就泄漏到 OOM 加全局 map 当缓存被多 goroutine 无锁并发读写高并发下 concurrent map fatal 崩进程加到处 if err != nil 直接 return 把错误上下文丢得一干二净加压根不传 context 超时不可控取消不传播加靠 interface{} 加类型断言硬凑运行时 panic 加用 GOPATH 加手动 vendor 锁不住依赖加靠 fmt.Println 调试的早期 Go 祖传微服务、核心是支撑公司订单聚合与支付回调的微服务功能上一直能用在并发量不大的那些年里默默聚合订单接收支付回调直到一次大促把并发推高一个数量级这套从骨子里就没认真对待过 goroutine 生命周期和并发安全的代码在最不该出事的那一夜集中爆炸、把我们打醒的是大促当晚的连环崩溃第一记重拳来自那些没人管死活的 goroutine 网关给每个请求裸起一个 goroutine 去并发调下游再用无缓冲 channel 把结果收回来可一旦下游变慢上游请求因超时提前 return 走了那个还在傻等着往无缓冲 channel 里发结果的 goroutine 就因为再也没有接收方而永远阻塞在那条发送语句上再也退不出去大促那晚下游一变慢这种泄漏的 goroutine 开始成千上万地堆积每个都死死攥着自己那份请求上下文和缓冲区不放内存一路飙升最终 OOM 进程被杀几乎同时第二记重拳砸下另一处用一个全局 map 当本地缓存好几个 goroutine 并发地读它写它却没有加任何锁大促高并发下并发的读和写终于真正撞在一起 Go runtime 检测到 concurrent map read and map write 直接抛出一个连 recover 都救不回来的 fatal error 把整个进程当场干掉 → 2026 现代 Go 工程体系 context 控制生命周期加超时取消加 errgroup 编排 goroutine 启动即规划退出 + RWMutex 加 sync.Map 保护并发安全加 go test -race 检测竞态写代码时就被揪出 + fmt.Errorf 的 %w 包装错误链加 errors.Is/As 精确判断错误带完整来龙去脉 + context.Context 贯穿调用链超时与取消沿链传播一处慢则一起及时放手 + generics 类型参数编译期类型安全一份逻辑适配多类型无运行时断言 + 规范 defer 紧跟获取显式生命周期循环内资源即时释放绝不泄漏句柄 + Go Modules 加 go.mod go.sum 锁定整棵依赖树任何机器拉出完全一致的版本 + slog 结构化日志分级别带字段可检索聚合告警 87 天战役复盘:8 个 P0 复盘 + 6 条工程哲学 + 7 个关键数字
5 人的后端团队 87 天把一套支撑公司订单聚合与支付回调的核心 Go 微服务,从一堆裸起 goroutine 不管生命周期、用全局 map 当缓存却不加锁、到处 if err != nil 直接 return 把错误上下文丢光、压根不传 context、靠 interface{} 加类型断言硬凑、用 GOPATH 加手动 vendor 锁不住依赖、靠 fmt.Println 调试的早期 Go 代…- 7
- 0
-
从粗放 Go 微服务裸 go 无限开 goroutine 泄漏暴涨几十万 OOM + 无 context 传递被慢下游拖垮全链路无限干等堆积雪崩 + err 被 _ 丢掉漏接出错不知哪层报的 + 滥用 panic 当错误处理没 recover 一个边角崩整个进程 + 共享变量裸读写 data race 偶发脏数据和 concurrent map writes 崩溃 + interface{} 加类型断言丢类型安全运行时动不动 panic + 手撸 WaitGroup 加 error channel 协调并发又长又易错死锁漏收 + 热点频繁分配小对象 GC 压力大 STW 停顿肉眼可见 + 线上黑盒哪慢哪漏全靠猜 + 部署直接 kill 硬断在途请求连接 → 2026 现代高并发 Go 工程 worker pool 受控并发加 context 退出 + context 全链路传递超时取消 + 显式 error 加 %w wrapping 加 errors.Is/As + Mutex/atomic/channel 保护加 -race 检测 + 泛型 type-safe 编译期保证 + errgroup 统一并发错误取消 + sync.Pool 复用减少逃逸 + pprof/trace/metrics 可观测 + signal 加 context 优雅关闭 drain 排空 87 天战役复盘:47 套工程修法 + 7 个 P0 复盘 + 6 条工程哲学
10 人的 Go 基础设施与后端团队 87 天把一套用了四年、流量从每秒几千涨到每秒几十万后种种粗放并发写法集中爆雷的 Go 微服务集群——到处是裸 go 启动 goroutine 不加任何数量限制也没有退出机制高峰期 goroutine 暴涨到几十万 OOM、很多 goroutine 因为在阻塞 channel 上永远等不到信号而悄无声息泄漏越积越多、请求链路上完全没有 context 传递一个…- 4
- 0
-
从 Go 1.x GOPATH 时代 + dep/手工 vendor 依赖对不上 + 无泛型到处 interface{} 断言 + 裸 errors.New 拼字符串无上下文 + 不传 context 协程无法取消超时 + 满世界 sync.Mutex 加锁易死锁 + 手撸 goroutine 不管生命周期到处泄漏 + 标准 log 无结构 古老 Go 体系 → 2026 Go 1.22 + Go Modules 可复现依赖 + 泛型类型安全 + errors.Is/As 错误包装错误链 + context 全链路传递取消超时 + channel/errgroup 并发编排 + slog 结构化日志 + 泛型容器函数式工具 现代 Go 体系 87 天战役复盘:47 套工程修法 + 7 个 P0 复盘 + 6 条工程哲学
13 位 Go 后端工程师 87 天把一套跑了六年的古老 Go 体系——GOPATH 目录束缚、dep 和手工 vendor 管依赖经常对不上、完全没有泛型到处 interface{} 加类型断言、裸 errors.New 拼字符串没堆栈没上下文、不传 context 协程无法取消无法超时、满世界 sync.Mutex 共享内存加锁还经常死锁、手撸 goroutine 不管生命周期到处泄漏、标准 …- 6
- 0
-
从 Go 1.11 + GOPATH + dep + 无泛型 + interface{} + 裸 goroutine + log 纯文本 远古集群 → Go 1.24 + Modules/Workspaces + 泛型 + errgroup/context + net/http 1.22 + log/slog + PGO + fuzzing 现代工程体系 67 天踩坑录:版本阶梯 + 38 套修法 + 7 个 P0 复盘 + 6 条工程哲学
19 位 Go 后端工程师 67 天用版本阶梯 + 灰度替换策略把一套跑了六年、累计 38 万行的 Go 1.11 + GOPATH + dep + 无泛型 + 裸 goroutine 远古集群,平滑迁移到 2026 年 Go 1.24 + Modules/Workspaces + 泛型 + errgroup/context + net/http 1.22 增强路由 + log/slog + PG…- 0
- 0
-
从 Go 1.22 → 1.24 + chi v6 + Ent + franz-go + Uber FX 全栈现代化 21 天踩坑录:10 反模式 + 13 修法
某 14 微服务集群(日 7.2 亿请求)从 Go 1.22 + chi v5 + GORM v1 + Wire + Kafka segmentio 升级到 Go 1.24 + chi v6 + Ent v0.14 + Uber FX + Kafka franz-go 1.18 + Otel SDK 1.30 + golangci-lint 1.62 + sqlc 1.27。21 天踩 10 个反…- 6
- 0
-
Go 推荐服务 P99 480ms 每几小时抖动一次的 4 天复盘:容器化下被动 forced GC 三因素叠加 + GOMEMLIMIT/GOGC 双保险落地
2026 年 3 月,Go 推荐服务 reco-service 每隔 3-6 小时 P99 飙到 480ms,持续 10-30 秒后自动恢复,4 个月查不到根因。这次带 GODEBUG=gctrace=1 抓现场,定位是 Go GC 在大堆 + K8s memory limit 3GB + 默认 GOGC=100 三因素叠加引发的被动 forced GC,Mark phase 抢 25% CPU …- 2
- 0
Golang
幸运之星正在降临...
点击领取今天的签到奖励!
恭喜!您今天获得了{{mission.data.mission.credit}}积分
我的优惠劵
-
¥优惠劵使用时效:无法使用使用时效:
之前
使用时效:永久有效优惠劵ID:×
没有优惠劵可用!






















