go中命令模式应避免java式三层结构,推荐用workerfunc函数类型和闭包封装动作,配合context控制生命周期,注重资源复用、channel背压、超时控制与安全退出。

Go 里用 command 接口模拟命令模式,但别真照搬 Java 那套
Go 没有抽象类、不鼓励接口膨胀,硬套“Command + Invoker + Receiver”三层结构反而难维护。实际中更常见的是用函数值或闭包封装动作,配合 context.Context 控制生命周期。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 定义统一的执行函数类型:
type WorkerFunc func(ctx context.Context) error,比空接口或大接口更轻、更易测试 - 命令参数不要塞进结构体再实现
Execute(),直接通过闭包捕获——比如func(id int) WorkerFunc { return func(ctx context.Context) error { ... } } - 避免在
Command实现里做资源初始化(如打开 DB 连接),应由 Worker 启动时一次性完成,否则并发下容易泄漏或竞争
用 channel 做任务队列时,缓冲区大小不是越大越好
常见错误是设个超大 chan WorkerFunc(比如 make(chan WorkerFunc, 10000)),以为能扛住突发流量,结果内存暴涨、GC 压力大,甚至因堆积任务迟迟不执行而掩盖超时问题。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 缓冲区大小建议等于或略大于预期并发 Worker 数量(如 4 个 goroutine 就设
16),靠背压让生产者感知压力 - 务必配超时控制:往 channel 写入前用
select+time.After,避免阻塞主线程;消费端也要检查ctx.Done() - 别把失败重试逻辑全丢给 channel —— 重试策略(退避、最大次数)应在
WorkerFunc内部处理,channel 只负责传递一次任务
sync.WaitGroup 和 context.WithCancel 必须配对使用
Worker 系统关机时最常见的 panic 是“sync: WaitGroup is reused before previous Wait has returned”,或者 goroutine 泄漏导致进程无法退出。根本原因是没协调好等待与取消。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 启动 Worker 时用
wg.Add(1),每个 Worker 在 defer 中调用wg.Done(),但必须确保它只在真正退出前调用一次 - 取消信号要穿透到所有层级:主
ctx→ Worker 循环 → 单次WorkerFunc执行 → 底层 HTTP/DB 调用 - 别依赖
defer wg.Done()在 panic 时执行——加一层 recover,或改用runtime.Goexit()配合显式wg.Done()
异步 Worker 不等于无状态,http.Client 或 sql.DB 不能每个任务都 new
有人为图省事,在 WorkerFunc 里每次新建 http.Client 或开新 DB 连接,短期看不出问题,压测时连接数爆炸、TLS 握手耗时飙升,错误频出(net/http: request canceled (Client.Timeout exceeded while awaiting headers))。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 所有共享资源(HTTP 客户端、DB 句柄、Redis 连接池)必须在 Worker 启动前初始化,并作为参数传入闭包或结构体字段
- 注意
http.Client的Timeout和Transport配置:默认无超时,且MaxIdleConnsPerHost默认是 2,高并发下会排队 - 如果 Worker 需要用户上下文(如租户 ID),别塞进全局变量,用
context.WithValue传,但要定义明确的type ctxKey string类型,避免 key 冲突
最麻烦的从来不是怎么启动 goroutine,而是怎么让它安全、可控、可观测地停下来——尤其是当任务链路横跨 HTTP、DB、消息队列时,cancel 信号漏掉任意一环,就可能卡死整个 shutdown 流程。










