从单体到分布式:一个DBA转后端的微服务血泪史

NullPointer青年
2025-12-25 11:13
阅读 1502

去年双11前两周,我们组的老大拍了拍我肩膀:“老张,系统撑不住了,拆微服务吧。”
我当时正盯着 MySQL 慢查询日志发呆,手边还摊着一本翻烂的《数据库系统概念》。作为从 DBA 转岗过来的后端,我对“拆库分表”比对“微服务”更敏感——但现实是,单体应用已经成了线上事故的定时炸弹。

我在这家公司干了快两年,从最初帮业务线调优索引、写存储过程,到现在负责核心订单模块。团队氛围不错,就是产品需求永远比 deadline 跑得快。上个月刚因为“用户下单偶尔卡住5秒”被拉进紧急会议,运维甩锅网络,前端说接口慢,测试贴出一堆 JMeter 报告……最后发现是数据库锁等待。那一刻我就知道:这坨单体,必须拆!


为什么不是“重构”,而是“拆”?

很多人一提微服务就想到“优雅架构”“云原生”,但对我们这种中小团队来说,动机往往很朴素:扛不住了

我们的老系统用 Go 写的(没错,我这个 DBA 现在主力语言是 Go),所有功能——用户、商品、订单、支付——全塞在一个进程里。数据库就一个主库,读写压力全压在上面。高峰期 CPU 飙到 90%,连接池爆满,连 SHOW PROCESSLIST 都要等三秒。

更可怕的是,改一行代码可能引发连锁反应。上周五晚上加班改个优惠券逻辑,结果把退款流程搞崩了,回滚到凌晨两点。当时真的想砸电脑。

所以这次拆微服务,不是为了追新,而是为了活下去


拆之前先问自己:数据库准备好了吗?

作为一个有数据库执念的人,我坚持一点:微服务拆分,本质是数据边界划分

很多团队直接按业务模块切服务,结果发现跨服务事务处理不了,最后又搞出一堆补偿机制、消息队列兜底,复杂度爆炸。我们花了整整一周时间,拉着产品经理和测试一起画数据流图,重点回答三个问题:

  1. 哪些数据强关联?(比如订单和订单项)
  2. 哪些操作必须原子?(比如扣库存+生成订单)
  3. 哪些查询可以最终一致?(比如用户积分)

基于此,我们定下原则:每个微服务独占自己的数据库,禁止跨库 JOIN。哪怕前端要展示“订单+用户昵称”,也由 BFF 层聚合,而不是让订单服务去查 user 表。

吐槽一句:有些前端同学总想“一次接口拿全数据”,但分布式世界里,网络调用比内存访问贵一万倍。我们后来干脆搞了个 GraphQL BFF,让他们自己组合字段,省得天天找我加字段。


技术选型:稳定压倒一切

虽然我喜欢折腾新技术(家里树莓派跑着 etcd + Consul + Linkerd 的玩具集群),但生产环境?能不用就不用。

组件 选择理由
服务框架 原生 Go net/http + 自研中间件(轻量、可控)
服务发现 Consul(运维已熟悉,比 Eureka 更适合混合云)
配置中心 自研(基于 Git + Webhook,避免引入 Apollo/Nacos 学习成本)
分布式追踪 Jaeger(开源、集成简单)
数据库 MySQL 8.0 + TiDB(核心交易用 MySQL,日志类用 TiDB)

Go 在这里发挥了巨大优势:编译快、部署简单、内存占用低。我们甚至把原来 PHP 写的几个边缘服务也重构成了 Go,资源消耗直接砍半。

但最让我头疼的,其实是接口兼容性。老前端还在用 Vue 2,新项目要用 React,而 API 网关还没上线。于是我们搞了个“版本化接口策略”:

// v1/order/create -> 保持旧参数结构
// v2/order/create -> 新协议,带 trace_id、request_id

虽然丑,但有效。毕竟求职市场上,能保证系统稳定上线的人,比会写 fancy 架构的人更值钱(笑)。


踩过的坑:那些文档不会告诉你的事

1. 分布式事务?别碰!

我们曾试图用 Seata,结果发现性能开销太大,且 MySQL 的 XA 事务在高并发下容易死锁。最后回归Saga 模式:每个服务暴露“补偿接口”,配合消息队列做异步重试。比如“创建订单失败” → 发送 order.cancel 消息 → 库存服务回滚。

2. 日志分散如天女散花

拆完之后,查一个请求要登录五台机器看日志。赶紧上 ELK,但发现日志量暴增三倍(每个服务都打 trace)。后来强制要求:

  • 所有日志必须带 trace_id
  • 关键路径打 span_id
  • 非 debug 日志默认关闭

3. 数据库连接池爆炸

每个微服务都开独立连接池,实例一多,数据库 max_connections 直接打满。解决方案:

  • 按服务重要性分级(核心服务优先分配连接)
  • 引入连接池代理(类似 ProxySQL)
  • 最关键:限制每个服务的最大实例数(别让 K8s 无脑扩缩容)

效果如何?数字不会骗人

拆完三个月,系统稳定性肉眼可见提升:

指标 拆分前 拆分后
平均响应时间 850ms 210ms
P99 延迟 3200ms 680ms
数据库 CPU 使用率 85%~95% 40%~60%
线上事故/月 4.2 次 0.8 次

最爽的是,现在改订单模块再也不用担心影响用户登录了。上周产品经理临时要加“预售定金膨胀”,我只改了两个服务,两小时上线,连测试都没惊动——当然,这归功于完善的自动化测试覆盖率(这是另一个故事了)。


给想跳槽的同学一句真心话

最近有不少朋友问我:“现在学微服务对求职有帮助吗?”

我的答案是:会拆微服务不如会拆问题

招聘方不关心你用了 Spring Cloud 还是 Go-kit,他们关心你是否理解为什么拆拆完怎么活。我面试时最爱问:“如果让你把现在的系统拆成微服务,你会先动哪一块?为什么?”

建议大家:

  • 先啃透一本经典,比如《微服务架构设计模式》(比网上碎片文章靠谱多了)
  • 动手搭个最小可运行 demo(Docker Compose + 3 个 Go 服务足矣)
  • 重点理解 CAP、BASE、幂等性、熔断这些底层逻辑

毕竟,技术会过时,但系统思维不会


最后:DBA 的执念还在

虽然现在写 Go 比写 SQL 多,但我每天早上第一件事还是看数据库监控。上周还因为某个服务的慢查询半夜被 PagerDuty 叫醒——结果发现是新来的实习生写了 SELECT * 还没加索引。

唉,微服务拆得再漂亮,数据库还是那个数据库。只要数据在,我就睡不踏实。

不过话说回来,看到系统稳稳扛住流量高峰,用户不再投诉“下单失败”,那种成就感,比调优一条 SQL 快 100 倍还爽。

对了,下周技术分享会我打算讲“微服务下的数据库治理”,欢迎来听——带咖啡就行,PPT 里有我整理的避坑清单。

评论 0

最热最新
暂无评论
NullPointer青年Lv.1
0
影响力
0
文章
0
粉丝