Kratos本身不内置WebSocket支持,需通过插件化transport层扩展;社区成熟方案是基于gorilla/websocket封装的kratos-transport/transport/websocket,须手动注册并注意握手配置、超时对齐与分布式状态管理。

kratos 本身不内置 WebSocket 支持,得靠 transport 层扩展
Kratos 框架的 transport 设计是插件化的,http 和 grpc 是官方维护的 transport,但 websocket 不在默认列表里。你不能直接在 server.NewServer() 里传一个 transport.WebSocket —— 它压根不存在。实际做法是基于第三方库(比如 gorilla/websocket)自己封装一个 transport 实现,再注册进 Kratos 的 server.Server 生命周期中。
目前社区较成熟的方案是 kratos-transport/transport/websocket 这个独立仓库,它把 gorilla/websocket 的连接生命周期、升级逻辑、消息读写都包装成 Kratos 风格的 transport.Transport 接口实现。你可以把它当 dependency 引入,也可以 fork 后按需改写 handshake 路由、鉴权方式或消息编解码逻辑。
- 必须手动注册到
server.Server,不能靠kratos.NewApp()自动发现 - WebSocket 路由和 HTTP 路由共享同一个端口(比如 8000),但路径要显式区分,例如
/ws或/chat - 不能复用 Kratos 的
http.Middleware,因为握手阶段是 raw HTTP 请求,中间件链在 upgrade 成功后就断开了
WebSocket 握手失败常见原因及排查点
浏览器控制台报 WebSocket connection to 'wss://...' failed,90% 不是代码写错了,而是握手阶段被拦在了某一层。Kratos 应用跑在网关或反向代理后面时尤其容易出问题。
- Nginx 默认不透传
Upgrade和Connection头,必须显式配置:proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection "upgrade"; - Kratos 的 HTTP server 如果启用了
gzip中间件,可能干扰Sec-WebSocket-Accept计算,建议对 WebSocket 路径禁用压缩 - 如果走的是 WSS,证书必须有效;自签名证书在 Chrome 里会直接拒绝握手,连
onerror都不会触发 - 客户端发的
Sec-WebSocket-Key是 base64 编码的 16 字节随机数,服务端必须原样拼接"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"后 SHA1 + base64,少一个字符或大小写错都会返回 400
连接频繁断开?先看是不是心跳和超时没对齐
Kratos 自身没有心跳管理,gorilla/websocket 默认也只设了 WriteDeadline,没管读超时。而真实环境里,Nginx、ELB、云防火墙普遍会在 60–300 秒无数据时主动 kill 连接。这时候客户端看到的是 CloseEvent { code: 1006 },服务端日志可能只有 read: connection reset by peer。
- 服务端必须设置
SetReadDeadline,且间隔要比代理超时短至少 10 秒,例如 Nginx 设了proxy_read_timeout 120,你就得在gorilla/websocket里设110 * time.Second - 心跳必须双向:客户端发
ping,服务端收到后立即回pong;反过来服务端也要定期发ping,客户端收到后回pong;单向心跳防不住某些中间件 -
gorilla/websocket的EnableWriteCompression在高并发下可能引发 goroutine 泄漏,生产环境建议关掉
多实例部署时连接状态无法共享,别硬扛
Kratos 单实例跑 WebSocket 没问题,但一旦加了负载均衡,用户 A 第一次连到 node-1,第二次重连落到 node-2,node-2 根本不知道这个用户之前在线、有哪些未读消息、正在参与哪个房间——这是分布式场景下最典型的“连接孤岛”问题。
你没法靠 Kratos 的 registry 或 config 中心解决这个问题,因为连接本身是 TCP 级别的,不属于服务发现范畴。真正可行的路径只有两条:
- 用 Redis + Pub/Sub 做跨节点广播:每个 node 订阅统一 channel,用户上线/下线/发消息都 publish 到 Redis,其他 node 监听并同步本地状态
- 引入专门的连接网关(如
goim或自研),把 WebSocket 连接收敛到少数几台 gateway 节点,业务 Kratos 微服务只通过 RPC 或消息队列和 gateway 通信 - 完全放弃“连接状态集中管理”,转为无状态设计:所有消息走 MQ,客户端每次重连都拉取最新状态,服务端不保存任何连接上下文
最后这点最容易被忽略:WebSocket 连接本身是昂贵资源,不是必须长期持有。很多业务场景其实只需要“最终一致性”,而非“实时连接可见”。强行维持百万级长连接,代价远高于重构消息模型。


















