CHARLIE SAYS

查理如是说
DATE 2026-08-24
THEME
SERIES / JHIPSTER / P-240 · JHipster 项目开发

JHipster 开发 25:微服务拆分实战

第 5 篇讲过”单体还是微服务怎么选”,当时我们的答案是单体起步。三年后,主系统长到了 20 多人共同开发、每月 40+ 次发布,单体开始顶不住——这篇记录我们用一年时间把它拆成微服务的完整过程:为什么拆、怎么拆、代价付在哪、哪些反模式差点踩进去。

拆分的触发信号

拆分不是架构审美问题,是具体的工程约束。我们当时的四个信号:

信号我们的实测阈值参考
部署频率每月 40+ 次,全量回归 2 小时发布排队开始常态化
团队规模22 人改同一个仓库经验值:10-15 人/单体仓库是舒适上限
故障隔离报表慢查询拖垮在线下单单点故障影响面 > 一个业务域
构建时长CI 全量 50 分钟修复一个字段的反馈周期不可接受

只中一条不必拆,中了三条还硬扛,成本会以故障和人员流失的形式追讨。

JHipster 微服务拓扑回顾

第 5 篇的拓扑图,拆分前再复习一遍各角色职责:

flowchart LR
  U["浏览器/App"] --> GW["JHipster Gateway<br/>路由聚合 / OAuth2-JWT 下发"]
  GW -->|服务发现| REG["JHipster Registry<br/>Eureka + Config Server"]
  subgraph SVCS["业务服务(独立库)"]
    ORD["order-service"]
    INV["inventory-service"]
    USR["user-service"]
  end
  GW --> ORD & INV & USR
  ORD -.->|同步 API| INV
  ORD -.->|事件| K["Kafka"]

要点:Gateway 只做路由、限流与鉴权传递,不写业务;Registry 同时承担服务发现与集中配置(第 21 篇的 Config Server 落点);服务间同步走 REST/Feign,异步走 Kafka(第 12 篇)。

拆分四步法

总原则一句话:先把单体内部拆干净,再拆进程边界。顺序反了就是分布式单体。

第一步:单体内模块化

不引入任何分布式设施,只做包边界硬化(正好用上第 23 篇的模块化形态二):

# 拆分前的单体:包按技术分层
com/mycompany/myapp/{domain,service,web/rest}   # 订单用户库存混在一起

# 模块化后:Maven module 按业务域
myapp/
├── myapp-order/       # 订单域:自己的 domain/service/api(接口)
├── myapp-inventory/
├── myapp-user/
└── myapp-app/         # 组装壳:依赖三个模块,跑成单体

这一步的验收标准:模块间依赖只通过 api 接口,用 ArchUnit 固化成测试。数据库同步处理——三个域仍共库,但表按前缀划分(ord_/inv_/usr_),跨域直接查表被 CI 禁止。我们在单体内跑稳了三个月,期间修掉的隐式耦合(跨包 private 方法调用、JOIN 跨域表)比后面所有步骤加起来都多,也都可以在单进程里低成本调试。

第二步:按业务域抽服务

从最独立、发布最频繁的域开始。我们是订单域先行。JHipster 这一步的体验很顺:新起微服务工程,JDL 里重建实体(或从 .jhipster/*.json 迁移),网关重新注册:

mkdir order-service && cd order-service
jhipster --application-type microservice --jwt --database-type postgresql
# 在网关侧导入微服务实体路由
jhipster entity Order --microservice-name order-service

迁移代码就是把 myapp-order module 平移进新服务,删掉对单体的 api 依赖。抽出一个上线一个,单体里对应模块冻结只读。六个月内三个服务全部出舱:

服务实体数上线耗时
order-service14独立 PostgreSQL6 周(含数据迁移演练)
inventory-service9独立 PostgreSQL3 周
user-service6独立库2 周

第三步:共享数据拆分(最难的一段)

模块化时按前缀分的表,现在按域切库。三条铁律:

没有共享库,只有共享 API。 订单服务要库存余量,走 inventory-service 的 REST 接口,不连它的库。JHipster 生成代码里加 Feign client:

@FeignClient(name = "inventory-service")
public interface InventoryClient {
    @GetMapping("/api/inventory/available")
    List<AvailabilityDTO> available(@RequestParam List<String> skus);
}

读走 API,写用事件。 跨域的数据复制用事件同步:库存变更发 InventoryChangedEvent 到 Kafka,订单服务消费后更新本地冗余表。为避免”发消息和落库”的双写不一致,用了outbox 模式——业务事务里先写 outbox 表,relay 进程再搬运到 Kafka:

-- 与业务同事务
INSERT INTO ord_order (...) VALUES (...);
INSERT INTO ord_outbox (topic, payload) VALUES ('order-events', '{"orderId":...}');
-- 独立 relay 进程扫 outbox 发 Kafka,发成功标记,失败重试

跨库事务不做 2PC,用 Saga 补偿。 下单扣库存:订单创建(pending)→ 扣库存 → 确认;扣失败则订单走补偿取消。 saga 状态机就一张 ord_saga_state 表加一个驱动器类,没有引入重型框架。

第四步:网关路由与鉴权下沉

拆分后认证统一收敛:JWT 由网关下发(或对接公司 OAuth2/OIDC),各微服务只做令牌校验与角色判断,JHipster 生成的 SecurityConfiguration(9.x 下含 EnableWebSocketSecurity 的场景也在网关终结握手)天然支持这个模式。服务间调用透传 Authorization header。网关路由即配置:

# gateway 的 application.yml
spring:
  cloud:
    gateway:
      routes:
        - id: order
          uri: lb://order-service
          predicates: [ Path=/services/order/api/** ]
        - id: inventory
          uri: lb://inventory-service
          predicates: [ Path=/services/inventory/api/** ]

拆分的代价表

拆完不是终点,是新的成本结构开始:

维度单体时拆分后我们的对冲
分布式事务单库 ACIDSaga/最终一致,心智负担高outbox + 补偿任务 + 对账日报
联调本地起一个进程起 5 个进程 + Registrydocker compose 一键拓扑 + 契约测试
排障一个日志文件跨服务链路集中日志 + traceId 贯穿(第 20 篇)
运维1 个部署单元4 个 + Registry + GatewayK8s + GitOps(第 19 篇)
数据一致性立即可见秒级延迟可接受化业务上明确”读己之写”的例外通道
发布全量独立发布,频率 4 倍收益本体

诚实地说:总成本没有下降,只是成本结构从”发布摩擦”换成了”复杂度税”。划算与否取决于发布频率收益是否大于税。我们是 40 次/月变 160 次/月,换来了。

反模式警示:分布式单体

最危险的结果不是拆失败,而是拆出分布式单体:服务边界按技术切而不是按业务切、服务间同步调用链长到一环挂全环、共享库导致谁也不敢先升级。症状自检:

  • 一次需求平均要改 3 个以上服务才能上线——边界切错了;
  • 发布仍要”按顺序停全部服务”——你在用微服务的成本买单体的行为;
  • 数据库视图/共享表跨服务直接 JOIN——数据所有权没拆干净。

这类问题的完整论述推荐站内文章《走出微服务误区》。我们拆分第一版就踩了”按技术分层切服务”的坑(一个 service 层服务、一个 repository 服务),三个月后推倒按业务域重切——这也是第一步必须先做单体内模块化的原因:边界错了,在单体内重划只要一周。

小结

  • 拆分看信号不看审美:部署频率、团队规模、故障隔离、构建时长,三条以上再动手。
  • 四步走:单体内模块化(验收是 ArchUnit 硬边界)→ 按域抽服务(独立独立再独立)→ 数据拆分(API + outbox 事件 + Saga)→ 网关路由与鉴权下沉。
  • JHipster 的微服务拓扑(Gateway/Registry/服务)让第 0 天就有正规拓扑,拆分工作聚焦在业务边界本身。
  • 最大的反模式是分布式单体,先模块化后拆分是最好的疫苗。

系列导航

← 设计模式 025:模板方法(Template Method) 目录 算法 026:图:最短路径(Dijkstra & Floyd) →
← 返回文章列表