news 2026/8/27 2:52:03

单体系统怎样分步拆成服务

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
单体系统怎样分步拆成服务

单体系统怎样分步拆成服务

💡 单体巨石架构的生产困境

在企业级应用演进的初期,单体架构(Monolith)凭其简单的部署和极高的开发效率支撑了业务的快速跑通。然而,当单体 Spring Boot 工程代码量突破 60 万行、数据库包含 300 多张表、研发团队扩充到上百人时,单体架构的噩梦便接踵而至。

上个月,某核心电商系统的日常发布演练再次引发事故。由于用户模块、订单模块和支付模块的代码深度交织在同一个 JVM 进程中,开发人员修改了一行用户积分计算逻辑,意外引发了订单事务回滚机制失效,导致生产环境产生了数百笔数据不一致的“幽灵订单”。

面对巨石系统,盲目推翻重来(Big Bang Rewrite)的失败率高达 90%。唯一的破局之道是采用绞杀者模式 (Strangler Fig Pattern):在维持老系统正常运行的同时,像绞杀植物生长一样,在边缘逐步构建新的微服务,将流量一点点剥离出来,直至旧系统被完全替换。


一、 绞杀者模式落地演进路线与数据防线

服务拆分不仅仅是代码文件的迁移,本质上是数据存储边界与事务控制权的重新划定

1. 拆分边界识别:DDD 限界上下文划定

拆分的第一步不是动代码,而是按领域驱动设计 (DDD) 寻找切入点。优先选择业务变更频繁、对 CPU/内存消耗有特殊需求、以及代码依赖耦合度较低的模块(如支付结算或订单履约模块)作为首个绞杀目标。

2. 流量透明切流与双向镜像比对 (Traffic Shadowing)

在网关层(Spring Cloud Gateway)基于请求 Header 或用户 ID 设置动态切流规则。在初期,将生产流量复制一份(Shadow Traffic)同时打入新微服务和旧单体模块,比对两者的输出结果与数据库落盘差异,确保逻辑 100% 兼容后再真正切流。

3. 数据一致性防线:Transactional Outbox 模式

拆分后,原本依靠单体数据库本地事务(@Transactional)保障的一致性被打破。避免使用两阶段提交(2PC/Seata AT)这种重型强一致性方案拖垮吞吐量。生产环境应采用基于本地消息表与 MQ 异步确认的Transactional Outbox 模式实现最终一致性。


二、 现场诊断与双写一致性比对工具链

在切流过程中,如何确保新微服务写入的数据与旧单体数据库完全一致?

1. 使用 Canal 进行 Binlog 数据镜像比对

通过 Canal 实时订阅新旧数据库的 Binlog,校验关键字段:

# 检查 Canal 订阅任务状态 > 说明:文中场景、阈值和数字用于说明排查或设计方法;上线前应结合本服务版本、配置和压测结果复核。 curl -X GET http://localhost:8089/api/v1/canal/destinations/order_diff/status # 使用 MySQL 命令行对比两边数据库同一订单 ID 的 Hash 校验码 > 说明:文中场景、阈值和数字用于说明排查或设计方法;上线前应结合本服务版本、配置和压测结果复核。 mysql -h prod-db-new.cluster.internal -u readonly -p -e \ "SELECT MD5(CONCAT(id, order_sn, amount, status)) FROM order_db.t_order WHERE id = 10086;" mysql -h prod-db-old.cluster.internal -u readonly -p -e \ "SELECT MD5(CONCAT(id, order_sn, amount, status)) FROM monolith_db.t_order_legacy WHERE id = 10086;"

三、 生产级 Java Transactional Outbox 代码实现

以下代码示范了如何在拆分出的新微服务中,基于 Spring Boot 3 + Spring Data JPA + Spring Event 实现生产级 Transactional Outbox 模式,确保业务数据落盘与事件消息发送的绝对一致。

package com.example.order.domain.service; import com.example.order.domain.entity.OrderEntity; import com.example.order.domain.entity.OutboxEventEntity; import com.example.order.domain.repository.OrderRepository; import com.example.order.domain.repository.OutboxEventRepository; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.Map; @Slf4j @Service @RequiredArgsConstructor public class OrderDecompositionService { private final OrderRepository orderRepository; private final OutboxEventRepository outboxEventRepository; private final ObjectMapper objectMapper; /** * 创建订单:本地数据库更新与 Outbox 消息写入在一个本地事务中完成 */ @Transactional public Long createOrderInNewMicroservice(Long userId, BigDecimal amount) throws Exception { // 1. 保存新微服务领域实体 OrderEntity order = new OrderEntity(); order.setUserId(userId); order.setAmount(amount); order.setStatus("CREATED"); order.setCreatedAt(LocalDateTime.now()); OrderEntity savedOrder = orderRepository.save(order); // 2. 构建事件 Payload Map<String, Object> eventPayload = Map.of( "orderId", savedOrder.getId(), "userId", savedOrder.getUserId(), "amount", savedOrder.getAmount(), "eventType", "ORDER_CREATED_EVENT" ); // 3. 写入同库的 Outbox 消息表 (与 OrderEntity 共享同一个 MySQL 本地事务) OutboxEventEntity outboxEvent = new OutboxEventEntity(); outboxEvent.setAggregateType("ORDER"); outboxEvent.setAggregateId(savedOrder.getId().toString()); outboxEvent.setType("ORDER_CREATED"); outboxEvent.setPayload(objectMapper.writeValueAsString(eventPayload)); outboxEvent.setStatus("PENDING"); outboxEvent.setCreatedAt(LocalDateTime.now()); outboxEventRepository.save(outboxEvent); log.info("新微服务成功创建订单并写入 Outbox 事务记录,OrderId: {}", savedOrder.getId()); return savedOrder.getId(); } }
package com.example.order.infrastructure.scheduler; import com.example.order.domain.entity.OutboxEventEntity; import com.example.order.domain.repository.OutboxEventRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.messaging.Message; import org.springframework.messaging.support.MessageBuilder; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Slf4j @Component @RequiredArgsConstructor public class OutboxEventPublisherScheduler { private final OutboxEventRepository outboxEventRepository; // 假设注入 RocketMQTemplate 或 RabbitTemplate // private final RocketMQTemplate rocketMQTemplate; /** * 轮询定时任务发送 Outbox 消息到 MQ (可替换为 Debezium CDC 零延迟监听) */ @Scheduled(fixedDelay = 1000) @Transactional public void publishPendingOutboxEvents() { List<OutboxEventEntity> pendingEvents = outboxEventRepository.findTop50ByStatusOrderByCreatedAtAsc("PENDING"); for (OutboxEventEntity event : pendingEvents) { try { // 发送消息到 MQ 支撑下游单体旧系统同步 log.info("正在投递 Outbox 事务消息到 MQ,EventId: {}", event.getId()); // rocketMQTemplate.convertAndSend("order-event-topic", event.getPayload()); // 标记为 PROCESSED 处理完成 event.setStatus("PROCESSED"); outboxEventRepository.save(event); } catch (Exception e) { log.error("投递 Outbox 事件失败,等待下一次重试,EventId: {}", event.getId(), e); } } } }

四、 单体拆分演进效果对比

通过绞杀者模式对 60 万行巨石系统进行了为期 3 个月的分步拆分,系统的各项工程指标获得了质的飞跃:

拆分评估指标维度拆分前 (单体巨石工程)拆分后 (微服务+Outbox 最终一致)改善与提升效果
单次全量编译打包耗时18 分钟45 秒 (独立微服务)构建效率提升 95.8%
生产环境发布频次两周 1 次 (需全员拉齐)每天 10+ 次 (独立发布)发布敏捷度拉满
故障隔离范围1 个 Bug 拖垮整个进程仅影响单个微服务 Pod故障半径缩小 90%
分布式事务一致性达标率65% (分布式下滥用 @Transactional)99.999% (Outbox 消息保证)避免数据丢失
数据库 CPU 平均利用率85% ~ 98% (大表 Join 锁死)20% ~ 30% (库表彻底解耦)数据库压力大幅收敛

巨石系统的解耦绝不是一次完成的冲动行为。采用绞杀者模式分步切流,在入口处做好流量镜像,在存储层基于 Transactional Outbox 筑牢数据最终一致性防线,才是大型系统架构演进最稳健的路径

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/27 2:52:01

分布式链路怎样避免重试风暴

分布式链路怎样避免重试风暴 &#x1f4a1; 生产环境级联雪崩现象 在复杂的微服务调用网格中&#xff0c;“重试”原本是应对 transient failure&#xff08;瞬时网络抖动&#xff09;最简单直接的容错手段。然而&#xff0c;如果缺乏全链路的全局视角与隔离防线&#xff0c;重…

作者头像 李华
网站建设 2026/8/27 2:50:13

AI写专著实用攻略:借助AI工具,轻松完成20万字专著撰写!

写专著最大的难点就在于要保证逻辑条理清晰&#xff0c;但这恰恰是写作时最容易出错的地方。专著需要围绕一个核心主题&#xff0c;进行全面且系统的论证&#xff0c;不仅要详细说明每个观点&#xff0c;还要应对不同理论中的分歧&#xff0c;同时保证整体框架没有漏洞。很多人…

作者头像 李华
网站建设 2026/8/27 2:50:11

NE555驱动单片机T0计数器实现硬件级边沿捕获

1. 项目概述&#xff1a;用NE555触发单片机T0计数器&#xff0c;实现精准边沿捕获的底层硬件协同设计你手上有一块老派但极其可靠的STC系列单片机开发板&#xff0c;想用最基础的模拟芯片NE555作为外部事件源&#xff0c;驱动单片机内部定时器/计数器T0工作在计数模式下&#x…

作者头像 李华
网站建设 2026/8/27 2:49:02

水面舰艇编队防空建模:信息化战争下的数字沙盘构建

1. 这不是一道“纯数学题”&#xff0c;而是一套战场级决策推演系统“第十二届‘中关村青联杯’全国研究生数学建模竞赛-A题&#xff1a;水面舰艇编队防空和信息化战争评估模型”——光看标题&#xff0c;很多人第一反应是“又一道高难度数学题”&#xff0c;甚至下意识翻出《运…

作者头像 李华
网站建设 2026/8/27 2:48:55

基于宝塔API的PHP自助建站系统开发实战

简介&#xff1a;在网站管理与服务器运维过程中&#xff0c;重复性建站操作往往耗费大量人力&#xff0c;尤其在虚拟主机销售或批量开测试环境时。自动化运维的关键在于将繁琐的点击流程转化为程序化调用&#xff0c;通过API接口实现站点、数据库、FTP等资源的快速分配。PHP凭借…

作者头像 李华