微服务系列(一) 我们的WMS单体应用终于扛不住了

张开发
2026/4/16 0:50:59 15 分钟阅读

分享文章

微服务系列(一) 我们的WMS单体应用终于扛不住了
我们的 WMS 单体应用终于扛不住了副标题从一个 200 万行代码的 Spring Boot 项目说起一、那个让人崩溃的周五你有没有经历过这种周五去年双十一前夜我们团队还在加班。产品经理急匆匆地跑来“出库规则要改客户临时要求把优先发货仓库从 A 仓换成 B 仓”我心想这不就是改个配置嘛分分钟的事。结果一翻代码傻眼了——出库规则散落在七八个类里有的写在Service里有的埋在Mapper的 SQL 里还有一段祖传逻辑躲在某个叫OutStockUtilV2的工具类中。改完一处IDE 的引用分析显示还有 23 处调用。更可怕的是其中 3 处是反射调用的IDE 根本搜不到。“全量回归测一下吧。” 测试同学淡淡地说。3 天。就为了一个仓库优先级的调整我们测了整整 3 天。期间还顺手修了两个因为改一行崩三处引发的 Bug。那天晚上我坐在工位上看着 Jenkins 上那个跑了 2 小时 17 分钟的发布流水线陷入了沉思这系统是不是该动大手术了二、我们的 WMS 长什么样先给不熟悉 WMS 的朋友科普一下WMS 就是仓库管理系统Warehouse Management System。咱们这套系统服务了公司 20 多个仓库日均 10 万单大促峰值能冲到 50 万单。说白了它的核心工作就这几件事入库货来了验收入库上架出库订单来了拣货、打包、发运库存实时算库存别让超卖把老板坑了库内作业盘点、移库、补货这些脏活累活基础资料商品、仓位、客户信息报表给老板看的各种数据大屏那它的技术架构长啥样呢我用文字给你画一张简图┌─────────────────────────────────────┐ │ 前端页面JSP jQuery │ │ 前后端还没分离呢 │ └─────────────┬───────────────────────┘ │ ┌─────────────▼───────────────────────┐ │ 单体 Spring Boot 应用 │ │ 200 万行代码一个 fat jar │ │ │ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │ 入库模块 │ │ 出库模块 │ │ 库存模块 │ │ │ └────────┘ └────────┘ └────────┘ │ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │ 库内作业 │ │ 基础资料 │ │ 报表模块 │ │ │ └────────┘ └────────┘ └────────┘ │ └─────────────┬───────────────────────┘ │ ┌─────────────▼───────────────────────┐ │ 单库 MySQL主从复制 │ │ CPU 常年 80%磁盘快满了 │ └─────────────────────────────────────┘对你没看错。200 万行代码打成一个 jar 包30 多个前端 JSP 页面直接塞在src/main/webapp里数据库就一台主库扛所有读写从库只用来跑报表。这套架构在创业早期是功臣——开发快、部署简单、出了问题翻日志就行。但五年过去它已经从功臣变成了功臣沉重地。三、为什么现在必须拆说实话拆微服务这件事我们管理层吵了快一年。有人觉得能跑就别动有人觉得再不动就晚了。最后促成决策的其实是三个维度的压力一起爆发了。3.1 业务维度新渠道需求互相打架公司这两年业务扩张得厉害原来只服务自营电商现在又接了跨境电商、B2B 批发、线下门店。每个渠道的出库逻辑都不一样自营电商优先发离用户近的仓求快跨境电商要报关、要合单流程复杂B2B一单单量巨大要按批次先进先出门店得支持到店自提和门店调拨这些需求塞进同一个代码库结果就是if-else满天飞。最夸张的一个方法我数过127 行代码里面嵌了 9 层 if-else。每次加新渠道都得小心翼翼地问“我这段逻辑会不会把老渠道搞坏”3.2 技术维度数据库真的加不动索引了我们的核心库存表数据量已经飙到 3 亿多条。为了支撑查询这张表上的索引多达 17 个。DBA 老哥有一次在群里发了一张截图主库 CPU 飙到 97%慢查询日志一分钟刷了 2000 多条。他我说“兄弟这表不能再加索引了再加快写操作要崩。”我回他“那怎么办”他回了我一个表情包一只猫在键盘上躺平。其实问题很清晰——单库单表扛不住了。但单体应用里所有模块共享同一个数据库连接池。报表模块跑一个复杂统计查询能把连接池占满导致出库接口超时。这就是典型的一颗老鼠屎坏了一锅粥。3.3 团队维度30 个人抢一个代码库我们后端团队从 5 个人扩张到 30 个人代码库还是那一个。每天早上第一件事是什么不是写代码是解决合并冲突。你改StockService.java他也改StockService.javaGit 冲突解决到怀疑人生。更魔幻的是发布。原来 10 分钟就能发完后来变成了 2 小时——因为要等所有模块的测试都通过要协调各个团队的发布窗口。有一次报表团队的功能已经测好了但因为入库团队发现了一个 Bug 要修整个发布被推迟到第二天。30 个人被一个 jar 包绑在了一起。这效率想想都心酸。四、微服务是不是银弹聊到这估计有读者要说了“你们这不就是要上微服务嘛直接干啊”别急。作为一个踩过坑的老司机我得诚实地说一句微服务不是银弹它甚至可能是新的坑。拆完之后复杂度一定会上升。原来一个方法调用就能搞定的事现在可能要跨服务调 HTTP 或 RPC原来本地事务就能保证的一致性现在得考虑分布式事务原来只有一个日志文件要翻现在得在十几个服务的日志里排查问题。所以咱们不能为了拆而拆。我画了个简单的判断标准供你参考维度我们的现状是否适合拆团队规模30 后端分 5 个业务小组✅ 适合按团队边界拆分业务复杂度多渠道、多仓库、规则差异大✅ 适合按业务领域拆分运维能力有 3 人 DevOps 小组玩过 K8s⚠️ 勉强需要补基础设施系统稳定性要求电商系统 downtime 成本极高⚠️ 拆分过程必须平滑过渡结论是什么可以拆但要拆得慢、拆得稳不能一刀切。我们最终决定走绞杀者模式——新功能用微服务写老功能逐步迁移而不是直接把 200 万行代码一把推翻重写。这个决策后来证明救了我们好几次。五、本系列要讲什么既然决定拆了那怎么拆拆成什么样拆的过程中会遇到哪些坑这些问题我会用接下来 8 篇文章一步一步跟你分享我们的真实经历。不是教科书式的理论而是有血有肉的实战记录。先给你剧透一下后续内容第 2 篇DDD 领域拆分实战—— 我们是怎么用领域驱动设计把 6 大模块拆成 12 个服务的第 3 篇数据库拆分与分库分表—— 3 亿条库存表怎么平滑迁移不停机、不丢数据第 4 篇服务间通信选型—— 同步 or 异步HTTP or gRPC消息队列怎么兜底第 5 篇库存服务设计—— 超卖问题怎么解预占、冻结、释放的完整状态机第 6 篇Saga 分布式事务落地—— 出库失败怎么回滚我们用 Saga 模式踩过的坑第 7 篇基础设施搭建—— K8s、Nacos、Gateway、CI/CD 流水线的搭建实录第 8 篇监控与排障体系—— 链路追踪、日志聚合、告警规则出问题 5 分钟定位第 9 篇一年后的复盘—— 哪些决策做对了哪些坑其实可以避开六、写在最后写这篇文章的时候我翻了一下去年的项目文档看到那个双十一前夜的发布记录2 小时 17 分钟全量回归 3 天合并冲突 47 处。现在呢同样的改动15 分钟就能上线回归测试只覆盖对应服务合并冲突基本没有了。当然这一路上我们也付出了代价基础设施投入增加了团队学习成本提高了第一次做分布式事务的时候差点把测试环境搞崩。但回头看值。如果你也正在经历类似的困境——单体应用越来越重改不动、发不动、扛不住——希望这个系列能给你一些参考。不敢说我们的方案是最优的但至少是真实的、踩过坑的。你们公司现在是什么架构有没有遇到过改一行怕崩三处的恐惧欢迎在评论区聊聊咱们一起交流本文是 WMS 微服务改造系列第 1 篇下篇预告《DDD 领域拆分实战——6 大模块怎么拆成 12 个服务》

更多文章