从单体到微服务,SpringBoot拆分实践中的经验记录 📅 2026/8/14 14:50:04 接手这个SpringBoot单体时代码行数已经逼近三十万部署一次需要十几分钟测试环境启动就要五分钟。老板拍板说必须拆微服务但真正让我心里一紧的是一次退货功能的改动要横跨订单、库存、支付、用户、优惠券五个模块。那一刻我意识到我们不是被技术逼到了墙角是被混乱的代码边界逼到了墙角。拆分前团队花了整整两周回答同一个问题到底为什么拆压测报告显示80%的请求卡在数据库索引上缓存命中率不到40%每次发布要拉上后端、前端、DBA一起熬夜改一行代码就要全量回归一个线程池满了整个系统跟着瘫痪。微服务不是银弹但单体也不是原罪。如果只是性能不够先优化缓存和索引只有当交付速度、故障隔离、团队自治三个维度同时亮起红灯才值得启动拆分。拆分的动机藏在边界里很多团队一上来就按技术分层拆controller变成网关service变成逻辑服务mapper变成数据服务。看起来服务变多了其实耦合一点没少。共享数据库是微服务最大的谎言只要多个服务连同一个库你拆的只是代码不是架构。我们用了事件风暴把订单、库存、支付、会员当成独立业务能力每个能力必须拥有自己的数据库边界。这个过程花了整整一周但事后证明这可能是拆分中最值的投资。边界不清晰的时候强行拆分只会制造分布式版的意大利面条。我们定了一条铁律两个服务之间只允许通过API或事件通信任何直接读取对方数据库的行为都算故障。刚开始执行时很痛苦连查询一个用户积分都要绕一圈接口但半年后这套逻辑撑住了复杂的促销活动。领域边界画清楚后面所有代码改动都变得安全了。第一刀切在最不痛的地方第一次拆分千万别选核心链路。我们最先拆的是短信通知服务它不碰核心事务调用关系简单挂了也不会让下单失败。这个选择带来的红利是团队快速跑通了注册中心、网关、配置中心、链路追踪整条流水线并且积累了信心。拆分的第一刀应该切在最不痛的地方而不是最有价值的地方。核心链路要留在团队对微服务有手感之后再动否则一旦出问题所有人都会怀疑拆分是否正确。拆完短信服务后我们又拆了积分服务这时才开始触及用户域。每次拆分都遵循同一个套路先抽接口再改调用方最后迁移数据整个过程保持单体仍可运行。直到订单服务动工前我们已经在生产环境跑通了五个小服务部署工具、监控告警、故障演练全部到位。一次只拆一个模块宁可慢也不要在生产环境做花式秀。那段日子推进得很慢但线上几乎没有因为拆分出现过一次重大事故。分布式事务先从强一致的幻觉中醒来单体里的Transactional太舒服了拆开之后第一个噩梦就是跨服务事务。我们最初试图用Seata做全局事务压测发现性能损耗和锁等待远超预期。后来改成事件驱动订单服务在本地事务里写订单表和消息表通过MQ通知库存服务消费端用唯一键做幂等。能异步就不要同步因为同步调用链越长稳定性越差。但这个转换不是免费的消息丢失、重复投递、死信队列、最终一致性核对每一项都要专门设计。后来我们又加了定时对账任务每天凌晨比对订单和库存的流水把不一致的数据捞出来补偿。这个对账比任何分布式事务框架都可靠。先保住最终一致性再谈强一致。如果某个业务场景真的要求跨服务强一致我会重新问自己这个模块是不是根本不该拆出来数据库事务的强一致是单体架构的独特优势放弃它之前要有充分理由。服务治理的暗坑藏在细节里Spring Cloud Alibaba 的版本对应关系能让人折腾整整一周。Nacos做注册中心没问题但一定要把命名空间、分组、环境隔离设计好否则测试环境的消费者会路由到生产环境的节点。OpenFeign要设置合理的超时和重试但重试是一把双刃剑用户下单超时Feign自动重试两次库存已经扣减订单却显示失败。超时重试比链路故障更可怕它会造成重复扣款、重复发券。我们的对策是所有写接口的请求头里带上全局唯一ID下游拿着ID做去重并且把Feign的重试策略改成只重试幂等请求非幂等请求一律不重试。熔断降级也不是配置个Sentinel就完事。微服务的复杂度会在你没注意的地方冒出来比如A服务熔断后返回一个固定的兜底数据但B服务拿这个兜底数据接着算最后得到完全错误的结果。我们后来针对每个下游接口单独编写降级函数宁可返回空集合也不返回可能被误解的假值。降级方案必须跟着业务语义走不能搞一刀切。K8s部署探针不是写了就生效服务上了Kubernetes后新的问题接踵而至。启动时Pod还没注册到Nacos网关就已经把流量转发过去导致一批连接拒绝滚动更新时旧Pod还在处理请求新Pod已经开始接流量日志里到处是Connection refused。我们统一封装了启动探针、存活探针和就绪探针并且在应用里监听SIGTERM信号收到后先通知注册中心下线再休眠30秒等待存量请求处理完毕。微服务拆得好不好看凌晨三点被电话叫醒的次数而不是看有多少个Pod在跑。这些部署细节在单体时代根本不需要关心但微服务会逼着你认真对待每一个生命周期。我们还补了链路追踪用Micrometer Tracing Zipkin给每个请求生成唯一traceId日志聚合到ELK排查跨服务问题时再也不用在三套终端里来回切换。告警规则也从“CPU超80%”改成“支付成功率低于99.9%”因为业务指标比资源指标更能反映真实故障。数据库拆分比代码拆分更伤筋动骨微服务拆分最容易低估的是数据层。我们拆订单服务时发现原本几十个表都混在一个库里订单表、库存表、流水表之间还有大量外键关联。共享数据库是微服务最大的谎言——但拆数据库不是把表移过去那么简单。我们采用双写迁移法先让新服务接收增量数据用Canal同步旧库变更每天比对数据一致性最后通过灰度路由逐步把读流量切过去。这个过程中最麻烦的是报表查询一个统计SQL要跨六个库。我们最终建了独立的数据仓库用Flink同步各服务的binlog到数仓让报表服务直接查数仓而不是去查业务库。教训是如果你拆完服务之后还要跨库join一张表那说明拆分没完成。每个服务应该对自己拥有的数据负全责需要别人的数据时用API或事件来获取。团队协作模式要跟着拆分一起变微服务拆分不光是技术问题也是组织问题。我们原来十个人维护一个仓库拆服务时顺便把仓库拆成了多个但很快发现跨服务调试变得更难。后来采用了折中方案每个服务独立仓库但用CI Pipeline统一构建并且约定在本地开发时用Docker Compose把依赖的服务都拉起来。没有架构演进能力的团队拆成微服务只是把单体问题复制了十份。如果团队不适应变更再好的架构也会变成灾难。每个服务目录下都要有一份README写清楚服务职责、依赖关系、启动方式、常见故障处理。这听起来琐碎但在人员流动时能节省大量时间。我们还要求每个服务负责人每月轮换一次确保知识不沉淀在某个人的脑子里。服务边界越清晰团队协作越轻松如果某个服务频繁需要别的服务的人来救火那就是边界又画错了。拆完不是结束而是演进的开端拆分半年后我们做了一次复盘部署频率从每周一次变成每天五次新功能平均上线时间从三天缩短到四小时但事故数并没有下降定位问题的时间反而拉长了。后来通过链路追踪和告警治理才把MTTR压下来。微服务拆得好不好看凌晨三点被电话叫醒的次数——这个指标永远比微服务数量诚实。如果你问我下一次要不要继续拆我的答案是先看数据再看团队感受。拆分是为了让团队长期睡好觉不是为了让简历好看。真正的架构师不追求潮流的架构只追求能让团队长期睡得着觉的系统。在微服务这条路上停下来的勇气和拆分下去的勇气一样重要。单体的简单是一种美德微服务的复杂必须用收益来对价否则你其实是在用更昂贵的方式维护同一个混乱。