HTTP回调接收端不能靠goroutine处理,必须用Redis Stream等持久化队列解耦;重试由消费者组+XACK/XCLAIM自动触发,而非handler内实现。

直接说结论:HTTP 回调接收端不能靠 go 启动 goroutine 处理业务,必须用持久化队列(如 Redis Stream)解耦;重试逻辑不在 handler 里写,而由消费者组 + XACK/XCLAIM 自动触发。
为什么 http.HandlerFunc 里写 go process() 是错的
这不是“不够优雅”的问题,而是会直接导致任务丢失、panic 静默、上下文失效。常见现象包括:
-
r.Body在 handler 返回后关闭,goroutine 里再读就是EOF或空字节 - 进程重启时所有未执行的 goroutine 彻底消失,无日志、无痕迹
- 没做并发控制,突发流量打爆数据库连接或内存
- goroutine 内 panic 不会传播,
recover()也抓不到——因为不是同个栈
真正该做的只有三件事:hmac.Equal 校验签名、io.ReadAll(r.Body) 一次性读完原始 body、把结构化任务同步写入 Redis Stream 或 DB,然后立刻 w.WriteHeader(http.StatusOK)。
如何用 Redis Stream 实现带重试的回调消费者
Redis Stream 天然支持消费者组、消息确认和超时重分配,比手写定时轮询或 time.AfterFunc 可靠得多。
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
立即学习“go语言免费学习笔记(深入)”;
- 首次消费前必须执行:
XGROUP CREATE order_events orders $ MKSTREAM,否则XREADGROUP报NOGROUP - 拉取消息用:
XREADGROUP GROUP orders worker-01 COUNT 10 BLOCK 5000 STREAMS order_events >,其中>表示只读新消息 - 业务成功后才调
XACK;提前 ACK = 消息丢失 - 失败时不
XACK,等 Redis 超时(默认 60s)后通过XCLAIM重新分配给其他 worker - 防幂等:在业务逻辑开头用
SETNX task_id_ttl 3600加锁,失败则直接 return
回调响应必须满足的硬性规范
第三方系统(GitHub/Stripe/微信支付)对响应极其敏感,不按规则来就会被重试甚至拉黑地址。
- 只允许返回
200 OK状态码,不能是201、204或其他 - 响应体必须为空——别用
json.NewEncoder(w).Encode(),它会写{"status":"ok"}导致失败 - 响应头不能含多余字段,比如
X-Request-ID或自定义 header,某些平台会校验 header 数量 - 不要在 handler 里解析 JSON 后再校验签名,
json.Decode会吞掉原始字节流,导致X-Hub-Signature-256校验失败
JSON 解析要防 panic 和字段漂移
Webhook 来源不可控,字段可能新增、缺失、为 null,或时间字段混用字符串/数字格式。
- 永远用指针接收结构体:
var payload *GitHubPushEvent,避免零值覆盖已有数据 - 非关键字段用
json.RawMessage接住,后续按需解析,不强求一次Unmarshal到底 - 时间字段别依赖默认解析,自己实现
UnmarshalJSON方法兼容字符串和数字 - 检查
err类型:json.SyntaxError是格式错,json.UnmarshalTypeError是类型不匹配,不能全丢给log.Fatal
最易被忽略的是:签名必须在 io.ReadAll(r.Body) 后立即计算,且必须用 hmac.Equal 做常数时间比对——任何字符串 == 或 bytes.Equal 都存在时序攻击风险。

















