开发心得-SSE架构与注意事项

3 阅读4分钟

开发心得:VSS SSE

本文是 2.3 VSS-SSE架构设计配套的开发笔记:在「独立端口、/eventsmessageChanFlush」模型之上,补充联调、部署、扩展开仓时容易忽略的点。

项目地址 github.com/openskeye/g…


1. 明确三个要点

  1. 推送只认 messageChan:业务 Logic 不要把 SSE 的 http.ResponseWriter 往下传并自行 Write;统一 messageChan <- &SSEResponse{...},由 server/sse.gohandlertoResp + Flush,否则帧格式、结束语义、关闭顺序会分裂。
  2. for range messageChan 退出后必然会 close(messageChan)DelayClose 时延迟 2s)。Logic 禁止在向 channel 已关闭后仍发送,否则会 panic;长协程必须在 ctx.Done() 后停止写 channel。
  3. 高频/洪峰要考虑背压messageChan 有界,满则 发送方阻塞——SIP 日志类场景要与上游限流、丢包策略(见 §5)。

2. 与 [VSS-SSE架构设计]的「实现更新」:messageChan 容量

文档中已说明「缓冲 10」;实现以配置为准:

var buf = l.svcCtx.Config.SSE.MessageChanBuffer
if buf <= 0 {
    buf = 256
}
var (
    messageChan = make(chan *types.SSEResponse, buf)
  • 配置项:SSE.MessageChanBuffercore/tps/conf/config.go0 或未配则服务端用 256)。
  • 开发心得:变更环境时别假设固定 10;压测/日志洪峰前应调大或做业务侧节流

3. HTTP 层:帧格式、Flusher、代理

3.1 data: {"data": ...} 嵌套

toResp 成功帧为 data: {"data": <JSON>}\n\n,不是业务 JSON。
前端用 EventSource 时,event.data 是一段字符串,需 JSON.parse 后再读 .data,否则联调会误以为服务端乱码。

3.2 http.Flusher 强转

写出路径使用 w.(http.Flusher).Flush()。若反向代理或中间件把 ResponseWriter 包了一层且未实现 Flusher,会 panic
部署心得:网关/自管 middleware 需透传 Flusher;Nginx 等对 SSE 常见配置为 proxy_buffering off、合适的 proxy_read_timeout

3.3 结束语义

  • Errevent: end + error 字段 → handler 跳出循环
  • Doneevent: end + data: {}
  • DelayClose: truedefer2s 后再 close(messageChan),给尾包/对端处理留时间;Logic 若在 Done 后仍异步写 channel,仍有 竞态 风险,应 Done 即停写

3.4 CORS

响应头 Access-Control-Allow-Origin: *。若浏览器要带 Cookie 鉴权,常与 * 冲突——当前 handler 未做认证(见 2.3 中的第七节),公网应靠网关认证 / 内网隔离


4. 路由与并发模型:sseHandler vs sip_logs

func sseHandler[Logic any, Req types.SSERequestType](...) {
	// ... schema + validator,失败则直写 messageChan ...
	go handler.DO(req)
}
  • 带 Query 的类型go handler.DO(req),解析/校验在当前 goroutine业务在子 goroutine
  • sip_logs同步 VSipLogs.New(...).DO()DO 内部再如何起 goroutine 由其实现决定(2.3 已述)。

心得:新增 type 时要想清楚——是否需要在 DO 前再阻塞做资源抢占(例如 sip_logs 的全局单活),不要照抄 sseHandler 或 sip_logs 而不读实现。

4.1 默认分支

switchdefault未识别 type 以及 type 为空 统一返回 type 不能为空 类错误。
联调:缺参拼错 type 表现接近,应用枚举文档或常量,避免手写字符串,这里我做的简易实现需要根据业务做出调整。


5. 背压、阻塞与 SIP 日志类场景

  • messageChan → Logic 里 messageChan <- 阻塞,若发生在持有锁或与 SIP 广播同线程路上,可能间接 拖慢其它模块
  • ** sip_logs 等:若已实现限流/非阻塞发送**,更新后应以实际 sip_logs.go 为准做容量估算;未做限流时,应降低广播频率加大 MessageChanBuffer

心得:把 SSE.MessageChanBuffer 当成与 SipLogMaxPerSecond(若配置)同级看待,一起调。


6. 生命周期与 context

  • ctx 来自 context.WithCancel(r.Context()),客户端断开时 ctx 取消
  • handlerfor range messageChan 并不 select ctx.Done()(见 2.3 第九节):客户端断开后,若 Logic 仍持续 messageChan <-,channel 可能迟迟不被消费直至 Logic 停止;若 Logic 已停而 channel 里还有数据,行为依赖剩余事务。

心得:每个Logic 应 明确

  1. select { case <-ctx.Done(): return; case ... } 停止生产;
  2. 结束前视情况发 DoneErr

7. 扩展新 type 的检查清单

  • routers.go 增加 caseGetType() 与 query type 常量一致。
  • 有 Query:structform/validate tag,与前端一致。
  • New(ctx, svcCtx, messageChan) 保存 ctx禁止在 goroutine 里无取消地死循环。
  • 预估 QPS:是否会塞满 messageChan,是否要做聚批/采样
  • 结束路径:必须在错误/完成时让 handler走出 for rangeErr/Done/空串跳出规则见 toResp)。
  • 若对公网:网关鉴权、限流、 与 SSE 超时 评审,根据需求调整。

8. 与其它通道对比选型

能力SSE /events Gin /apiWS
端口SSE.PortHttp.PortWS.Port
方向服务端推送为主请求-响应双向
鉴权handler 未内置视业务子协议 Token
文本 SSE,固定 toResp 包装JSON body自定

不要用 SSE 传大文件二进制;大流量用 下载接口 + file_download 进度 或独立存储链。


9. 相关文档与源码

说明路径
架构总览2.3 VSS-SSE架构设计
服务实现core/app/sev/vss/internal/server/sse.go
路由core/app/sev/vss/internal/handler/sse/routers.go
业务 Logiccore/app/sev/vss/internal/logic/sse/*.go
ServiceContext开发心得-ServiceContext设计与使用