从单体到分布式:一个应届生的微服务拆分血泪史
去年秋招拿到深圳某腾讯系公司的 offer 后,我本以为可以躺平半年等入职。结果刚签完三方,就被导师拉进了一个“历史遗留系统重构”项目——美其名曰“提前熟悉业务”,实则是团队缺人手,让我这个还没毕业的菜鸟顶上。
那个系统,用 Go 写的单体应用,跑了快五年,代码量将近 30 万行。每次发版都像在雷区跳舞,CI/CD 流水线跑一次要 40 分钟,测试同学一看到 PR 就叹气。更离谱的是,数据库居然是 MySQL 单点 + 手动主从切换,双 11 前运维大哥天天念经:“别崩、别崩、千万别崩……”
直到去年 12 月,系统在促销高峰直接 OOM,整个用户下单链路瘫了 27 分钟。老板震怒,CTO 亲自下场拍板:“必须拆!三个月内完成微服务化!”——于是,我和另外两个实习生,加上一位刚转管理的前架构师,组成了“微服务敢死队”。
为什么非拆不可?
一开始我也觉得小题大做。不就是加点机器、优化下 SQL 吗?但当我真正去读那坨“意大利面条式”的代码时,我悟了。
比如一个看似简单的“创建订单”接口:
func CreateOrder(w http.ResponseWriter, r *http.Request) {
// 1. 校验用户登录状态(调用 Auth 模块)
// 2. 查询商品库存(查 Product 表)
// 3. 计算优惠券(Coupon 模块)
// 4. 冻结库存(Inventory 模块)
// 5. 生成支付链接(Payment 模块)
// 6. 发送短信通知(SMS 模块)
// 7. 记录操作日志(Log 模块)
// 8. 更新用户积分(User 模块)
}
所有逻辑全塞在一个函数里,事务横跨 8 张表。更可怕的是,每个模块之间高度耦合:改个优惠券规则,可能影响库存冻结;发个短信失败,整个订单回滚。这哪是单体,简直是“单体炸弹”。
而且技术栈混乱得离谱:核心下单用 Go,但营销活动用 Python Flask,报表系统又是 Django。三个语言混跑,部署脚本写了上千行 Shell,连 Dockerfile 都有五个版本。
“这系统不是不能跑,是不敢动。” —— 我们组长在周会上苦笑。
拆!但怎么拆?
我们没搞什么高大上的 DDD(领域驱动设计),毕竟 deadline 在追命。而是采用 “垂直切片 + 能力下沉” 的土办法:
- 按业务域拆:用户、商品、订单、支付、营销五大模块
- 优先拆高频、高风险路径:下单 → 支付 → 库存
- 老系统保留只读接口,新功能全部走新服务
有意思的是,团队对语言选型吵了一架。后端老哥坚持全 Go,但营销团队那边全是 Python 熟手。最后折中:核心交易链路用 Go(性能+并发强),营销、BI 类服务用 Python(开发快、生态好)。
于是架构图变成了这样:
[Client]
│
├── [API Gateway] (Go)
│
├── [User Service] (Go)
├── [Product Service] (Go)
├── [Order Service] (Go)
├── [Payment Service] (Go)
└── [Promotion Service] (Python)
API Gateway 用 Go 写,轻量又快;而促销服务用 Python + FastAPI,三天就搭出 MVP,产品经理直呼“神速”。
通信协议:gRPC vs REST?我们都要!
一开始想统一用 gRPC,毕竟 Go 生态支持好。但 Python 团队抱怨 Protobuf 学习成本高,而且他们还要对接第三方 BI 工具,人家只认 JSON。
最后我们定了规矩:
- 内部服务间通信:Go ↔ Go 用 gRPC(高性能、强类型)
- 对外或异构系统:统一 RESTful + JSON(兼容性好)
举个例子,Order Service 调用 Payment Service:
// Go 客户端(gRPC)
paymentClient := pb.NewPaymentServiceClient(conn)
resp, err := paymentClient.CreatePayment(ctx, &pb.CreatePaymentRequest{
OrderId: "123",
Amount: 99.9,
})
而 Promotion Service(Python)需要查用户等级:
# Python 客户端(REST)
resp = requests.get("http://user-service/api/v1/users/123")
user_level = resp.json()["level"]
虽然混合架构有点“缝合怪”,但务实比纯洁更重要。上线后压测发现,gRPC 路径延迟 < 15ms,REST 路径 < 50ms,都在可接受范围。
数据库:终于告别“一张表打天下”
原系统所有业务共用一个 MySQL 实例,orders 表居然有 87 个字段!拆分时我们做了三件事:
- 按服务拆库:每个微服务拥有自己的数据库(甚至可以不同引擎)
- 读写分离:写主库,读从库(Go 用
gorm自带支持) - 关键数据冗余:比如 Order 服务本地缓存用户昵称,避免频繁调 User 服务
这里踩了个大坑:分布式事务。
比如“下单成功后扣减库存”,如果 Order 成功但 Inventory 失败,数据就乱了。我们试过 Saga 模式,但流程太复杂。最后用了 “本地消息表 + 定时补偿” 的土办法:
// Order Service 中
tx := db.Begin()
tx.Create(&Order{...})
tx.Create(&LocalMessage{
Target: "inventory-service",
Action: "decrease_stock",
Payload: `{"sku_id": "A1", "qty": 1}`,
Status: "pending",
})
tx.Commit()
// 后台 goroutine 轮询 pending 消息,调 Inventory 服务
// 成功则标记为 done,失败则重试(最多 3 次)
虽然不够优雅,但简单可靠,而且和现有系统兼容。Python 的 Promotion 服务也照搬这套逻辑,用 Celery 做异步任务,效果不错。
配置与部署:别再手动改 YAML 了!
以前改个配置要登录五台服务器,scp 文件,重启进程。现在我们用:
- 配置中心:Apollo(公司自研,类似 Spring Cloud Config)
- 服务注册发现:Consul
- 容器化:Docker + K8s(测试环境用 Minikube)
每个服务启动时自动从 Apollo 拉配置:
// Go 服务
config := apollo.GetConfig("order-service")
dbHost := config.GetString("db.host")
# Python 服务
from apollo.client import Client
client = Client(app_id="promotion-service")
sms_key = client.get_value("sms.api_key")
部署流程也标准化了:
# 开发提交代码 → GitLab CI → 构建镜像 → 推送到 Harbor → K8s 自动滚动更新
最爽的是,再也不用求运维大哥帮忙上线了!自己一条 kubectl apply -f deployment.yaml,服务就跑起来了。
监控告警:没有监控的微服务等于裸奔
拆完之后,问题从“整个系统挂了”变成“哪个服务慢了?”。我们立刻接入公司统一监控体系:
| 组件 | 技术栈 | 作用 |
|---|---|---|
| 日志 | ELK | 全链路日志追踪 |
| 指标 | Prometheus | QPS、延迟、错误率 |
| 链路追踪 | Jaeger | 跨服务调用链可视化 |
| 告警 | AlertManager | 企业微信机器人通知 |
特别有用的是 Jaeger。有一次用户反馈“下单慢”,我们直接搜 TraceID,发现卡在 Python 的 Promotion 服务里——原来他们用 Pandas 处理实时优惠计算,大数据量时 CPU 打满。立马优化算法,QPS 从 50 提升到 800。
性能对比:值不值得折腾?
我们拿“创建订单”接口做了压测(4核8G 机器,模拟 1000 并发):
| 指标 | 单体架构 | 微服务架构 | 提升 |
|---|---|---|---|
| 平均延迟 (ms) | 320 | 85 | ↓ 73% |
| 错误率 (%) | 4.2 | 0.3 | ↓ 93% |
| 发布耗时 (分钟) | 40 | 8 | ↓ 80% |
| 故障隔离能力 | ❌ 全挂 | ✅ 局部降级 | ✔️ |
最直观的感受是:现在改促销逻辑,再也不用提心吊胆怕影响下单了。上周五晚上,产品经理临时要加一个“分享得红包”功能,Python 同学两小时搞定,Go 这边完全无感。
给后来者的几点建议
作为一个刚入行就被扔进火坑的应届生,我想说:
- 不要为了微服务而微服务:如果你的系统 QPS 不到 100,单体完全够用。我们拆,是因为真的扛不住了。
- 语言不是重点,接口契约才是:Go 和 Python 能共存,前提是 API 文档清晰(我们用 Swagger + Protobuf 双维护)。
- 先解决“能跑”,再追求“跑得漂亮”:本地消息表虽土,但救了我们三次线上事故。
- 监控必须前置:没监控的微服务,就像闭眼开车。
最后吐槽一句:微服务最大的成本不是技术,是沟通。现在每天站会要对齐五个服务的状态,产品经理已经学会说“你们那个 gRPC 接口啥时候 ready?”了(笑)。
现在我坐在深圳南山的工位上,看着 K8s Dashboard 里十几个 Pod 稳稳运行,突然觉得——被逼着啃源码、画架构图、通宵 debug 的日子,好像也没那么糟。
毕竟,谁不想在简历上写一句:“主导完成核心系统微服务化,支撑日订单量 50W+” 呢?
(P.S. 下周正式入职,希望别让我维护自己写的代码……)

评论 0