mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
5235 字
14 分钟
什么是微服务?
2021-06-10

某电商平台在双十一大促前夜,四十人的开发团队挤在会议室里等待部署,一个单体应用的任何改动都需要整体重新打包、停机发布,某个模块的 Bug 可能拖垮整个系统。凌晨三点,一次数据库连接池泄漏导致全站宕机,订单、支付、库存全部瘫痪。痛定思痛后,团队将系统按业务拆分为独立服务:订单服务挂了,支付和搜索照常运行;库存服务需要扩容,单独加机器就行,不用牵动全局。这就是微服务架构带来的改变。

这个故事听起来很美好。但故事的后半段往往被忽略:拆分后,一次下单操作跨越 5 个服务、3 个数据库,调试链路从看一个日志文件变成了查 5 个服务的分布式日志;服务间的网络调用比本地方法调用慢了两个数量级,还要处理超时、重试、幂等;原来一个事务能保证的一致性,现在需要 Saga 模式做补偿。代码量翻倍,出错概率翻倍。

微服务不是免费的午餐。它用运维复杂度交付速度,用一致性代价故障隔离,用分布式系统难题独立扩展能力。理解这个交换的代价,比理解它的好处更重要。

一、微服务解决了什么问题#

1.1 单体架构的瓶颈#

单体架构(Monolith)把所有功能打包在一个进程中。在项目初期,这是最合理的选择:部署简单、调试方便、事务一致性天然保证。问题出在规模增长之后:

  • 部署耦合:改一行代码就要重新打包、重新部署整个应用。凌晨三点停机发布不是因为喜欢熬夜,是因为所有模块绑在一起,没法只更新其中一个
  • 故障级联:一个模块的内存泄漏或死循环,会拖垮共享同一进程的所有模块
  • 扩展浪费:只有商品搜索需要扩容,但你不得不把整个应用多部署几份,连同不需要扩容的支付模块和报表模块一起

这三个问题的本质是一样的:不同业务模块的生命周期被强行绑定在一起。微服务的核心价值就是解绑:让每个模块有独立的生命周期。

1.2 微服务不是架构升级,是组织规模适配#

Martin Fowler 和 James Lewis 在 2014 年的论文 Microservices 中明确提出:微服务的首要驱动因素不是技术,而是组织结构。康威定律(Conway’s Law)指出,系统的架构会趋同于设计它的组织的沟通结构。反过来,当团队规模增长到需要独立交付时,架构也必须配合拆分。

一个 5 人团队用单体架构完全没问题,沟通成本低,改了什么大家都清楚。但当团队增长到 50 人、5 个业务小组,单体架构就变成了瓶颈:每次发布都要协调所有小组,代码合并冲突频繁,一个小组的 Bug 影响所有人的上线节奏。

微服务的本质是:让团队结构和系统结构对齐。每个服务由一个独立团队负责,团队有权独立设计、独立部署、独立扩展自己的服务。

二、拆分的代价:每个好处都有对应的成本#

微服务的每个好处,都附带一个代价。只看好处不看代价,是微服务项目失败的最常见原因。

2.1 好处与代价的对照#

好处对应的代价
独立部署:各服务可以随时发布部署复杂度激增:需要 CI/CD 流水线、蓝绿部署/金丝雀发布、服务版本兼容策略
故障隔离:一个服务挂了不影响其他分布式故障模式:网络超时、服务降级、雪崩效应,需要熔断器、限流器、降级策略
独立扩展:只扩需要的服务运维成本翻倍:更多服务实例、更多监控指标、更多日志来源
技术异构:各服务可选最适合的技术栈技术栈碎片化:团队间无法共享经验,招聘和培训成本上升
独立数据:每个服务有自己的数据库数据一致性变难:跨服务事务需要 Saga/2PC,最终一致性比强一致性难理解和调试

2.2 什么时候不该用微服务#

微服务的适用条件很明确,不符合这些条件时不要拆:

  • 团队小(< 10 人):沟通成本低,单体架构的部署耦合不是痛点
  • 业务简单或初期:领域边界不清晰时强行拆分,只会拆出”分布式单体”,服务间强依赖,但多了网络调用的开销
  • 需要快速原型验证:微服务的初始搭建成本(服务发现、配置中心、链路追踪)会拖慢验证速度
  • 强一致性要求高:金融交易等场景需要跨业务实体的强一致性,分布式事务的复杂度可能抵消微服务的好处

三、服务拆分:最难的不是怎么拆,而是怎么判断拆对了#

3.1 拆分维度#

拆分有三种主要维度,每种背后是一种不同的思维方式:

按业务能力拆:订单、支付、库存、用户:每个服务对应一个业务能力。这是最直觉的方式,也是 Martin Fowler 推荐的首选方式。好处是业务边界相对稳定,坏处是”业务能力”的粒度不好把握,“订单”是一个服务,还是”下单""退单""查询”各一个服务?

按子域拆(DDD):领域驱动设计(Domain-Driven Design)用限界上下文(Bounded Context)定义服务边界。比按业务能力更严格:限界上下文有明确的通用语言(Ubiquitous Language)和上下文映射(Context Map)。适合业务复杂的系统,但学习门槛高。

按团队结构拆(康威定律):2 Pizza Team(6~10 人)对应一个服务。团队边界就是服务边界。这听起来像是在回避技术问题,但在实践中最有效,因为服务的维护者是人,人对了,边界就对了。

3.2 过度拆分的信号#

拆分粒度没有精确公式,但有明确的”过度拆分”信号:

  • 服务间强依赖:改一个服务必须同时改另一个,这不是微服务,是”分布式单体”
  • 跨服务事务频繁:如果大部分操作都涉及多个服务的数据一致性,说明拆分切断了本该在一起的逻辑
  • 服务间调用链过深:一个请求经过 5 个以上的服务才返回,延迟和可靠性都成问题
  • 运维成本超过收益:花在服务治理上的时间比业务开发还多

3.3 演进式拆分:从单体开始#

不要一开始就微服务。从单体开始,在明确的痛点驱动下逐步拆分:

  1. 先做单体,确保业务逻辑正确
  2. 当部署耦合成为痛点,把最独立的模块拆出去(通常是通知、搜索等非核心功能)
  3. 当某个模块需要独立扩展,再拆一个
  4. 每拆一个,观察:网络延迟是否可接受?事务一致性是否还保证?调试是否还方便?

这种”按需拆分”的策略,比”预先拆分”安全得多。拆分是单向操作,拆了再合回来成本极高,但不拆随时可以拆。

四、服务通信:同步 vs 异步不是选一个#

4.1 同步通信:简单但脆弱#

REST/gRPC 是最常见的同步通信方式。调用方发出请求,等待响应,编程模型简单,和本地方法调用类似。

但同步通信有一个致命问题:调用方和被调用方在时间上耦合。被调用方宕机、超时、响应慢,调用方也被阻塞。当调用链上有 5 个服务时,任何一个慢都会拖慢整条链路。

gRPC 比 REST 性能更好(Protocol Buffers 二进制编码、HTTP/2 多路复用),但调试更困难(无法用 curl 直接调用)。选 REST 还是 gRPC,本质是在”开发效率”和”运行效率”之间权衡。

4.2 异步通信:解耦但复杂#

消息队列(Kafka / RabbitMQ / RocketMQ)实现异步通信:调用方发布事件,不等待响应;消费方按自己的节奏处理。生产者和消费者在时间上完全解耦,即使消费者宕机,消息留在队列里,恢复后继续处理。

sequenceDiagram participant C as 订单服务 participant M as 消息队列 participant I as 库存服务 participant N as 通知服务 C->>M: 发布订单创建事件 M->>I: 推送事件 M->>N: 推送事件 I-->>M: 库存扣减完成 N-->>M: 发送通知

异步通信的代价:调用方不知道操作的结果。订单服务发出”订单创建”事件后,不知道库存是否扣减成功。需要额外的机制(回调、事件溯源、轮询)来获知结果。这使得调试和排错比同步调用困难得多。一个 bug 可能藏在事件顺序、消息丢失、重复消费等分布式问题中。

4.3 混合策略:核心链路同步,旁路逻辑异步#

实践中很少全同步或全异步。更合理的策略是:

  • 核心业务链路用同步:下单 → 扣库存 → 扣款,这些操作的结果直接影响用户体验,必须即时返回
  • 旁路逻辑用异步:发送通知、更新搜索索引、记录审计日志,这些操作延迟几秒甚至几分钟都无所谓

4.4 消息队列选型:不是”哪个更好”,是”问题特征匹配”#

队列核心特征适合什么不适合什么
Kafka高吞吐、日志持久化、分区有序事件流处理、日志聚合、大数据管道需要精确一次消费和复杂路由的场景
RabbitMQ灵活路由(Exchange/Binding)、消息确认复杂路由逻辑、任务分发超高吞吐(百万级 TPS)场景
RocketMQ事务消息、延迟消息金融场景、订单状态变更非 Java 生态(客户端支持不如前两者广)
Redis Streams轻量级、低延迟简单异步、已有 Redis 基础设施需要持久化保证和集群扩展的场景

五、服务治理:微服务的”免疫系统”#

微服务把系统拆成了独立的服务,但系统作为一个整体仍然需要协调:服务之间怎么找到对方?怎么保证一个服务的故障不拖垮其他服务?怎么追踪一个请求经过了哪些服务?这些问题统称为”服务治理”。

5.1 服务发现#

在单体架构中,所有模块在同一个进程里,调用就是方法调用。微服务中,调用变成网络请求。调用方需要知道被调用方的 IP 和端口。但服务的实例会动态变化(扩缩容、重启、迁移),硬编码地址行不通。

服务发现的两种模式:

  • 客户端发现:服务实例启动时向注册中心(如 Eureka)注册,调用方从注册中心查询。调用方直连服务实例,少一次网络跳转,但客户端需要实现负载均衡逻辑
  • 服务端发现:通过 API Gateway 或负载均衡器(如 Nginx + Consul)代理请求。调用方只知 Gateway 地址,不需要知道后端实例。多一次网络跳转,但客户端逻辑更简单

Kubernetes 的 Service 是第三种模式:DNS 发现:每个 Service 分配一个稳定的 ClusterIP 和 DNS 名称,kube-proxy 在节点上维护 iptables/IPVS 规则将流量转发到后端 Pod。开发者不需要关心服务发现,DNS 解析搞定一切。

5.2 熔断与限流:防止雪崩效应#

分布式系统中最危险的不是单个服务故障,而是雪崩效应:服务 A 调用服务 B,B 响应慢导致 A 的线程池耗尽,A 也变慢,依赖 A 的 C 也受影响……一个点的故障像多米诺骨牌一样扩散。

熔断器(Circuit Breaker) 的工作原理和电路保险丝一样:当失败次数超过阈值,熔断器”跳闸”,后续请求直接返回错误而不是继续尝试调用。过一段时间后进入”半开”状态,放一个请求试探,成功则恢复,失败则继续熔断。

# 熔断器模式简化实现
class CircuitBreaker:
def __init__(self):
self.failure_threshold = 5
self.timeout = 60
self.state = "closed" # closed → open → half_open → closed
def call(self, func):
if self.state == "open":
raise CircuitOpenError() # 直接失败,不调用下游
try:
result = func()
self.on_success()
return result
except Exception:
self.on_failure()
if self.should_trip():
self.state = "open" # 失败次数超阈值,跳闸
raise

限流是另一道防线,从入口控制流量:

算法特点适用场景
令牌桶允许突发流量,但平均速率受控API 限流(允许短时突发,但整体不超限)
漏桶严格匀速输出需要精确控制处理速率的场景
滑动窗口统计最近 N 秒的请求数精度比固定窗口高,避免窗口边界的突发

5.3 链路追踪:分布式系统的”黑匣子”#

一个请求跨越 5 个服务,其中一个服务报错了,是哪个?链路追踪(Distributed Tracing)通过三个核心概念解决这个问题:

  • Trace ID:贯穿整个调用链的唯一标识
  • Span ID:每个服务的操作片段
  • Parent ID:调用关系(谁调了谁)

OpenTelemetry 已经成为链路追踪的事实标准,Jaeger 和 Zipkin 是最常用的后端存储和可视化工具。没有链路追踪,微服务的调试基本是盲人摸象。

六、分布式事务:微服务最难的问题#

单体架构中,一个数据库事务就能保证一致性。微服务中,一个业务操作可能涉及多个服务的数据,每个服务有自己的数据库。没有分布式锁,没有跨库事务,一致性怎么保证?

6.1 CAP 定理的约束#

CAP 定理告诉我们:在网络分区(P)不可避免的情况下,一致性(C)和可用性(A)只能选一个。这不是理论上的可能性,而是工程上的现实:网络一定会断,超时一定会发生,你必须决定:是返回可能过时的数据(AP),还是拒绝服务等待恢复(CP)?

大多数互联网系统选择 AP,宁可返回几秒前的数据,也不能让用户完全无法使用。金融系统选择 CP,宁可让交易暂停,也不能让账户余额出错。

6.2 事务模式:从强一致到最终一致#

模式一致性性能复杂度什么时候用
2PC强一致低(同步阻塞)只有在绝对需要强一致性时(如跨库转账)
TCC最终一致高(需实现 Try/Confirm/Cancel 三个接口)资源预留型业务(如扣库存)
Saga最终一致中(需定义补偿操作)长流程业务(如订单→支付→发货)
本地消息表最终一致低(最简单可靠)对一致性要求不高但需要保证最终完成

Saga 是微服务中最常用的事务模式。它的核心思想是:每个服务执行本地事务后发布事件,下一个服务消费事件执行自己的事务;如果某一步失败,执行之前步骤的补偿操作(而不是回滚)。补偿操作是业务层面的”撤销”:比如”扣款”的补偿是”退款”,“创建订单”的补偿是”取消订单”。

Saga 的代价:补偿逻辑必须由开发者实现。不是每个操作都有简单的逆操作,“发送邮件”怎么补偿?这需要业务层面的设计,不是技术框架能自动解决的。

七、微服务安全:零信任网络#

微服务之间的网络不再是”内网可信”,服务间通信也需要认证和加密。

7.1 身份认证的双层模型#

  • 外部请求 → API Gateway:用户身份认证(JWT Token / OAuth2),Gateway 验证后转发请求
  • 服务间调用:服务身份认证(mTLS / SPIFFE),每个服务有独立的证书,通信双向验证

7.2 服务网格:把安全策略从代码中抽出来#

Istio / Linkerd 等服务网格通过 Sidecar Proxy(Envoy)拦截所有服务间流量,自动实现 mTLS、访问控制、流量观测。应用代码不需要关心安全逻辑。这是”基础设施关注点从应用代码中剥离”的又一个例子。

但服务网格本身是复杂的,Sidecar 引入额外的延迟(约 1~2ms/跳),管理 Envoy 的配置需要专门的知识。在团队没有足够的运维能力之前,不要引入服务网格。

八、微服务的成熟度:不是一步到位#

微服务的实施是渐进的,不是”拆完就好了”。每个成熟度层级有明确的前置条件:

  1. 服务拆分:领域边界清晰,服务独立部署
  2. 容器化部署:Docker + K8s,自动化部署流水线
  3. 服务治理:服务发现、熔断限流、链路追踪就位
  4. 全链路可观测:Metrics + Logs + Traces 三位一体,故障定位在分钟级
  5. 自动化运维:自愈、弹性伸缩、混沌工程验证可靠性

跳级是危险的,在没有链路追踪的情况下拆了 20 个服务,调试会变成噩梦;在没有熔断器的情况下让服务互相调用,一次故障就能雪崩整个系统。


微服务架构的每个设计决策,本质上都在回答同一个问题:这个系统的瓶颈,到底是”部署耦合”还是”分布式复杂度”? 如果是前者,拆分带来收益;如果是后者,拆分只会让情况更糟。

大多数团队高估了自己处理分布式复杂度的能力,低估了单体架构能承载的规模。Netflix、Amazon、Uber 拆微服务,是因为它们的规模真的到了不拆不行的程度。如果你的系统还没有到那个规模,单体架构加上清晰的模块边界,可能比微服务更务实。

参考资料#


支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

什么是微服务?
https://blog.souloss.cn/posts/whatis/what-is-microservice/
作者
Souloss
发布于
2021-06-10
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时