
goroutine + channel 做事件总线时,为什么一发就 panic?
直接 make(chan Event) 后往里塞事件,很容易触发 send on closed channel 或死锁。这不是代码写错了,而是没封装控制逻辑。
- 无缓冲 channel 在没有 goroutine 立即接收时,
publish会永久阻塞——HTTP handler 就卡死了 - 多个 goroutine 并发写同一个 channel,但没人 close 它,或 close 时机不对,就会 panic
- 用
select { case ch 而不是裸写 <code>ch ,是防止阻塞的底线操作 - 缓冲大小别拍脑袋设:
make(chan Event, 16)适合中低频事件;高频日志类可到 128,但超过 256 就该怀疑 worker 处理能力不足了
asynq 连不上 Redis 怎么快速确认是不是配置问题?
错误信息 failed to dial redis: dial tcp [::1]:6379: connect: connection refused 几乎 100% 是地址或网络不通,不是 asynq 本身 bug。
- Docker 环境下别用
localhost,改用宿主机网关 IP(如redis://172.17.0.1:6379/0)或 compose 服务名(redis://redis:6379/0) - 用
redis-cli -u redis://... ping验证连通性,比看 Go 日志快得多 - Redis 6.0+ 启用了 ACL 的话,用户至少要有
+ping、+replconf、+psubscribe权限,asynq 内部依赖这些命令 -
asynq.NewClient初始化时传的asynq.RedisClientOpt必须显式包含Addr和Password(如有),漏掉任一字段都静默失败
worker 进程启动后任务不执行,最常被忽略的一步是什么?
不是 handler 没注册,也不是 Redis 没连上,而是根本没调 srv.Run(mux) —— 这个调用是阻塞的,必须在 main 最后执行,且不能加 go 前缀。
-
srv := asynq.NewServer(...); srv.Run(mux)是唯一让 worker 开始消费任务的方式 - 写成
go srv.Run(mux)会导致主 goroutine 立即退出,整个进程结束,任务永远 pending - handler 注册(
mux.HandleFunc("send_email", sendEmailHandler))只是“告诉 server 怎么处理”,不等于“server 已开始运行” - 并发数默认是 10,如果 CPU 密集型任务多,建议降到
Concurrency: 2,避免 goroutine 争抢调度器
监听器 panic 为什么会让整个事件循环停摆?
用 for range ch 分发事件时,只要任意一个 EventHandler panic,且没 recover,for 循环就退出,后续所有事件丢失——这比 HTTP panic 更隐蔽,因为没日志、没报错、只“静默停止”。
- 必须在每个 handler 执行前加
defer func() { if r := recover(); r != nil { log.Printf("handler panic: %v", r) } }() - 别只打日志,panic 发生时也该上报监控(比如 incr Prometheus counter 或发 Sentry event)
- handler 内部起 goroutine 处理耗时逻辑时,要传入子 context:
ctx, cancel := context.WithTimeout(eventCtx, 5*time.Second),避免泄漏 - event 结构体字段必须导出且不可变;别传
*map[string]interface{},否则多个 handler 修改同一份内存,数据竞争不可避免
实际项目里,最难绷住的不是怎么发事件,而是怎么确保每条事件都有始有终:从发布时不阻塞、传输时不丢、消费时不 panic、失败时可追溯。这些点串起来,才是异步事件驱动真正落地的毛细血管。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











