跳转到内容

从 0 到 1:初创公司单体架构向微服务演进的时机

架构选型的结论是反直觉的:团队小、业务早期时,单体架构(DHH 称之为 Majestic Monolith)几乎总是更好的起点。微服务解决的主要不是性能问题,而是团队规模上来之后的协作问题——过早拆分,付出的分布式事务、RPC 调试、运维复杂度成本会超过收益。

判断该不该拆,看三个信号(第 2 节):代码合并冲突成灾、模块间资源消耗特性严重不同、发布爆炸半径过大。三个都不满足时,扩机器比拆服务有效得多。

1. 为什么项目早期通常不该用微服务?

Section titled “1. 为什么项目早期通常不该用微服务?”

微服务的本质,不是为了解决性能问题,而是为了解决“人的问题”与“团队协同问题”。

当你的项目只有不到 10 个开发人员时,单体架构(所有的代码在一个工程里,连着同一个数据库)拥有无与伦比的优势:

  • 开发极快:没有 RPC 接口定义,没有网络序列化开销,直接 new Service().doSomething()。
  • 天然的强一致性:一个 @Transactional 搞定所有跨表逻辑,不用去碰那些让人掉头发的 Saga 或是 TCC 分布式事务。
  • 极低的运维成本:打成一个 Jar 包,扔到单台服务器或者简单的 Docker 里就能跑。

如果性能遇到瓶颈,在初期最简单的做法是:垂直扩展(买更好的 CPU 和更大的内存),或者水平扩展(多部署几个相同的单体实例,前面挂个 Nginx 做负载均衡)。

2. 触发微服务拆分的三个关键信号

Section titled “2. 触发微服务拆分的三个关键信号”

只有当你的单体应用遭遇了以下三种无法通过“加机器”来解决的瓶颈时,才是你拔出微服务这把手术刀的时候。

信号一:代码合并的灾难(人月神话的破灭)

Section titled “信号一:代码合并的灾难(人月神话的破灭)”

当研发团队扩张到 20 人甚至 50 人以上,所有人都在同一个 Git 仓库里疯狂提交代码。 每天早上来公司的第一件事就是解代码冲突。A 组改了一个公共工具类,直接导致 B 组和 C 组的业务逻辑在生产环境集体崩溃。 此时,你需要用微服务的物理边界,来强行隔离不同团队的职责(康威定律)。

在一个单体应用中,可能既有“订单服务”(I/O 密集型,需要大量内存缓存),又有“图片压缩与报表导出服务”(CPU 密集型,需要大量计算算力)。 当遇到大促时,图片服务把 CPU 榨干了,导致同在一个 JVM 里的订单服务线程被饿死。你发现无论怎么加机器,总有一部分资源是严重浪费的。 此时,你需要将不同资源消耗特性的模块拆分,独立进行弹性扩缩容。

哪怕只是修改了首页上的一个文案错别字,你也需要重新编译整个几百兆的单体应用,耗时 10 分钟重启整个进程,并且承担可能因为其他模块隐患导致整个系统无法启动的风险。 此时,微服务能将每次发版的“爆炸半径”控制在单个领域内。

3. 架构拆分的“第一刀”怎么切?

Section titled “3. 架构拆分的“第一刀”怎么切?”

决定要拆了,千万不要按技术层拆(比如把 Controller 拆个服务,Service 拆个服务)。 必须按业务领域(Business Capability)来拆。

  1. 先剥离边缘、无状态的计算服务:比如把发邮件、发短信、图片处理这种最容易剥离的独立成微服务。
  2. 抽取公共底座(核心域):将最核心的用户系统(User)独立出来,因为所有模块都要查用户。此时旧单体通过 RPC 调用独立出来的用户微服务。
  3. 最痛苦的战役:拆分数据库:微服务拆分的核心不仅是代码,而是数据。严禁两个微服务直连同一个物理数据库表!如果有跨库的连表查询(JOIN),必须通过应用层的 API 聚合,或者将数据同步到 Elasticsearch 中建立宽表供查询。

架构演进是在为业务增长付费:先忍受单体的臃肿,直到它确实成为效率瓶颈,再按业务领域把微服务切分出来。拆分的依据是第 2 节的三个信号,而不是行业里的流行度。