微服务(二):组件全景、服务治理与弹性设计
微服务(二):组件全景、服务治理与弹性设计
导语:本篇回答"微服务系统到底长什么样、怎么让它稳定运行"。先给出组件全景图与一次请求的完整链路(这是架构题的骨架),再逐个展开注册发现的上下线机制、网关与 BFF 的分层、配置中心的治理,然后是同步 vs 异步通信的选型矩阵,最后落到弹性设计五件套的正确使用顺序与服务间鉴权,共 7 题。
一、组件全景
8. 微服务架构由哪些核心组件构成?一次请求的完整链路是怎样的?
答: 微服务不是"服务 + 注册中心"这么简单,它是一整套支撑体系。按职责分层:
一次典型外部请求(如"提交订单")的完整链路:
每层的核心职责:
| 组件 | 核心职责 | 常见选型 |
|---|---|---|
| 注册中心 | 服务实例的注册、发现与健康检查 | Nacos、Eureka、Consul、ZooKeeper |
| 配置中心 | 集中配置、动态推送、多环境与审计 | Nacos、Apollo、Spring Cloud Config |
| API 网关 | 统一入口:路由、鉴权、限流、灰度、日志、协议转换 | Spring Cloud Gateway、Kong、APISIX |
| 服务通信 | 同步调用框架与协议 | OpenFeign、Dubbo、gRPC |
| 容错治理 | 熔断、限流、隔离、降级 | Sentinel、Resilience4j |
| 消息中间件 | 异步解耦、削峰、事件驱动 | Kafka、RocketMQ、RabbitMQ |
| 链路追踪 | TraceId 透传、调用拓扑、耗时分析 | SkyWalking、Jaeger、Zipkin |
| 监控告警 | 指标采集、看板、告警 | Prometheus + Grafana |
| 日志 | 集中采集、检索、关联 | ELK / Loki |
| CI/CD 与编排 | 构建、镜像、发布、伸缩 | Jenkins/GitLab CI + Kubernetes |
答题加分点:面试官常问"这么多组件,如果只能先建三个,你建哪三个?"——答:配置中心、可观测性(监控+链路追踪)、网关。没有可观测性,微服务等于盲飞;没有配置中心,改配置要全量重启。
二、服务注册与发现
9. 注册中心的服务实例是怎么上下线的?优雅上下线怎么做?
答:
基本机制:
- 注册:实例启动后向注册中心登记地址与元数据(版本、机房、灰度标签)。
- 续约:定期心跳;超时未续约则剔除(Nacos 临时实例默认心跳 5s、15s 不健康、30s 剔除)。
- 发现:消费者拉取/订阅实例列表并本地缓存,再用本地负载均衡挑选实例——所以注册中心短暂不可用,已有调用通常仍能继续。
优雅上线(防止"冷启动被打崩"):
- 延迟注册:等连接池、线程池、本地缓存、JIT 热身后再注册,避免刚启动就被打满。
- 流量预热:注册后按权重渐进放量(如 Dubbo 预热 60s 内权重从 1 涨到 100),避免新实例被瞬时全量流量击穿。
优雅下线(防止"发布时报错"):
常见坑:
| 坑 | 现象 | 解决 |
|---|---|---|
直接 kill -9 | 消费者本地缓存未更新,仍在调已死实例 → 大量超时 | 必须先反注册 + 等待生效 |
K8s 滚动发布未配 preStop | Pod 被杀的同时仍在上游列表里 | 配 preStop hook sleep 一段时间,再走销毁流程 |
| 只配了 liveness 没配 readiness | 实例还没准备好就进流量 | 用 readiness 探针控制流量进入 |
| 心跳网络抖动导致误剔除 | 服务正常却被摘掉 | 健康检查阈值放宽 + 服务端主动探活 |
| 注册中心全挂 | 新实例无法注册、变更无法同步 | 消费者本地缓存兜底;注册中心本身做集群高可用 |
重要认知:注册中心是 AP 系统(如 Eureka/Nacos 临时实例)为主流——宁可返回一份稍旧的实例列表,也不能因为注册中心不可用而拒绝服务;已缓存列表 + 客户端健康检查可以兜底。详细 CAP 取舍见本站「分布式(二)」。
三、网关与配置
10. API 网关在架构中的定位是什么?BFF 和网关有什么区别?
答:
网关是所有外部流量的统一入口,处理的是与技术相关的横切关注点,与具体业务无关:
- 路由与协议转换(HTTP → gRPC、外部 REST → 内部 RPC)
- 安全:认证鉴权、防重放、黑白名单、WAF
- 稳定性:限流、熔断、降级、超时
- 可观测:统一日志、TraceId 注入、访问审计
- 发布:灰度路由、流量镜像、AB 分流
BFF(Backend For Frontend) 是按客户端/使用场景定制的后端聚合层,处理的是与业务/端相关的逻辑:一次请求聚合多个下游服务、裁剪字段、转换为端友好的结构。
| 维度 | API 网关 | BFF |
|---|---|---|
| 关注点 | 技术横切(安全、流控、路由) | 业务聚合与端适配 |
| 归属 | 基础设施团队维护 | 业务/前端团队维护 |
| 是否含业务逻辑 | 绝不应该 | 可以,也正为此存在 |
| 复用方式 | 全公司/全站共用一个 | 每个端一套 |
两条工程红线:
- 不要在网关里写业务逻辑。一旦网关里塞了"下单校验",它就变成新的"上帝服务",成为所有团队的发布瓶颈。
- 不要让客户端直连十几个微服务。移动端一次页面请求如果打 8 个服务,弱网下是灾难——用 BFF 收敛成 1~2 个请求。
加分点:追问"网关本身挂了怎么办?"——网关必须集群部署 + 多可用区 + 无状态水平扩容,并且它的可用性等级是全站最高的(网关挂 = 全站挂)。
11. 为什么微服务必须要有配置中心?配置治理怎么做?
答:
没有配置中心时的四个痛点:
| 痛点 | 表现 |
|---|---|
| 配置散落 | 几百个实例各自一份 application.yml,改一个开关要改几百次 |
| 改动要重启 | 线上调一次限流阈值需要全量重启,等于一次发布事故风险 |
| 环境错乱 | 测试环境配置被误带到生产("配置漂移") |
| 无审计 | 谁在什么时候改了什么、出问题无法回滚 |
| 明文泄密 | 数据库密码写在代码仓库里 |
配置中心应具备的能力:
优先级规则(以 Spring Cloud Alibaba Nacos 为例):
bootstrap / 启动参数 > 配置中心(带 profile) > 配置中心(通用) > 应用本地 application.yml配置治理的最佳实践:
- 区分三类配置:启动配置(端口、注册中心地址,变更即需重启,可本地保留兜底)/运行配置(限流阈值、功能开关,必须动态)/敏感配置(密码、密钥,必须加密 + 权限管控,或托管到 KMS/Vault)。
- 配置中心自身要高可用,且客户端必须有本地快照缓存:配置中心全挂时,服务应能用上一次的配置正常启动,而不是起不来。
- 功能开关(Feature Toggle) 要定期清理,否则会积累成"开关债务",让代码分支不可理解。
四、通信与弹性
12. 微服务之间的通信方式怎么选?同步还是异步?
答: 先给决策树,再给三种通信模式。
三种通信模式:
| 模式 | 说明 | 适用场景 |
|---|---|---|
| 请求-响应(Request/Response) | 同步等待结果,REST/HTTP、gRPC、Dubbo | 需要立即结果:查库存、创建订单 |
| 事件通知(Event Notification) | 只广播"发生了什么",消费者按需回查 | 解耦旁路:下单后发券、发通知 |
| 事件携带状态转移(Event-Carried State Transfer) | 事件里带上完整数据快照,消费者无需回查 | 消费者需要数据但不想依赖生产者的可用性(如构建读模型) |
同步 vs 异步对比:
| 维度 | 同步(RPC/HTTP) | 异步(MQ) |
|---|---|---|
| 时延 | 低,立即得到结果 | 高,有排队与消费延迟 |
| 耦合 | 时间与空间耦合:下游必须活着 | 解耦:下游挂了消息不丢,恢复后继续消费 |
| 削峰 | 不支持,流量直接压到下游 | 天然支持,是削峰填谷的核心手段 |
| 一致性 | 简单(配合本地事务 + 补偿) | 最终一致,需要幂等与对账 |
| 排障 | 链路清晰,易追 | 链路隐晦,依赖消息轨迹 |
| 典型问题 | 雪崩、重试风暴 | 消息堆积、重复消费、乱序 |
实践建议:
- 读多写少的列表/详情页:优先用 CQRS 读模型,而不是串行调 5 个服务再拼装。
- 跨服务的状态变更:用事务消息 / Saga,别用同步调用链去"假装"有事务。
- 异步必须配套:幂等消费 + 失败重试 + 死信队列 + 定时对账——缺一个,数据迟早出错。
关键原则:能用异步解耦的地方尽量异步,把同步调用留给真正需要立即结果的场景。 同步调用链越长,系统可用性越低(串联可用性相乘)。
13. 微服务如何做弹性设计?超时、重试、熔断、隔离、降级的顺序是什么?
答: 这五件套不是并列的选项,而是一套有明确层次和顺序的防护体系。顺序搞错,前面做的往往是负作用。
逐层要点:
① 超时——性价比最高的一项改造。
- 每个外部依赖必须有超时,没有超时 = 请求永远挂着 = 连接池耗尽 = 全站雪崩。
- 超时逐层收敛:上游超时应大于下游超时之和(如网关 3s > 服务 A 2s > 服务 B 1s),否则下游还没判超时,上游就已经放弃并重试,制造重复请求。
② 重试——最容易好心办坏事。
- 只对幂等操作重试;非幂等的写操作重试会造成重复扣款。
- 只重试可重试错误(超时、503、限流);不要重试业务错误(400、参数错误)。
- 必配 指数退避 + 抖动(Jitter),避免"重试风暴"——所有客户端同时重试会形成同步的流量尖峰。
- 配 重试预算:重试请求占比超过阈值(如 10%)就停止重试,防止重试本身把下游打垮。
③ 熔断——下游持续失败时,快速失败而不是继续消耗资源。状态机:Closed → Open(失败率超阈值,直接拒绝)→ Half-Open(放行少量探测)→ 恢复/再熔断。
④ 隔离(舱壁模式)——按依赖隔离资源:慢依赖只能耗尽它自己的线程池/信号量/连接池,不能拖垮整个服务。
⑤ 降级——返回兜底:缓存旧数据、返回默认值、关闭推荐/评价等非核心功能。降级预案必须演练,否则线上不敢开。
⑥ 限流——入口与网关侧兜底,保护自己不被外部流量打垮。
一句话记忆:先超时止损,再退避重试;还不行就熔断;同时用隔离把爆炸半径关小;最后用降级保住核心可用。 熔断、限流、隔离的详细参数与算法见本站「高可用(一)(二)」。
14. 微服务中的服务鉴权怎么做?Token 透传与边界鉴权怎么选?
答: 微服务鉴权回答"两个问题":外部用户是谁、内部服务之间怎么互信。
三种常见方案对比:
| 方案 | 做法 | 优点 | 问题 |
|---|---|---|---|
| 边界鉴权(推荐) | 只在网关做认证,网关把身份转成内部上下文(如 userId + 权限列表)传给下游,内部网络可信 | 简单、性能好、服务无感知 | 内部被攻破后可横向移动,权限粒度粗 |
| Token 透传 | 用户 JWT 一路透传到所有内部服务,各服务自行校验与授权 | 每个服务都能做细粒度授权,链路可追溯 | ① Token 体积大,每一跳都要验签;② 权限膨胀——用户的权限被内部服务无条件继承,一个被入侵的服务可拿着用户 Token 调用任意服务;③ 令牌过期/轮换在长链路中难处理 |
| 零信任 / 服务身份 | 服务间用 mTLS 双向认证 + 服务级授权(谁可以调我),用户身份作为上下文传递 | 服务身份与用户身份分离,权限最小化 | 需要证书管理/服务网格或 SDK 支持,复杂度高 |
推荐实践(分层):
- 南北向(外部 → 网关):认证用户身份(OAuth2 / JWT / Session),网关完成验签、黑名单、防重放。
- 网关 → 服务:把用户身份收敛成精简的内部上下文(
userId、tenantId、权限标签),不要把原始 JWT 无脑透传。 - 东西向(服务 → 服务):用 mTLS 做服务身份认证 + 服务级授权(订单服务只允许调库存服务的扣减接口,不允许调结算接口)。
- 敏感操作:涉及资金、数据的接口做二次校验(用户级权限 + 服务端幂等键),不能只依赖网关。
答题加分点:一句"认证在边界,授权在服务内"能概括大部分场景——认证可以集中做(否则每个服务都要实现一遍登录体系),但授权(这个调用方有没有权限做这件事)必须在服务内做,因为只有服务自己知道资源的归属。
