微服务面试题 - 2026-06-19 18:19
生成时间:2026-06-19 18:19 | 共 10 题 | 难度递进(基础→进阶→高阶)
【面试题】1. 可观测性三根支柱——Logging、Metrics、Tracing 各自解决什么问题?为什么说"光有日志不够"?
【标准答案】三支柱分工明确。Logging(日志)——记录"发生了什么":某个请求在哪个时间点执行了哪行代码、入参出参是什么、异常堆栈是啥。日志是离散的、非结构化的,适合单点排查但不适合全局分析。Metrics(指标)——回答"系统现在怎么样":QPS、P99延迟、错误率、CPU、内存,按时间窗口聚合。Metrics是聚合后的数字,能一眼看出趋势但看不到单次请求细节。Tracing(链路追踪)——回答"一次请求经过了哪些服务":每个 Span 记录服务间调用关系和耗时分布。为什么光有日志不够?30个微服务、QPS过万的时候,出了慢请求你去 grep 日志——请求ID相同的日志散落在30个服务的日志文件里,拼出一条完整调用链要半小时。Prometheus看Metrics告诉你"订单服务P99飙了",然后Tracing告诉你"是订单→风控这一段慢了",最后Logging告诉你"风控执行了一条慢SQL"。三者是"宏观趋势→链路定位→微观根因"的递进关系。面试定音:可观测性不是装三个工具就完事,核心是让三者串联——Metrics告警、Tracing定位、Logging定因。
【面试题】2. 灰度发布和金丝雀部署有什么区别?Kubernetes + Istio 怎么做金丝雀发布?
【标准答案】两者都是"先让一小部分流量吃新版本",但粒度和成熟度不同。灰度发布更宽泛——按用户ID、地区、白名单分流,可以在网关层、Nginx、Nacos权重都能搞。金丝雀部署是灰度的特化——严格按流量百分比切(比如 5%→20%→50%→100%),每个阶段观察 Metrics 自动判断是否继续。类比:灰度是"先让上海的用户升级",金丝雀是"先让 5% 的流量打新版本,不管你是谁"。K8s + Istio 金丝雀三步:一、部署 v1(旧版)和 v2(新版)两套 Deployment,Service 的 label selector 只选 v1。二、Istio 的 VirtualService 配 weight: v1: 95, v2: 5——5% 流量路由到新版。三、观察 Grafana 的 v2 错误率和延迟,Istio 支持自动渐进加权重。核心差异一句话:灰度是按"谁"来切,金丝雀是按"多少"来切。面试亮点:金丝雀的"金丝雀"来自矿工——下矿前先放一只金丝雀,鸟扛不住了人就不能下。新版就是那金丝雀,挂了立刻撤回,不会全量爆炸。
【面试题】3. ACID 和 BASE 的区别是什么?为什么分布式系统只能追求 BASE 而不追求 ACID?
【标准答案】ACID 是传统关系型数据库的四大金刚:Atomicity(原子性——一个事务要么全做要么全不做)、Consistency(一致性——事务前后数据满足所有约束)、Isolation(隔离性——并发事务互不干扰)、Durability(持久性——提交后数据不丢)。BASE 是分布式系统的不完美妥协:Basically Available(基本可用——挂几个节点照样服务)、Soft state(软状态——允许中间状态,不要求每时每刻一致)、Eventually consistent(最终一致性——给点时间数据最终会一致)。为什么分布式系统追不了 ACID:两个不同微服务各管各的数据库,要保证"订单创建→扣库存"的强一致性,就得加分布式事务——2PC 锁资源锁几十秒,性能没法接受。分布式系统的本质是用"短暂的不一致"换"高性能+高可用"。BASE 落地就是你该发消息发消息、该做补偿做补偿,最终两边数据对上就行。经典例子:支付宝转钱——转出方扣了、转入方还没收到那几秒是"软状态",最终两边一致就是"最终一致性"。面试一句:ACID 是理想——在封闭系统里可以,BASE 是现实——在开放分布式环境里是唯一可行的路。
【面试题】4. Eureka、Consul、Nacos 三款注册中心的核心架构差异在哪?为什么 Nacos 在国内一统江湖了?
【标准答案】Eureka(Netflix 开源)——纯 AP 系统,强调可用性。每个节点平等,客户端拉取全量注册表本地缓存。致命伤是自我保护模式:服务器和客户端心跳断了也不剔除实例——宁可让请求打到挂了也不踢,AP 到极致但容易误导。Netflix 已停止维护。Consul(HashiCorp)——纯 CP 系统,强一致性靠 Raft 算法选举 Leader。健康检查很丰富(HTTP/TCP/gRPC/脚本),多数据中心支持原生一级。代价是 Leader 挂了整个集群暂时不可写,性能比 AP 系统低。Nacos(阿里)——EPHEMERAL 临时实例走 AP(Distro 协议),PERSISTENT 持久实例走 CP(Raft)。一套注册中心同时满足两种诉求,外加配置中心二合一。三个优势:一,完美融入 Spring Cloud Alibaba 全家桶——sentinel、seata、rocketmq、dubbo 全部原生对接;二,AP+CP 切换灵活——金融场景用 CP 保证强一致、互联网场景用 AP 保证高可用;三,中文文档和社区活跃——国内排查问题搜索效率高一个数量级。面试定音:Eureka 死了,Consul 出海强,Nacos 在国内就是默认答案——不是因为技术多强,是因为生态完整度和学习成本最低。
【面试题】5. 可靠消息最终一致性方案中,"本地消息表"和"事务消息"两种实现各自怎么保证不丢消息?RocketMQ 的事务消息是怎么做的?
【标准答案】两种实现目标一样——业务操作和发消息要么都成功要么都失败。本地消息表最朴素:在订单库同一事务内,插订单记录 + 插入一条"待发送消息"记录(message_status=0),然后异步定时任务扫描消息表,轮询待发送的消息→发 MQ→发送成功后把状态改成 1。兜底:MQ 发送失败就重试,直到成功;消费端做幂等。事务消息(RocketMQ 专有)更优雅——发半个消息给 Broker(half message,消费者暂时不可见)→ 执行本地事务 → 事务成功返回 COMMIT,Broker 把消息改成可见 → 事务失败返回 ROLLBACK,Broker 丢弃。关键兜底:本地事务执行太久或挂了没给回应,Broker 定期回查(check)——调生产者的 check 接口,查本地事务状态决定提交还是回滚。核心区别:本地消息表靠"定时轮询",事务消息靠"Broker 回查"。本地消息表不用依赖特供版 MQ(任意 MQ 都能用),但定时任务扫描有延迟;RocketMQ 事务消息零延迟实时但绑定 RocketMQ。面试总结:两种方案的本质都是"2PC 的异步版本"——用一个中间态(消息记录 / half message)桥接本地事务和消息发送,用重试或回查做兜底。
【面试题】6. 漏桶算法、令牌桶算法、滑动窗口计数三者有什么区别?Sentinel 和 Gateway 默认用的什么限流策略?
【标准答案】三种限流算法解决不同层面的问题。漏桶(Leaky Bucket)——请求先进桶,桶以恒定速率漏水(处理请求)。桶满了多出来的请求直接丢弃。漏桶把"突发流量"强制整形成"匀速流量"——不管你来多猛,我按固定速率处理。令牌桶(Token Bucket)——以恒定速率往桶里放令牌,请求要先拿令牌才能处理。桶能存令牌——桶容量 100、速率每秒 50,意味着瞬间能放行 100 个请求(桶里积攒的令牌),然后再按 50/s 的速率走。令牌桶允许突发。滑动窗口计数——把时间轴切成小格子,统计当前窗口内通过的请求数,超了就限流。比固定窗口精准在:不整点重置(11:59:59 发了 1000 个、12:00:00 归零又发 1000 个不叫限流)。Sentinel 默认用滑动窗口 + 令牌桶——QPS 限流用滑动窗口,预热模式用令牌桶(冷启动时速率慢慢拉到正常,和 Guava RateLimiter 的 SmoothWarmingUp 原理一致)。Gateway 内置 RequestRateLimiter 用 Redis + Lua 实现令牌桶。选型口诀:要削峰填谷、绝对匀速→漏桶;要允许合理突发→令牌桶;要精确 QPS 控制→滑动窗口。面试一句:漏桶和令牌桶的核心区别是"桶里装的是请求还是令牌"——漏桶存请求(排队),令牌桶存令牌(允许突发),这是所有限流器的底层 DNA。
【面试题】7. Redisson 分布式锁的"看门狗"机制怎么防止锁在业务没执行完就自动过期?底层 Lua 脚本加锁和续期的流程怎么走?
【标准答案】普通 Redis 分布式锁的经典痛点:设了个 30 秒过期,结果业务跑了 35 秒——锁在 30 秒时过期了,另一个线程拿到锁进去,两个线程同时操作同一条数据,锁白加了。Redisson 的解决手段就是看门狗(Watchdog)——在加锁成功后启动一个后台定时任务,每 10 秒(lockWatchdogTimeout/3,默认 30 秒 internalLockLeaseTime)给锁续期 30 秒。续期不是无限续——业务执行完 unlock 时取消续期任务。底层 Lua 加锁脚本做了三件事:EXISTS key 检查锁是否存在→不存在就 HSET key threadId 1 + PEXPIRE key 30000(存线程 ID 作为字段值,可重入计数 + 设 30 秒过期)→返回 nil 加锁成功。锁已存在就检查是否线程 ID 一样——是就 HINCRBY key threadId 1 重入计数 + 续期→不是就返回锁的剩余 TTL 让当前线程等待。续期 Lua 脚本简单:EXISTS key → PEXPIRE key 30000。两个重要细节:一、lock() 不传 leaseTime 才自动续期——如果你手动指定了过期时间(比如 lock(10, TimeUnit.SECONDS)),看门狗不启动,到点就过期;二、Redis 主从切换时锁可能丢——主挂从升主,看门狗续期在主上的数据没同步到从,所以高一致性场景还是得 Redlock 或多 Redis 实例。面试定音:看门狗就是把"我告诉你什么时候过期"改成"我死了你再过期"——加锁方还活着就续,挂了就自然过期,完美解决了锁提前释放的问题。
【面试题】8. CQRS 的"命令"和"查询"为什么要分两个模型?是不是所有微服务都值得上 CQRS?写库和读库的数据同步有哪些方案?
【标准答案】CQRS(Command Query Responsibility Segregation)的核心思想是把"写操作"和"读操作"用不同的数据模型和数据库——命令模型负责业务规则校验 + 写入,用严格的关系型数据库;查询模型负责高性能读取,字段按查询需求定制,用 ES 或宽表甚至 Redis。为什么要分?传统 CRUD 模式下同一个订单表既要支持复杂的写校验(订单状态机、库存扣减校验)又要支持多维度的查(按用户查、按时间查、按商品查、按状态查)——一个表挂几十个索引,写一行业务锁表+更新索引,读一个多字段 JOIN 慢到飞。分拆后各干各的最擅长的事。但不是所有服务都配 CQRS——CURD 简单的如字典服务、简单配置服务上 CQRS 纯属过度设计。CQRS 适合读远大于写且读查询模式复杂的场景——订单列表、用户订单历史、BI 报表。读写库同步三种方案:一、应用双写——Service 层同时写 MySQL 和 ES,简单但强耦合;二、CDC(Change Data Capture)——用 Canal/Debezium 监听 MySQL binlog,解析后写到 ES / Redis,无侵入但增加运维组件;三、事件驱动——写完 MySQL 后发 Domain Event,独立的同步服务消费事件更新读库,最终一致性最好。面试总结:CQRS 不是免费的——代价是"数据最终一致性 + 架构复杂度"。不是所有服务都值得,但当你的"查"和"写"互相拖累时,CQRS 就是解药。
【面试题】9. Nacos 客户端怎么感知到配置变更?长轮询和长连接推送哪个更靠谱?配置变更后 Spring Bean 不刷新的坑怎么填?
【标准答案】Nacos 配置变更感知的核心机制是长轮询(Long Polling)——客户端发起 HTTP 请求拉到当前配置 + MD5 值,服务端不是立即返回,而是"挂住"这个连接。挂住期间如果 Nacos Server 上的配置 MD5 变了→立刻返回新配置;如果挂住 30 秒还没变化→返回空并告诉客户端"没变"。客户端收到返回后立即发下一个长轮询请求——客户端和 Server 之间始终保持一个"待命连接",变更从"查询"变成"推送"。为什么用长轮询而不是长连接推送?长轮询基于 HTTP,穿透防火墙代理无压力;长连接(WebSocket/gRPC stream)需要保持连接,大规模客户端(几千个微服务实例)对服务端内存压力大。长轮询在 Nacos 1.x 用 HTTP 实现,2.x 升级为 gRPC——本质还是长轮询但走 gRPC 双向流,性能更好。填坑:配置变更后 @Value 标注的字段自动刷新,但如果你在 Bean 初始化时用配置做了缓存(比如 @PostConstruct 里把配置值赋给静态变量),那配置变了这个缓存不动。解法:监听 RefreshEvent——@EventListener(RefreshEvent.class) 里重新初始化缓存;或者用 @RefreshScope 让 Bean 本身懒加载重建——代价是每次取 Bean 走代理,有性能开销。面试一句:Nacos 的"实时推送"其实是假推送——客户端一直在门口等着,Server 有变化就在下一个 30 秒窗口里通知到,不是真正的"我来敲门"。
【面试题】10. 你是技术负责人,公司要把一个 20 万行代码的单体 Spring Boot 应用拆成微服务——从 0 到 1 你准备分几步走?哪些是最容易踩的坑?
【标准答案】分五步走,每步至少 2-4 周的迭代窗口。第一步:不下刀、先摸底。花两周画清楚现有模块间的依赖图(Maven 依赖 + 数据库表关联),识别出"最独立的模块"。判断标准:这个模块有自己的 Controller、独立的数据表、不跨表 JOIN 其他模块的表——它就是第一候选。第二步:数据库拆分明细外键。有外键的→先扒掉改成应用层校验;有跨表 JOIN →改成两次查询在应用层拼合。这一步最疼也是最重要——数据库不拆干净,微服务就是分布式单体。第三步:拆第一个服务。选一个"改得少、风险低、业务价值高"的模块(比如短信/文件服务),用 API 网关路由灰度切流,老和新各扛 50%。第四步:建立基础设施。这一步很多人漏——注册中心(Nacos)、配置中心、网关、链路追踪(SkyWalking)、统一日志(ELK),没这些硬上微服务就是盲飞。第五步:一个接一个拆,每次切流灰度→监控→稳定→全量。最容易踩的三个坑:一、上来按功能拆太细——一个人维护 5 个微服务,部署、联调、排查成本指数级上升,3 人 3 服务最健康;二、共享数据库——以为拆了代码就是微服务,结果 3 个服务读同一张表,一个改字段其他全挂;三、忽视分布式事务——以为和单体一样加个 @Transactional 就完事,跨服务的数据一致性没有补偿机制,上线一个月对账对得想哭。面试核心:微服务转型的本质不是拆代码而是拆"团队能力"——团队不具备 DevOps 和分布式问题排查能力之前,单体不是坑、乱拆才是坑。
📋 考点覆盖:可观测性三支柱 | 灰度发布与金丝雀部署 | 分布式核心理论(BASE)| 服务注册与发现(Eureka/Consul/Nacos 对比)| 分布式事务(可靠消息最终一致性)| 限流算法 | 分布式锁(Redisson Watchdog)| 消息驱动架构(CQRS)| 配置中心(长轮询原理)| 项目从 0 到 1 全流程实战