微服务面试题总结:服务拆分、通信、数据一致性与可观测性

这部分内容结合 JavaGuide 的分布式系统常见面试题总结、高可用系统设计常见面试题总结和系统设计与场景题总结,重点补齐微服务架构的高频追问。
微服务不是把应用拆成多个进程就结束了。一次拆分会同时带来网络通信、数据一致性、部署、监控和组织协作问题。回答这类题时,既要讲收益,也要主动说明复杂度和适用边界。
微服务基础与选型
⭐️什么是微服务架构?
微服务架构把一个应用拆成一组围绕业务能力构建、可以独立开发和部署的服务。每个服务拥有明确边界,通过 API 或消息协作,并对自己负责的数据和业务规则保持控制。
“服务数量多”不是微服务的定义。真正重要的是:
- 服务是否围绕业务边界拆分。
- 能否独立开发、测试、部署和扩缩容。
- 数据和接口边界是否清晰。
- 团队是否能独立负责服务的完整生命周期。
如果多个所谓的微服务必须一起改、一起发版,还共享同一批数据库表,它们很可能只是一个分布式单体。
单体、分布式系统、集群和微服务有什么区别?
| 概念 | 关注点 |
|---|---|
| 单体架构 | 应用的功能通常作为一个整体构建和部署 |
| 分布式系统 | 组件分布在多个网络节点上,共同完成任务 |
| 集群 | 多个实例共同提供同一种能力,常用于扩容和容灾 |
| 微服务架构 | 按业务能力拆分为可独立演进的服务,是分布式架构的一种形式 |
一个单体应用也可以部署多个实例组成集群;一个分布式系统也不一定采用微服务;同一个微服务通常还会部署多个实例形成集群。
⭐️微服务架构有哪些优点和缺点?
主要优点包括:
- 独立交付:服务可以按自己的节奏开发、测试和发布。
- 独立扩容:只扩容压力大的服务,不必复制整个应用。
- 故障隔离:设计得当时,单个非核心服务故障不会拖垮整个系统。
- 技术与组织自治:团队可以围绕业务能力负责端到端交付。
- 降低单个代码库的认知负担:服务边界稳定时,一个团队不必理解整个系统才能修改局部功能。
对应的成本也很明显:
- 本地调用变成网络调用,会遇到超时、重试、部分失败和版本兼容问题。
- 跨服务事务和查询更复杂。
- 部署、配置、服务发现、流量治理和可观测性要求更高。
- 集成测试与故障排查链路变长。
- 边界拆错后,跨服务调用和协作成本可能比单体更高。
微服务是在用系统和运维复杂度换取独立演进能力,不是默认更先进的答案。
哪些场景不适合直接上微服务?
下面几种情况通常应该谨慎:
- 产品仍在快速试错,业务边界频繁变化。
- 团队规模小,一个服务长期只有一两个人维护。
- 流量和交付频率不高,单体尚未遇到明确瓶颈。
- 自动化测试、持续交付、监控告警和容器平台等基础能力不足。
- 业务有大量强事务操作,拆分后收益不够抵消一致性成本。
这时可以先采用模块化单体:在一个部署单元内建立清晰模块边界,限制跨模块依赖。等团队、业务和基础设施达到一定阶段,再逐步抽取服务。
服务拆分
⭐️微服务应该如何拆分?
优先按照业务能力和领域边界拆分,而不是按 Controller、Service、DAO 技术分层,也不要机械地“一张表一个服务”。
一个实用的拆分过程是:
- 梳理核心业务流程、领域概念和团队职责。
- 找出内聚的业务能力和明确的上下游关系。
- 为每个候选服务定义职责、数据所有权和对外契约。
- 检查跨服务调用频率、强一致性要求和共同变更频率。
- 从边界清晰、耦合较低的模块开始验证,再迭代调整。
服务粒度没有固定行数或接口数。两个模块如果总是一起修改、一起发布,并且需要大量同步调用,可能不该过早拆开。
什么是限界上下文?它和服务拆分有什么关系?
限界上下文来自领域驱动设计,表示一套领域模型和术语有效的明确边界。同一个词在不同上下文里可以有不同含义。例如,“用户”在会员系统里关注等级和积分,在物流系统里关注收货人与地址。
限界上下文可以作为服务边界的重要候选,但不是机械的一一对应关系。一个上下文可能根据规模由多个服务实现,小型系统也可能让多个上下文先存在于一个模块化单体中。关键是不要让一个服务直接修改另一个上下文的内部模型和数据。
⭐️什么是分布式单体?有哪些典型表现?
分布式单体表面上拆成多个服务,实际上仍像一个紧耦合的单体运行。常见表现有:
- 多个服务共享数据库表,任一服务都能直接修改别人的数据。
- 一次需求要同时修改和发布多个服务。
- 核心请求串联大量同步调用,任何节点失败都会让整条链路失败。
- 服务之间共享内部类库和数据库模型,版本升级必须步调一致。
- 没有契约兼容策略,只能安排“停机联调”或统一发版。
- 每个服务很小,但跨服务沟通和排障成本很高。
改进方向不是继续拆得更细,而是重新梳理业务边界、数据所有权和接口契约。必要时合并总是共同变化的服务,也是一种合理优化。
服务通信与基础设施
⭐️微服务之间如何选择 REST、RPC 和消息队列?
| 方式 | 特点 | 适用场景 |
|---|---|---|
| REST/HTTP | 通用、可读、生态成熟 | 对外 API、跨技术栈、普通查询和命令 |
| RPC | 接口约束强、序列化高效、调用体验接近本地 | 内部低延迟调用、接口稳定的服务间通信 |
| 消息队列 | 异步、削峰、解耦,可广播 | 事件通知、耗时任务、最终一致性和削峰 |

需要立即返回结果的查询通常适合同步调用;下游可以稍后处理、一个事件有多个订阅者或需要削峰时,更适合消息。实际系统经常混合使用。
无论选哪种方式,都要补充超时、幂等、重试、版本兼容和可观测性。RPC 框架让远程调用写起来像本地调用,但不会消除网络的不可靠性。
同步调用链过长会有什么问题?
假设请求依次调用 A → B → C → D,整条链路的延迟会叠加,任意依赖超时都可能让请求失败。上游重试还可能放大下游流量,引发级联故障。

常见优化包括:
- 合并高度耦合、每次都要连续调用的服务。
- 能并行的查询并行执行,但设置总超时预算。
- 非实时步骤改成异步事件。
- 缓存稳定数据,或者为查询建立读模型。
- 为每一跳设置超时、限流、熔断和并发隔离。
- 避免在循环中发起大量逐条 RPC,改为批量接口。
服务拆分后,接口数量增加很正常;每个页面请求触发几十次串行 RPC,则通常是在提醒边界或聚合方式需要调整。
⭐️什么是服务注册与发现?
服务实例会扩缩容、重启,IP 和端口并不固定。服务注册与发现用于维护服务名到可用实例的映射,让调用方不用写死地址。
常见流程是:
- 服务实例启动后向注册中心注册地址,并持续上报健康状态或租约。
- 调用方或负载均衡组件根据服务名获取可用实例列表。
- 负载均衡算法选择一个实例发起请求。
- 故障实例被摘除,新实例加入列表。
Kubernetes 中通常由 Service 为一组 Pod 提供稳定访问入口和服务发现,不一定需要应用再接入独立注册中心。具体选择取决于运行平台、治理能力和历史架构。
客户端发现和服务端发现有什么区别?
- 客户端发现:调用方获取实例列表并自己做负载均衡。优点是少一次转发、策略灵活;缺点是每种语言都要接入发现和负载均衡能力。
- 服务端发现:客户端请求负载均衡器、网关或代理,由中间层选择实例。优点是客户端简单、治理集中;缺点是多一跳,并且中间层本身需要高可用。
Service Mesh 常通过 Sidecar 或节点代理下沉服务发现、负载均衡、mTLS 和可观测性,让业务代码少感知治理逻辑,但也会增加平台复杂度和资源开销。
⭐️API 网关应该负责什么?
API 网关位于客户端和后端服务之间,常见职责有:
- 路由与协议适配。
- 统一认证入口与基础授权校验。
- 限流、黑白名单和请求大小限制。
- 灰度发布和流量分配。
- 日志、指标、追踪上下文和统一错误处理。
- 针对特定客户端做轻量聚合。

网关不应该承载大量领域业务逻辑,也不能成为所有服务共享的“超级 Service”。业务规则放进网关后,任何需求都要改网关,最终会形成新的单体和性能瓶颈。
配置中心解决什么问题?配置变更要注意什么?
配置中心统一管理不同服务、环境和集群的配置,并提供版本、权限、审计和动态更新能力。它解决的是大量实例配置分散、变更难追踪和环境容易不一致的问题。
动态配置并非越快生效越好。配置项需要说明:
- 是否支持运行时刷新,还是必须重启实例。
- 是否有类型、范围和依赖校验。
- 如何灰度、回滚和审计。
- 配置中心不可用时,实例能否使用本地快照启动或继续运行。
- 密钥是否交给专门的密钥管理系统,而不是作为普通明文配置保存。
核心配置变更应像代码发布一样可验证、可回滚,不能把配置中心当成线上临时改参数的记事本。
数据拆分与一致性
⭐️为什么强调每个微服务拥有自己的数据?
数据库按服务拆分的核心是数据所有权。其他服务只能通过该服务公开的 API 或事件使用数据,不能绕过它直接修改内部表。
这样做的好处是服务可以独立调整模型、索引和存储技术,也避免多个服务通过数据库结构形成隐式耦合。但代价是跨服务 Join、事务和统计查询会变复杂。
“每服务一个数据库”是逻辑隔离原则,不一定要求每个服务都独占物理数据库实例。早期可以共享实例但使用独立 Schema 和账号权限,随着规模增长再做物理隔离。
为什么不建议多个服务直接共享数据库表?
共享表看起来省掉了接口调用,却会带来长期耦合:
- 一个服务改表结构可能破坏其他服务。
- 数据校验和业务规则被多套代码重复实现。
- 无法确认谁对数据负责,排查修改来源困难。
- 服务无法独立扩容、迁移存储或发布。
- 任意服务都可能绕过拥有者的权限与审计逻辑。
迁移阶段可以短期共享,但应明确表的唯一拥有者,并通过变更数据捕获、事件或 API 逐步切断其他服务的直接访问。
⭐️跨服务查询如何实现?
常见方案有:
- API 组合:聚合服务并行调用多个服务,再组合结果。实现直接,适合数据量小、实时性要求高的查询。
- CQRS/物化视图:订阅各服务事件,建立面向查询的冗余读模型。查询快、可减少运行时调用,但要接受延迟并处理事件重放与修复。
- 离线数仓或湖仓:面向报表和分析,把业务数据同步到分析系统,不在生产库做大规模跨服务统计。
- 搜索索引:把需要检索的字段同步到 Elasticsearch 等搜索系统,适合复杂搜索而非权威数据判断。
选择时要说明实时性、数据量、查询频率和一致性要求。不要为了还原单体里的一个 Join,就让多个服务共享数据库。
⭐️跨服务业务如何保证数据一致性?
微服务通常无法用一个本地事务包住所有服务。常见做法是把业务拆成多个本地事务,通过事件推进,并使用 Saga、TCC、事务消息或 Outbox 等方案实现最终一致性。

以创建订单并扣减库存为例,可以这样设计:
- 订单服务在本地事务中创建“处理中”订单,并记录待发布事件。
- 事件可靠投递后,库存服务执行幂等扣减。
- 库存服务发布成功或失败事件。
- 订单服务把状态更新为“已确认”或执行取消补偿。
- 定时任务扫描长时间未完成的状态,重试或进入人工处理。
关键不是喊出“最终一致性”,而是讲清楚中间状态、幂等、重试、补偿、超时和对账。
Saga 模式是什么?
Saga 把一个跨服务长事务拆成一系列本地事务。每一步成功后推进下一步;某一步失败时,按业务定义执行前面步骤的补偿操作。

Saga 有两种常见协调方式:
- 编排式:各服务监听事件并发布下一事件。耦合较低,但流程长后不容易看清全局状态。
- 协调式:由协调器显式发送命令并记录流程状态。流程可见性更强,但协调器要保持高可用,且不能塞入所有领域逻辑。
补偿不是数据库回滚。例如,已经发送的优惠券不能“撤回时间”,只能作废或发起反向操作。中间状态也可能被其他请求看到,因此状态机和业务约束必须一起设计。
⭐️Transactional Outbox 如何避免“数据库写成功但消息发送失败”?
Transactional Outbox 把业务数据和待发送事件写入同一个本地数据库事务。事务提交后,由后台任务或 CDC 读取 Outbox 表并发送消息,发送成功后再标记或清理记录。
它保证的是:只要业务事务提交,待发送事件就不会凭空丢失。但它不保证消息只发送一次。发布端可能在“消息发送成功、状态还没更新”时崩溃,导致重复发送,所以消费者必须幂等。
Outbox 还要处理:
- 失败重试与退避。
- 积压、死信和长时间未发送告警。
- 事件顺序和分区键。
- 历史记录清理。
- 事件 Schema 的兼容升级。
消费者如何实现幂等?
常见方式是为每个业务操作提供稳定的幂等键或事件 ID,在本地事务中同时完成“检查/记录消费结果”和业务更新。
可以使用:
- 数据库唯一约束防止重复插入。
- 去重表记录已经处理的事件 ID。
- 按业务状态机拒绝重复或非法状态迁移。
- 更新语句带版本号或当前状态条件。
- 对天然可覆盖的操作使用幂等写入。
仅在 Redis 中 SETNX 一个短期 Key 不一定可靠:Key 可能先过期,业务事务也可能在记录成功后回滚。幂等记录最好与核心业务变更处于同一个本地事务,或者能够通过业务状态恢复判断。
稳定性与发布
⭐️超时、重试、熔断、限流和隔离分别解决什么问题?
| 手段 | 主要作用 |
|---|---|
| 超时 | 限制一次调用最多等待多久,避免资源无限占用 |
| 重试 | 应对短暂故障,但要求操作幂等,并设置次数、退避和抖动 |
| 熔断 | 下游持续失败时快速拒绝,减少无效调用并等待恢复 |
| 限流 | 控制进入系统或依赖的请求速率,保护容量边界 |
| 隔离 | 限制单个依赖占用的线程、连接或并发,避免故障扩散 |

这些手段必须组合设计。没有超时的重试会堆积更多请求;多层同时重试会产生重试风暴;只有熔断没有降级,用户仍然只会得到失败。
更完整的容错和流量治理题见高可用系统设计常见面试题总结。
如何设置微服务调用的超时与重试?
先为整条用户请求设置总时间预算,再把预算分配给各个下游,内层超时应小于外层剩余时间。超时需要覆盖连接、读取、连接池等待等阶段。
重试只适合短暂故障和可幂等操作,通常使用有限次数的指数退避并加入随机抖动。下列场景不要盲目重试:
- 请求已经接近总超时。
- 明确的参数错误或权限错误。
- 非幂等写操作没有幂等键。
- 下游已经过载或熔断。
- 上下游多层都在重试。
对写请求,“客户端超时”不代表服务端没有执行成功。调用方应通过幂等键或查询接口确认最终结果,而不是立刻重复提交。
⭐️Liveness、Readiness 和 Startup 探针有什么区别?
- Liveness Probe(存活探针):判断容器是否需要重启。只有进程进入无法自愈的状态时才应失败。
- Readiness Probe(就绪探针):判断实例是否应该接收流量。未就绪时实例会从服务端点中移除,但不一定重启。
- Startup Probe(启动探针):给慢启动应用更长的初始化时间;成功前不会执行存活和就绪探测。
不要把所有外部依赖都放进存活探针。数据库短暂故障时,如果所有实例同时判定不存活并重启,反而会扩大故障。外部依赖影响接流量时,更适合体现在就绪状态中。
微服务如何实现优雅停机?
发布或缩容时,实例不能收到终止信号后立刻退出。常见流程是:
- 先把实例标记为不就绪,停止接收新流量。
- 给注册中心、负载均衡器和调用方留出摘除传播时间。
- 等待正在处理的请求结束,并停止领取新的消息任务。
- 提交或回滚进行中的本地事务,释放连接和线程。
- 超过最大宽限时间后再强制退出。
同时要处理长连接、消息消费者和定时任务。只关闭 HTTP 端口,却让消息处理到一半被终止,仍可能产生重复消费和中间状态。
微服务接口如何做到向后兼容?
接口升级优先采用“扩展而非破坏”:新增可选字段、让旧字段保留一段迁移期、消费者忽略未知字段。删除字段、改变语义、修改枚举或把可选字段改为必填都可能破坏旧客户端。
常见保障手段包括:
- OpenAPI、Protobuf 或事件 Schema 版本管理。
- Consumer-driven Contract Test(消费者驱动契约测试)。
- 先发布兼容新旧格式的消费者,再发布生产者,最后清理旧字段。
- 灰度发布并监控错误率、延迟和业务指标。
- 数据库变更采用 expand-and-contract,避免代码和表结构必须同时切换。
版本号能帮助区分不兼容接口,但不能代替兼容设计和迁移流程。
可观测性与安全
⭐️微服务为什么需要日志、指标和链路追踪?
一次请求可能跨越多个服务和消息队列,只看单机日志很难回答“慢在哪里、失败发生在哪一跳”。三类信号关注点不同:
- 指标(Metrics):适合看趋势和告警,例如请求率、错误率、延迟分位数、CPU、连接池和消息积压。
- 链路追踪(Traces):展示一次请求跨服务的调用路径、每个 Span 的耗时和错误。
- 日志(Logs):记录具体事件和上下文,适合查看错误细节与业务线索。
服务之间需要传播 Trace Context,把同一次请求的 Span 关联起来;异步消息也要在消息头中携带并恢复上下文。日志中保留 trace ID,才能从告警和链路快速跳到具体日志。
微服务应该监控哪些关键指标?
可以按四层回答:
- 入口服务:请求率、成功率、P50/P95/P99 延迟、限流与熔断次数。
- 应用资源:CPU、内存、GC、线程池、连接池和队列等待时间。
- 依赖资源:数据库慢查询与连接数、缓存命中率、MQ 消费延迟与积压、下游调用耗时。
- 业务指标:下单成功率、支付成功率、库存异常、订单停留在中间状态的数量。
技术指标正常不代表业务正常。比如 HTTP 都返回 200,但支付成功率突然下降,只有业务指标能第一时间暴露问题。
⭐️认证和授权应该放在网关还是服务内部?
网关适合统一完成 Token 基础校验、流量限制和外部入口保护,但服务内部仍要做与业务资源相关的授权。否则,内部调用绕过网关、错误路由或网络边界变化时,就可能出现越权。
一个常见分工是:
- 网关验证外部凭据,生成或转发经过签名保护的身份上下文。
- 服务验证上下文来源,并根据用户、租户、角色和目标资源执行授权。
- 服务间调用使用工作负载身份和 mTLS,不能把“来自内网”等同于可信。
- 敏感操作记录审计日志,凭据和个人信息不进入普通日志。
详细的 Token、SSO 和权限模型见认证与授权常见面试题总结,常见攻击与防护见 Web 安全常见面试题总结。
微服务迁移
⭐️如何把单体系统逐步改造成微服务?
不建议一次性重写。比较稳妥的是绞杀者模式:在保留单体运行的同时,把边界清晰的能力逐步抽到新服务,并通过路由把相应流量切过去。
可以按下面的顺序推进:
- 先在单体内部建立模块边界,治理跨模块直接依赖。
- 补齐自动化测试、监控、发布和回滚能力。
- 选择变化独立、耦合低、收益明确的模块作为第一个服务。
- 明确新服务的数据所有权,通过 API、事件或 CDC 迁移数据访问。
- 灰度切流,比较新旧链路的结果和业务指标。
- 稳定后下线单体中的旧逻辑,再选择下一个模块。
第一个被拆出的模块不一定是最核心、最复杂的模块。用边界清晰的非核心能力验证平台和团队协作方式,失败成本通常更低。
设计一个微服务方案时,回答应包含哪些内容?
可以用下面的顺序组织:
- 业务和约束:用户量、核心流程、一致性、延迟和可用性目标。
- 服务边界:为什么这样拆,谁拥有数据,哪些模块暂时不拆。
- 接口与事件:同步/异步选择、契约、幂等键和版本策略。
- 数据一致性:本地事务、中间状态、重试、补偿和对账。
- 稳定性:超时、限流、熔断、隔离、降级和容量预估。
- 可观测性:日志、指标、追踪、业务告警和审计。
- 交付与迁移:灰度、回滚、数据迁移和故障预案。
面试官更关注取舍是否与约束一致,而不是服务数量。能够主动说出“这里先不拆”和“这个方案会带来什么成本”,通常比堆砌组件名更有说服力。
参考资料
- Microsoft Azure Architecture Center:Microservices Architecture Style
- Kubernetes Documentation:Service
- Kubernetes Documentation:Liveness, Readiness, and Startup Probes
- AWS Prescriptive Guidance:Database-per-service Pattern
- Microservices.io:Saga Pattern
- Microservices.io:Transactional Outbox Pattern
- OpenTelemetry:Observability Primer
写在最后
感谢你能看到这里,也希望这篇文章对你有点用。
JavaGuide 坚持更新 6 年多,近 6000 次提交、600+ 位贡献者一起打磨。如果这些内容对你有帮助,非常欢迎点个免费的 Star 支持下(完全自愿,觉得有收获再点就好):GitHub | Gitee。
如果你想要付费支持/面试辅导(比如简历优化、一对一提问、高频考点突击资料等)的话,欢迎了解我的知识星球。已经坚持维护六年,内容持续更新,虽白菜价(0.4元/天)但质量很高,主打一个良心!

