48个微服务:为什么要拆这么细?

当我们告诉新同事"我们的系统有40多个微服务"时,他们的表情通常是这样的:😳

"拆这么细?有必要吗?"

今天就来聊聊,为什么我们走上了这条路,以及这一路上的血泪教训。


一切从"大泥球"说起

还记得单体应用的日子吗?一个代码仓库,几百个模块,部署一次半小时。

修改一个功能,可能影响十个模块;发布一个版本,需要协调三个团队。

[配图建议:一个巨大的、缠绕的毛线球,象征单体架构的复杂性]

单体架构的问题,本质上是耦合度的问题:

当业务简单时,单体架构没问题。

但当业务复杂到一定程度,团队规模超过一定阈值,单体就会变成"大泥球"——没人能完全理解它,没人敢轻易改动它。

一个真实的故事

2019年,我们的系统还是一个单体应用。有一次,一个新同事改了一行代码,影响了用户登录模块。测试环境没问题,上线后才发现:这行代码导致VIP用户的折扣计算出错。

结果呢?回滚、道歉、赔偿优惠券,折腾了一整天。

这就是"大泥球"的典型症状——模块之间的边界模糊,牵一发而动全身。


微服务:拆了真的好吗?

微服务的核心思想很简单:把大系统拆成小服务,每个服务专注一件事。

听起来很美好,但收益到底在哪?

🎯 收益一:独立部署

服务A的更新,不影响服务B的运行。

发布频率可以从"一个月一次"提升到"一天多次"。

团队可以自主决定发布时机,不再需要协调所有相关方。

🎯 收益二:技术选型自由

服务A用Java,服务B用Go,服务C用Node.js。

每个团队可以选择最适合自己场景的技术栈。

不用再为"统一技术栈"而妥协。

🎯 收益三:独立扩展

只有用户服务负载高?那就只扩容用户服务。

不用为整个系统买单,资源利用率更高。

🎯 收益四:故障隔离

服务A挂了,服务B还能正常运行。

问题不会像病毒一样蔓延到整个系统。

[配图建议:一边是倒下的多米诺骨牌(单体),一边是独立的积木块(微服务),其中一个倒了不影响其他]


微服务拆分的设计原则

拆分不是乱砍一气,而是要遵循一定的设计原则。这些原则决定了你拆出来的服务是"优雅的微服务"还是"混乱的分布式单体"。

原则一:单一职责原则(SRP)

这听起来简单,但实践中很容易跑偏。

每个服务都有独立的团队、独立的数据库、独立的发布周期。

原则二:高内聚低耦合

原则三:围绕业务能力拆分

这是领域驱动设计(DDD)的核心思想:服务的边界要和业务的边界一致。

  1. 识别业务领域:和业务方聊,理解他们的语言。他们说的"订单"、"库存"、"用户"就是天然的边界。
  1. 定义限界上下文(Bounded Context):同一个词在不同上下文中可能有不同含义。比如"用户"在营销上下文中是"潜在客户",在交易上下文中是"买家"。
  1. 一个限界上下文 = 一个服务:不要强行合并不同的上下文,也不要把同一个上下文拆到不同服务。

如果强行用同一个"角色服务",就会导致游戏逻辑影响充值逻辑,反之亦然。所以它们是两个独立的服务。

原则四:数据所有权清晰

这条原则非常重要,但经常被忽视。

原则五:渐进式拆分

微服务拆分是一个渐进的过程,不是一次性的项目。

  1. 先拆边界最清晰的:比如支付、短信、推送,这些和其他业务耦合最少。
  2. 再拆变化最频繁的:经常改动的模块,拆出来后可以独立发布。
  3. 最后拆最复杂的:复杂的模块拆分风险大,有了前面的经验再动手。

服务边界划分方法

知道了原则,具体怎么划分边界呢?这里介绍几种实用的方法。

方法一:事件风暴(Event Storming)

这是DDD社区最流行的方法,非常适合复杂业务领域的边界划分。

  1. 准备便利贴:不同颜色代表不同元素:
- 橙色 = 领域事件(比如"订单已创建"、"支付已完成")

- 蓝色 = 命令(比如"创建订单"、"处理支付") - 黄色 = 聚合/实体(比如"订单"、"用户") - 绿色 = 策略/规则(比如"VIP用户享受9折")

  1. 组织工作坊:把业务方、产品、开发都叫来,一起贴便利贴。
  1. 按时间线排列事件:从左到右,按照业务流程的时间顺序排列。
  1. 识别边界:找到事件之间的"边界",通常是一组相关的事件和命令。

每个边界后来都成了一个独立的服务。

方法二:CRUD矩阵法

这是一种更简单直接的方法,适合快速识别数据边界。

  1. 列出所有数据实体(用户、订单、商品、库存……)
  2. 列出所有功能模块
  3. 画出矩阵,标记每个模块对每个实体的操作(C=创建,R=读取,U=更新,D=删除)
  1. 分析矩阵:
- 如果一个模块只读某个实体,不写,那这个实体不应该属于这个模块

- 如果多个模块都写同一个实体,说明可能有边界问题

方法三:依赖分析

通过分析代码依赖,发现潜在的边界问题。

  1. 使用工具(如Dependency-Check、ArchUnit)分析代码依赖
  2. 画出依赖图
  3. 识别问题:
- 循环依赖:A依赖B,B依赖A

- 过度依赖:一个模块被太多模块依赖 - 跨层依赖:绕过中间层直接访问底层

方法四:团队对齐

有时候边界划分不是技术问题,而是组织问题。

  1. 让团队自己决定边界:他们最了解自己的业务
  2. 一个团队负责的服务数量要合理:太少会无聊,太多会失控
  3. 服务边界要和团队边界对齐:跨团队的服务是灾难

拆多细合适?这是个玄学问题

拆分不是越细越好。

⚠️ 过细的代价

想象一下,一个简单的"用户下单"流程,需要调用:

如果每个服务都拆得更细呢?

比如"价格服务"再拆成"基础定价"、"会员折扣"、"活动优惠"、"优惠券计算"……

一个流程调用十几个服务,任何一个环节出问题,整个流程就挂了。

🎯 如何找到平衡点?

几个经验法则:

不要按"技术功能"拆(比如"数据库服务"、"缓存服务"),要按"业务能力"拆(比如"订单管理"、"库存管理")。

如果一个服务会被其他服务频繁调用,把它拆得太细会增加网络开销。

如果两个功能总是需要同时访问同一份数据,强行拆开会带来分布式事务的噩梦。

一开始不要拆太细,等问题暴露了再细化。

一个服务的复杂度,应该和一个5-8人的团队能掌控的范围匹配。

如果一个服务需要20个人维护,说明太大了;如果一个服务只需要1个人维护,说明太小了。

服务粒度的量化指标

除了经验法则,我们还有一些量化指标来判断服务粒度是否合理:

指标 太粗 合适 太细
代码行数 >10万行 1-5万行 <5000行
数据库表数 >50张 10-20张 <3张
API数量 >100个 20-50个 <5个
团队人数 >15人 5-8人 1-2人
发布频率 <1次/月 1-5次/周 >10次/天
依赖服务数 >20个 5-10个 0-2个

注意:这些只是参考值,具体情况要具体分析。


我们的实践:按领域划分的40+服务

回到开头的问题:为什么是40多个?

因为我们是按领域划分的。

🗂️ 领域划分

我们的系统主要分为三大块:

[配图建议:一个三栏布局,分别展示三大块服务,每块标注服务数量]

🎨 服务分层

每个领域内部,服务还分了三层:

这种分层让每个服务的职责更清晰,也方便了横向扩展。

服务拆分时间线


微服务治理:拆完不是结束,是开始

很多团队以为服务拆完就大功告成了,其实拆分只是第一步,真正的挑战在于治理

微服务治理包含很多方面,这里重点讲几个最重要的。

1. 服务注册与发现

我们使用腾讯云 TKE(Tencent Kubernetes Engine),K8s 自带的服务注册和发现功能完全够用。

  1. 服务启动时,K8s 自动将其注册到 API Server
  2. Service 资源创建 DNS 记录,其他服务通过服务名访问
  3. K8s 通过 kube-proxy 做负载均衡,将请求分发到健康的 Pod
  4. Pod 副本增减时,服务发现自动更新

我们的业务复杂度还不够高,K8s 原生能力已经满足需求。多引入一个组件,就多一份运维成本。

2. 配置管理

我们没有使用开源方案(如 Apollo/Nacos),而是基于业务需求自研了配置中心,原因是:

全局配置(所有服务共享)
  └── 环境配置(dev/test/prod)
      └── 服务配置(服务特有的配置)
          └── 实例配置(特定实例的配置)

3. API网关

我们选择了自研而不是用 Spring Cloud Gateway,原因是:

我们在网关层实现了多维度限流:

限流维度 策略 目的
IP 令牌桶算法 防止单个IP恶意请求
用户 滑动窗口 保护用户账户安全
API 固定窗口 保护后端服务
全局 漏桶算法 保护整个系统

4. 服务间通信

我们的业务复杂度还不够高,RocketMQ/Kafka 这样的重量级消息队列有点杀鸡用牛刀。Redis 队列已经能满足需求。

场景 实现方式 示例
业务解耦 Redis LIST 订单创建后发送通知
削峰填谷 Redis Stream 大促时的订单处理
事件通知 Redis Pub/Sub 用户注册后触发营销流程

5. 服务容错

目前的服务规模和业务复杂度,还不需要这么复杂的容错机制。网关的限流已经能应对大部分场景。

等业务继续增长,我们会逐步引入服务网格(Istio)来实现更精细的流量控制。

6. 链路追踪

Jaeger 是 Uber 开源的分布式追踪系统,CNCF 孵化项目。

  1. 每个请求生成一个唯一的 TraceID
  2. 每个服务调用生成一个 Span
  3. 所有 Span 都关联到同一个 TraceID
  4. 通过 TraceID 可以串联整个调用链

7. 监控告警

类型 指标 告警阈值
基础指标 CPU使用率 >80%
基础指标 内存使用率 >85%
应用指标 QPS 突然下降50%
应用指标 响应时间 P99 > 1s
应用指标 错误率 >1%
业务指标 订单量 环比下降30%
- 通知方式:飞书机器人 + 短信 + 电话

- 响应要求:5分钟内响应

- 通知方式:飞书机器人 + 短信

- 响应要求:30分钟内响应

- 通知方式:飞书机器人

- 响应要求:2小时内响应

- 通知方式:飞书机器人

- 响应要求:工作时间处理

8. 日志管理

各服务 → Filebeat(采集) → 
Logstash(处理) → 
Elasticsearch(存储) → 
Kibana(展示)
# 查询某个用户的所有操作日志
userId: "12345"

# 查询某个TraceID的所有日志
traceId: "abc-def-ghi"

# 查询最近1小时的错误日志
level: ERROR AND @timestamp: [now-1h TO now]

拆完之后:那些没人告诉你的坑

如果以为拆完就万事大吉了,那就太天真了。

🌐 挑战一:服务间通信

单体应用里,调用一个方法就完了。

微服务里,调用另一个服务要:

  1. 超时设置不当:曾经把超时设成60秒,结果数据库慢查询导致整个系统卡死。后来改成:连接超时500ms,读取超时3s。
  1. 重试策略错误:曾经设置"失败无限重试",结果一个服务挂了,重试风暴把其他服务也打挂了。后来改成:最多重试3次,指数退避。
  1. 同步调用过多:曾经一个请求同步调用10个服务,任何一个慢了整个请求就慢。后来把非核心调用改成异步,响应时间降低了50%。

💾 挑战二:数据一致性

单体应用里,一个数据库事务搞定。

微服务里,每个服务有自己的数据库,跨服务的事务是噩梦。

比如"用户下单"这个场景:

怎么办?回滚?但订单和库存已经改了啊!

这就需要分布式事务方案。

方案 适用场景 优点 缺点
Saga 长流程业务 实现相对简单 最终一致性
本地消息表 简单场景 简单可靠 需要定时任务
异步补偿 高吞吐场景 性能好 需要设计补偿逻辑

🔧 挑战三:运维复杂度

40多个服务意味着:

  1. 容器化:所有服务都打包成 Docker 镜像,统一管理
  2. 容器编排:使用腾讯云 TKE 管理服务部署、扩缩容、故障恢复
  3. CI/CD:使用 GitLab CI 实现自动化构建、测试、部署
  4. 服务网格:使用 Istio 统一管理服务间通信、监控、安全
代码提交 → 单元测试 → 构建镜像 → 推送到镜像仓库 → 
自动部署到测试环境 → 自动化测试 → 人工审批 → 
部署到生产环境(滚动更新)

🔍 挑战四:调试困难

用户反馈"下单失败",你开始排查:

但组合起来就是不行。

没有它,调试就像在黑屋子里找黑猫。

[配图建议:一张复杂的网状图,节点是各个服务,连线是调用关系,标注"这就是你的系统"]


微服务拆分的成本收益分析

在决定是否拆分之前,先算一笔账。

成本

收益

什么时候值得拆?


总结:拆还是不拆?

最后,回到最根本的问题:到底要不要拆成微服务?

✅ 适合拆的场景

❌ 不适合拆的场景

🎯 核心原则

微服务是手段,不是目的。

目的是让系统更易于开发、部署、扩展、维护。

如果拆分反而让这些变得更难,那就是过度拆分。


要点总结

  1. 单体架构的核心问题是耦合,微服务通过拆分来解决耦合问题,但也带来了新的复杂度。
  1. 拆分原则:单一职责、高内聚低耦合、围绕业务能力、数据所有权清晰、渐进式拆分。
  1. 边界划分方法:事件风暴、CRUD矩阵、依赖分析、团队对齐。选择适合自己团队的方法。
  1. 拆分不是越细越好,要找到"业务能力"和"技术复杂度"的平衡点,避免"纳米服务"。
  1. 微服务治理是关键:K8s服务发现、自研配置中心、自研网关、HTTP/2通信、Redis队列、Jaeger追踪、Prometheus监控、ELK日志。
  1. 微服务不是银弹,是否拆分要根据团队规模、业务复杂度、运维能力综合判断,不要盲目跟风。
  1. 成本收益要算清楚:微服务前期投入大,回报周期长,要有足够的耐心和资源。
  1. 技术选型要务实:不要追求最新最炫的技术,选择适合自己业务复杂度的方案。我们的 K8s + Redis + Jaeger + Prometheus + ELK 组合简单但够用。



💡 写在最后

微服务不是终点,而是一段旅程。

在这段旅程中,你会遇到各种坑,也会收获各种成长。

48个服务不是答案,而是我们在这个阶段的选择。

你的选择可能不同,但思考的过程是一样的。

愿你在微服务的路上,少踩坑,多收获!🚀


💬 评论 (0)

0/500
排序: