知

模块五 · 架构与表达

第 9 / 11 节20 分钟阅读型

微服务与项目:从接口到系统

微服务不是"多几个项目",而是边界、通信、韧性与运维的组合。这一节讲拆分标准、通信方式、网关与四件套,以及怎么讲项目。

读完这一节,你能做到

  1. 用业务能力与数据边界判断服务拆分。
  2. 在 HTTP、gRPC 与消息之间做出选择。
  3. 设计网关、配置与可观测性。
  4. 用超时、重试、熔断、限流解释系统韧性。

一、服务边界

拆分标准不是"代码少",而是三个独立:

  • 独立变更:改一个服务不需要同时改其他服务。
  • 独立扩容:热点服务可以单独加实例。
  • 独立故障:一个服务挂掉,核心链路还能降级运行。

常见误区:按技术分层拆(Controller 服务、Service 服务),结果每次需求都要改一圈;正确做法是按业务能力拆(订单、库存、支付),每个服务拥有自己的数据与写入口。

二、通信方式

方式特点适用
HTTP/JSON简单、通用、可读对外 API、低频调用
gRPC二进制、强类型、流式内部高频调用
消息异步、解耦、削峰事件通知、长流程

同步调用简单但耦合;异步解耦但增加一致性与可观测性成本。判断标准:调用方是否需要立即拿到结果。

三、网关与配置

网关负责:路由、鉴权、限流、灰度、日志。不要把所有业务逻辑塞进网关,它只做横切。

配置中心负责:集中管理、热更新、环境隔离。注意配置变更的原子性与回滚。

服务发现:Kubernetes 时代多用 DNS + Service,不一定需要独立的注册中心。

四、韧性四件套

手段作用
超时防止线程被慢调用拖死
重试处理瞬时故障,必须配合退避与幂等
熔断下游持续失败时快速失败
限流保护自身不被压垮

在 .NET 里用 HttpClientFactory + Polly(或 Microsoft.Extensions.Http.Resilience)组合这些策略。

builder.Services.AddHttpClient("inventory")
    .AddStandardResilienceHandler();

五、项目案例:用 STAR 讲微服务

S:单体,发布耦合,订单高峰拖垮全站
T:让订单链路可独立扩容,故障不影响浏览
A:先模块化单体,再拆订单/库存/支付;引入消息与网关
R:发布频率、P99、故障隔离的指标变化 + 代价

代价通常是:分布式事务、链路追踪、运维复杂度上升。讲清代价,比只讲收益更可信。

六、常见坑

  1. 按技术拆服务,导致跨服务改一圈。
  2. 服务共享数据库,边界形同虚设。
  3. 重试没有退避与幂等,放大故障。
  4. 网关里写业务逻辑,变成新的单体。
  5. 没有链路追踪,排查靠猜。

七、动手任务:自写 → AI 写 → 对比 → 取舍

四步不跳:① 自己写(限时,关掉补全)→ ② 让 AI 写同一题 → ③ 逐条比对 → ④ 说出保留谁的写法。 完整动作与记录模板见《AI 协作与代码评审》。

① 自己写

  1. 画一个单体到微服务的拆分图,标出数据边界。
  2. 用 Polly 配超时、重试、熔断,模拟下游故障。
  3. 给两个服务加关联 ID,串起一次跨服务调用。

② 让 AI 写同一题

把第 2 题(服务拆分与一致性)交给 AI,补一句 说清边界依据,以及跨服务写的一致性方案。

③ 对比要盯的点

  • 拆分依据它是“按技术分层”还是“按业务能力”?前者会拆出耦合。
  • 跨服务一致性它给了 outbox、补偿还是 2PC?有没有说清代价。
  • 接口幂等与重试它考虑了吗?网络抖动会带来重复请求。

④ 一句话取舍

下游服务超时但实际成功时,它那版会重复下单吗?

八、自测

  1. 服务拆分的三个独立是什么?
  2. HTTP、gRPC、消息分别适合什么?
  3. 网关应该承担哪些职责?
  4. 重试为什么必须配合幂等?
  5. 微服务的主要代价是什么?

参考答案要点:变更/扩容/故障;同步/高频/异步;路由鉴权限流;重复请求;一致性与运维复杂度。

附录:高频追问

问:什么时候不该上微服务? 团队小、边界不清、运维能力不足;模块化单体往往更合适。

问:分布式事务怎么选? 优先本地消息表 + 幂等;资金场景再考虑 TCC/Saga。

问:gRPC 比 HTTP 快在哪? 二进制协议、HTTP/2 多路复用、强类型;但调试与浏览器支持较弱。

问:服务发现需要吗? Kubernetes 里用 Service/DNS 即可;虚拟机部署才需要注册中心。

问:如何做灰度发布? 网关按用户/流量比例路由,配合指标观察,异常快速回滚。

问:链路追踪怎么落地? 统一 TraceId,OpenTelemetry 采集,Jaeger/Tempo 展示。

问:如何避免雪崩? 超时、熔断、隔离、限流,加上下游降级。

问:微服务的数据怎么查? 按服务 API 组合,或建读模型/搜索;不要跨库 JOIN。

问:如何评估拆分收益? 看发布频率、故障影响面、扩容成本,而不是服务数量。

问:单体如何演进? 先模块化、再拆最独立的模块、补齐运维设施,逐步推进。

附:从单体到微服务的检查表

  • 业务边界清晰,每个服务有独立数据
  • 有网关与统一鉴权
  • 有配置中心与密钥管理
  • 有链路追踪与集中日志
  • 有超时、重试、熔断、限流
  • 有灰度与快速回滚能力
  • 有团队与值班机制

缺三项以上,先别拆。

数据一致性的三种做法

  1. 强一致:分布式事务,代价高,只在资金等场景用。
  2. 最终一致:本地消息表/Outbox + 幂等消费,工程主流。
  3. 对账修复:定时对账 + 人工处理,兜底手段。

大多数业务用第二种,第三种作为兜底;面试时要能说清“为什么不用强一致”。

补充:服务拆分的反模式

  • 按技术层拆:改一个需求要动多个服务。
  • 共享数据库:边界形同虚设。
  • 服务过细:运维与调用成本爆炸。
  • 没有可观测性就拆:出事无法定位。

拆分的收益是独立变更与扩容,不是“看起来先进”。

补充:通信选择示例

  • 前台查订单状态:HTTP,简单可缓存。
  • 内部高频查库存:gRPC,低延迟强类型。
  • 下单后发通知:消息,异步解耦。

同一个系统里三种都会用,按“是否需要立即结果”选择。

拆分验收

拆完后至少验证:能独立发布、故障不影响核心链路、单服务能独立扩容。做不到,说明拆早了。不要按技术拆;不要共享数据库;不要没有链路追踪。

一个判断

拆之前先问:这个模块能否独立发布、独立回滚?不能,就先留在单体里。先问发布,再问回滚。

补遗:YARP、Ocelot 与 gRPC

YARP 是微软的逆向代理库,可在 .NET 里做网关:路由、负载均衡、限流、鉴权;Ocelot 更早、配置驱动,生态弱于 YARP。

gRPC 用 .proto 定义契约,支持一元/服务端流/客户端流/双向流;内部高频调用优先,浏览器场景用 gRPC-Web。

补遗:CAP 与最终一致

CAP 同样适用于 .NET 微服务:P 必须保留,在 C 与 A 间取舍。大多数业务选 AP + 最终一致:本地消息表/Outbox + 幂等消费,强一致只在资金等场景用。

配套题目

★ 越多越该先背;点题目可看完整答案与深入版。

提示:读完可以顺手做几道对应题目,检验是否真的理解