直接用 channel 做跨服务通知会出问题,因为 channel 是进程内通信机制,多进程部署时数据静默丢弃,导致上线后通知不触发;应改用 redis streams 等跨进程消息中间件实现可靠投递与重试。

为什么直接用 channel 做跨服务通知会出问题
因为 channel 是进程内通信机制,服务一拆成多个独立进程(比如 HTTP 服务、任务服务、消息推送服务),channel 就彻底失效了。你发过去的数据根本到不了另一个服务里,连 panic 都不会报——它只是静默丢弃。常见现象是:本地测试一切正常,上线后通知完全不触发。
- 别把
chan struct{}或chan *NotifyEvent当成跨服务总线用 - 哪怕加了
sync.Mutex或atomic,也解决不了进程隔离问题 - 想靠 HTTP 轮询模拟“监听 channel”?延迟高、连接多、易超时,运维同学会找你喝茶
go.mod 里怎么声明和复用通知模块
核心不是写多少代码,而是让各服务能一致地序列化、投递、重试。模块要导出的是接口和标准结构体,不是具体实现。
- 模块路径建议用
github.com/your-org/notify,别放在主服务的internal/下 - 暴露
SendAsync(ctx, event *notify.Event)和RegisterHandler(topic string, fn notify.Handler)两个关键函数 - 所有事件结构体必须带
Topic字段(如"order.created")和RetryLimit int,方便下游按主题路由和控制重试 - 在各服务的
go.mod中统一 requiregithub.com/your-org/notify v0.3.1,避免版本错乱导致 JSON 序列化字段不一致
用 Redis Streams 实现可靠投递的关键配置
不用 Kafka 或 RabbitMQ 并不意味着要自己造轮子;Redis Streams 天然支持消费者组、ACK、pending list,足够撑住中等规模异步通知。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 投递端用
client.XAdd(ctx, &redis.XAddArgs{Stream: "notify:stream", Values: map[string]interface{}{"topic": e.Topic, "payload": data}}) - 消费端必须用
XReadGroup,且consumer名需包含服务名+PID(如"order-service-12345"),否则重启后消息被其他实例吞掉 - 务必设置
Block: 5000(毫秒),而不是0,避免空轮询打爆 Redis CPU - ACK 前做幂等校验:先查
notify:ack:<topic>:<event_id></event_id></topic>是否存在,再写入,最后XAck—— 顺序不能反
失败重试逻辑必须脱离业务代码硬编码
每个服务自己写 time.AfterFunc 或 for i := 0; i 会导致重试策略碎片化,且无法统一降级(比如大促时关掉邮件通知只留短信)。
- 重试由通知模块内部处理:投递失败时,自动塞进
notify:retrySorted Set,score 设为time.Now().Add(1 - 单独起一个
retryWorkergoroutine,每 200ms 扫描ZRANGEBYSCORE notify:retry -inf (now,拉出到期任务重新投递 - 重试次数超过
event.RetryLimit后,写入notify:failedStream 并发 Slack 告警,别只打日志 - 业务代码里只管调
notify.SendAsync(ctx, ¬ify.Event{Topic: "user.registered", Payload: u}),其余全交给模块
真正麻烦的不是投出去,是确保每条通知最终被某处消费且不重复。模块封装得越薄,越容易在不同服务里验证行为一致性——比如改个重试间隔,所有服务立刻生效,而不是翻十个项目去 grep 修改。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










