老架构走到头了
先交代下背景。我之前在一家做宠物医疗 SaaS 的公司,老版本是典型的 C/S 架构:Windows 客户端通过 fasthttp 与服务端保持长连接,消息分发用自定义二进制协议。早期跑得很稳,但门店扩张到数千家之后,问题一个个冒出来:客户端发版困难,协议升级要双端兼容,服务端没法水平扩容,毕竟长连接是绑在节点上的。最要命的是业务逻辑全塞在一个单体里,改一个挂号流程,整个系统要全量回归。
2023 年我们决定迁到 B/S 架构,浏览器直接访问,后端选了 go-zero。
怎么拆的
go-zero 吸引我的点是它自带的微服务治理能力:熔断、限流、降级、超时控制都是开箱即用,不用自己在业务代码里堆中间件。整体拆成 API 网关层和 RPC 服务层:
- API 层用 go-zero 的
.api文件定义 RESTful 接口,通过goctl生成路由、handler、types 骨架。 - RPC 层用
goctl rpc new生成 gRPC 服务,按领域拆成挂号、诊疗、收费、库存四个服务。 - 服务注册用 etcd,网关通过
zrpc直连 RPC 客户端,带客户端负载均衡。
下面是 .api 文件的一个片段:
| |
生成的 handler 只做参数校验和调用 logic,业务全部下沉到 logic 层,再通过 zrpc 调用下游:
| |
zrpc 的客户端在 servicecontext.go 里初始化,自带 etcd 服务发现和中间件:
| |
迁移路上的三个坑
第一个坑是长连接怎么下线。老客户端还有存量门店在用,不能一刀切,我们在网关侧做了一层协议适配:fasthttp 接收老协议,转成 gRPC 调用新服务,灰度了两个月才彻底切掉。期间最麻烦的是老协议没有 trace 字段,跨协议排查问题完全是黑盒,只能在适配层强制注入 traceId。
第二个坑是 goctl 模板定制。默认生成的代码结构和我们团队的 DAO 规范有出入,我们 fork 了一份 template,把 GORM 和 Zap 日志注入进去,后续新服务直接用定制模板生成,省了不少重复劳动。
第三个坑是超时配置,也是最隐蔽的一个。go-zero 的超时是分层的,API 层、RPC 客户端、RPC 服务端都要设,而且要呈"倒金字塔":外层超时必须大于内层,否则会出现上游已经返回超时、下游还在执行的空转。我们把 API 设成 5s,RPC 设成 3s,DB 查询设成 1s,基本覆盖了业务场景。
迁完之后
从 fasthttp C/S 迁到 go-zero B/S,最大的收益不是性能,是交付节奏。浏览器即开即用,发版不再依赖客户端升级;微服务拆分之后,单个服务可以独立部署。go-zero 的工具链和治理能力,让我们在没有专职中间件团队的情况下也能把微服务跑起来,这对中小团队很实在。
封面图:cbowns / Flickr · CC BY-SA 2.0
