微服务面试的本质是边界设计能力:为什么拆、数据归谁、失败了怎么降级。本文按拆分、通信、一致性、稳定性、可观测与安全五条主线展开。

考察地图

  • 架构与拆分:概念辨析、拆分维度、分布式单体的识别。考察设计判断而非名词记忆。
  • 服务通信:REST、RPC、MQ 的选型逻辑。
  • 数据与一致性:Database per Service 的原因、跨服务一致性方案选型。
  • 稳定性与发布:超时/重试/熔断/限流的分工、优雅停机。
  • 可观测性与安全:TraceId 跨协议传递、认证与授权的位置。
  • 建议自行查阅:康威定律、Service Mesh 架构、配置中心灰度推送、CDC 数据同步——广度题,优先级低于主线。

架构与拆分

单体、集群、分布式、微服务有什么区别?

结论:单体是一个进程承载全部功能;集群是同一应用部署多份;分布式是按功能拆到多节点协作;微服务是分布式的一种特定风格——按业务能力拆分、独立部署、各自持有数据。

原理:四者不在同一维度:单体也能部署成集群,微服务天然是集群。区分关键在拆分依据:集群横向复制同一单元;普通分布式可能按技术层拆;微服务按业务边界垂直切分,每个服务可独立开发、部署、扩缩容,靠 API 而非共享库协作。判断标准看「能否独立发布」,而非进程数量。

延伸:微服务不是免费午餐:网络调用带来延迟与故障面,数据分散带来一致性难题,服务数量带来运维成本,小团队从模块化单体起步往往更务实。

追问:什么信号说明单体该拆?提示:交付被合并冲突拖垮、模块资源争抢、需要差异化扩缩容——而不是「大家都拆了」。

服务应该按什么维度拆分?限界上下文和服务边界是什么关系?

结论:优先按业务能力和领域边界拆,辅以数据归属校验;限界上下文是 DDD 中模型的一致边界,通常映射为一个或少数几个服务,是画服务边界最可靠的工具。

原理:限界上下文解决「同一个词在不同业务里含义不同」——商品在交易上下文是 SKU,在物流上下文是有重量的包裹。按上下文拆,服务内部模型自洽,跨服务靠防腐层翻译。两个经典反模式:按数据库表拆会让一次下单跨七八个网络调用;按技术层拆会让一次业务变更牵动所有服务。落地用事件风暴梳理流程、识别聚合与上下文,再对照团队结构——康威定律决定系统会长成组织架构的样子。

延伸:经验法则:两个服务总要同时变更发布,大概率该合并;一个服务有两个变化原因(如既扛促销流量又扛报表),可再拆。

追问:拆得太细的后果?提示:分布式单体、链路延迟放大、事务复杂化、本地调试困难。

什么是分布式单体?怎么判断系统已经陷进去了?

结论:分布式单体是物理上拆成多服务、逻辑上仍强耦合的架构——共享数据库、必须同步发布、牵一发动全身,集单体与分布式的缺点于一身。

原理:典型症状四条:多服务直连同库甚至同表,改表结构要所有方评审;服务间环状同步依赖,A 调 B、B 回调 A;发布必须多服务排队一起上;同步调用层数加深,P99 随链路线性恶化。成因多是只切代码没切数据,或按技术层切。治理:先切共享库、每张表明确唯一属主;环状依赖改为事件驱动单向流动;长链路异步化。

延伸:面试中主动承认「系统某些部分是分布式单体,正在按属主收敛」,比硬撑微服务叙事更加分,说明有真实架构体感。

追问:共享数据库危害在哪?提示:schema 耦合、资源争抢、事务边界模糊、无法独立扩缩容。

服务通信

同步 REST/RPC 和异步消息队列怎么选?

结论:调用方需立刻拿到结果做决策的用同步(查询、库存校验);只关心事件送达、允许延迟的用异步(通知、积分、数据同步)。两者共存,按业务时效与一致性要求分工。

原理:同步语义直观,但可用性沿链相乘——每加一跳整体可用性下降,延迟按最慢一跳累加。异步消息在时间、容量、速率三个维度解耦,还能削峰填谷。代价是语义复杂化:消息可能重复(需幂等)、乱序(需分区键)、丢失(需确认机制),一致性从「立即」退化为「最终」。

延伸:反模式是把 MQ 当 RPC 用——发消息后轮询等结果,既丢解耦收益又背消息复杂度,说明那步本就该同步。

追问:同步链路过长有哪些危害?提示:可用性乘法衰减、尾延迟主导 P99、无熔断时故障沿链传播雪崩。

数据与一致性

为什么强调每个微服务拥有自己的数据库?

结论:数据归属是服务独立的前提:谁拥有数据,谁才能独立改 schema、扩容、发布;共享库没收这些自由,服务退化为「用数据库当 API」的隐式耦合。

原理:共享库耦合有四层:schema 耦合——一方改表炸掉其他服务的 SQL;语义耦合——同一字段被解释出不同含义;资源耦合——一个慢查询拖垮连接池殃及所有服务;发布耦合——加索引要协调窗口。Database per Service 强制数据只能经显式 API 访问,内部实现可自由演进。跨服务查询改用 API 聚合、CQRS 读模型、搜索引擎或 CDC 同步出的只读副本,而不是直接 join 别人的库。

延伸:难点在存量系统:双写期一致性、历史迁移、灰度切流都易出事故,常见做法是先库内逻辑隔离(独立 schema 与账号权限)再择机物理拆。

追问:跨服务 join 有哪些替代方案?提示:API 组合、宽表读模型、ES、CDC 同步,权衡实时性与成本。

跨服务业务怎么保证一致性?Saga、事务消息、Outbox 怎么选?

结论:强一致用 TCC;最终一致优先「本地状态 + 可靠消息」——Outbox 解决写库与发消息的原子性,Saga 编排多步补偿,事务消息是 Outbox 的中间件级实现。

原理:Outbox 把业务数据与待发消息表写进同一本地事务,后台 relay 扫表投递 MQ,消灭「库写成功消息没发出去」的窗口;RocketMQ 事务消息用 half 消息加回查达到同等效果。Saga 面向多步长流程:每步本地事务配一个补偿动作,失败时逆序执行,适合跨外部系统的场景(下单→锁库存→支付→发货)。无论哪种方案,消费端都必须幂等——至少投递一次是 MQ 的普遍语义。

延伸:补偿不是技术回滚而是业务动作:已扣款走退款、已发货走拦截。设计 Saga 先问「每步失败时业务允许停在哪」;补偿本身也要幂等并有重试与人工兜底。

追问:Outbox relay 宕机怎么办?提示:relay 无状态可水平扩容,重复投递由消费端幂等兜底。

稳定性与发布

超时、重试、熔断、限流分别解决什么问题?为什么会有重试风暴?

结论:超时限制等待防线程堆积;重试消化瞬时抖动;熔断在下游持续异常时快速失败;限流保护容量上限。重试风暴指多层同时重试导致流量指数放大。

原理:没有超时,下游慢会拖光上游线程池,故障沿链逆流而上。重试只对幂等操作和瞬时错误有意义,且要配指数退避加抖动。熔断器统计滑动窗口失败率,超阈值进入 Open 状态直接拒绝调用,半开状态试探放行确认恢复。重试风暴的算术很简单:三层链路各重试 3 次,底层实际收到 3³=27 倍请求,瞬时抖动被放大成持续过载。对策是只在入口层重试,或让重试预算沿链递减。

延伸:超时结合上游 deadline 与下游 P99 设置,并沿链单调递减——上游等 1 秒、下游配 3 秒毫无意义。

追问:熔断与降级的关系?提示:熔断是触发机制,降级是兜底动作(缓存、默认值、静态页),二者配套使用。

服务优雅停机应该做哪些事?

结论:目标是不让在途请求被腰斩:先从流量入口摘除自己,等存量请求处理完,再释放资源,最后退出进程。

原理:标准四步:一、摘流量——从注册中心主动注销,Readiness 置失败让 K8s 剔除 Pod(注册中心有缓存与心跳周期,摘除不等于立即无流量);二、静默等待——preStop sleep 几秒,等上游缓存刷新;三、排空——容器停止接新连接,在途请求限时完成,MQ 消费者停止拉取并提交位点;四、关闭连接池与线程池。terminationGracePeriodSeconds 必须大于以上全部耗时。

延伸:上线侧同样讲究:Readiness 就绪再接流量避免冷启动被打挂;大促前小流量预热触发 JIT。

追问:停机时消息消费者怎么处理?提示:先 pause 拉取、提交位点、rebalance 迁移分区,让至少一次语义下重复量最小。

可观测性与安全

TraceId 怎么跨 HTTP、RPC 和消息队列传递?

结论:靠上下文传播约定:入口生成全局唯一 TraceId,进程内放 ThreadLocal 或 Reactor Context,跨边界注入协议载体——HTTP 用 Header、RPC 用隐式传参、消息用消息属性,下游提取后续接。

原理:OpenTelemetry 模型里 Trace 是一次完整调用,Span 是每一跳,SpanContext 携带 traceId、spanId 与采样标记。HTTP 由网关 Filter 与客户端拦截器读写 traceparent Header;Dubbo 经 attachment 隐式传递;MQ 场景生产者把 SpanContext 写入消息头,消费者以它为父 Span,异步链路不断档。进程内的坑在线程切换:线程池、@Async、响应式编程都会丢 ThreadLocal,需装饰器包装任务或用框架的上下文快照。

延伸:三支柱分工:日志看现场、指标看趋势告警、追踪看链路拓扑;同一 traceId 串起三者,才能从告警钻取到具体日志行。

追问:采样率怎么定?提示:全量成本高;头部采样简单但漏错,尾部采样(按错误与否决定保留)是生产常用折中。

认证应该放在网关还是业务服务?

结论:认证(你是谁)适合在网关统一做,授权(能不能动这条数据)必须由拥有业务数据的服务做。两层职责不同,省掉哪层都会留洞。

原理:网关做 Token 校验与粗粒度路径控制,拦住明显非法请求,避免各服务重复实现认证,密钥轮换便于集中管理。但网关不知道「订单 12345 属于用户 A」——对象级越权只能由订单服务查库判断。更关键的是纵深防御:内网服务间调用可能绕过网关(直连 IP、sidecar 误配置),业务服务把「网关一定验过」当前提就是单点失守。常见做法:网关验签后向下游签发内部短时凭证(签名身份 Header),业务服务校验来源再做资源级授权。

延伸:零信任架构彻底废弃「内网默认可信」,服务间 mTLS 加逐次鉴权是终态;中小团队至少做到外部请求必过网关、敏感接口服务内二次校验。

追问:网关解析 JWT 后怎么把身份传给下游?提示:签名 Header 只传必要 claims,防 Header 膨胀与下游伪造。