模块五 · 架构与表达
微服务与项目:从接口到系统
微服务不是"多几个项目",而是边界、通信、韧性与运维的组合。这一节讲拆分标准、通信方式、网关与四件套,以及怎么讲项目。
读完这一节,你能做到
- 用业务能力与数据边界判断服务拆分。
- 在 HTTP、gRPC 与消息之间做出选择。
- 设计网关、配置与可观测性。
- 用超时、重试、熔断、限流解释系统韧性。
一、服务边界
拆分标准不是"代码少",而是三个独立:
- 独立变更:改一个服务不需要同时改其他服务。
- 独立扩容:热点服务可以单独加实例。
- 独立故障:一个服务挂掉,核心链路还能降级运行。
常见误区:按技术分层拆(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、故障隔离的指标变化 + 代价
代价通常是:分布式事务、链路追踪、运维复杂度上升。讲清代价,比只讲收益更可信。
六、常见坑
- 按技术拆服务,导致跨服务改一圈。
- 服务共享数据库,边界形同虚设。
- 重试没有退避与幂等,放大故障。
- 网关里写业务逻辑,变成新的单体。
- 没有链路追踪,排查靠猜。
七、动手任务:自写 → AI 写 → 对比 → 取舍
四步不跳:① 自己写(限时,关掉补全)→ ② 让 AI 写同一题 → ③ 逐条比对 → ④ 说出保留谁的写法。 完整动作与记录模板见《AI 协作与代码评审》。
① 自己写
- 画一个单体到微服务的拆分图,标出数据边界。
- 用 Polly 配超时、重试、熔断,模拟下游故障。
- 给两个服务加关联 ID,串起一次跨服务调用。
② 让 AI 写同一题
把第 2 题(服务拆分与一致性)交给 AI,补一句 说清边界依据,以及跨服务写的一致性方案。
③ 对比要盯的点
- 拆分依据它是“按技术分层”还是“按业务能力”?前者会拆出耦合。
- 跨服务一致性它给了 outbox、补偿还是 2PC?有没有说清代价。
- 接口幂等与重试它考虑了吗?网络抖动会带来重复请求。
④ 一句话取舍
下游服务超时但实际成功时,它那版会重复下单吗?
八、自测
- 服务拆分的三个独立是什么?
- HTTP、gRPC、消息分别适合什么?
- 网关应该承担哪些职责?
- 重试为什么必须配合幂等?
- 微服务的主要代价是什么?
参考答案要点:变更/扩容/故障;同步/高频/异步;路由鉴权限流;重复请求;一致性与运维复杂度。
附录:高频追问
问:什么时候不该上微服务? 团队小、边界不清、运维能力不足;模块化单体往往更合适。
问:分布式事务怎么选? 优先本地消息表 + 幂等;资金场景再考虑 TCC/Saga。
问:gRPC 比 HTTP 快在哪? 二进制协议、HTTP/2 多路复用、强类型;但调试与浏览器支持较弱。
问:服务发现需要吗? Kubernetes 里用 Service/DNS 即可;虚拟机部署才需要注册中心。
问:如何做灰度发布? 网关按用户/流量比例路由,配合指标观察,异常快速回滚。
问:链路追踪怎么落地? 统一 TraceId,OpenTelemetry 采集,Jaeger/Tempo 展示。
问:如何避免雪崩? 超时、熔断、隔离、限流,加上下游降级。
问:微服务的数据怎么查? 按服务 API 组合,或建读模型/搜索;不要跨库 JOIN。
问:如何评估拆分收益? 看发布频率、故障影响面、扩容成本,而不是服务数量。
问:单体如何演进? 先模块化、再拆最独立的模块、补齐运维设施,逐步推进。
附:从单体到微服务的检查表
- 业务边界清晰,每个服务有独立数据
- 有网关与统一鉴权
- 有配置中心与密钥管理
- 有链路追踪与集中日志
- 有超时、重试、熔断、限流
- 有灰度与快速回滚能力
- 有团队与值班机制
缺三项以上,先别拆。
数据一致性的三种做法
- 强一致:分布式事务,代价高,只在资金等场景用。
- 最终一致:本地消息表/Outbox + 幂等消费,工程主流。
- 对账修复:定时对账 + 人工处理,兜底手段。
大多数业务用第二种,第三种作为兜底;面试时要能说清“为什么不用强一致”。
补充:服务拆分的反模式
- 按技术层拆:改一个需求要动多个服务。
- 共享数据库:边界形同虚设。
- 服务过细:运维与调用成本爆炸。
- 没有可观测性就拆:出事无法定位。
拆分的收益是独立变更与扩容,不是“看起来先进”。
补充:通信选择示例
- 前台查订单状态:HTTP,简单可缓存。
- 内部高频查库存:gRPC,低延迟强类型。
- 下单后发通知:消息,异步解耦。
同一个系统里三种都会用,按“是否需要立即结果”选择。
拆分验收
拆完后至少验证:能独立发布、故障不影响核心链路、单服务能独立扩容。做不到,说明拆早了。不要按技术拆;不要共享数据库;不要没有链路追踪。
一个判断
拆之前先问:这个模块能否独立发布、独立回滚?不能,就先留在单体里。先问发布,再问回滚。
补遗:YARP、Ocelot 与 gRPC
YARP 是微软的逆向代理库,可在 .NET 里做网关:路由、负载均衡、限流、鉴权;Ocelot 更早、配置驱动,生态弱于 YARP。
gRPC 用 .proto 定义契约,支持一元/服务端流/客户端流/双向流;内部高频调用优先,浏览器场景用 gRPC-Web。
补遗:CAP 与最终一致
CAP 同样适用于 .NET 微服务:P 必须保留,在 C 与 A 间取舍。大多数业务选 AP + 最终一致:本地消息表/Outbox + 幂等消费,强一致只在资金等场景用。
配套题目
★ 越多越该先背;点题目可看完整答案与深入版。
- ★★★★☆ gRPC 和 REST 有什么区别?什么时候该用 gRPC?
- ★★★★☆ 服务间调用怎么做熔断、重试和超时?
- ★★★☆☆ API Gateway 在 .NET 微服务里承担什么职责?
- ★★★☆☆ 分层架构和 Clean Architecture、CQRS 有什么区别?什么时候值得引入?
- ★★★★★ 一个支付回调接口怎么做幂等?
提示:读完可以顺手做几道对应题目,检验是否真的理解