最新
-
date_range 20/01/2023 18:34
点击量:次infosort网随云动label
项目深挖话术 · 6 大卖点场景化作答(追问版)
这份是 50 题的「实战补丁」。你知识都有,缺的是“结合场景说出来”。 用法:每个追问下的话术可以直接念出口。先背「万能公式」,再过 6 个王牌点。
〇、万能公式:任何追问都按这四步说
面试官追问时,别背知识点清单(那是初级表现),按这个节奏说:
- 场景钉死(1 句)——先把面试官拉进你的项目:规模 + 约束 + 痛点。 例:”我们那套 SaaS 中台是数十个租户共用一套系统,租户里既有金融这种强监管大客户,也有大量长尾小租户……”
- 知识点 → “我当时的决定”——不要说”X 适合 Y 场景”(教科书),要说”所以我选了 X“。把客观知识变成主观决策。
- 主动抛取舍(关键加分)——”为什么不用另一个方案?因为……代价是……”。P8/P9 考的是判断力,不是知识量。
- 数字收口 + 抛钩子——用量化结果收尾,再把话头引到你下一个想聊的强项。
反模式(别这么说):
- ❌ “穿透是查不存在的、击穿是热点 key 过期、雪崩是大批过期……”(背书,像复习八股)
- ✅ “我对着我那个热点配置接口说——有人拿不存在的 tenantId 来查,这是穿透,我用空值缓存挡……”(场景里长出来的)
一句话:让每个答案听起来像”我干过”,而不是”我学过”。
卖点 ① 多租户三级隔离 + ShardingSphere 分库分表
场景开场(先念这句钉住面试官):
“我们那套 SaaS 业务中台,数十个租户共用一套系统,但租户不是一类——有金融征信这种强监管、要数据主权的大客户,也有大量长尾小客户。隔离方案没法一刀切,所以我做的是三档隔离、按租户价值分级。”
追问①:实例 / Schema / 行级三种隔离各自什么场景用?
我不是三选一,是三档并存、按租户价值和监管要求分:
- 金融征信这种强监管、要数据主权、要单独备份和审计的大客户 → 独立实例,物理隔离最干净,爆炸半径最小,单租户出事不波及别人。
- 业务量中等、又想省资源的 → 独立 Schema,一个实例挂多个 Schema,连接池能复用,迁移也比独立库轻。
- 大量长尾小租户 → 共享库共享表 + tenant_id 行级隔离,成本最低、资源利用率最高。
一句话取舍:隔离性、成本、运维复杂度三者不可兼得,我用”分档”把不同价值的租户放进不同隔离级别。 这也正是我”机制下沉、策略上浮”的体现——隔离机制由底座统一提供,具体落哪一档是配置策略,不改代码。
追问②:租户 ID 怎么从网关一路透传到 MyBatis 拦截器?
一条链路四步:
- 网关从 JWT 里解出 tenantId——第一道,也是身份可信的来源(签名防篡改)。
- 进到服务,我在 Web 拦截器里把 tenantId 塞进 ThreadLocal,封了个
TenantContextHolder。 - MyBatis 拦截器在 SQL 执行前从 ThreadLocal 取 tenantId,用 JSqlParser 把 where 自动注入
tenant_id = ?——业务 Mapper 一行租户代码都不用写,这就是机制下沉。 - 写入时再自动填充 tenant_id,防漏填。
这里有两个坑我专门踩过、要主动讲:
- 异步丢上下文:线程池、
@Async、MQ 消费线程拿不到 ThreadLocal,我用 TransmittableThreadLocal 透传。 - 越权是 P0:万一某条手写 SQL 绕过拦截器就是跨租户越权事故,所以我做了 Fail-Fast——上下文为空时拦截器直接抛异常、绝不放行,再加 DB 层 RLS 兜底。隔离不能只靠一层拦截器。
追问③:分片键是不是租户 ID?大租户数据倾斜怎么办?
行级隔离那一档的共享表,分片键主要就是 tenant_id,因为这样租户内查询不跨片。但纯 tenant_id 分片有个必然的坑——数据倾斜,头部大租户的数据量可能是小租户的几百倍,全压一个分片就成热点分片。
我分两步治:
- 复合分片键——tenant_id 再叠一个业务键(user_id / order_id)二次散列,把单个大租户的数据打散到多个分片。
- 超大租户单独绑定路由到独立库——又回到分档思想:大租户物理隔离,长尾共享。
再配合:大租户读多就加多级缓存挡读、历史数据冷热分离归档。
收口钩子:所以你看,我整个隔离方案的内核就一个词——分级。它把”独立库 vs 共享表”这个看似二选一的难题,变成了一个可演进的框架,最后支撑了数十租户、多个行业,新租户上线周期从约 2 周缩到 3 天。
卖点 ② 状态机 + 事务消息处理开通 / 计费 / 审计
场景开场:
“租户开通在我们这不是一个动作,是一条长流程——建库 → 初始化数据 → 分配权限 → 计费开通 → 通知,跨租户、计费、权限、审计好几个服务,中途可能宕机、要能重试。所以我用状态机驱动 + 事务消息来扛。”
追问①:为什么选事务消息,而不是 TCC 或 Saga?
先纠正一个前提——这条链路我不是只用一种,是混合的:
- 整条开通长流程编排我用 Saga + 状态机:步骤多、每步要能补偿、要可视可追踪,状态机驱动正合适。
- 具体到”本地状态改完要通知计费、审计这些下游”这种一对多异步解耦的点,我用事务消息。
为什么不同点选不同方案:
- 不用 TCC:TCC 要每个服务写 Try / Confirm / Cancel 三个接口,侵入太重,它适合资金、库存这种要资源预留、强隔离的场景;开通通知下游不需要预留资源,没必要付这个侵入成本。
- 下游不用纯 RPC 同步编排:审计、通知这些我要的是解耦、削峰、可异步重试,发消息比同步 RPC 更松耦合,下游慢了也不拖死主流程。
一句话取舍:长流程编排用 Saga,跨服务异步通知用事务消息,强隔离资金类才上 TCC——按”一致性要求 × 流程长短 × 隔离需求”选,不是一套打天下。
追问②:本地事务和发消息怎么保证原子?本地消息表 vs RocketMQ 事务消息?
核心矛盾就是——”改本地状态”和”发消息通知下游”不能各做各的,可能状态改了消息没发出去。两种实现我都用过:
- RocketMQ 事务消息:先发半消息(下游看不到)→ 执行本地事务改状态 → 成功就 commit 半消息、失败 rollback;万一中间宕机,MQ 会回调我的本地事务回查接口,我查状态表告诉它该 commit 还是丢弃。靠的是 MQ 的事务能力。
- 本地消息表:业务操作 + 插一条消息记录在同一个本地事务里原子提交,再用定时任务扫”待发送”投出去。好处是不依赖 MQ 事务特性,任何 MQ 都能用,代价是多一张表 + 轮询延迟。
我的选择逻辑:用了 RocketMQ 就直接上事务消息;如果 MQ 不支持事务、或想要更强的自主可控,就本地消息表。两者本质一样——把”本地事务 + 发消息”凑成一个原子单元。
追问③:消费失败和事务回查怎么兜?
消费侧我兜三层:
- 消费幂等——这是至少一次投递,必然有重复,我用
tenantId + 业务ID做去重表唯一键。 - 失败自动重试,带退避。
- 重试到上限进死信队列,告警人工。
回查侧:回查接口必须幂等、且能查到本地事务的最终结果——所以我本地事务里一定会留痕(状态表 / 流水表),回查就是查这条留痕判断 commit 还是丢。
最后还有一道对账兜底——再可靠也有极端漏网。我这块真踩过坑(这段是你的故事,重点讲):曾经下游消费成功了、但回执消息丢了,上游状态卡在”处理中”。对账时表现成”上游处理中、下游已完成”,我靠对账识别这个模式 → 查下游真实状态 → 幂等补推上游状态机到终态修回来。事后加固:回执也改成可靠投递 + 状态机超时自动触发回查,从源头减少这类偏差。
收口钩子:所以我做最终一致系统的信条是——事务消息保”状态变更和通知原子”,消费幂等保”不重”,对账保”最后的兜底”,三道防线,而不是指望某一个方案 100% 可靠。
卖点 ③ 三态库存 + 分布式锁叠乐观锁双层防超卖
场景开场:
“RPA 调度引擎里有个物料调度,高并发下要抢有限的物料 / 算力资源——既要防超卖,又要防重复扣,还不能’占了不用’把资源永久占死。所以我设计了预占 / 实占 / 释放三态库存模型 + 双层锁。”
追问①:分布式锁已经互斥了,为什么还要再叠一层乐观锁?单层不够吗?(这题最关键,答出判断力)
好问题,这正是我当时反复权衡的点。单层各有各的死穴:
- 只用分布式锁:锁粒度粗、持锁久,高并发下所有请求串行排队等锁,吞吐塌下来;而且 Redis 分布式锁不是绝对安全——主从切换、锁过期、看门狗失效都可能让两个线程同时拿到锁,一旦失效就超卖。
- 只用乐观锁:高并发下大量请求同时 CAS,冲突率极高、全在重试,退化成活锁,CPU 空转。
所以我叠起来用:
- 分布式锁做粗粒度的”宏观限流”,把瞬时并发量先压下来,减少真正打到 DB 的冲突。
- 乐观锁在 DB 层做”微观正确性”兜底——扣减用一条原子 SQL:
update stock set available=available-1, reserved=reserved+1 where id=? and available>=1 and version=?,把”判断 + 扣减”做成原子的,CAS 失败就是有并发冲突。
关键在于——即使分布式锁因为主从切换失效了,乐观锁这条 SQL 仍然防得住超卖。锁负责性能、乐观锁负责正确性,这是双保险不是冗余。 这也是我一贯态度:不迷信单一锁,强一致的兜底永远压在数据层。
追问②:预占超时未确认自动回滚靠什么触发?
预占时我记录预占时间 + TTL,触发回滚用延迟队列为主、定时扫描兜底:
- 主力是延迟队列——预占成功就投一条 RocketMQ 延迟消息(或 Redis ZSet 按到期时间排序),到点消费触发释放,精准、实时。
- 定时任务兜底——扫描超时还没释放的预占,捞延迟队列漏网的(消息也可能丢)。
回滚本身必须幂等——因为”用户主动取消”和”超时自动回滚”可能同时触发,不能释放两次把库存还多了。
追问③:锁粒度怎么控制?
我按资源维度锁,绝不锁全局——锁的 key 是具体某个物料 ID / 资源池 ID,不同物料的预占互不阻塞,最大化并行。临界区也尽量短,锁里只做”判断 + 预占”,绝不把远程调用、复杂计算塞进锁里。能用乐观锁原子 SQL 替代的细粒度操作就不进分布式锁。后来我还用分库分表替换了部分单表悲观锁,把锁竞争进一步打散。
一句话:锁的范围越小越好,能不锁就用乐观锁的原子 SQL 顶。
收口钩子:所以这套的设计哲学是——三态管”资源生命周期不泄漏”,双层锁管”性能和正确性的平衡”,幂等管”不重复扣”,三个维度各司其职。
卖点 ④ 责任链 + 异步非阻塞 + 无锁队列,TPS 300 → 千级
场景开场:
“RPA 低代码平台的任务调度引擎是我从 0 到 1 搭的,支撑财务、办公这些重复流程的自动化。最早调度器是同步阻塞的,TPS 卡在 300 上不去。我重构它,靠三招把吞吐拉到千级。”
追问①:无锁队列具体怎么落地(Disruptor / CAS 环形缓冲)?
任务分发那一环我用的是 Disruptor 的思路——RingBuffer 环形数组 + 序号 + CAS:
- 为什么不用 BlockingQueue:它底层是锁 + 条件变量,高并发下生产者消费者抢同一把锁,大量线程挂起 / 唤醒,上下文切换开销很大。
- Disruptor 怎么做:用 CAS 抢序号占位、不加锁,失败就自旋;RingBuffer 预分配、内存连续,对 CPU 缓存友好;还做了缓存行填充防伪共享(False Sharing),避免多核间缓存行来回失效。
这里我会主动抛边界:无锁是用 CPU 自旋换掉锁的上下文切换,适合生产消费速率匹配、延迟敏感的场景;如果消费长期跟不上、队列老空转,自旋反而烧 CPU,那还不如阻塞队列让出 CPU。我的调度分发正好是高频、速率匹配的,所以收益明显——知道什么时候不该用,比会用更重要。
追问②:责任链每一环干什么?
我把调度流程拆成一串可插拔的 Handler,每个职责单一:鉴权 → 参数校验 → 资源 / 物料分配 → 限流 → 执行下发。
好处是开闭原则——新增一种校验就加个 Handler,不动主流程;每一环可独立测试、可动态编排。比如某个行业要加一道额外合规校验,我插一个 Handler 就行,底座不改。这又是”机制下沉、策略上浮”在调度引擎里的落地。
追问③:300 到千级的瓶颈你是怎么定位、怎么打掉的?(体现方法论,不是瞎调)
我是先压测定位、再对症下药,不是猜:
- 压测发现 CPU、内存都没满,但 TPS 上不去,线程大量阻塞在 IO 等待——说明瓶颈是同步阻塞,线程都在傻等 DB 和远程调用。
- 第一刀(收益最大):阻塞 IO 改异步非阻塞(CompletableFuture / 事件驱动),线程不再等 IO,一个线程能处理多个任务。这步把 TPS 拉起来最多。
- 第二刀:分发环节抢锁竞争激烈 → 换无锁队列,打掉锁竞争的上下文切换开销。
- 第三刀:DB 层单表悲观锁竞争 → 分库分表把锁竞争打散。
配合责任链让流程可拆分、可异步化,最后从 300 干到千级。关键是每一步都有压测数据支撑’哪个才是瓶颈’,对症下药,而不是一上来就堆技术。
收口钩子:这个项目我最想强调的不是”用了 Disruptor”,而是“先量化定位瓶颈、再一刀一刀打”的工程方法——技术是手段,定位是本事。
卖点 ⑤ 缓存三件套 + 多级缓存,热点接口 P95 降 30%
场景开场:
“中台里有一批热点接口——租户配置、权限、字典这类,高频读、读多写少,原来直接打 DB,并发一上来 P95 就高。我给这些接口做了多级缓存 + 缓存三件套防护,热点接口 P95 降了约 30%。”
追问①:穿透 / 击穿 / 雪崩分别怎么解?(对着你的接口说,别背书)
我对着我那个热点配置接口说:
- 穿透——有人拿不存在的 tenantId 来查,缓存永远不命中、全打 DB。我用空值缓存(查不到也缓存个 null,短 TTL)+ 参数校验,量大就上布隆过滤器先拦掉一定不存在的。
- 击穿——某个热点租户的配置缓存刚好过期,瞬间一堆并发一起重建打 DB。我用 Redisson 互斥锁只放一个线程去重建、其他等;更核心的热点 key 干脆逻辑过期——不设物理过期,过期了异步重建、其他线程先返回旧值,最丝滑。
- 雪崩——大批 key 同一时间过期、或 Redis 整体挂。我给 TTL 加随机抖动打散过期时间,Redis 上集群高可用,再加本地缓存兜底(Redis 挂了还有 Caffeine 扛一阵),DB 层限流熔断兜最后一道。
三件事我是一起上的,不是只防一个——因为线上这三种会同时来。
追问②:本地缓存 + Redis 的一致性怎么保证?
这是多级缓存最难的点:本地缓存分散在每个实例上,一个实例改了数据,别的实例的本地缓存还是旧的。我的做法:
- 能容忍秒级不一致的(配置、字典这类)→ 本地缓存设短 TTL,最简单。
- 不能容忍的 → 数据变更发一个 MQ / Redis pub-sub 广播,通知所有实例清掉本地缓存。
- 原则:强一致的数据根本不放本地缓存。多级缓存本来就是拿一致性换性能,所以我只把”读多写少、能容忍短暂不一致”的热点放本地,关键热点配置走广播失效。
追问③:这 30% 到底是哪个改动带来的、怎么量出来的?(最考验”用数据说话”,要诚实)
先说怎么量的:上线前后对比,全链路压测 + 生产监控(Prometheus + Grafana)看同一批热点接口的 P95,灰度放量逐步对比,不是拍脑袋报的。
这 30% 主要来自热点接口的多级缓存这一个改动——原来每次查 DB,加了 Redis + 本地缓存后,命中缓存的请求绕开了 DB 和网络 RTT,P95 自然降。
我会诚实地区分边界:这 30% 是热点接口这一类的 P95 改善,不是全站所有接口;网关限流熔断那些主要保稳定性、防雪崩,对 P95 也有贡献但不是主要来源。把因果链讲清楚,比报一个含糊的大数字更可信——面试官最爱追”这个数是怎么来的”,你主动讲清就稳了。
收口钩子:所以缓存这块我的认知是——没有完美的一致性,只有按业务容忍度做权衡;缓存是用一致性换性能,关键是想清楚哪些数据值得换。
卖点 ⑥ Kafka 海量遥测:分区 + 幂等去重 + 积压治理
场景开场:
“那个 40 多个微服务的网络管理平台,海量设备遥测和告警数据往里涌。我用 Kafka 统一采集,做分区、幂等去重和消费积压治理,把告警平均发现时间从约 30 分钟压到 1 到 3 分钟。”
追问①:幂等去重的 key 怎么设计?
设备遥测重复不可避免——设备重传、Kafka 至少一次投递都会造成重复。我的去重 key 用 设备ID + 指标类型 + 数据时间戳 组合做唯一标识,因为”同一设备、同一时刻、同一指标”只应有一条。
落地:消费前查去重表 / Redis(SETNX),已处理就跳过;去重表本身按时间窗滚动清理,别无限膨胀。如果是要落库的,干脆靠 DB 唯一索引天然挡重复插入,最省事。
追问②:真积压了怎么快速消费(扩分区 / 扩消费者 / 批量拉取)?(分”定位”和”处理”)
先定位:看 consumer lag 确认积压量和增速,再判断是”消费者挂了 / 在重平衡”还是”消费者活着但处理慢”——慢的话是下游慢、还是分区不够并行度上不去。
再处理,分应急和根治:
- 应急:① 扩消费者——但消费并行度不能超过分区数,分区不够要先加分区;② 临时关非核心逻辑、批量拉取批量处理;③ 实在压不住,新建一个多分区临时 topic,把积压消息搬过去并行消费。
- 根治:分区数预留余量、消费端批量化、下游慢的加缓存 / 限速、生产端削峰。
我这平台就是靠分区规划 + 批量消费 + lag 监控告警,把告警发现时间从 30 分钟压到 1–3 分钟。
追问③:要不要保顺序?exactly-once 怎么做?
要不要保顺序,看业务——这点要先讲清楚:
- 设备遥测大多数指标是独立打点,不需要全局有序。
- 但同一设备的状态变更事件(上线 → 告警 → 恢复)必须有序,否则状态会乱。我的做法是用设备ID 做 key 路由到同一分区,分区内 FIFO,保证”同一设备的事件有序”——不追求也做不起全局有序,那会把吞吐压成单线程。
exactly-once 我的理解:不追求 MQ 层面的精确一次,太重、代价大。我用 “Kafka 至少一次投递(不丢)+ 消费端幂等去重(不重)”凑出业务上的精确一次,这是性价比最高的工程做法。Kafka 自己的事务 / EOS 我知道有,但遥测这种海量场景上它得不偿失。
收口钩子:所以消息这块我的判断是——先问”这个场景到底需不需要顺序、需不需要精确一次”,再决定投入。不分场景一律上最强保证,是把简单问题做复杂了。
收尾:临场时把这 3 句挂在嘴边
- “我不是三选一,我是按……分级 / 分场景。”(你所有方案的共同内核:分级、混合、对症,体现架构判断力)
- “这里我专门踩过一个坑……”(主动抛真实复盘——对账偏差、字节缓冲 OOM、ISR 缩水——比答对题更打动人)
- “为什么不用另一个方案?因为……代价是……”(每个决策都给取舍,这是 P8/P9 和高级工程师的分水岭)
核心心法:面试官追问,不是要考倒你,是想看你做决策的思维过程。把”我学过”讲成”我干过、我权衡过、我踩过、我改进过”——你这 6 个点全都有真实项目支撑,稳住,讲故事。🎯
面试突击 · 业务中台 / SaaS 架构方向
候选人:石洋洋 | 10 年 Java 后端 · 分布式中间件治理 + 业务中台架构 用法:先背熟「3 分钟自我介绍」与「临场提醒」,再按板块过 50 题。每题答案可直接当口述话术,括号里是关联你简历哪句。 面试时间:今天 14:00
一、3 分钟自我介绍(口播稿,约 600 字 / 3 分钟)
语速建议 180–200 字/分钟,留 2~3 处停顿。开场报姓名+定位,主体讲一个王牌项目讲透,结尾给匹配度。
各位面试官好,我叫石洋洋,有 10 年 Java 后端研发经验,最近 5 年专注在业务中台架构和分布式中间件治理两个方向,能独立扛大型系统从 0 到 1 的整体设计,也带过 6 到 8 人的小组。
我的经历分三段。最早在青燕祥云做 AI 医学影像和工业测量系统,主攻高并发数据处理和 JVM 调优,处理过线上 OOM 和历史 SQL 重构,打下了性能和排障的底子。之后在卓朗科技做到高级工程师兼技术经理,负责公司的 SaaS 业务中台,还有金融征信报送、能源 ERP、RPA 低代码这几个行业项目。现在在紫金山实验室做技术负责人,负责云网融合平台的后端,基于 Spring Cloud 和 Kubernetes 搭了 40 多个微服务。
我最有代表性的是企业级 SaaS 业务中台,数十个租户共用一套系统。我负责整体架构,做了三件关键的事:第一,多租户数据隔离,我用 MyBatis 拦截器做到实例、Schema、行级三档隔离,再用 ShardingSphere 分库分表撑数据增长;第二,用状态机加事务消息处理租户开通、计费、审计这些跨服务流程,保证最终一致;第三,网关层做限流、熔断和灰度发布。最后的结果是,新租户上线周期从大约两周缩短到 3 天,热点接口 P95 响应时间下降了约 30%。
我做中台有一条设计准绳,叫「机制下沉、策略上浮」——底座只沉淀通用机制,行业特性以策略的方式上浮,新行业接入只声明模块清单和数据权限、不改底座。
中间件这块我也踩得比较深:扛过日均 5 亿级的消息总线,做过跨机房多级缓存、跨库分布式事务对账,复盘过脑裂、缓存雪崩、消息积压、Watch 风暴这些典型线上故障。云原生方向,我用 controller-runtime 落地过 3 个生产级 K8s 控制器,熟悉 Reconcile 和状态自愈。
总的来说,我比较适合既要从 0 搭架构、又要扛线上稳定性的角色。以上是我的简单介绍,谢谢。
速记版(忘词时抓这 6 个钩子)
- 定位:10 年 Java,业务中台 + 中间件治理双主线,带过 6–8 人。
- 三段履历:青燕祥云(性能/排障)→ 卓朗(SaaS 中台+行业项目)→ 紫金山(云网融合 40+ 微服务)。
- 王牌项目:SaaS 业务中台,数十租户一套系统。
- 三件事:三档数据隔离 / 状态机+事务消息 / 网关限流熔断灰度。
- 两个数字:上线周期 2 周→3 天,P95 降约 30%。
- 设计观:机制下沉、策略上浮;底座不改、行业上浮。
二、临场提醒(开口前默念)
- 答题用 STAR-R:背景(Situation) → 任务(Task) → 动作(Action) → 结果(Result) → 复盘(Reflection)。结果一定带数字,复盘体现你比别人多想一层。
- 先结论后展开:每题先一句话给答案/选型,再讲为什么、讲取舍。别一上来铺细节。
- 主动抛取舍:P8/P9 面试考的是判断力,不是知识点。多说「我们当时在 A 和 B 之间选了 A,因为……代价是……」。
- 承认边界:不会的别硬编。说「这块我没深入实践过,但我的理解是……,如果落地我会先验证 X」。
- 把问题往简历强项引:聊到一致性就引你的状态机+事务消息;聊到隔离就引三档隔离;聊到锁就引三态防超卖。
- 数字要一致:5 亿/日消息、40+ 微服务、TPS 300→千级、上线 2 周→3 天、P95 -30%、告警 30 分→1–3 分。别现场报出互相打架的数。
三、50 道面试题 · 详细参考答案
板块分布:① 多租户业务中台 12 | ② 微服务治理 8 | ③ 分布式事务/一致性 6 | ④ 消息队列 5 | ⑤ 缓存 5 | ⑥ 数据库/分库分表 5 | ⑦ 高并发/防超卖/调度 5 | ⑧ JVM/排障 2 | ⑨ 云原生 K8s 1 | ⑩ 架构决策/软技能 1
① 多租户业务中台(12 题)— 你的主战场,必须答到滚瓜烂熟
Q1. 多租户数据隔离有哪几种方案?各自取舍?你为什么做三档隔离?
三种主流方案,隔离强度从高到低、成本从高到低:
- 独立数据库(Database per Tenant):每租户一个库。隔离最强、爆炸半径最小、可单独备份/迁移/计费;但成本最高、运维实例多、跨租户统计难。适合大客户、强监管(金融)、数据主权要求高的租户。
- 共享库独立 Schema(Schema per Tenant):一个实例多个 Schema。隔离中等、连接池可复用、迁移比独立库轻;但 Schema 数量上千后元数据膨胀、DDL 变更要遍历所有 Schema。适合中等规模租户。
- 共享库共享表 + 行级隔离(Discriminator,tenant_id 字段):所有租户一张表,靠
tenant_id区分。成本最低、资源利用率最高、统一升级最容易;但隔离最弱,漏挂 tenant_id 就是越权事故,单表数据量大需分库分表。适合海量小租户。
我的落地是三档并存、按租户分级路由:头部/监管租户走独立实例或独立 Schema,长尾小租户走共享表行级隔离,再用 ShardingSphere 分库分表支撑共享表的数据增长。一句话总结取舍:隔离性、成本、运维复杂度三者不可兼得,我用”分级”把不同价值的租户放进不同档。(对应简历:「通过 MyBatis 拦截器实现实例、Schema、行级三级隔离」)
Q2. 行级隔离用 MyBatis 拦截器具体怎么实现?拦截哪个点?租户上下文怎么传?
核心是让每条 SQL 自动带上 tenant_id 条件,业务代码无感知——这正是「机制下沉」的体现,业务 Mapper 不写一行租户过滤。
- 拦截点:实现
Interceptor,@Intercepts拦StatementHandler#prepare(拿到将要执行的 SQL,改写最干净)或Executor#query/update。我倾向在 SQL 解析层用 JSqlParser 解析后给where注入tenant_id = ?,比字符串拼接安全。 - 租户上下文传递:请求入口(网关/拦截器)从 JWT/Header 解出
tenant_id,放进ThreadLocal(TenantContextHolder)。拦截器从 ThreadLocal 取值改写 SQL。 - 异步陷阱:
ThreadLocal在线程池/异步任务/@Async/MQ 消费线程里会丢失。我用TransmittableThreadLocal(TTL)或在任务提交时显式透传租户上下文。 - 写入侧:insert 时自动填充
tenant_id(类似 MyBatis-Plus 的MetaObjectHandler),防止漏填。
成熟方案可直接用 MyBatis-Plus 的 TenantLineInnerInterceptor,但我会强调”知道它底层就是 JSqlParser 改 where + 忽略表白名单”,而不是只会配置。(对应简历:「MyBatis 拦截器实现……行级隔离」)
Q3. 行级隔离最大的安全风险是什么?怎么防漏挂租户条件导致的越权?
最大风险就是某条 SQL 绕过了拦截器,把别的租户数据查出来/改掉了,这在 SaaS 是 P0 数据安全事故。几个绕过点和防御:
- 原生/手写 SQL、
@Select注解 SQL、多表 join、子查询:拦截器改写可能覆盖不全。防御:强制走统一 Mapper 规范 + JSqlParser 对 join/子查询每张表都注入条件 + 维护”免租户表白名单”(字典表等)显式登记。 - 批量/导入、定时任务、MQ 消费:这些不走 HTTP 入口,ThreadLocal 没值 → 要么查全表、要么报错。防御:默认 Fail-Fast——上下文为空时拦截器直接抛异常而不是放行(”宁可错杀”),系统级任务用显式的 system 上下文白名单。
- 纵深防御:数据库层再加一道 行级安全策略(PostgreSQL RLS / MySQL 视图) 兜底;关键表查询日志审计
tenant_id分布,发现跨租户访问告警。
我的原则:隔离不能只靠一层拦截器,要应用层改写 + Fail-Fast 兜底 + DB 层 RLS 三层纵深。(这点能体现你”故障复盘 + 防御性设计”的成熟度)
Q4. 租户全生命周期为什么用状态机?怎么设计?
直觉:租户开通不是一个动作,是一条有状态、可能中断、要重试的长流程(建库→初始化数据→分配权限→计费开通→通知),用 if-else 散在各处必然漏状态、难重试、难审计。状态机把”允许哪些状态迁移“显式建模,非法迁移直接拒绝。
- 四要素:状态(待开通/开通中/已开通/停用中/已停用/开通失败)、事件(提交开通/初始化完成/计费成功/触发停用)、动作(迁移时执行的 reconcile)、守卫(迁移前置条件)。
- 关键设计:① 每个状态的动作幂等,失败可重入(开通中宕机,重启后从当前状态继续);② 状态持久化到 DB,不放内存;③ 配合事务消息驱动跨服务动作,保证”状态已改 + 下游已通知”原子;④ 用一张
tenant_lifecycle_event表记录所有迁移,天然满足审计。
我会把它和 K8s 控制器的 Reconcile 类比:声明期望状态,控制器不断把实际状态收敛过去——我的租户状态机本质是同一套”声明式 + 自愈”思想。(对应简历:「用状态机加事务消息处理租户开通、计费、审计等跨服务流程」)
Q5. 状态机 + 事务消息怎么保证跨服务最终一致?为什么不用强一致(2PC/XA)?
场景:租户”计费成功”这个状态迁移,要同时(a)把租户状态改成已开通、(b)通知计费/权限/通知三个下游服务。难点是”改本地状态”和”发消息通知下游”不能各做各的——可能状态改了消息没发出去。
- 为什么不上强一致:跨多个微服务 + 多个库,XA/2PC 要全程持锁、同步阻塞,吞吐塌方、协调者单点、长事务放大故障半径,SaaS 这种高并发开通场景扛不住。开通”晚 1 秒到账”完全可接受,所以选最终一致。
- 事务消息怎么保原子:以 RocketMQ 事务消息为例——① 发”半消息”(下游不可见);② 执行本地事务(改租户状态);③ 本地成功就 commit 半消息(下游可见),失败就 rollback;④ 万一第 2/3 步之间宕机,MQ 回调本地事务回查接口,根据状态表判定补发还是丢弃。本质是把”本地事务 + 发消息”凑成一个原子单元。
- 下游侧:消费幂等(去重表/唯一键)+ 失败重试 + 死信兜底人工。
一句话:用事务消息把”状态变更”和”通知下游”绑成原子,再靠下游幂等消费 + 重试达成最终一致,可靠性和吞吐的平衡点。(对应简历:「保证数据最终一致」)
Q6. 「机制下沉、策略上浮」具体怎么落地?新行业接入怎么做到不改底座?
这是我中台设计的核心准绳,回答时给具体边界,别只喊口号。
- 机制下沉到底座:所有行业都要的通用能力沉到底座——多租户 RBAC、三档数据隔离、租户生命周期状态机、网关鉴权限流、统一认证、消息/调度基础设施。底座只认抽象、不认行业。
- 策略上浮到行业视图:行业差异(金融征信的报送规则、能源 ERP 的计量口径)做成可插拔策略——配置化(数据权限、模块清单、字段元数据)+ 扩展点(SPI/策略接口,行业实现自己的 Handler)+ 行业视图投影(同一份底座数据,按行业投影出不同视图)。
- 不改底座的判据:新行业接入只允许做三件事——声明启用哪些模块、声明数据权限范围、注册行业策略实现;只要需要改底座代码,就说明这个”差异”其实是通用机制没抽对,要回炉抽象。
- 代价:抽象前置成本高、扩展点设计难、过度抽象会增加理解成本——所以我遵循 YAGNI,等”第二个行业出现同类需求”才把它下沉,而不是一开始就过度设计。
(对应简历底层设计观,能直接体现你的架构判断力,是 P8/P9 加分项)
Q7. 数十个租户共用一套系统,怎么防止单个租户把整个系统拖垮(噪声邻居)?
噪声邻居(Noisy Neighbor)是共享多租户的核心风险,要分层答:
- 流量层:网关做租户级限流(不是全局限流),每租户独立令牌桶/配额,单租户突发不影响其他租户;热点大租户单独限流阈值。
- 计算层:线程池/连接池按租户或租户分组隔离(舱壁模式 Bulkhead),一个租户打满连接池不会饿死全局;重租户可路由到专属实例分组。
- 数据层:大租户走独立库/独立 Schema 物理隔离;共享表的大租户单独分片,避免热点集中。
- 异步层:MQ 按租户分区/分队列,单租户消息积压不阻塞他人;慢消费租户限速。
- 观测层:按
tenant_id打点资源消耗,识别 Top-N 租户,提前扩容或下沉到独立资源。
核心思想:共享是为了成本,隔离是为了稳定,关键在于给”价值高/负载大”的租户做选择性物理隔离。(呼应你的三档隔离和网关限流)
Q8. 租户级限流、配额、熔断怎么做?和全局限流有什么区别?
- 区别:全局限流保护的是”系统总容量”,租户限流保护的是”租户间公平 + 防噪声邻居”。SaaS 必须有租户维度,否则一个大租户就能正常地把配额吃光。
- 限流落地:以 Sentinel 为例,把
tenant_id作为热点参数限流(ParamFlowRule)的参数,对不同租户设不同 QPS 阈值;集群部署用 Sentinel 集群限流(Token Server),避免每个网关实例各算各的导致总量超限。 - 配额:分两种——速率配额(QPS,令牌桶,实时)和总量配额(每月调用次数/存储量,Redis 计数 + 定时归零)。总量配额要注意原子扣减(Lua)和并发超扣(同防超卖思路)。
- 熔断:按”租户 + 接口”维度熔断,慢调用比例/异常比例触发,降级返回兜底;避免一个租户的慢接口拖垮共享线程池。
- 分级:付费档位不同配额不同(Free/Pro/Enterprise),这本身就是 SaaS 商业模型的一部分。
(对应简历:「网关层做限流、熔断和灰度发布」,把它升级成”租户维度”是亮点)
Q9. 新租户上线周期从 2 周缩到 3 天,你具体做了什么自动化?
这是你简历的硬成果,要能拆解出”为什么是 2 周、砍掉了哪些环节”。
- 原来为什么慢:人工建库建 Schema、手工初始化字典/权限/菜单数据、手工配置网关路由和限流、手工灰度验证、跨团队工单流转。串行 + 人工 = 2 周。
- 做了什么:① 租户开通状态机一键编排——建库/初始化/权限/计费/路由注册全自动化,失败自动重试,不再人工工单;② 元数据驱动初始化——行业模板(模块清单+数据权限+字典)配置化,新租户选模板即生成,不写代码;③ 网关路由/限流动态下发——开通完成自动注册到 Nacos,无需手工改配置重启;④ 自动化冒烟验证——开通后跑一组租户级健康检查再放量。
- 结果:2 周 → 3 天,且可重复、可审计、出错可回滚。
本质:把一条人工串行流程,重构成状态机驱动的自动化流水线 + 元数据驱动配置。(直接对应简历该句,背熟)
Q10. 多租户分库分表,分片键怎么选?按租户分还是按业务分?大租户热点怎么办?
- 核心矛盾:分片键要同时满足”数据均匀”和”查询不跨片”。多租户最自然的分片键是
tenant_id,但它会带来数据倾斜——大租户数据量远超小租户。 - 选型:
- 纯
tenant_id分片:同租户数据落同片,租户内查询不跨片,但大租户热点。 tenant_id + 业务键(如 user_id/order_id)复合分片:先按租户再按业务二次散列,缓解单租户热点。- 大租户单独成库(绑定路由),长尾小租户共享分片——又回到”分级”思想。
- 纯
- 大租户热点解法:① 大租户独立物理库;② 复合分片键二次打散;③ 读多写少的大租户加多级缓存挡读;④ 历史数据冷热分离归档。
- 避免跨片:分片键尽量覆盖高频查询条件;跨片聚合走 ES/数仓而不是硬 join。
(对应简历:「结合 ShardingSphere 分库分表支撑数据增长」,把”倾斜”这个坑讲出来显深度)
Q11. 多租户下灰度发布怎么做?能按租户灰度吗?
可以,而且 SaaS 最适合”按租户灰度”,比按流量百分比灰度更可控(同一租户体验一致,不会一会儿新一会儿旧)。
- 灰度维度:按租户白名单 → 按租户档位(先内部租户/Free 租户)→ 按比例放量 → 全量。
- 路由实现:网关识别
tenant_id,查灰度规则(Nacos 配置/灰度中心),打灰度标签,通过元数据路由(Nacos/Spring Cloud LoadBalancer 自定义)把请求导到灰度实例组;标签全链路透传(Header → RPC → MQ)。 - 数据兼容:灰度涉及 DB 变更时遵循扩展-收缩(expand-contract)——先加兼容字段、双写、灰度验证、再切读、最后删旧字段,保证新旧版本同时在线不冲突。
- 回滚:灰度规则即时下发,出问题秒级把租户切回稳定版。
核心:租户是 SaaS 天然的灰度单元,结合标签路由 + 配置中心动态规则,做到可控放量、秒级回滚。(对应简历:「灰度发布」)
Q12. 中台从单体到微服务,服务怎么拆?拆分依据是什么?
P8/P9 必考”拆分判断力”,别只说”按功能拆”。
- 首要依据:业务域 / DDD 限界上下文——按”高内聚的业务能力”划边界(认证、租户、计费、审计、工单各成域),而不是按技术分层拆。一个服务应能独立演进、独立部署、有清晰的数据所有权。
- 辅助判据:① 变更频率(一起变的放一起);② 团队边界(康威定律,一个团队维护一个服务);③ 数据所有权(一个聚合根的数据归一个服务,禁止跨服务直连别人的库);④ 非功能差异(高并发只读的可单独拆出来独立扩容)。
- 避免的坑:拆太细 → 分布式事务和调用链爆炸(你简历的事务消息就是为收拾这个);拆太粗 → 退化成分布式单体。我的经验是先按域粗拆,等出现明确的独立演进/独立扩容压力再细拆(演进式拆分,不一步到位)。
- 拆分后配套:网关、注册发现、配置中心、链路追踪、分布式事务,缺一不可——这正是我中台底座先建好的原因。
(对应简历:「按业务域拆分服务」,把 DDD + 康威 + 演进式三点讲出来就到位)
② 微服务治理 / Spring Cloud(8 题)
Q13. API 网关都干什么?网关层和业务层的职责怎么划?
直觉:网关是所有流量的统一入口,干的是”每个请求都要做、但和业务无关”的横切关注点。
- 网关职责:路由转发、统一认证鉴权(验 token)、限流熔断、灰度路由、协议转换、跨域、日志/链路埋点、黑白名单、请求/响应改写。
- 划界原则(SoC 关注点分离):通用、与业务无关、所有请求都要做的 → 下沉网关;和具体业务规则强相关的 → 留业务层。例如”验 token 合法性”在网关,”这个租户有没有权限操作这条数据”在业务层(因为要查业务数据)。
- 取舍:网关别做太重——复杂业务逻辑塞网关会让它变成单点瓶颈和发布瓶颈。我的边界是”网关只做能在不查业务库的情况下完成的横切逻辑”。
- 选型:Spring Cloud Gateway(基于 WebFlux/Reactor,异步非阻塞,比 Zuul1 的同步阻塞吞吐高),自定义
GlobalFilter实现租户鉴权、限流。
(对应简历:「API 网关……网关层做限流、熔断和灰度发布」)
Q14. 多租户下 OAuth2 统一认证怎么设计?token 里放什么?
- 整体:统一认证中心(Authorization Server)签发 token,各微服务做 Resource Server 验签,网关做第一道关。多租户的关键是 token 要携带租户身份。
- token 设计:用 JWT,claims 里放
userId、tenantId、roles/scopes、exp。tenantId入 token 后,网关解析即得租户上下文,注入 ThreadLocal 全链路透传(接 Q2)。 - 为什么 JWT:无状态、自包含,微服务本地验签不用每次回认证中心,适合多服务。代价是撤销难(没到期没法主动失效)——补救:短有效期 access token + refresh token + 黑名单(Redis 存被强制下线的 jti)。
- 多租户陷阱:① 必须校验”这个用户属于这个租户”,防止伪造
tenantId越权(签名保证不可篡改,但要防同一用户跨租户);② 租户级 RBAC——权限要带租户作用域;③ SSO 时按租户隔离的客户端配置。 - 网关 + 服务双校验:网关验 token 合法性 + 基础鉴权,服务内做细粒度数据权限(接 Q13)。
(对应简历:「统一认证(OAuth2)」)
Q15. 服务注册发现(Nacos)是 CP 还是 AP?心跳和服务剔除怎么工作?
- Nacos 可切 CP/AP:默认 AP(基于 Distro 协议,临时实例),强调可用性——网络分区时仍能注册发现,可能短暂读到不一致的实例列表;CP 模式基于 Raft(持久实例),用于强一致场景(如配置)。
- 为什么注册中心通常选 AP:服务发现场景里,”拿到的实例列表稍微旧一点”远比”拿不到列表导致整个调用瘫痪”可接受。这是 CAP 在服务发现场景的经典取舍——可用性优先,一致性靠客户端重试/熔断兜底。
- 心跳:临时实例客户端每 5s 发心跳,15s 没心跳标记不健康,30s 没心跳剔除。
- 对比 Eureka/ZK:Eureka 也是 AP(自我保护机制防止网络抖动误剔除);ZooKeeper 是 CP(ZAB 协议),分区时少数派不可用——所以拿 ZK 做注册中心在大规模下可用性反而是短板。
- 关联:我在 K8s 里也踩过类似——etcd 是 CP(Raft),Watch 风暴时的一致性 vs 性能取舍我复盘过。(呼应简历的 Watch 风暴/协调服务经验)
Q16. 配置中心动态刷新的原理是什么?怎么做配置灰度和回滚?
- 动态刷新原理(Nacos):客户端长轮询(不是长连接也不是短轮询)——客户端发请求,服务端 hold 住最长 29.5s,期间配置变了立即返回、没变到点返回 304,客户端再发起下一轮。比短轮询省资源、比长连接简单、近实时(秒级)。配置变更后客户端收到通知,Spring 通过
@RefreshScope销毁并重建 Bean,使新配置生效。 @RefreshScope原理:把 Bean 放进 refresh 作用域,刷新时清缓存,下次获取触发重新创建,重新注入新配置值。- 配置灰度(Nacos Beta 发布):先把新配置推给指定 IP 的实例(灰度),验证无误再全量发布。
- 回滚:Nacos 保留配置历史版本,一键回滚到上一版本;重要配置走审批流。
- 生产经验:动态配置要小心——改连接池/线程数这类配置
@RefreshScope重建可能有副作用,关键开关我会做幂等的监听器手动处理而不是无脑 refresh。
(对应简历:「配置中心」)
Q17. 限流算法有哪几种?区别?你怎么选?集群限流怎么做?
| 算法 | 原理 | 特点 | 适用 |
|---|---|---|---|
| 计数器(固定窗口) | 单位时间计数 | 简单;临界突刺(窗口切换瞬间 2 倍流量) | 粗粒度 |
| 滑动窗口 | 把窗口切小格滚动 | 平滑临界问题 | 通用 |
| 漏桶 | 固定速率漏水 | 强制恒定速率、削峰、不允许突发 | 保护下游恒定处理能力 |
| 令牌桶 | 固定速率发令牌、可攒 | 允许一定突发、最常用 | 大多数 API 限流 |
- 选型:网关入口 API 限流我用令牌桶(Sentinel/Guava RateLimiter),允许合理突发体验好;保护脆弱下游(如写库、第三方)用漏桶强制匀速。
- 集群限流:单机限流每个节点各算各的,10 个网关节点 × 100 QPS = 实际放行 1000,超了。解决:Sentinel 集群限流——选一个 Token Server 统一发令牌,或用 Redis + Lua 原子计数做全局窗口。代价是引入中心节点(Token Server 高可用要做),权衡精度和复杂度。
- 关联:我在多租户做的是租户维度热点参数限流 + 集群限流(接 Q8)。
(对应简历:「限流降级」)
Q18. 熔断降级怎么工作?熔断器的状态机讲一下。
- 为什么熔断:防故障扩散/雪崩——下游挂了还一直打,调用线程全堵在等待上,拖垮上游,连锁雪崩。熔断器在下游故障时快速失败,给下游喘息时间。
- 三态状态机:
- Closed(关闭):正常放行,统计失败率/慢调用比例。
- Open(打开):失败率超阈值 → 跳闸,所有请求直接走降级不再打下游,持续一个冷却窗口。
- Half-Open(半开):冷却结束放少量探测请求,成功则回 Closed,失败则回 Open。
- 触发条件(Sentinel):慢调用比例(RT 超阈值的比例)、异常比例、异常数。
- 降级兜底:返回默认值/缓存旧值/友好提示/排队,绝不把异常直接抛给用户。
- 取舍:阈值太敏感会误熔断(抖动就跳闸),太钝起不到保护作用——要结合压测和线上 RT 分布调。
- 对比 Hystrix vs Sentinel:Hystrix 线程池隔离(强隔离但线程开销大)/ 信号量隔离;Sentinel 更轻量、规则更丰富、有控制台。Hystrix 已停更,新项目用 Sentinel/Resilience4j。
(对应简历:「Hystrix/Sentinel」「熔断」)
Q19. OpenFeign 调用链路上,超时、重试、负载均衡怎么配?怎么防雪崩?
- 链路:Feign(声明式 HTTP)→ 负载均衡(Spring Cloud LoadBalancer,Ribbon 已退役)→ 实际 HTTP 调用,可叠加 Sentinel 熔断。
- 超时:必须设连接超时 + 读超时,且下游超时 < 上游超时(否则上游早放弃了下游还在跑,浪费资源)。超时是防雪崩第一道闸。
- 重试:要非常克制——只对幂等接口(GET/幂等写)重试,非幂等重试会重复下单/重复扣款。重试要配合退避,且总超时受控,避免重试放大故障(下游已经过载,重试等于火上浇油)。
- 负载均衡:默认轮询,可配权重/同区域优先;配合注册中心健康检查剔除坏节点。
- 防雪崩组合拳:超时控制 + 熔断(Q18)+ 舱壁隔离(线程池/信号量隔离不同下游)+ 限流 + 降级兜底。
- 关联:我把这套和”租户级熔断”结合,避免单租户慢接口拖垮共享调用链。
(对应简历:「OpenFeign」)
Q20. 微服务拆分后,分布式事务边界怎么定?哪些该拆哪些该合?
- 第一原则:能不用分布式事务就不用。分布式事务是拆分的成本,不是收益。如果两块数据强一致且总是一起变,它们可能就不该拆开——合到一个服务一个本地事务最简单。
- 判断:① 强一致 + 高频一起变更 + 同一聚合根 → 合(一个本地事务搞定);② 可接受最终一致 / 不同业务域 / 独立演进 → 拆,用最终一致方案(事务消息/Saga,接第③板块)。
- 设计手法:按 DDD 聚合根划事务边界——一个本地事务只改一个聚合根,跨聚合根用领域事件最终一致。这样大多数操作是本地事务,只有少数真正跨域的才走分布式事务。
- 我的实践:租户开通跨”租户/计费/权限”三个域,是真跨域 → 状态机 + 事务消息最终一致;而租户内的权限和角色强一致 → 同一服务本地事务。先用边界设计消灭掉大部分分布式事务,剩下的才上方案。
(对应简历:把”拆分”和”事务消息”串起来讲,体现你拆分时就考虑了一致性成本)
③ 分布式事务 / 一致性(6 题)
Q21. TCC、Saga、事务消息、本地消息表怎么选?给个决策矩阵。
先给判据:一致性强度、对业务侵入性、隔离性、性能。
| 方案 | 一致性 | 侵入性 | 隔离性 | 适用场景 |
|---|---|---|---|---|
| TCC | 准实时、较强 | 高(每服务写 Try/Confirm/Cancel 三个接口) | 好(Try 阶段预留资源) | 短流程、强隔离、对一致性要求高,如资金/库存(你的三态防超卖本质就是 TCC 思想) |
| Saga | 最终一致 | 中(每步写补偿) | 差(无预留,中间态可见) | 长流程、多步骤,如租户开通编排 |
| 事务消息 | 最终一致 | 低(发消息 + 本地事务 + 回查) | 差 | 本地操作 + 异步通知下游,解耦,如计费/审计 |
| 本地消息表 | 最终一致 | 低(多张消息表 + 轮询投递) | 差 | 不想引强事务 MQ,用 DB 兜可靠投递 |
- 我的选型逻辑:
- 租户开通这种长流程编排 → Saga(状态机驱动,每段有补偿)。
- 计费/审计这种本地改完通知下游 → 事务消息。
- 库存扣减这种强隔离防超卖 → TCC(预占/实占/释放三态,接 Q42)。
- 一句话:没有银弹,按”一致性要求 × 流程长短 × 隔离需求”选,一个系统里常常多种并存。
Q22. 事务消息(RocketMQ)的两阶段提交 + 回查原理?
接 Q5 再深一层,把”回查”讲透是亮点。
- 两阶段:① Prepare:Producer 发半消息到 Broker,半消息存在特殊 topic(
RMQ_SYS_TRANS_HALF_TOPIC),消费者看不到;② Producer 执行本地事务;③ Commit/Rollback:本地成功 → 发 commit,Broker 把半消息转到真实 topic 对消费者可见;本地失败 → 发 rollback,丢弃半消息。 - 回查(核心兜底):如果第②③步之间 Producer 宕机/超时,Broker 收不到 commit/rollback,会定时回调 Producer 的本地事务回查接口,Producer 查自己的业务状态(如订单表/状态机)告诉 Broker 该 commit 还是 rollback。回查接口必须幂等、能查到本地事务最终结果——所以本地事务和”留痕”要在一个事务里。
- 为什么需要回查:commit 消息可能丢、Producer 可能崩,光靠”发 commit”不可靠,回查是最终一致的保证。
- 代价:消息可能延迟(回查间隔)、要实现回查接口、Broker 要扛半消息存储。
Q23. 本地消息表怎么保证消息不丢、不重?
直觉:把”要发的消息”和”业务数据”写进同一个本地事务的同一个库,利用本地事务的原子性保证”业务成功 ⟺ 消息已记录”,再异步投递。
- 不丢:① 业务操作 + 插入消息表(status=待发送)在同一本地事务,原子提交——业务成功消息必然落库;② 一个定时任务/轮询扫描待发送消息投递到 MQ,投递成功更新 status=已发送;③ 投递失败重试,重试到上限进死信人工。即使应用崩了,消息还在表里,重启接着投。
- 不重:① 投递可能重复(投出去了但更新 status 前崩了,下轮又投)→ 所以消费端必须幂等(去重表/唯一键);② 这是”至少一次”投递 + 消费幂等 = 最终精确一次的标准组合。
- 对比事务消息:本地消息表是”自己用 DB + 定时任务实现了事务消息”,不依赖 MQ 的事务特性,任何 MQ 都能用,代价是多一张表 + 轮询延迟 + 自己运维。
- 优化:轮询有延迟,可结合”业务提交后立即触发一次投递 + 定时任务兜底漏网的”。
(对应简历:「事务消息」,能讲清本地消息表说明你不是只会用框架)
Q24. Saga 的正向/补偿、补偿失败怎么办?和 TCC 比缺点是什么?
- 机制:Saga 把长事务拆成 N 个本地事务 T1…Tn,每个配一个补偿 C1…Cn。正向依次执行 T1→Tn;某步失败则反向执行补偿 Ci…C1 回滚已完成的步骤。两种协调:编排式(中心 Saga 协调器,状态机驱动) vs 协同式(事件驱动,各服务监听)——长流程我用编排式(可视、可追踪,对应我的状态机)。
- 补偿失败怎么办:这是 Saga 最棘手处。① 补偿必须幂等 + 可重试;② 重试到上限仍失败 → 进死信 + 告警 + 人工介入,记录到对账系统;③ 关键是补偿要设计成”最终一定能成功“(语义补偿而非物理回滚,比如”取消订单”而不是”删除记录”)。
- 缺点(vs TCC):无隔离性——Saga 没有 Try 预留阶段,中间状态对外可见,可能出现”脏读”(别的事务看到了将被补偿的中间态)。要靠业务层加语义锁/状态标记(如订单标记”处理中”对外不可操作)弥补。TCC 有 Try 预留所以隔离好,但侵入性高。
- 取舍:长流程、能容忍中间态 → Saga;强隔离、短流程 → TCC。
(对应简历:「Saga」「状态机」,租户开通就是编排式 Saga 的活例子)
Q25. 幂等有哪几种实现方式?分别适合什么场景?
幂等是分布式系统的地基(重试、消息重复、用户重复点击都要它)。从轻到重:
- 唯一索引/主键去重:天然幂等,插入重复直接唯一键冲突。适合”创建类”操作(同一订单号只能建一次)。最简单可靠。
- 去重表 / Redis 去重键:用业务唯一 ID(消息 ID/请求 ID)做 key,处理前先
SETNX,存在就跳过。适合消息消费幂等。注意 key 过期时间和原子性(Lua)。 - 状态机约束:只允许合法状态迁移,”已支付”再来一次支付请求直接拒绝。适合有明确状态流转的业务(你的租户生命周期)。
- 乐观锁(版本号/CAS):
update ... where version = ?,更新带版本,重复执行第二次影响 0 行。适合更新类、防并发覆盖。 - Token / 防重令牌:下单前先领一次性 token,提交时校验并消费 token。适合防表单重复提交。
- 选型:创建用唯一键,消费用去重表/Redis,更新用乐观锁,状态流转用状态机,防重复提交用 Token。常常组合使用。
(对应简历:「幂等去重」,三态库存里你就是乐观锁 + 去重表组合)
Q26. 跨库分布式事务的对账怎么做?你踩过的对账偏差是怎么修的?
最终一致系统必须有对账兜底——再可靠的方案也会有极端 case 漏网,对账是最后一道防线。
- 为什么需要:消息丢、补偿失败、网络分区、bug 都可能导致两边数据不一致。对账 = 定时把两边数据拉出来比对,发现差异 → 修复。
- 怎么做:① T+1 全量对账 + 准实时增量对账结合;② 以一方为基准(如计费流水),比对另一方(租户账户),找出”我有你没有 / 你有我没有 / 都有但不一致”三类差异;③ 差异进差错表,可自动修复的(如补发消息、触发补偿)自动修,不能的告警人工;④ 对账本身要幂等、可重入。
- 我踩过的偏差(复盘话术):跨库分布式事务下,曾出现下游消费成功但回执消息丢失,导致上游状态停在”处理中”,对账时表现为”上游未完成、下游已完成”。修复:对账识别该模式 → 查下游真实状态 → 幂等补推上游状态机迁移到终态。事后加固:回执也走可靠投递 + 状态机超时自动触发回查,从源头减少这类偏差。
- 升华:对账数据还能反哺——差异率是衡量最终一致系统健康度的核心 SLI。
(对应简历:「跨库分布式事务对账偏差」,这是你简历里 CLAUDE.md 提到的复盘点,面试官追问必问,背熟)
④ 消息队列 Kafka / RocketMQ(5 题)
Q27. 消息可靠投递端到端不丢,三个环节各怎么保证?
消息会在三处丢:生产→Broker、Broker 存储、Broker→消费。三段都要管。
- 生产端:① 同步发送 + 确认(Kafka
acks=all,等所有 ISR 副本落盘才算成功;RocketMQ 同步发送收 SendResult);② 发送失败重试;③ 最稳是事务消息/本地消息表(接 Q22/23),保证”业务成功⟺消息发出”。 - Broker 存储:① 多副本(Kafka
replication.factor>=3,min.insync.replicas>=2);② 刷盘策略——同步刷盘可靠但慢,异步刷盘快但宕机可能丢一点。金融级同步刷盘 + 多副本,一般业务异步刷盘 + 多副本(靠副本兜)。 - 消费端:① 手动 ack / 手动提交 offset——业务处理成功后再提交 offset,绝不能”先提交 offset 再处理”(处理中崩了消息就丢了);② 处理失败不提交,触发重试;③ 重试到上限进死信队列人工兜底。
- 代价:可靠性越高,吞吐越低、延迟越大。
acks=all+同步刷盘最可靠但最慢——按业务定级,不是所有消息都要最高级别。 - 组合结论:生产确认 + 多副本 + 消费后置 ack + 死信兜底,再叠加消费幂等(因为这是”至少一次”,必然有重复)。
(对应简历:「可靠投递」「Kafka 统一采集设备遥测」)
Q28. 消费积压怎么治理?根因怎么定位?
直觉:积压 = 生产速率 > 消费速率,要么生产突增,要么消费变慢/挂了。先定位再处理。
- 定位根因:① 看 consumer lag(Kafka lag 指标)确认积压量和增长趋势;② 区分是”消费者挂了/重平衡中”还是”消费者活着但处理慢”;③ 慢的话看是下游慢(DB/第三方)、单条处理重、还是分区数不够并行度上不去。
- 应急处理:① 消费者扩容——但 Kafka 消费并行度 ≤ 分区数,分区不够扩消费者也没用,要先加分区;② 临时关掉非核心逻辑、批量处理、异步化下游慢调用;③ 实在压不下去,新建临时 topic 多分区 + 临时消费者组把积压消息搬过去并行处理(应急扩容法)。
- 根治:① 分区数预留余量;② 消费端批量拉取 + 批量处理 + 批量提交;③ 下游慢的做缓存/限速/异步;④ 生产端削峰(限流)。
- 我的实践:40+ 微服务的设备遥测平台,Kafka 做了分区 + 消费积压治理——预留分区、批量消费、消费幂等,把告警平均发现时间从约 30 分钟压到 1–3 分钟。
(对应简历:「消息积压治理」「告警平均发现时间从约 30 分钟降到 1 到 3 分钟」,强成果,背熟)
Q29. 顺序消息怎么保证?分区有序和全局有序的区别?重平衡会破坏顺序吗?
- Kafka 只保证分区内有序,不保证 topic 全局有序。
- 分区有序:把需要保序的消息用同一个 key 路由到同一分区(如同一订单的所有事件用 orderId 做 key),分区内 FIFO,单分区单消费者顺序消费。这是最常用的——业务上通常只需要”同一实体的事件有序”,不需要全局有序。
- 全局有序:整个 topic 只能 1 个分区 + 1 个消费者,吞吐塌成单线程,几乎不用。要全局有序通常是设计有问题,应该退化成”按实体分区有序”。
- 重平衡破坏顺序:会。消费者增减/宕机触发 rebalance,分区重新分配,若消费了但没提交 offset 就 rebalance,新消费者会重复消费 → 顺序和幂等都受影响。缓解:① 消费幂等兜底;② 缩短处理时间避免 rebalance 超时;③ Kafka 的 cooperative rebalance(增量再平衡) 减少 stop-the-world;④ 消费端保证”同一 key 同一时刻只被一个线程处理”(消费内再按 key 派发要小心多线程乱序)。
- RocketMQ:顺序消息用 MessageQueueSelector 把同一业务 ID 投到同一队列,消费用
MessageListenerOrderly。
(对应简历:「顺序消息」)
Q30. 消费幂等具体怎么落地?为什么消息一定会重复?
- 为什么必然重复:主流 MQ 都是至少一次(at-least-once)投递语义——为了不丢消息,”宁可重发”。重平衡、消费成功但 offset 没提交、生产重试都会产生重复。所谓”精确一次”也是”至少一次 + 消费幂等”凑出来的。
- 落地(接 Q25):① 去重表/Redis——消息带全局唯一 ID(msgId/业务 ID),消费前
SETNX或查去重表,已处理就跳过;要注意”判重 + 处理 + 标记已处理”的原子性,理想是和业务在同一事务(去重表落业务库),否则用 Redis 要处理”标记成功但业务失败”的回滚。② 业务唯一键——靠 DB 唯一索引天然挡重复插入。③ 状态机——重复的状态迁移被非法迁移拦掉。 - 坑:Redis 去重 + DB 业务不在一个事务,存在”Redis 标记了但业务挂了”导致消息被误吞——所以强一致场景我把去重表放业务库和业务同事务提交。
- 结论:MQ 保证不丢(至少一次)+ 消费端保证不重(幂等)= 业务上的恰好一次。
(对应简历:「幂等去重」)
Q31. Kafka 的分区/副本/ISR 讲一下,你在 40+ 微服务平台怎么治理消息?
- 分区(Partition):topic 的并行单位,决定最大消费并行度;分区内有序、分区间无序。
- 副本(Replica):每分区有 1 leader + N follower,读写都走 leader,follower 同步。
- ISR(In-Sync Replicas):与 leader 保持同步的副本集合。
acks=all时要 ISR 全确认才算提交;leader 挂了从 ISR 选新 leader。min.insync.replicas控制最少同步副本数——设 2 表示至少 2 个副本有数据才允许写,防丢。 - 我的治理实践(设备遥测平台):① 分区规划——按设备/区域 key 分区,预留并行度余量;② 幂等去重——海量遥测重复不可避免,消费端去重;③ 消费积压治理——批量消费 + lag 监控告警(接 Q28);④ 冷热/时序分流——遥测时序数据量大,配合 ShardingSphere 分库分表落地(接简历);⑤ 资源/租户隔离——关键告警 topic 和普通遥测 topic 分开,避免大流量淹没告警。
- 踩过的坑:副本同步缩水(ISR 收缩到只剩 leader 时 leader 挂 = 丢数据)→ 监控 ISR 大小 +
min.insync.replicas=2兜底。
(对应简历:「用 Kafka 统一采集设备遥测、告警等海量数据,做分区、幂等去重和消费积压治理」,整句能展开成 3 分钟讲透)
⑤ 缓存 Redis / Redisson(5 题)
Q32. 缓存穿透、击穿、雪崩的区别和各自解法?
一句话区分:穿透 = 查不存在的;击穿 = 单个热 key 过期瞬间;雪崩 = 大批 key 同时过期/Redis 挂。
- 穿透(Penetration):查根本不存在的数据,缓存永远不命中,每次都打到 DB(常见于恶意攻击/爬不存在 ID)。解法:① 缓存空值(null 也缓存,设短 TTL);② 布隆过滤器(BloomFilter 拦掉一定不存在的 key,省空间但有误判、不能删);③ 参数校验 + 限流。
- 击穿(Breakdown):某个热点 key 过期的瞬间,大量并发同时发现没缓存一起打 DB,把 DB 打垮。解法:① 互斥锁/分布式锁——只放一个线程去重建缓存,其他等待(Redisson
tryLock);② 逻辑过期——key 不设物理过期,存一个逻辑过期时间,过期了异步重建、其他线程先返回旧值(缓存永不真过期,最丝滑);③ 热点 key 永不过期 + 后台刷新。 - 雪崩(Avalanche):大批 key 在同一时间集中过期,或 Redis 整体宕机,流量全压 DB。解法:① TTL 加随机抖动(base + random)打散过期时间;② Redis 高可用(哨兵/集群)防整体挂;③ 多级缓存(本地缓存兜底,Redis 挂了还有 Caffeine);④ DB 层限流 + 熔断兜底。
- 我的实践:跨机房多级缓存场景,穿透用空值+布隆、击穿用 Redisson 互斥锁+逻辑过期、雪崩用随机 TTL+本地缓存兜底,三件一起上。
(对应简历:「缓存穿透、击穿、雪崩的处理」,这是必问题,三个一定要分清不能混)
Q33. 多级缓存(本地 + Redis)怎么设计?一致性怎么保证?热 key 怎么办?
- 结构:L1 本地缓存(Caffeine,进程内,纳秒级,扛热点)→ L2 分布式缓存(Redis,跨实例共享)→ DB。读:L1 命中返回 → 未命中查 L2 回填 L1 → 再未命中查 DB 回填 L2/L1。
- 为什么要本地缓存:Redis 再快也有网络 RTT,超热点 key(每秒几万次)光网络就打满;本地缓存把超热数据挡在进程内,还能在 Redis 挂时兜底(接雪崩)。
- 一致性难点:本地缓存分散在每个实例,更新时一个实例改了,别的实例的 L1 还是旧的。解法:① L1 设短 TTL容忍秒级不一致(最简单,适合能容忍的场景);② 广播失效——数据变更发 MQ/Redis pub-sub 通知所有实例清本地缓存;③ 接受”本地缓存最终一致”——多级缓存本就是用一致性换性能,强一致的别放本地缓存。
- 热 key 探测与打散:① 监控/采样发现热 key;② 热 key 做本地缓存(不走 Redis);③ 极端热点 key 分片(key 后缀加 0~N,读随机选一个,把单 key 压力散到多个);④ 读多写少的热 key 逻辑过期 + 后台刷新。
- 取舍:多级缓存提升性能但增加一致性复杂度和运维成本,只给真正的热点上多级,不是所有数据都套。
(对应简历:「多级缓存」「热点接口用 Redis 做多级缓存……P95 响应时间下降约 30%」,这是量化成果,背熟因果链)
Q34. Redisson 分布式锁的看门狗、可重入怎么实现?红锁有什么争议?
- 基本实现:Redisson 用 Hash 结构存锁(key=锁名,field=线程标识
UUID:threadId,value=重入次数),加锁/解锁用 Lua 脚本保证原子。 - 可重入:同一线程再次加锁,Lua 判断 field 是自己就把 value(重入次数)+1,解锁 -1,减到 0 才真正删 key。这就是可重入锁,避免自己等自己死锁。
- 看门狗(Watchdog):不指定锁超时时间时,Redisson 默认锁 30s,并启动一个后台定时任务每 10s(1/3 周期)续期到 30s——只要业务没执行完、客户端没崩,锁不会过期。防止”业务还没跑完锁先过期,别人拿到锁并发执行”。客户端宕机则看门狗停,锁 30s 后自动释放,不会死锁。注意:只有不传 leaseTime 才有看门狗,传了固定过期时间就没有自动续期。
- 红锁(RedLock)争议:为解决”单 Redis 主从切换时锁丢失”,RedLock 要求向 N 个独立 master 多数派加锁。Martin Kleppmann 质疑它:① 依赖各节点时钟同步(时钟漂移破坏安全性);② GC/网络停顿下仍可能两个客户端同时持锁。结论:RedLock 成本高、争议大,绝对正确的互斥应该用 CP 系统(ZooKeeper/etcd)或给资源加 fencing token;Redis 锁本质是”高性能但非绝对安全”的锁,适合”偶尔失效可接受、靠业务幂等兜底”的场景。
- 我的态度:Redis 分布式锁 + 业务幂等/乐观锁兜底(双保险),强一致互斥才上 etcd——锁不是银弹,要配合幂等。
(对应简历:「Redisson……分布式锁」「分布式锁加乐观锁双层控制」,正好呼应你不迷信单一锁)
Q35. 缓存和 DB 的一致性怎么保证?为什么是删缓存不是更新缓存?
- 主流策略 Cache Aside(旁路缓存):读——先读缓存,没有则读 DB 并回填缓存;写——先更新 DB,再删除缓存(不是更新缓存)。
- 为什么删不是更新:① 更新缓存可能是无效计算(写多读少,更新了半天没人读);② 并发更新缓存会乱序覆盖(两个写线程更新缓存的顺序和更新 DB 的顺序可能相反,导致脏数据);删除则下次读时按最新 DB 重建,更安全(懒加载思想)。
- 为什么先更 DB 再删缓存:若先删缓存再更 DB,删完到更完之间有读请求会把旧值重新加载进缓存 → 长期脏。先更 DB 再删,窗口更小。
- 仍有的极端不一致:先更 DB 再删缓存,删缓存失败 → 脏。解法:① 延迟双删——更完 DB 删一次,过一会儿再删一次(覆盖主从延迟期间的回填);② binlog 订阅(Canal)——监听 DB binlog 异步删缓存,解耦且可靠重试;③ 给缓存设 TTL 兜底,最坏 TTL 到期自愈。
- 取舍:要严格强一致就别用缓存(或读走 DB);用缓存就接受最终一致 + 短暂窗口,按业务容忍度选方案。
(体现你知道”缓存一致性没有完美解,只有按容忍度权衡”)
Q36. 热点 Key / 大 Key 问题怎么发现和处理?
- 热点 Key:某个 key 访问量远超其他(爆款商品、头部租户)。危害:打满单个 Redis 分片(集群下一个 key 只落一个 slot),形成热点分片。发现:① Redis 4.0+
redis-cli --hotkeys;② 客户端/代理层采样统计;③ 监控分片 QPS 不均。处理:① 本地缓存挡(接 Q33);② key 分片打散(key 加随机后缀散到多 slot,读随机选);③ 读写分离 + 多副本扛读。 - 大 Key:单个 value 过大(大 String、元素超多的 Hash/Set/List)。危害:① 网络/序列化慢,阻塞其他请求;②
del大 key 阻塞主线程(Redis 单线程);③ 数据倾斜,内存不均;④ 主从同步抖动。发现:redis-cli --bigkeys、MEMORY USAGE、SCAN离线分析 RDB。处理:① 拆分(大 Hash 按字段哈希拆成多个小 Hash);② 删大 key 用UNLINK(异步删,4.0+)或HSCAN分批删,别用del/keys;③ 压缩 value;④ 合理选数据结构。 - 运维红线:生产禁用
keys *、flushall、对大 key 用del——这些会阻塞单线程 Redis 引发抖动。
(细节扎实度题,体现你有真实 Redis 运维经验,呼应你”字节缓冲泄漏/资源治理”的工程敏感度)
⑥ 数据库 / 分库分表 ShardingSphere(5 题)
Q37. 分库分表的分片键怎么选?为什么分片键这么关键?
- 分片键决定一切:它决定数据落哪个库哪张表,也决定查询能否”精准路由”还是”全库扫描”。选错分片键,分库分表反而更慢(每次查都广播到所有分片再归并)。
- 选型三原则:① 高频查询条件——绝大多数查询都带分片键,才能单分片命中,避免广播;② 数据均匀——分片键散列后各分片数据量/流量均衡,避免热点(接多租户 tenant_id 倾斜,Q10);③ 业务不可变——分片键值不应变化(变了要数据迁移)。
- 分片算法:取模(均匀但扩容要迁移大量数据)、范围(易扩容、利于范围查但易热点)、一致性哈希(扩容迁移少)、复合分片(如 tenant_id + 时间)。
- 我的实践:① 多租户业务表用
tenant_id(+ 二级散列防大租户热点);② 时序遥测数据用时间范围分片(按天/月,利于时序范围查询和冷数据归档)。分片键跟着”最高频的查询模式”走。
(对应简历:「ShardingSphere/Sharding-JDBC 分库分表」「时序流量数据用 ShardingSphere 分库分表」)
Q38. 分库分表后有哪些难题?怎么解决?
分库分表是”用复杂度换扩展性”,要清醒知道它带来的代价:
- 分布式 ID:不能再用自增主键。方案:雪花算法 Snowflake(时间戳+机器+序列,趋势递增、要解决时钟回拨)、号段模式(美团 Leaf,批量取号)、Redis incr。我用 Snowflake 为主。
- 跨片 join:避免——① 冗余字段(反范式,把要 join 的字段冗余进来);② 绑定表(ShardingSphere binding table,关联表用相同分片键落同片);③ 广播表(小字典表每片一份);④ 复杂关联查询走 ES/数仓。
- 跨片分页/排序/聚合:
order by + limit跨片要每片取limit (offset+size)再归并,深翻页性能差。解法:禁深翻页、用游标分页(基于上次最大 ID)、二次查询优化(ShardingSphere 的分页优化)。 - 跨片分布式事务:接第③板块(事务消息/Saga)。
- 扩容迁移:取模分片扩容要 rehash 搬数据。用一致性哈希/翻倍扩容/双写迁移减少影响。
- 全局唯一约束:跨片没法靠唯一索引保证全局唯一,要业务层或分片键设计保证。
- 结论:能不分就不分(先读写分离、加缓存、归档冷数据),数据量真到千万/亿级单表扛不住才分。
(对应简历:「有千万级数据量上读写分离、分片落地的实战经验」,强调你是”扛不住才分”而非过度设计)
Q39. 慢 SQL 怎么定位和优化?讲讲索引和回表、覆盖索引。
- 定位:① 开慢查询日志(
slow_query_log,long_query_time)抓慢 SQL;②EXPLAIN看执行计划——重点看type(全表ALL最差 →index→range→ref→const)、key(实际用的索引)、rows(扫描行数)、Extra(Using filesort/Using temporary是危险信号);③ 看是否用上索引、扫描行数是否合理。 - 回表:二级索引(非聚簇)只存索引列 + 主键,
select *时要拿主键回聚簇索引再查一次完整行,这就是回表,多一次 IO。 - 覆盖索引:如果查询的列都在索引里(
select的列被索引覆盖),就不用回表,Extra显示Using index。优化手段:把高频查询的列做成联合索引覆盖,消除回表。 - 常见优化:① 建合适的联合索引(遵循最左前缀);② 覆盖索引消回表;③ 避免索引失效(函数包裹列、
!=、like %xx、隐式类型转换、or);④ 深翻页改游标;⑤ 大事务拆小;⑥ 避免select *。 - 联合索引最左前缀:
(a,b,c)索引,where a/a,b/a,b,c能用,where b/b,c用不上——所以索引列顺序按区分度和查询频率排。 - 关联:我在青燕祥云做过历史 SQL 重构和慢 SQL 优化(接简历),这套是日常。
(对应简历:「MySQL(索引与慢 SQL 优化……)」「历史 SQL 重构」)
Q40. 事务隔离级别有哪几种?MVCC、间隙锁怎么防幻读?
- 四个隔离级别(隔离性递增、并发性递减):读未提交(脏读)→ 读已提交 RC(防脏读,有不可重复读)→ 可重复读 RR(MySQL 默认,防不可重复读) → 串行化(防幻读,性能最差)。
- 三类问题:脏读(读到未提交)、不可重复读(两次读同一行结果不同,因别人改了)、幻读(两次范围查询行数不同,因别人插入了)。
- MVCC(多版本并发控制):靠隐藏列(事务 ID、回滚指针)+ undo log 版本链 + ReadView 实现快照读。读不加锁,读的是某个版本的快照,实现”读不阻塞写、写不阻塞读”。RC 每次读生成新 ReadView,RR 事务第一次读生成 ReadView 后复用(所以可重复读)。
- 幻读怎么防:① 快照读(普通 select) 靠 MVCC,RR 下读的是一致快照,看不到别人新插入的 → 防住快照读的幻读;② 当前读(
select for update/update/insert) 靠 间隙锁 Gap Lock + 临键锁 Next-Key Lock 锁住范围间隙,别人无法在范围内插入 → 防当前读的幻读。MySQL RR 通过 MVCC + Next-Key Lock 组合,基本解决了幻读(这是 MySQL RR 比标准 RR 强的地方)。 - 取舍:隔离级别越高锁越多、并发越低。多数互联网业务用 RC(并发好、间隙锁少、死锁少),金融强一致用 RR/串行化。
(深度题,能讲 MVCC + ReadView + Next-Key 就是中高级水准,呼应你”事务隔离”简历点)
Q41. 读写分离怎么做?主从延迟导致读到旧数据怎么办?
- 读写分离:写走主库,读走从库,从库可多个分摊读压力。中间件(ShardingSphere、MyCat)或应用层
@Master/@Slave路由。 - 核心痛点:主从延迟——主库写完,binlog 同步到从库有延迟(毫秒到秒,高负载/大事务时更久),此时读从库会读到旧数据(”写完立即查查不到”)。
- 解法:
- 强制读主——对一致性敏感的读(写后立即读、下单后查订单)强制走主库(ShardingSphere
HintManager强制主库路由)。 - 会话/单调读——写后一段时间内该用户的读都走主库(基于时间窗或标记)。
- 延迟监控 + 摘除——监控从库
Seconds_Behind_Master,延迟过大的从库临时摘出读负载。 - 半同步复制——至少一个从库确认收到 binlog 才返回,降低丢数据风险(但增延迟)。
- 业务容忍——能容忍的读(列表、统计)走从库,不能容忍的走主库,按读的一致性要求分级路由。
- 强制读主——对一致性敏感的读(写后立即读、下单后查订单)强制走主库(ShardingSphere
- 取舍:读写分离提升读吞吐,但引入延迟和复杂度;不是所有读都能走从库,要按一致性分级。
- 关联:我在千万级数据场景做过读写分离 + 分库分表组合落地(接简历)。
(对应简历:「读写分离」「千万级数据量上读写分离……实战经验」)
⑦ 高并发 / 防超卖 / 调度(5 题)
Q42. 三态库存模型(预占/实占/释放)怎么防超卖?为什么分布式锁 + 乐观锁双层?
这是你 RPA 调度引擎的硬核设计,要讲透”为什么是三态、为什么双层锁”。
- 为什么三态:库存不是”扣减”一个动作,而是”先占住、确认后真扣、失败要还回”的生命周期。预占(Reserve)——下单时先冻结库存(可用→预占),但不真扣;实占(Commit)——支付/确认成功后预占转实扣;释放(Release)——超时未确认/取消,预占还回可用。这样既防超卖,又不会因为”占了没付”永久占死库存。
- 为什么双层锁:
- 分布式锁(粗粒度):保证”同一资源的预占操作串行化”,挡住瞬时并发,但锁粒度粗、持锁久会限制吞吐——所以只在必要的临界区用。
- 乐观锁(细粒度):扣减用
update stock set available=available-1, reserved=reserved+1 where id=? and available>=1 and version=?,把”判断 + 扣减”做成一条原子 SQL,CAS 失败说明并发冲突,重试或拒绝。乐观锁无阻塞、吞吐高。 - 双层逻辑:分布式锁防住跨节点的”宏观并发”,乐观锁在 DB 层兜住”微观并发”——锁是第一道闸控制并发量,乐观锁是最后一道闸保证数据正确,即使分布式锁因为主从切换等失效(接 Q34 RedLock 争议),乐观锁仍能防超卖。这就是我”不迷信单一锁”的体现。
- 配套:① 幂等去重防重复扣减(同一请求重试不会扣两次);② 超时未确认自动回滚(预占设 TTL,定时任务/延迟队列扫描超时预占自动释放)。
- 取舍:纯乐观锁高并发下冲突重试多(活锁);纯分布式锁吞吐低。双层是”性能 + 正确性”的平衡。
(对应简历:「预占、实占、释放三态库存模型,用分布式锁加乐观锁双层控制防止超卖,幂等去重避免重复扣减,超时未确认自动回滚」——这句几乎可以逐字背,每个词都对应上面)
Q43. RPA 调度器 TPS 从 300 提到千级,责任链 + 异步非阻塞 + 无锁队列各解决什么?
拆成”为什么慢 → 三招各治什么 → 结果”。
- 原来为什么 300:同步阻塞——一个任务从校验、解析、分配、执行串行跑,线程阻塞在 IO(DB、远程调用)上,线程池很快被占满,吞吐上不去。
- 责任链(Chain of Responsibility):把调度流程拆成一串可插拔的 Handler(鉴权→参数校验→资源分配→限流→执行),每个职责单一、可独立增删(开闭原则)。好处是流程清晰、易扩展,新增一种校验加个 Handler 即可,不改主流程。
- 异步非阻塞:把阻塞 IO(DB/远程)改成异步回调/CompletableFuture/事件驱动,线程不再傻等 IO,一个线程能处理更多任务,吞吐随之上去。这是从 300 到千级的主要来源。
- 无锁队列(Lock-Free Queue):任务分发用无锁队列(CAS/Disruptor 的 RingBuffer)替代
synchronized/BlockingQueue的锁竞争,高并发下避免线程因抢锁挂起/唤醒的上下文切换开销,生产者消费者吞吐大幅提升。 - 组合效应:责任链解决”可维护/可扩展”,异步非阻塞解决”线程不被 IO 拖死”,无锁队列解决”分发环节的锁竞争”——三者叠加把 TPS 拉到千级。
- 配套:再用分库分表替换部分单表悲观锁,缓解高并发下的锁竞争(接简历)。
(对应简历:「重构调度器,采用责任链加异步非阻塞加无锁队列,调度 TPS 从约 300 提升到千级」,逐词对应)
Q44. 无锁队列(Disruptor/CAS)的原理是什么?什么时候该用、什么时候不该用?
- 原理:无锁不是没有同步,而是用 CAS(Compare-And-Swap,硬件级原子指令) 替代互斥锁。生产者/消费者通过 CAS 抢占队列位置,失败就自旋重试,不会像锁那样把线程挂起/唤醒(避免内核态切换和上下文切换开销)。
- Disruptor 的关键设计:① RingBuffer 环形数组——预分配、内存连续、对 CPU 缓存友好(避免链表的缓存不命中);② 序号 + CAS 协调生产消费;③ 缓存行填充(Padding)防伪共享 False Sharing——让热点变量独占缓存行,避免多核间缓存行来回失效;④ 等待策略可选(忙等/阻塞,吞吐 vs CPU 权衡)。
- 适合:高吞吐、低延迟、生产消费速率匹配的场景(你的调度分发、日志、撮合);单机内的高频任务流转。
- 不适合:① 队列可能长期满/空(消费跟不上,自旋空转烧 CPU,不如阻塞队列让出 CPU);② 任务本身很重(瓶颈在处理不在队列,无锁优化收益小);③ 需要复杂的阻塞/超时/公平语义。
- 取舍:无锁用 CPU 自旋换掉锁的上下文切换——CPU 富裕、延迟敏感时划算;CPU 紧张、吞吐不高时,传统阻塞队列反而更省。
- 关联:我在 PereDoc 影像中间件也用过无锁多生产多消费队列 + 零拷贝 + 两级缓存 + JVM 调优(接 CLAUDE.md),能横向印证我对无锁的实战掌握。
(对应简历:「无锁队列」,PereDoc 项目可作为第二个无锁实战佐证)
Q45. 高并发系统的限流降级全链路怎么设计?
分层兜底,每一层都是一道闸:
- 接入层(网关):限流(令牌桶/集群限流,接 Q17)+ 黑白名单 + 租户配额,把超额流量挡在最外面。
- 应用层:① 熔断降级(Sentinel,接 Q18)防下游故障扩散;② 舱壁隔离(线程池/信号量隔离不同依赖);③ 热点参数限流(防单一热点 key 打爆)。
- 服务调用层:超时控制 + 克制重试(接 Q19)+ 快速失败。
- 数据层:缓存挡读(接缓存板块)、连接池限制、慢 SQL 治理、读写分离分摊。
- 异步削峰:突发写用 MQ 缓冲,下游匀速消费(漏桶思想),把”洪峰”摊平。
- 降级策略:① 功能降级(关掉非核心功能保核心);② 数据降级(返回缓存/默认值/兜底页);③ 读降级(强一致读降级为最终一致读)。
- 配套:全链路压测找瓶颈、预案演练、监控告警(Prometheus/Grafana,接简历)、限流阈值基于压测数据设定。
- 核心思想:宁可少服务一部分请求/降低体验,也要保证系统不雪崩——可用性优先,有损服务优于全损。
(对应简历:「高并发设计、限流降级、幂等、防超卖」「Prometheus、Grafana」,串成一套完整方法论)
Q46. 防重复扣减、超时回滚具体怎么实现?
- 防重复扣减(幂等):① 每个扣减请求带全局唯一业务 ID(订单号/流水号);② 扣减前查去重表/Redis,已处理直接返回上次结果,不再扣;③ 或用乐观锁版本号,重复请求第二次
update影响 0 行视为已处理;④ 去重记录和扣减最好同事务,避免”扣了但没记录去重”。 - 超时回滚(接三态):① 预占时记录预占时间 + TTL;② 延迟队列(RocketMQ 延迟消息 / Redis ZSet 按到期时间) 或定时任务扫描超时未确认的预占;③ 超时则触发释放(预占→可用),并幂等地通知相关方;④ 回滚本身要幂等(可能定时任务和用户取消同时触发)。
- 为什么要超时回滚:没有它,”占了不付”的库存会被永久占死,可用库存越来越少(资源泄漏),最终明明有货却下不了单。
- 延迟队列 vs 定时扫描:延迟队列精准、实时但依赖 MQ;定时扫描简单但有扫描间隔延迟和全表扫描压力。我倾向延迟队列为主、定时任务兜底漏网。
(对应简历:「幂等去重避免重复扣减,超时未确认自动回滚」,把”为什么”和”用什么数据结构”讲出来)
⑧ JVM / 排障(2 题)
Q47. 线上 OOM 怎么排查?讲一个你处理过的 OOM。
- 第一步定性:看 OOM 类型——
Java heap space(堆不够/泄漏)、Metaspace(类太多/类加载器泄漏)、GC overhead limit exceeded(GC 占用 >98% 但回收 <2%)、Direct buffer memory(堆外/Netty)、unable to create new native thread(线程太多)。不同类型方向完全不同。 - 抓现场:① 启动配置
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=...自动 dump;②jmap -histo:live <pid>看对象分布,jstack看线程,jstat -gcutil看 GC 频率。 - 分析:用 MAT(Memory Analyzer) 打开 dump——看 Dominator Tree(谁占内存最多)、Leak Suspects 报告、查 GC Roots 引用链定位”谁还拿着本该回收的对象”。
- 常见泄漏点:static 集合只加不删、ThreadLocal 未 remove(线程池场景,接 Q2)、监听器/回调未注销、连接/流未关、缓存无上限无淘汰、大对象/大结果集一次性加载。
- 我处理过的 OOM(复盘话术):青燕祥云时,高并发数据处理模块线上
Java heap space。dump 分析发现是字节缓冲未及时释放 + 一次性加载大结果集导致堆积。修复:① 大结果集改流式/分页处理;② 缓冲池化 + 用完归还,杜绝泄漏;③ 加堆外/缓冲使用监控告警。事后这块也成了我对”资源生命周期管理”的认知(呼应 CLAUDE.md 的”字节缓冲泄漏”复盘)。 - 升华:OOM 排查的核心是“现场(dump)+ 引用链(GC Roots)+ 业务还原”三步,光看异常栈没用。
(对应简历:「JVM 调优」「处理过线上 OOM」,CLAUDE.md 的字节缓冲泄漏复盘点)
Q48. Full GC 频繁、线程死锁怎么定位和调优?
- Full GC 频繁定位:①
jstat -gcutil <pid> 1000看各区占用和 GC 次数/耗时;② GC 日志(-Xlog:gc*)分析触发原因——老年代满(对象晋升太快/内存泄漏)、Metaspace 满、System.gc()显式调用、大对象直接进老年代;③ 结合 dump 看是不是泄漏。 - Full GC 调优方向:① 内存泄漏 → 先修泄漏(接 Q47),不是调参数;② 对象晋升太快 → 调大新生代/Survivor,让短命对象在 Young GC 就死掉,别晋升;③ 换低延迟收集器——G1(默认,分 Region、可控停顿
MaxGCPauseMillis)、ZGC/Shenandoah(超大堆、亚毫秒停顿);④ 合理设堆大小、避免大对象。调优前先明确目标:是降停顿还是提吞吐(鱼和熊掌)。 - 线程死锁定位:①
jstack <pid>直接看,JVM 会标Found one Java-level deadlock并打印互相等待的线程和锁;②jconsole/VisualVM有死锁检测按钮;③ Arthasthread -b一键找阻塞其他线程的线程。 - 死锁根因与预防:典型是多个线程以不同顺序获取多把锁形成循环等待。预防:① 固定加锁顺序(所有线程按同一全局顺序拿锁,破坏循环等待);②
tryLock带超时(破坏”不可剥夺”);③ 减少锁粒度/用无锁结构(接 Q44);④ 一次性申请所有资源。 - 我的实践:线上排障常备 Arthas(在线诊断,不用重启)+ jstack + MAT,覆盖 CPU 飙高、死锁、内存泄漏、热点方法(CLAUDE.md 的”线程死锁”复盘点)。
(对应简历:「JVM 调优」「Full GC、线程死锁」复盘点,体现你有完整排障方法论)
⑨ 云原生 K8s(1 题)
Q49. K8s 控制器的 Reconcile、Finalizer 是什么?你落地的控制器怎么做状态自愈?
- 声明式 + 控制循环:K8s 的核心范式是声明期望状态(Spec),控制器不断把实际状态(Status)收敛到期望——这就是 Reconcile(调和)。控制器 watch 资源变化,每次变化触发 Reconcile,计算”期望 vs 实际”的差异并执行,直到一致。Reconcile 必须幂等(同一状态多次调用结果一致),因为它会被反复触发。
- 为什么是”水平触发”而非”边缘触发”:控制器不依赖”收到了哪个事件”,而是每次都读当前完整状态来调和——所以漏了事件、重启了、重复触发都不影响最终收敛,这是 K8s 自愈和健壮性的根基(和我做最终一致系统”以当前状态为准、可重入”的思想完全一致)。
- Finalizer(终结器):删除资源前的”钩子”。打了 Finalizer 的资源被删时不会立即消失,而是进入
Terminating,等控制器完成级联清理(释放外部资源:网络路径、云资源、关联数据)后移除 Finalizer,对象才真正删除。防止”删了 CR 但外部资源泄漏”。 - 状态自愈怎么做:① Reconcile 里对比期望与实际,缺失则重建、漂移则纠正;② 失败重新入队(requeue)+ 指数退避重试;③ Status 字段 + Condition 记录真实状态供观测;④ Finalizer 兜住删除清理。
- 我落地的 3 个控制器(接 CLAUDE.md):租户生命周期、网络路径(CRD)、虚拟机资源池/算力池化。其中租户生命周期控制器就是把”租户开通状态机”用 Reconcile + Finalizer 实现的——这正好和我业务中台的状态机是同一套”声明式 + 自愈”思想在 Java 侧和 Go 侧的两次落地。
- 升华话术:我做中台的状态机和做 K8s 控制器,本质是同一个方法论——声明期望、持续收敛、幂等可重入、终态自愈,只是一个在业务层一个在基础设施层。
(对应简历:「Kubernetes 容器化部署」+ CLAUDE.md 的 3 个生产控制器,这是你 Java/Go 双栈的差异化亮点,主动抛出来)
⑩ 架构决策 / 软技能(1 题)
Q50. 带 6–8 人团队时,你怎么做技术选型和架构决策?讲一个最有挑战的技术决策。
P8/P9 考的是”技术领导力 + 决策方法论”,不是写代码。
- 决策方法论:① 从约束出发——先明确业务目标、SLA、团队能力、时间预算、运维成本,再选技术(技术服务于约束,不是追新);② 列候选 + 对比取舍——像 ADR(架构决策记录)那样写下”选项 A/B/C、各自优劣、为什么选 A、放弃了什么、什么情况下要重新评估”;③ 可逆性优先——可逆的决策快速试错,不可逆的(数据模型、分片键、对外契约)慎重并预留演进空间;④ 演进式而非一步到位——先满足当下、保留扩展点(YAGNI),别过度设计。
- 带团队落地:① 定开发规范和技术底座(机制下沉,减少重复造轮子);② 关键设计开评审、对齐再动手;③ 复杂模块自己啃硬骨头 + code review 把质量关;④ 把”为什么这么设计”讲清楚,让团队理解而不只是执行。
- 最有挑战的决策(举例话术,挑一个讲透):多租户隔离方案选型——是”一租户一库(隔离强但成本高、运维重)”还是”共享表行级隔离(成本低但有越权风险)”。我没有二选一,而是按租户价值分级(三档隔离),并设计了”机制下沉、策略上浮”让底座不随行业改动。挑战在于既要满足金融租户的强隔离监管要求,又要让长尾小租户成本可控,还要保证新行业接入不改底座。结果支撑了数十租户、多个行业、上线周期 2 周→3 天。这个决策的关键不是选了哪个技术,而是用”分级 + 抽象”把一个看似二选一的难题变成了可演进的框架。
- 复盘文化:每次线上故障写无指责复盘(脑裂、雪崩、消息积压我都复盘过),沉淀成 checklist 和底座能力,让同类问题不再发生。
(对应简历:「带 6 到 8 人开发小组」「制定开发规范」「技术负责人」,把”分级 + 抽象 + 演进 + 复盘”四点讲出来就是 P8/P9 的样子)
四、反问面试官的问题(结尾必备,体现你在挑公司)
面试官问”你有什么想问的”时,别说没有。按场景挑 2–3 个:
- 团队现在最大的技术挑战是什么?是规模、稳定性还是迭代速度?
- 这个岗位期望我前 3–6 个月解决的核心问题是什么?
- 团队的中台/平台现在处于什么阶段——从 0 搭建、治理优化还是规模化扩张?
- 技术决策是怎么做的?有没有架构评审 / ADR 机制?
- 线上稳定性文化怎么样——有没有 on-call、故障复盘、SLO 体系?
- 团队的技术演进方向(云原生、Service Mesh、AI 工程化)有什么规划?
- 这个级别(P8/P9 或对标)在贵司的核心衡量标准是什么?
收尾话术:”谢谢,我对这个方向很感兴趣,尤其是 XX 部分和我过去做中台/中间件治理的经历很契合,期待有机会深入。”
五、最后 30 分钟冲刺清单
- 自我介绍出声念 2 遍,掐表控制在 3 分钟内,确认 6 个钩子记得住。
- 三个核心数字脱口而出:2 周→3 天、P95 -30%、TPS 300→千级、告警 30 分→1–3 分。
- 王牌项目”三件事”能不看稿讲:三档隔离 / 状态机+事务消息 / 网关限流熔断灰度。
- 高频必问预热:穿透击穿雪崩(Q32)、分布式事务选型(Q21)、防超卖三态双层锁(Q42)、多租户隔离(Q1)。
- 准备 2 个”踩坑复盘”故事:对账偏差(Q26)、OOM/字节缓冲泄漏(Q47)——STAR-R 讲法。
- 想好 2–3 个反问问题。
- 心态:P8/P9 面试是双向选择,你是来解决他们问题的,不是来被审判的。稳住,先结论后展开,多抛取舍。
祝下午面试顺利!🎯
石洋洋 · 上海信息人才服务有限公司 — Java 开发(后端架构方向) · P8 专属面试题库 50 题
岗位:18-30K(1.8-3万)· 北京·东城·东四 · 国企 / 人力资源 / 100-299 人 · 5-10 年 / 大专(本科优先) · 要求 3 年以上微服务架构 招聘性质:招聘主体为人力资源公司,疑似第三方招聘 / 外派 / 代招——面试中需主动确认实际用人单位与用工性质(详见投递备注)。 匹配度:约 92%(投递备注口径)。岗位是”架构设计 + 开发”的资深档(5-10 年),候选人约 10 年 + 主导过微服务中台架构,硬命中分布式事务 / 分库分表 / 高并发。 主推项目:① Elevate-SaaS 多租户业务中台(微服务边界 / 段内 Saga / 事务消息 / 三档隔离);② RPA 流程引擎(分库分表 + 乐观锁 + 三态防超卖 + 混合补偿);③ PereDoc 影像中间件(高并发 + JVM + 两级缓存);辅 backbone-conroller(Commons Chain 动态工作流)、sword(集合注入式 SPI)。 源码锚点:
sword(PluginLocator.java:15/21/27/31/40已核行号)、backbone-conroller(EditFinishStep.java/DownloadLockStep.java/KafkaConsumer.java/RedisDelayQueueUtil.java已核路径)、架构方法论 4A(BA/AA/DA/TA)+ TOGAF ADM。 诚实边界:主力 Java 十年、Go 近 5 年(K8s 控制器,本岗 Docker/K8s 为”者优先”加分);Seata/TCC 框架”用过、懂原理、更多自研柔性事务”;数据指标均标内部压测口径。凡涉加分项/未实战项均显式标注层级。
题量分布
| 类别 | 数量 | 题号 |
|---|---|---|
| 技术深度题 | 12 | Q1–Q12 |
| 架构设计题(4A 四视角全覆盖) | 10 | Q13–Q22 |
| TOGAF 架构方法论题 | 5 | Q23–Q27 |
| 项目追问题(源码级) | 10 | Q28–Q37 |
| 管理决策题 | 8 | Q38–Q45 |
| 行业认知题 | 2 | Q46–Q47 |
| 薪资谈判题(HR 轮) | 3 | Q48–Q50 |
设计原则/模式专项:技术深度题与项目追问题里有 7 道(Q3/Q5/Q8/Q29/Q30/Q31/Q35)显式考”某原则/模式 + 为什么这么设计 + 取舍 + 对比 naive 写法 + 易错点”,落到真实源码锚点;另有 2 道反向坏设计题(Q11/Q12)。详见末尾「设计原则/模式覆盖清单」自检表。
P8 回答六要素(每题自检):①全局视野 ②取舍判断 ③数据支撑(引简历数字、标压测/内部口径)④踩坑经历 ⑤方法论 ⑥反吹嘘(加分项标边界)。专项纪律:能区分”何时该用模式 vs 过度设计(YAGNI/KISS)”。这是架构岗,答题要站在”系统全局 + 取舍 + 方法论”高度,而非单点实现。
一、技术深度题(Q1–Q12)
Q1|分布式系统设计要解决哪些核心问题?CAP 你怎么权衡?为什么说”一致性”有很多档?
一句直觉:单机变分布式,最大的变化是”网络会断、节点会挂、消息会乱”——所以一致性、可用性、容错要重新权衡,没有免费的强一致。
参考答案:
- 核心问题:一致性(多副本/多服务的数据看起来一不一致)、可用性(部分节点挂了还能不能服务)、分区容错(网络断了怎么办)、以及由此衍生的——分布式事务、幂等、顺序、时钟、共识。
- CAP 权衡:网络分区(P)在分布式里必然存在,所以本质是 C 和 A 的取舍。注册发现/缓存选 AP(可用优先,容忍短暂不一致);账务/库存核心选 CP 或用柔性事务补。CAP 是粗粒度的,工程上更常用 PACELC(无分区时也要在延迟 L 和一致性 C 间权衡)。
- 一致性分档:强一致(线性一致)→ 顺序一致 → 因果一致 → 最终一致。绝大多数业务最终一致就够(下单后积分晚几秒到无所谓),只有账务等少数要强一致。把一致性当”按业务付费的服务等级”来选,是架构师的基本功。
- 方法论:先问”这个数据不一致会造成什么后果”——后果可逆/可补偿就最终一致,不可逆(钱)才上强一致。
- 实战:Elevate-SaaS 注册发现 AP、计费扣减强一致(事务消息)、开通流程最终一致(Saga)——同一系统里按数据重要性分档(见 Q22/Q35)。
- 反吹嘘:Paxos/Raft 我懂原理、读过 etcd/client-go 相关源码,但没自研过共识算法,如实说。
Q2|分布式事务全景:2PC / TCC / Saga / 事务消息 / 本地消息表,原理与适用,你怎么选?
一句直觉:分布式事务就是”几个服务的操作要么一起成、要么一起回”,但因为跨网络做不到瞬时强一致,所以大多用”最终一致 + 补偿”换性能。
参考答案:
- 2PC/XA:协调者 prepare → commit 两阶段,强一致但同步阻塞、锁资源久、协调者单点,性能差,少用。
- TCC:Try(预留资源)- Confirm(确认)- Cancel(释放),业务层的两阶段,强一致 + 高性能但侵入大(每服务写三方法),要处理空回滚、悬挂、幂等。适合账户、库存预扣等强一致短事务。
- Saga:长事务拆成一串本地事务 + 每步补偿,失败逆序补偿。无锁、吞吐好、容忍中间态,适合步骤多可补偿的长流程(开通、履约)。
- 事务消息(RocketMQ):半消息 → 本地事务 → commit/rollback + 回查,保证”本地事务 + 发消息”原子。适合异步解耦最终一致,侵入最小、最常用。
- 本地消息表:业务库里建消息表,本地事务一起写,再异步投递 + 重试。事务消息的”穷人版”,不依赖 MQ 的事务特性。
- 怎么选(决策树):能最终一致吗?能 → 异步解耦用事务消息/本地消息表,长流程用 Saga;不能且短事务 → TCC;尽量不用 2PC。
- 实战 + 取舍:90% 业务我用事务消息/Saga,只在强一致预扣场景用 TCC。Seata 的 AT 模式(自动反向 SQL)方便但有全局锁、隔离性弱,我更倾向显式柔性事务可控(见 Q35)。
- 反吹嘘:Seata 四种模式我用过 AT/TCC、懂全局锁与回滚原理,但生产里自研柔性事务更多,不冒充 Seata 内核专家。
Q3|【设计模式专项】消息队列可靠投递 + 幂等怎么设计?对应哪些模式?对比 naive 写法。
一句直觉:MQ 一定会重复投递(at-least-once),所以”发得出、不丢、收了不重复处理”是三件必须设计的事,不能指望 MQ 帮你保证。
参考答案:
- 可靠投递三段:生产端事务消息/本地消息表保证”业务+发消息”原子;Broker 多副本持久化;消费端手动 ack、处理完再提交位点。
- 幂等设计:消费端用业务唯一键 + 去重表/Redis(
setnx标记已处理);或状态机只允许合法跃迁(重复消息落到已处理态直接丢弃)。 - 对应模式:① 幂等去重组件 = 代理/拦截器(在真正处理前拦一道判重);② 状态机判跃迁 = 状态模式;③ 重试退避 = 模板方法(”拉取→处理→ack→失败退避重投”是固定骨架);④ 本地消息表轮询投递 = 生产者-消费者。
- 对比 naive 写法:naive 是”收到消息直接处理、处理完不管”——丢消息(没事务消息)、重复处理(没幂等)、失败就丢(没重试)。正确做法把”可靠消费”封装成统一框架(消费幂等 + 重试 + 死信兜底),业务只写处理逻辑(DRY + 封装变化)。
- 易错点:① 幂等键选错(用了非唯一的字段);② 去重表和业务表不在一个事务,判重通过但业务失败、留下脏标记;③ 提交位点早于处理完成(见 Q36 真实坑)。
- 实战:Elevate-SaaS 计费/审计走事务消息 + 消费幂等;RPA 跨机器人步骤混合补偿 + 事务消息保最终一致。
Q4|分库分表深入:分片策略、分布式 ID、跨片查询、在线扩容、跨片事务,整套怎么设计?
一句直觉:分库分表是”把一张大表切成很多小块分散到多机”,切法决定了以后查询顺不顺、扩容疼不疼。
参考答案:
- 分片策略:哈希(均匀但扩容要迁数据,用一致性哈希/预分片缓解)vs 范围(易扩容但易热点)。分片键选高频查询 + 分布均匀的列(tenantId/userId),关联表用相同分片键(基因法)让 join 落同片,字典表做广播表。
- 分布式 ID:Snowflake(趋势递增、对索引友好,依赖时钟,时钟回拨要处理)或号段模式(DB 发段,无时钟依赖)。不用 UUID。
- 跨片查询:带分片键直查;不带的走异构索引(ES)或聚合层(各片查 + 归并);全局排序分页用各片 TopN 归并。
- 在线扩容:预分片(一开始分足够多逻辑分片、少量物理库,扩容只迁逻辑分片不改路由)是最省心的;否则一致性哈希 + 双写迁移 + 灰度切流。
- 跨片事务:避免;实在要用事务消息/Saga 柔性事务。
- 中间件:ShardingSphere(Sharding-JDBC,应用层、轻)vs 独立 Proxy(MyCat,对应用透明、多一跳)。我多用 Sharding-JDBC。
- 实战:RPA 流水/物料表分库分表 + Snowflake;不带分片键的运营报表走异构索引(见 Q31/Q33)。
- 取舍/反吹嘘:能不分就不分(先索引/读写分离/归档/缓存穷尽)。我做过的分库分表是中等规模(非互联网超大体量),如实说,不吹”亿级分片”。
Q5|【设计模式专项】缓存高可用 + 一致性怎么设计?穿透/击穿/雪崩 + Cache Aside/双删/binlog,对应哪些模式?对比 naive。
一句直觉:缓存有两道坎——失效时别把库压垮(三防),更新时别和库长期不一致(一致性)。
参考答案:
- 三防:穿透(布隆 + 空值缓存,代理前置)、击穿(互斥单飞重建,模板方法骨架)、雪崩(随机过期 + 多级缓存 + 限流,装饰器逐级回源)。
- 一致性:Cache Aside(先更 DB 再删缓存)+ 延迟双删(更新后延迟再删一次清并发窗口回填的旧值);强一致场景用 canal 订阅 binlog 异步刷缓存(观察者——监听数据变更事件刷缓存)。
- 多级缓存:本地 Caffeine(一级,抗瞬时热点、注意多副本本地缓存不一致)+ Redis(二级),逐级回源。
- 对应模式:代理、模板方法、装饰器、观察者(binlog 监听);统一封装成
get(key, loader, policy)= 外观 + 封装变化。 - 对比 naive 写法:naive “查缓存没有就查库回填、更新只更库不管缓存”——三防全裸奔、长期脏数据。正确做法把缓存读写 + 三防 + 一致性收口成组件,业务一行调用(DRY)。
- 易错点:① 本地缓存多副本不一致(要短 TTL 或广播失效);② 先删缓存再更库的并发回填旧值(见 Q12);③ binlog 刷缓存有延迟,强一致场景要评估。
- 实战:PereDoc 两级缓存 + 布隆 + 互斥;一次大批 key 同时过期雪崩,加随机 TTL + 本地缓存根治(见 Q34)。
Q6|高并发系统的高可用怎么设计?限流算法、降级、熔断、隔离、排队怎么配合?
一句直觉:高并发高可用 = “自己会拒绝(限流)、会断舍离(熔断降级)、会分舱(隔离)、会排队(削峰)”。
参考答案:
- 限流算法:计数器(简单、有临界问题)、滑动窗口(平滑)、令牌桶(允许突发)、漏桶(恒速、削峰)。秒杀用令牌桶/Warm Up 放量 + 漏桶排队。
- 熔断降级:下游慢/挂时快速失败 + fallback(缓存/兜底/排队),熔断三态(Closed/Open/Half-Open)自动恢复。
- 隔离:线程池/信号量隔离(舱壁),核心与非核心依赖分舱,一个挂不连累全局。
- 排队削峰:MQ 异步化把瞬时洪峰摊平(下单先入队再异步处理)。
- 多级缓存:读洪峰靠本地 + Redis 顶,DB 兜底。
- 配合:网关限流挡量 → 多级缓存扛读 → 库存乐观锁扣减 → 下单 MQ 削峰 → 下游熔断降级。层层泄洪。
- 实战:Elevate-SaaS 热点接口 Sentinel 限流 + 两级缓存 + 慢 SQL 治理,P95 降约三成;外部征信不稳熔断降级防雪崩。
- 方法论:高可用 = 减少故障 + 缩小爆炸半径 + 快速恢复;限流/熔断/隔离/排队各管一段,组合成纵深防御。
Q7|Spring Cloud 微服务治理体系,站在架构师视角你怎么选型和演进?
一句直觉:架构师选治理组件不是”哪个火用哪个”,是”团队 hold 得住 + 生态稳 + 退出成本低”。
参考答案:
- 治理五件事:注册发现、配置中心、网关、熔断限流、链路追踪——分别选 Nacos、Nacos Config、Spring Cloud Gateway、Sentinel、SkyWalking+ELK。
- 选型依据:① 生态成熟与活跃(Netflix 全家桶停更,转 Spring Cloud Alibaba);② 二合一降运维(Nacos 注册+配置);③ 可观测和控制台友好;④ 和 Spring Boot 版本基线对齐(Boot3/JDK17)。
- 演进路线:单体 → 按限界上下文拆服务(先粗后细)→ 引入网关 + 注册配置 → 量上来加链路追踪 + 精细限流 → 治理能力部分下沉 Service Mesh(Istio sidecar)。分阶段,不一步到位(YAGNI)。
- 架构师的额外职责:定服务拆分原则、接口契约规范、统一治理基线(限流/熔断默认值、链路埋点规范),用 ARB 看门防失控(见 Q25)。
- 实战:Elevate-SaaS 就是这套,从中台底座起逐步演进;Netflix 组件退场时我做过 Hystrix→Sentinel、Zuul→Gateway 的迁移评估。
- 取舍:Mesh 把治理下沉很美,但运维复杂度高,中等团队未必划算——我会评估”治理痛点是否真到了要 Mesh”再上,不为先进而先进。
Q8|【设计模式专项】Spring Cloud Gateway / Sentinel / OpenFeign 背后是哪些设计模式?对比 naive 写法。
一句直觉:这三个常用组件其实是三个经典模式的工业级范例——网关/限流是责任链,Feign 是代理。
参考答案:
- Gateway 过滤器链 = 责任链 + 装饰器:
GlobalFilter串成链,每个 filter 处理后chain.filter()传下一个,pre/post 两段。加能力只加 filter(OCP)。naive 是入口一个大方法if 鉴权 if 限流 if 日志,加一种改一片(违反 OCP)。 - Sentinel SlotChain = 责任链:每次资源访问过 NodeSelector→Statistic→Flow→Degrade 一串 slot,加规则加 slot。naive 是把统计/限流/熔断逻辑耦在一个方法里。
- OpenFeign = 动态代理 + 工厂:接口无实现,JDK 动态代理把方法调用翻译成 HTTP。naive 是 RestTemplate 手拼 URL 传参解析、样板重复(违反 DRY)。
- 为什么这么设计:横切关注点可插拔可编排(责任链)、远程调用伪装本地且面向接口(代理 + DIP)——本质都是开闭 + 关注点分离 + 封装变化。
- 易错点:① Gateway/Sentinel 是异步/拦截路径,filter/slot 里别写阻塞调用、别做重逻辑;② Feign 默认重试对非幂等接口危险(关掉);③ slot/filter 的 order 排错会让鉴权失效或限流误伤。
- 实战 + 反吹嘘:这些我在 Elevate-SaaS 生产里用并踩过坑(见对应项目题)。我能讲到原理和源码思路,但没改过这三个组件的内核,如实说。
Q9|JVM 调优 + 稳定性:Full GC / 内存泄漏 / 线程死锁 / 连接池耗尽怎么定位?
一句直觉:四大慢性病,套路一致——先看现象(GC 日志 / 线程 dump / 堆 dump / 监控),再定位根因,90% 是代码问题不是调参数。
参考答案:
- Full GC:GC 日志 +
jstat -gcutil看各代;根因常是大对象/缓存只进不出涨老年代、新生代太小过早晋升。 - 内存泄漏:堆缓慢涨、Full GC 后降不下来;
jmapdump + MAT dominator tree 找被 GC Root 引着不放的对象(静态集合、ThreadLocal 没 remove、监听器没注销)。 - 线程死锁:
jstack找 BLOCKED 环(”waiting to lock X held by Y”),根治是统一加锁顺序或tryLock超时。 - 连接池耗尽:获取连接超时;根因常是连接没归还(忘 close/事务挂住)、池配太小、慢 SQL 长占连接。
- 真实案例:PereDoc 夜间批处理 Full GC 每小时数次——一次性加载大结果集 + 过早晋升,改分页/流式 + 调堆结构后降到约每周一次以内;连接池耗尽是某慢 SQL 长期占连接,治慢 SQL + 设连接最大存活解决。
- 方法论:现象→dump/日志→根因→改代码→复盘进 checklist;建”线上故障排查四件套”清单带团队。
Q10|最终一致怎么保证不丢、不重、不乱序?对账和补偿怎么兜底?
一句直觉:最终一致不是”放任不管最后会好”,是”用补偿 + 对账主动把它纠到一致”。
参考答案:
- 不丢:事务消息/本地消息表保证业务和消息原子;消费手动 ack、处理完再提交;失败重试 + 死信。
- 不重:消费幂等(唯一键 + 去重表/状态机),因为重试必然重复。
- 不乱序:要顺序就用 MQ 的顺序消息(同一业务键路由到同一队列/分区,单线程消费该键);全局顺序代价高,尽量只保证”同实体内有序”。
- 对账兜底:定时对账 job 扫”长期处于中间态”的记录(预占未实占、消息发了没回执),自动补偿或人工介入;幂等保证补偿可重复执行。
- 补偿设计:每个跨服务步骤设计反向补偿动作,Saga 逆序补偿;补偿本身幂等(已补偿的当成功)。
- 实战:RPA 跨机器人步骤混合补偿 + 事务消息 + 超时延迟消息回滚 + 对账兜底(见 Q31/Q32);防超卖预占超时未确认自动释放也是这套。
- 方法论:最终一致 = 可靠投递 + 消费幂等 + 顺序保证(按需)+ 对账补偿四件套,缺一会出”丢/重/乱/悬挂”。
Q11|【反向题·坏设计】一段代码:synchronized 本地锁保护”扣配额”,且”先删缓存再更库”。多副本部署下错在哪?违反什么?怎么重构?
一句直觉:本地锁多副本下各锁各的等于没锁;先删缓存再更库,中间并发读把旧值又灌回去。
参考答案:
- 本地锁的问题:
synchronized只在单 JVM 内互斥,服务多副本时两个实例各持本地锁、同一配额被并发扣减 → 超卖。违反——对共享资源并发控制的边界判断错误、对”多副本部署形态”的最少知识缺失。 - 缓存顺序问题:先删缓存、库还没更完的窗口里,并发读未命中 → 读库旧值 → 回填缓存 → 库更新后缓存仍旧,长期不一致。
- 重构(锁):跨实例临界区用分布式锁(Redisson/etcd)或更优的 DB 乐观锁
update set used=used+n where used+n<=quota(CAS 原子防超卖、吞吐高),粗粒度分布式锁只用于必须串行的边界。 - 重构(缓存):Cache Aside(先更库再删缓存)+ 延迟双删;强一致用 binlog 异步刷。
- 违反的原则:并发控制选型错、缓存一致性边界考虑不全、对部署形态最少知识缺失。
- 实战印证:RPA 物料调度正是 DB 乐观锁(细)+ 分布式锁(粗边界)+ 幂等去重,就是为避开这两坑(见 Q32)。
- 架构师视角:这类坑应通过”统一并发组件 + CR checklist + 架构守护”系统性根治,而非逐个救火(见 Q44)。
Q12|【反向题·坏设计】一套”微服务”里:三个服务共享一张库、互相同步 Feign 调用成环、一个聚合服务几千行。这是什么反模式?怎么诊断和重构?
一句直觉:拆是拆开了,但一改全得一起改、一起发、还互相等——这是”分布式单体”,拿到了微服务的所有缺点、没拿到优点。
参考答案:
- 反模式诊断:① 共享库——多服务直连同一张表,数据所有权不清、改表全炸(违反限界上下文、数据归属单一服务);② 同步调用成环——A→B→C→A,一处慢全链路雪崩、还可能死锁等待(违反低耦合);③ 巨型聚合服务——几千行什么都管(违反 SRP)。这三条合起来就是分布式单体。
- 违反的原则:限界上下文、高内聚低耦合、单一职责、服务自治(每个服务管自己的数据)。
- 重构路径:① 数据解耦——每张表归一个服务所有,别人要数据走接口/事件,不直连库(先逻辑隔离、再物理拆库);② 打破调用环——把同步环改成异步事件(A 发事件 B/C 订阅),或重划边界把强耦合的合并;③ 拆巨型服务——按职责拆,用防腐层(ACL)隔离遗留;④ 小步迁移(绞杀者模式),不大爆改。
- 诊断工具:调用链分析(SkyWalking 看服务依赖图找环)、变更耦合分析(哪些服务总一起发)、ArchUnit 守护依赖规则。
- 实战:我做微服务边界划分时严守”一张表一个 owner、跨边界走事件”,正是为了不退化成分布式单体(见 Q14/Q28)。
- 架构师视角 + 取舍:有时”先合回单体再正确拆分”比硬拆更对——拆分不是越多越好(YAGNI),边界正确比数量重要。
二、架构设计题(Q13–Q22,4A 四视角全覆盖)
4A 覆盖矩阵(自检)
| 题号 | BA 业务 | AA 应用 | DA 数据 | TA 技术 |
|---|---|---|---|---|
| Q13 综合贯穿 | ✓ | ✓ | ✓ | ✓ |
| Q14 微服务边界 | ✓ | ✓ | ||
| Q15 高可用容灾 | ✓ | ✓ | ||
| Q16 数据架构分库分表 | ✓ | ✓ | ||
| Q17 业务能力分层平台化 | ✓ | ✓ | ||
| Q18 高并发接口优化 | ✓ | ✓ | ||
| Q19 可观测 + 容量 | ✓ | ✓ | ||
| Q20 核心流程状态机建模 | ✓ | ✓ | ||
| Q21 多级缓存一致性 | ✓ | ✓ | ||
| Q22 分布式事务落地 | ✓ | ✓ | ✓ | |
| 覆盖数 | 4 | 6 | 6 | 6 |
四视角均 ≥2 题,Q13 为一题打通 BA→AA→DA→TA 的综合贯穿题。
Q13|【BA+AA+DA+TA 综合】用 4A 设计一个”高可用、可扩展的核心业务后端系统”(这是 JD 第一条职责)。
一句直觉:架构师的看家本事就是从业务、应用、数据、技术四个视角把一个系统讲清楚,并让它高可用、能扩展。
参考答案:
- BA 业务架构:先理业务能力地图、核心价值流、干系人与 SLA 要求(哪些流程不能停、哪些数据不能错);按”共性下沉、个性上浮”分层能力。
- AA 应用架构:按限界上下文拆服务(先粗后细),定同步(命令查询,OpenFeign)vs 异步(事实通知,MQ)边界,服务自治(各管各的数据),网关统一入口。
- DA 数据架构:数据归属单一服务;大表分库分表(分片键 + Snowflake)、冷热分离归档;强一致数据用事务消息/TCC,其余最终一致;多副本 + 读写分离。
- TA 技术架构:Spring Cloud + MySQL + Redis + RocketMQ + K8s;高可用靠多副本/多活、限流降级熔断隔离、多级缓存、无单点;可观测 SkyWalking+ELK+Prometheus;容量按峰值压测定。
- 贯穿示例:一笔核心业务请求——BA 是某价值流的关键节点 → AA 由对应服务编排、跨边界发事件 → DA 落库分片 + 一致性保障 → TA 上限流缓存兜高并发、多副本兜高可用。
- 可扩展性:水平扩(无状态 + 分库分表 + HPA)、功能扩(扩展点/插件 OCP,见 Q17/Q30)。
- 实战:这就是 Elevate-SaaS 中台的真实骨架。
- 取舍:高可用和成本对冲——核心链路做多活/强保障,非核心简化(别全系统镀金,YAGNI)。
Q14|【AA】微服务边界怎么划?同步异步怎么选?怎么防止退化成分布式单体?
一句直觉:边界划错,微服务就是”分布式单体”——拆了反而更难维护。
参考答案:
- 划界:限界上下文(DDD,按业务语义内聚)+ 康威定律(对齐团队)+ 数据归属(一张表一个 owner)。一起变化的放一起、独立变化的拆开(高内聚低耦合)。
- 同步 vs 异步:命令/查询要立刻拿结果用同步 RPC;事实通知/解耦/削峰用异步事件。原则”命令查询同步、事实通知异步”。
- 防分布式单体:① 禁止共享库(跨服务走接口/事件);② 打破同步调用环(改异步或重划边界);③ 警惕”总一起发布”的服务(边界错了该合或重划);④ ArchUnit 守护依赖方向。
- 实战:Elevate-SaaS 认证/租户/计费独立上下文,开通同步建库表、计费审计异步事件;严守一张表一个 owner(见 Q12/Q28)。
- 取舍:先粗后细——证据充分(团队/变更频率分化)再细拆,别一上来 20 个微服务(YAGNI)。边界正确 > 服务数量。
Q15|【TA】高可用 + 容灾架构怎么设计?多活、容灾、无单点怎么落地?
一句直觉:高可用是”平时部分节点挂不影响服务”,容灾是”整个机房没了还能起来”——两个层级,成本不同。
参考答案:
- 消除单点:无状态服务多副本 + 多可用区;有状态组件(DB/Redis/MQ)主从/集群 + 自动故障转移;网关/LB 自身也要多副本。
- 多活/容灾分级:① 同城双活(双机房、RT 低、数据同步近实时);② 异地灾备(冷/温备,RPO/RTO 容忍更大);③ 单元化(按用户分片到不同单元,单元自治、各单元独立完整,故障隔离在单元内)——大体量才需要。
- 数据层:主从复制 + 半同步(防丢)、跨机房延迟要评估;强一致数据慎做多写(冲突难解)。
- 故障应对:限流降级预案 + 熔断隔离缩小爆炸半径 + 快速回滚 + 演练(混沌工程)。
- 容量:按峰值压测定容量 + 弹性扩容(HPA)+ 预留 buffer。
- 实战 + 反吹嘘:我做过多副本 + 主从 + 限流降级的高可用,异地多活/单元化是大体量互联网才上的,我懂架构原理、没在生产做过完整单元化,如实说,不冒充。
- 方法论:高可用按”无单点→同城双活→异地灾备→单元化”分级投入,按业务 RPO/RTO 要求选,不一步到位。
Q16|【DA+TA】数据架构怎么设计?分库分表 + 冷热分离 + 一致性 + 多副本怎么统筹?
一句直觉:数据架构是系统的地基——存哪、怎么分、怎么保一致、怎么不丢,定不好后面全是返工。
参考答案:
- 分库分表:见 Q4(分片键、Snowflake、跨片、扩容)。先穷尽索引/读写分离/归档/缓存再分。
- 冷热分离:热数据在线库(小而快)、冷数据归档库/对象存储(OLAP/ES 查询),按时间/状态迁移。降低在线库压力。
- 一致性:强一致数据(账务)事务消息/TCC;其余最终一致 + 对账(见 Q10)。
- 多副本与读写分离:主写从读,半同步复制防丢;从库延迟敏感的读走主库或强制路由。
- 数据治理:数据归属单一服务(防分布式单体)、统一数据字典、变更向前兼容(先加列不删列、双写过渡)。
- 实战:RPA 流水分库分表 + 冷归档 + 异构索引查询;Elevate-SaaS 多租户三档隔离 + 行级透明路由(见 Q21/Q31/Q33)。
- 取舍:数据架构改造成本最高,所以前期要按”3-5 年数据量增长”预留(预分片),但别过度(YAGNI)——预留到合理量级即可。
Q17|【BA+AA】业务能力怎么分层、复用、平台化?怎么避免”什么都塞进底座/中台”?
一句直觉:平台化就是把”每个项目都重写的通用能力”沉淀成共享底座,但底座只放真正通用的,行业特例别浇进去。
参考答案:
- 分层:机制下沉(认证、隔离、网关、调度、计费框架通用机制进底座)、策略上浮(行业特性挂扩展点)。判断进底座的标准:≥2 业务共用 + 稳定 + 机制类。
- 复用:抽象成构建块/starter(见 Q27 ABB/SBB),项目按参考架构起步只做差异。
- 防膨胀:底座只暴露扩展点(OCP,加行业不改底座);ARB 守门”凭什么进底座”;定期反审计把混进去的行业逻辑剥离上浮。
- 实战:Elevate-SaaS 底座沉淀租户/RBAC/中间件,金融征信/营销/停车是行业视图,新视图只声明模块清单 + 数据权限不改底座;曾把某大客户特例错塞底座、剥离上浮止血(见 Q28)。
- 架构师视角 + 取舍:平台化要警惕”过早平台化”——没有 2-3 个真实业务验证就抽”通用平台”,往往没人用还得维护(YAGNI)。先做透 1-2 个业务再沉淀。
Q18|【AA+TA】一个核心接口高并发下慢,端到端怎么优化(含可观测、灰度、回滚)?
一句直觉:优化先看监控定位、再分层下手、灰度上、留后路——不是凭感觉调。
参考答案:
- 量化定位:SkyWalking 看链路耗时分布(哪跳/哪条 SQL/哪次远程调用吃时间),定 SLO。
- 分层优化:DB(慢 SQL/索引/分页/分库分表)→ 缓存(多级 + 三防)→ 调用(并行化、批量、异步)→ 代码(减序列化/对象创建)。
- 削峰:写洪峰 MQ 异步化;读洪峰多级缓存。
- 灰度 + 回滚:网关按比例/租户灰度,feature flag 控开关,先 1% 观测再放量;回滚条件预设(错误率/延迟阈值),DB 变更向前兼容可回切。
- 观测兜底:上线盯 P95/错误率/GC/慢 SQL,埋点对比前后。
- 实战:Elevate-SaaS 热点接口 SkyWalking 定位慢 SQL + 缓存缺失,治理后 P95 降约三成,灰度无异常再全量。
- 方法论:度量→定位→分层优化→灰度→观测→回滚预案,按序走。
Q19|【DA+TA】可观测体系 + 容量规划怎么做?
一句直觉:可观测让你”出事查得到”,容量规划让你”洪峰扛得住”——一个事后定位、一个事前预防。
参考答案:
- 可观测三支柱:Metrics(Prometheus,趋势告警)+ Tracing(SkyWalking,定位哪跳慢)+ Logging(ELK,traceId 串明细)。三者用 traceId 打通;异步/MQ 边界透传 traceId(最易断)。
- 业务可观测:技术指标外加业务指标(成功率、一致性),业务异常也看得到。
- 容量规划:① 全链路压测找系统瓶颈和拐点(不是单接口压);② 按峰值 + buffer 定容量;③ 弹性扩容(HPA)应对波动;④ 容量水位告警(到 70% 预警扩容)。
- 实战:Elevate-SaaS 用 SkyWalking+ELK+Prometheus,tenantId 打进日志按租户排障;异步 traceId 透传踩过断链坑。
- 反吹嘘:OpenTelemetry/全链路压测平台我了解和用过部分,大规模全链路压测体系我参与不主导,如实说。
Q20|【BA+DA】核心业务流程怎么用状态机 + 元数据建模并保证一致性?
一句直觉:复杂业务流程画成状态图,每步只能合法跃迁,配上补偿,再绕也不乱。
参考答案:
- 状态机:业务实体状态 + 合法跃迁 + 每步动作 + 失败补偿,非法跃迁直接拒(Fail Fast)。
- 元数据驱动:流程步骤、规则、配额以元数据声明,加流程/规则改配置不改代码(CoC)。
- 一致性:多步跨服务用段内 Saga 逆序补偿;强一致步骤事务消息;状态与期望版本比对防漂移;超时未推进的状态用延迟消息/对账推进或回滚。
- 幂等:每步幂等(重复执行不产生副作用),补偿也幂等。
- 实战:Elevate-SaaS 租户开通状态机 + 段内 Saga(建库→部署→配额→计费→审计),开通周期 2 周→3 天;RPA 任务状态机 + 混合补偿(见 Q28/Q31)。
- 设计原则:状态模式 + Fail Fast + OCP(加状态/步骤走扩展不改核心)。
Q21|【DA+TA】多级缓存 + 数据一致性架构怎么落地?
一句直觉:多级缓存抗住读洪峰,但缓存越多层、和库的一致性越难——要设计好失效与回源。
参考答案:
- 多级:本地 Caffeine(一级,抗瞬时热点、ns 级)+ Redis(二级,共享)+ DB 兜底,逐级回源。
- 一致性:Cache Aside(先更库再删缓存)+ 延迟双删;强一致用 canal 订阅 binlog 异步刷各级缓存(观察者);本地缓存多副本不一致用短 TTL 或失效广播(Redis pub/sub / MQ 通知各节点失效)。
- 三防:穿透击穿雪崩(见 Q5),封装成统一组件。
- 热点处理:热点 key 探测 + 本地缓存 + 永不过期后台刷(防击穿)。
- 取舍:层级越多一致性越复杂、本地缓存一致性最难——非强一致、读多写少的数据才适合多级缓存;强一致数据少缓存或短 TTL。别盲目堆缓存层(YAGNI)。
- 实战:PereDoc 两级缓存 + 三防(见 Q34);多副本本地缓存不一致用短 TTL 控制在可接受窗口。
Q22|【BA+AA+DA】分布式事务在真实业务里怎么落地选型?给一个综合设计。
一句直觉:分布式事务的难点不在技术,在”哪个业务用哪个方案”——选错了要么性能崩、要么数据错。
参考答案:
- 场景分诊(以一个交易系统为例):① 下单扣库存(强一致、短)→ TCC 或 DB 乐观锁预扣;② 下单后发积分/通知(最终一致、解耦)→ 事务消息;③ 跨多服务的履约长流程(多步可补偿)→ Saga;④ 对账兜底所有柔性事务。
- BA 视角:先识别哪些业务”钱/库存不能错”(强一致)、哪些”晚点到无所谓”(最终一致)——这是选型前提。
- AA 视角:事务边界对齐服务边界,跨服务一致性走柔性事务,服务内用本地事务。
- DA 视角:去重表/状态机保幂等,对账表兜底,补偿记录可追溯。
- 统一框架:可靠消息 + 幂等消费 + 补偿 + 对账封装成平台能力,业务声明一致性等级即可(DRY)。
- 实战:Elevate-SaaS 计费事务消息、开通 Saga;RPA 物料预扣乐观锁 + 跨机器人混合补偿(见 Q31/Q32/Q35)。
- 取舍/反吹嘘:90% 最终一致够用,TCC 只在强一致短事务用(侵入大);Seata 用过 AT/TCC、懂全局锁原理,更多自研柔性事务,不冒充内核专家。
三、TOGAF 架构方法论题(Q23–Q27)
Q23|用 TOGAF ADM 说明你怎么从 0 规划并演进一个核心业务系统/中台。
一句直觉:ADM 是架构师的施工流程图——愿景→业务→信息系统→技术→机会→迁移→治理→变更,需求管理是轴心。
参考答案:
- Preliminary:定架构原则、范围、治理框架、团队工具。
- A 愿景:和干系人对齐目标与成功度量(高可用 SLA、扩展性、复用率)。
- B 业务架构:能力地图、价值流、组织(BA,见 Q13),能力分层。
- C 信息系统架构:应用(微服务边界、契约)+ 数据(模型、分库分表、一致性方案)。
- D 技术架构:技术栈选型、部署运行时、非功能基线。
- E 机会与方案:识别差距,排迁移项目群。
- F 迁移规划:分阶段路线(先核心域、再周边、再精细治理)。
- G 实施治理:ARB、合规、架构看护。
- H 变更管理:响应新需求/技术,决定小改还是触发新一轮 ADM。
- 需求管理(轴心):贯穿,受控进出(见 Q26)。
- 实战:Elevate-SaaS 演进贴这条主线,先底座(B/C/D)再行业视图(E/F),ARB 看门(G)。
- 反吹嘘:中小团队我裁剪 ADM、不教条出全套文档,重在主线和需求受控。
Q24|给两条你真正用过的架构原则(名称-陈述-理由-蕴含),说它怎么约束决策。
一句直觉:架构原则是拍板的依据,好原则能直接否掉一个提案。
参考答案:
- 原则一:能最终一致,不上强一致
- 陈述:跨服务一致性默认柔性事务(事务消息/Saga),强一致(TCC/2PC)需专项论证。
- 理由:强一致代价高(锁、吞吐、复杂度),多数业务最终一致即可。
- 蕴含:新接口声明一致性等级;引入 TCC/XA 走 ARB。
- 约束决策:否过”顺手上 Seata XA”,改事务消息(见 Q22)。
- 原则二:数据归属单一服务
- 陈述:每张表归一个服务所有,其他服务只能通过接口/事件访问,禁止共享库直连。
- 理由:共享库是分布式单体的根源,改表全炸、数据所有权混乱。
- 蕴含:跨服务取数走 API/事件;新建表要明确 owner 服务。
- 约束决策:否过”图省事直接 join 别的服务的库”,改为接口/事件(见 Q12)。
- 方法论:原则四要素 + 可落地约束,让它能拍板而非墙上标语(TOGAF 标准结构)。
Q25|架构治理:服务/组件越来越多,怎么治理不变官僚也不失控?
一句直觉:治理的度是”管住高风险不可逆决策、放开低风险日常迭代”。
参考答案:
- 分级治理:高风险/不可逆(新中间件、跨服务契约、数据模型大改、引入分布式事务)→ ARB;中风险(新服务、对外接口)→ 架构师 review + checklist;低风险(业务内迭代)→ 团队自治 + 自动化守门。
- 自动化守门:依赖规范、契约校验、ArchUnit 架构守护测试,把原则沉淀成 CI 卡点(CoC)。
- ADR + 原则:决策记 ADR(背景/选项/取舍/结论),原则作评审标尺。
- 轻量 ARB:固定节奏 + 异步评审 + 明确 SLA,别成交付瓶颈(否则团队绕过治理)。
- 实战:Elevate-SaaS 设”进底座要评审、行业视图自治”分级,ArchUnit 卡分层依赖。
- 反面:治理过度→团队私下绕过→治理形同虚设。有效性取决于”轻到大家愿意走”。
Q26|需求管理(ADM 轴心):业务需求一直加,怎么管住对架构的冲击?
一句直觉:不是不让加需求,是让需求受控地影响架构,别让一堆特例腐蚀架构。
参考答案:
- 需求分类:通用机制(可能进底座)/ 行业策略(走扩展点)/ 一次性特例(绝不进底座)——分错类是腐化起点。
- 受控变更:影响架构的需求走”影响评估→ARB→决定(扩展点/新服务/触发 ADM)”,不直接改核心。
- 扩展点优先:能在 SPI/策略/feature flag 满足的不改底座(OCP);不够先补扩展点,不是塞 if-else。
- 特例隔离:一次性特例放适配层/行业模块,不污染底座。
- 实战:Elevate-SaaS 新行业只声明模块清单 + 数据权限走扩展点,把需求冲击挡在扩展点外(见 Q17)。
- 方法论:需求受控进出 + 扩展点优先 + 特例隔离,是架构不腐化的三道闸。
Q27|架构资产沉淀:怎么把验证过的模式沉淀成可复用资产(ABB / SBB / 参考架构)?
一句直觉:把踩坑验证过的做法变成下次能直接拿的乐高块,别每个项目重造轮子。
参考答案:
- ABB(抽象构建块):抽象能力模式——”分布式锁构建块”“可靠消息构建块”“多租户隔离构建块”,描述职责/接口/约束。
- SBB(具体构建块):ABB 的实现——Redisson 分布式锁组件、事务消息 starter、MyBatis 行级隔离拦截器。可直接依赖。
- 参考架构:核心业务系统标准架构(组件清单 + 拓扑 + 部署 + 非功能基线)模板化,新项目按它起步只做差异。
- 沉淀机制:内部 starter / Maven archetype、组件库、ADR 库、最佳实践 checklist;准入要文档 + 测试 + owner。
- 复用价值:Elevate-SaaS 把开通流程模板化沉淀后新租户 2 周→3 天。
- 取舍/反吹嘘:只沉淀被 ≥2 项目验证过、稳定的(YAGNI),过早抽象的”通用框架”往往没人用还得维护。
四、项目追问题(Q28–Q37,源码级深挖)
Q28|Elevate-SaaS 的架构设计:微服务边界、分布式事务、多租户隔离,挑核心讲。
一句直觉:这是我主导的中台,三件最难的事是”边界怎么划、跨服务一致性怎么保、租户怎么不串”。
参考答案:
- 边界:按限界上下文拆网关、认证、租户、计费、消息、行业视图;数据归属单一服务、跨边界走事件(防分布式单体,见 Q12/Q14);同步走 OpenFeign、异步走 MQ。
- 分布式事务:租户开通串”建库表→部署模块→注册配额→写计费→审计落库”,用状态机 + 段内 Saga 逆序补偿;强一致计费扣减用事务消息(见 Q22)。
- 多租户隔离:三档(实例/schema/行),行级由 MyBatis 拦截器统一注入
tenant_id过滤(封装变化 + DRY),schema/实例级用AbstractRoutingDataSource动态切源;租户上下文全链路透传,异步任务/MQ 消费边界显式透传(最危险的坑,没透传会跨租户串数据)。 - 能力分层:底座沉淀通用机制,行业特性走扩展点,新行业只声明模块清单 + 数据权限不改底座(OCP)。
- 踩坑:异步导出任务没透传 tenantId 差点跨租户导数据,补线程池 TaskDecorator + 消息头透传根治;把某大客户特例错塞底座、剥离上浮止血。
- 数据支撑:数十个行业租户共用一套底座,新租户上线 2 周→3 天,热点接口 P95 降约三成(内部压测)。
Q29|【设计模式专项】backbone 动态工作流引擎用 Apache Commons Chain 怎么做的?DownloadLockStep/EditFinishStep 是哪些模式?为什么 postprocess 必释放锁、对比 naive。
一句直觉:把复杂操作拆成一串步骤卡按序执行、任一步出错都能逆序收尾——责任链 + 模板方法,框架管流程你只写步骤。
参考答案:
- 怎么做:基于 Apache Commons Chain,每步实现
Filter(execute正向 +postprocess逆向回调),多 step 组Chain共享Context。EditFinishStep(sword-physical/.../accessPoint/EditFinishStep.java,已核):execute()从 context 取设备表、对每设备submit一个SaveTask(Runnable)到COMMON_EXECUTOR异步保存、return false链继续。 - 是哪些模式:责任链(步骤可插拔编排)、模板方法(
Filterexecute/postprocess 骨架)、命令(SaveTask implements Runnable封装可异步执行的命令)。 - postprocess 必释放锁(
DownloadLockStep):Commons Chain 的postprocess在链执行完逆序回调——无论哪步抛异常,前面 step 的 postprocess 都会被调到,所以”加锁”在 execute、”释放锁”在 postprocess,保证异常路径也一定释放(链级别的 try/finally)。 - 对比 naive:naive 一个大方法
lock();step1();...;unlock();——任一步抛异常 unlock 漏执行、锁泄漏死锁;加步骤改大方法(违反 OCP)。Commons Chain 拆 step(OCP)+ postprocess 兜释放(不漏锁)。 - 易错点(真实坑):
EditFinishStep把任务 submit 到线程池就return false、execute 不等异步完成,若DownloadLockStep的 postprocess 此刻释放锁、而异步 SaveTask 没跑完 → 锁提前释放、并发保护失效(投递语义陷阱,和 Q36 同源);要 Future/CountDownLatch 等齐再放锁。另一坑:@Autowired private final PluginLocator(EditFinishStep:22-23)——final 已构造器注入再标@Autowired是冗余误导的坏味道。 - 取舍:动态工作流让”加 step 不改主流程”(OCP),但 Commons Chain 老、异步语义自己兜。够用就没必要换状态机引擎(KISS)。
Q30|【设计模式专项】sword 的 PluginLocator 集合注入式 SPI 怎么做的?哪些模式?对比 JDK ServiceLoader。
一句直觉:多厂商设备各有驱动,业务只想说”给我下发”、不想关心是哪家——PluginLocator 是那个按厂商自动找驱动的调度员。
参考答案:
- 怎么做(
PluginLocator.java,已核行号):@Autowired List<IDeviceDriverService> deviceDriverFactory(行 21,Spring 集合注入收集所有实现)→@PostConstruct init()(行 31)用getClass().getSimpleName().toLowerCase()建Map<beanName,driver>→getDeviceDriverService(vendor,class)(行 40)按vendor+class+"impl"取、找不到抛IllegalArgumentException(Fail Fast)→initDeviceDriverMap(行 53)遍历VendorEnum预热吞异常 →getDeviceDriverFromMap(行 68)懒加载 + 缓存。 - 是哪些模式:策略(每厂商驱动一策略,按 vendor 选)、简单工厂/服务定位器(按 key 产出)、面向接口 + DIP(业务依赖
IDeviceDriverService抽象)、泛型契约IDeviceDriverService<T>= 统一契约/模板。 - 对比 JDK ServiceLoader:ServiceLoader 靠
META-INF/services+ 反射实例化,脱离 Spring 容器(无 DI/AOP/生命周期),迭代式拿全部、不能按 key 精确取;PluginLocator 借 Spring 集合注入,驱动是 Spring Bean(能注 DAO、被 AOP 增强),可按 vendor 精确定位——更适合 Spring 工程。Dubbo SPI 更强但重,这里不需要(KISS)。 - 为什么这么设计:加厂商只写一个
@Component实现接口、命名合规,不改 PluginLocator/业务(OCP)。对比 naive 的if(vendor=="hw")...满屏分支(违反 OCP)。 - 易错点(真实坑):① bean 被 CGLIB 代理(加
@Transactional/AOP)时getSimpleName()变Xxx$$EnhancerBySpringCGLIB$$...、key 对不上拿不到驱动(最隐蔽);②List注入顺序不保证;③ 命名约定是隐式契约、要文档化(CoC 双刃)。 - 数据支撑:sword 多厂商接入、加厂商不改主流程。
Q31|【设计模式专项】RPA 责任链流程引擎 + 分库分表 + 三态防超卖怎么做的?用了哪些模式?
一句直觉:原来一把大锁同步串行,改成责任链异步 + 分库分表 + 乐观锁,吞吐和扩展都上来。
参考答案:
- 责任链调度:流程节点重构为责任链 + 异步非阻塞 + 无锁队列,新增节点按链插拔不改主流程(责任链 + OCP)。
- 分库分表:流水/物料表分库分表 + Snowflake ID,分散单表压力(见 Q4)。
- 三态防超卖:预占(乐观锁 CAS,占不住直接拒,Fail Fast)→ 实占 → 超时未确认延迟消息自动释放;粗粒度分布式锁锁串行边界、细粒度乐观锁锁高频扣减(双层);幂等键 + 去重表防重复扣减(见 Q32)。
- 最终一致:跨机器人步骤混合补偿 + 事务消息。
- 用了哪些模式:责任链(节点)、命令(任务封装)、策略(节点处理)、状态机(任务状态)、生产者-消费者(无锁队列)。
- 数据支撑:内部基准 TPS 约 300→千级(受流程复杂度影响,内部口径)。
- 易错点/取舍:无锁队列调试难、ABA 用版本号;乐观锁高冲突场景重试风暴反更慢——高冲突用悲观/分布式锁、低冲突用乐观锁,按场景选不是无锁处处优。
Q32|三态防超卖(预占/实占/释放)+ 双层锁怎么落地?和电商库存是一回事吗?
一句直觉:先占坑再确认、超时自动释放,和电商扣库存同构。
参考答案:
- 三态:预占(
update set used=used+n where used+n<=quota,乐观锁 CAS 占不住即拒)→ 实占(确认落地)→ 释放(超时未实占经延迟消息回滚)。 - 双层锁:粗粒度分布式锁锁必须串行的边界(资源池初始化)、细粒度乐观锁锁高频扣减(CAS 原子防超卖、吞吐高),不用一把粗锁锁全程。
- 幂等:幂等键 + 去重表防 MQ 重复投递导致的重复扣减。
- 超时释放:Redis 延迟队列 / RocketMQ 延迟消息(backbone
RedisDelayQueueUtil是 Redisson 延迟队列重试的真实实现)。 - 和电商库存:同构——预占=占库存、实占=下单确认、释放=超时取消还库存。
- 易错点:① 预占/实占状态要持久化防宕机丢;② 实占和释放的并发竞争用状态机 + 乐观锁保证只一个赢;③ 延迟消息丢了漏释放、对账兜底。
Q33|分库分表 + 慢查询治理,举一个真实案例。
参考答案:
- 现象:某流水查询接口 P95 飙高、偶超时,DB CPU 高。
- 定位:慢日志 +
EXPLAIN——type=ALL全表扫、单表千万级、条件列无索引且函数包裹列致索引失效、深分页limit 1000000,20扫海量行。 - 处置:补最左前缀联合索引、去列函数改范围条件、深分页改游标/延迟关联、单表分库分表 + Snowflake + 冷归档、不带分片键的多维查询走 ES 异构索引。
- 验证:该接口 P95 显著降;Elevate-SaaS 整体热点接口 P95 平均降约三成(内部压测)。
- 复盘:慢 SQL 上线前 EXPLAIN 卡点进 CR checklist,索引/分页规范进开发规约(CoC)。
- 取舍:分库分表最后一步——先索引/读写分离/归档/缓存穷尽(见 Q4)。
Q34|缓存三防真实线上故障复盘。
参考答案:
- 故障:一批 key 设相同 TTL 同时过期 → 瞬间全回源 → DB 连接池耗尽超时(雪崩)。
- 定位:DB QPS 某刻陡增、缓存命中率断崖,对上统一过期时间。
- 根治:随机 TTL 错峰 + 本地 Caffeine 一级兜瞬时 + 热点 key 互斥重建(防击穿)+ 布隆 + 空值缓存(防穿透)+ 回源限流降级。
- 锚点:PereDoc 两级缓存 + 布隆 + 互斥防穿透击穿,重复回源显著降低。
- 沉淀:缓存读取封装统一组件
get(key,loader,policy)内建三防(DRY + 封装变化,见 Q5)。 - 一致性:Cache Aside + 延迟双删;布隆误判不可删定期重建。
Q35|【设计模式专项】分布式事务真实落地:你怎么在 TCC / Saga / 事务消息里选?讲一个真实业务。
一句直觉:分布式事务选型是按”这个数据错了后果可不可逆”来定的,讲清你真实业务里怎么分诊。
参考答案:
- 真实业务(租户开通 + 计费):开通是多步长流程(建库→部署→配额→计费→审计)、可补偿、容忍中间态 → 用 Saga(段内逆序补偿);其中”计费扣减”要强保证”业务和消息一致” → 用事务消息;强一致预扣场景(资源池占用)→ DB 乐观锁/TCC 思路预扣。
- 是哪些模式:Saga = 状态机 + 命令 + 补偿(Memento 式回滚);事务消息 = 可靠消息 + 观察者(下游订阅);幂等去重 = 代理/拦截。
- 对比 naive:naive 跨服务直接同步调用一串、出错不回滚 → 数据不一致、悬挂;或一把 XA 锁住全程 → 性能崩。正确做法按一致性等级选柔性事务 + 补偿 + 对账。
- 易错点:① Saga 补偿要幂等且考虑”补偿也失败”(重试 + 人工兜底);② 事务消息回查逻辑要实现(半消息回查本地事务状态);③ TCC 空回滚/悬挂(Cancel 先于 Try 到、或 Try 超时后又到)要用事务记录防。
- 数据支撑/取舍:Elevate-SaaS 计费事务消息、开通 Saga;90% 最终一致够用,TCC 只在强一致短事务用。Seata AT/TCC 用过、懂全局锁,更多自研柔性事务(反吹嘘)。
Q36|backbone KafkaConsumer 双线程池 submit 的投递语义陷阱,你怎么发现和改的?
一句直觉:”我收到了”不等于”我处理完了”——收到就 ack 却把活丢线程池排队,宕机这批活就消失了。
参考答案:
- 陷阱:
KafkaConsumer(已核)早期在消费回调里 submit 到业务线程池就立即提交位点。submit 只是入队、任务可能还排队没执行——此时宕机/重启,位点已提交、Kafka 不重投,任务凭空丢失。把”接收语义”误当”处理完成语义”。 - 本质:违反”至少处理一次”——位点提交必须在处理真正完成之后。
- 发现:一次重启后少量业务没执行、Kafka 无堆积无报错,倒查发现”提交早于处理”。
- 改法:① 同步处理完再手动提交位点;② 要异步提吞吐就”批量拉→线程池并发处理→全部完成(Future/CountDownLatch)→统一提交位点”+ 控在途量防 OOM;③ 消费幂等兜重复(改后置提交后是 at-least-once)。
- 同源:
EditFinishStep的 submit 后return false(见 Q29)也是这类语义陷阱。 - 方法论:凡”接收→异步处理”边界都问一句”我提交/ack/释放锁的时机是不是真在处理完成之后”,进 CR checklist。
Q37|你怎么把 4A / TOGAF 架构方法论真正用在项目里,而不是停在 PPT?
一句直觉:方法论不是文档,是”做决策时的视角和约束”——4A 让你不漏视角,TOGAF 让你受控演进。
参考答案:
- 4A 落地:每个新系统/大改我都过一遍 BA(业务能力与价值流)→ AA(服务边界与契约)→ DA(数据模型与一致性)→ TA(技术栈与非功能),防止”只想着写代码漏了数据架构/非功能”。Elevate-SaaS 的设计就是按 4A 拆的(见 Q13/Q28)。
- TOGAF 落地:① 用 ADM 主线规划演进(愿景→业务→信息系统→技术→治理),中小团队裁剪文档;② 架构原则四要素拍板(见 Q24);③ ARB 分级治理 + ADR 记录决策(见 Q25);④ 需求受控(见 Q26);⑤ 资产沉淀 starter/参考架构(见 Q27)。
- 不教条:方法论是工具不是枷锁——小项目用其思想(视角全、决策有据、演进受控),不强行出全套交付物(否则成形式主义)。
- 数据支撑:方法论的价值体现在”开通流程沉淀成资产后 2 周→3 天”“底座不膨胀靠原则 + ARB 守住”。
- 反吹嘘:我有 4A/TOGAF 的实践但不是 TOGAF 认证讲师,讲的是工程落地不是考证背诵。
五、管理决策题(Q38–Q45)
Q38|带一个后端/架构团队,你怎么分工、建梯队、让团队不只依赖你?
参考答案:
- 分工:模块 owner + 主备制(防单点),新人配 mentor 结对;架构难题我搭框架、骨干填实现。
- 建梯队:刻意让骨干挑大梁、轮岗给全局视野,我做 review 兜底。
- 去依赖我:决策标准显性化(架构原则 + ADR + CR checklist)、资产沉淀(starter/文档)、故障复盘共享。
- 实战:卓朗带约 20 人全栈(直接管 6-8 人),引 Scrum + OKR + 中间件 CR 清单,迭代缩短约三至四成。
- 反吹嘘:直接管理 6-8 人,”约 20 人”含协作,不夸大。
- 方法论:目标是”团队没我也能转”——主备 + 标准显性化 + 资产沉淀。
Q39|技术选型/自研 vs 用社区,作为架构师你怎么决策?
参考答案:
- 框架:核心差异化能力倾向自研可控、非核心用成熟社区(别重造轮子 DRY);看社区成熟/活跃/坑深、团队能否 hold、迁移退出成本。
- 倾向:通用中间件用社区(Nacos/Gateway/RocketMQ/ShardingSphere),业务强相关、社区没有的才自研(租户隔离、行业工作流、柔性事务编排)。
- 实战:Elevate-SaaS 中间件全社区、只自研中台业务层;分布式事务用社区 + 自研结合(见 Q35)。
- 取舍/反吹嘘:自研易高估自己、低估维护(YAGNI),默认”先用社区,证明真不够再自研”,决策记 ADR。
Q40|跨团队/跨部门协作,目标不一致怎么推进?
参考答案:
- 对齐上层目标:把分歧从立场之争拉回共同目标(按时稳定交付)之下的方案之争。
- 数据/原则说话:架构原则 + 压测/故障数据做依据,不靠嗓门。
- 照顾对方约束:运维要稳就给灰度 + 回滚预案,业务要快就分阶段交付看到进展。
- 机制化:接口契约、联调计划、责任边界写清;ARB/技术评审作跨团队拍板场。
- 实战:紫金山课题多方联调靠”接口契约先行 + 分阶段验证”。
Q41|排期紧、人力不够,砍什么保什么?
参考答案:
- 保:核心价值路径、稳定性底线(不丢数据/不串租户)、架构正确性(边界/数据模型/一致性——后改成本指数级)。
- 砍/缓:锦上添花功能、过度设计的扩展性(YAGNI)、可后补的运营功能。
- 方法:价值×风险排序,砍的明确告知 + 记录,MVP 先跑通核心链路。
- 实战:多租户隔离先上行级跑通、大客户要强隔离再上 schema/实例级。
- 取舍:砍范围不砍质量,架构正确性绝不砍。
Q42|架构层面的技术债你怎么识别和还?
参考答案:
- 识别:改一处错一片、新人上手慢、同类 bug 反复、绕过架构的 hack 变多;量化用故障率/变更失败率/重复代码/ArchUnit 报警。
- 分类:主动债(赶进度故意欠、记账)vs 被动债;按利息(越频繁触碰越拖越贵)排。
- 还:童子军规则(顺手清理)+ 专项重构小步迭代 + 每迭代留还债预算;高息债(本地锁当分布式锁、共享库、缓存裸奔)优先专项。
- 实战:散落缓存/锁收敛成统一组件(见 Q44),”提交早于处理”类坑进 CR checklist 防新增。
- 方法论:记账 + 按利息排 + 持续小步还,别等”以后专门重构”。
Q43|【设计判断】CR 里有人提交”巨型 Service + 满屏 if-else 类型分支”,你怎么反馈?
参考答案:
- 态度:对事不对人,先肯定能跑通,再讲为什么改、怎么改(带样例)。
- 指出:巨型方法违反 SRP;
if(type==A)...是该用多态处用了条件、加类型改一片违反 OCP;难测难复用。 - 重构方向:策略 + 工厂/Map 注册(
Map<type,Handler>)替 if-else,正是 swordPluginLocator做法(见 Q30);巨型方法按职责拆。 - 把握度:3 个稳定分支不必上策略工厂(YAGNI),”分支会增长 + 逻辑复杂”才值得;教他判断”何时用模式 vs 过度设计”。
- 机制:规约进 CR checklist(CoC)自动卡。
Q44|【设计判断】缓存/锁代码到处复制粘贴、本地锁当分布式锁用,你怎么系统性治理?
参考答案:
- 止血:先扫”本地锁保护跨实例共享资源”高危点(多副本超卖,见 Q11),优先修。
- 根治:缓存读取、分布式锁封装统一组件/starter——
get(key,loader,policy)内建三防、@DistributedLock封装 Redisson,业务一行调用(DRY + 封装变化)。复制粘贴的本质是没有可复用抽象。 - 防新增:CR checklist 卡”禁裸写缓存/本地锁锁共享资源” + 架构守护/扫描自动拦 + 培训样例。
- 设计原则:DRY、封装变化、最少知识(业务不需知道锁是 Redis 还是 etcd)。
- 实战:Elevate-SaaS/RPA 缓存防护和分布式锁都收敛成统一组件。
- 方法论:坏味道治理 = 止血 + 根治(建抽象)+ 防新增(自动守门)。
Q45|招一个 Java 后端架构师/资深开发,你怎么判断靠谱?会问什么?
参考答案:
- 看深度与取舍:让他挑最熟项目追三层”为什么这么设计、放弃了什么、踩了什么坑”,能讲透取舍踩坑的是真做过。
- 必问硬核:分布式事务怎么选、本地锁 vs 分布式锁边界、缓存三防、分库分表跨片问题、一次 OOM/Full GC 怎么排——架构师分水岭。
- 看架构判断:边界划分、何时用模式 vs 过度设计(YAGNI)、怎么防分布式单体。
- 看协作与方法论:CR 怎么给反馈、4A/TOGAF 怎么落地不空谈。
- 反吹嘘:能诚实标”了解/没实战”边界的人,比什么都”精通”的更靠谱。
六、行业认知题(Q46–Q47)
Q46|这是国企背景、人力资源公司代招的岗位,你怎么看这类机会和自己的位置?
参考答案:
- 理性看待招聘性质:招聘主体是人力资源公司(上海信息人才服务),大概率是给某甲方(可能国企/政企)代招或外派。这类岗位的特点是——业务稳、合规要求高、技术节奏不如互联网激进,但对”架构规范、分布式事务正确性、数据一致性”很看重,正撞我的强项。
- 我的位置:政企/国企核心系统要的是”稳、规范、能扛、能落架构治理”,我做过金融征信强监管(民生银行)、能源 ERP(中石化)核心流程,合规和强一致经验匹配。
- 必须澄清的(面试中主动问):实际用人单位是谁、用工性质(直签还是驻场外包)、项目方向、薪资结构(18-30K 是否含奖金/13/15 薪)、工作地是否固定。用工性质不清前不轻易接 offer。
- 反吹嘘:我没在纯国企体制内长期待过,对其流程文化我了解、能适应,但不假装”深谙国企生态”,如实说。
Q47|Java 后端架构方向,未来几年你怎么判断?技术栈怎么再投资?
参考答案:
- Java/Spring:虚拟线程(Loom,JDK21)让高并发回归简单、冲击部分 Reactive 场景;GraalVM 原生镜像利好弹性但有反射坑;Spring Boot3/Spring6 升 JDK17 基线;Netflix 组件退场转 Spring Cloud Alibaba(Nacos/Sentinel)。
- 架构趋势:① 云原生融合——微服务长在 K8s 上、治理部分下沉 Service Mesh;② 分布式数据库(TiDB 等)让分库分表的一部分痛点上移到存储层;③ 可观测从三支柱走向 OpenTelemetry 统一;④ AI 工程化(RAG/Agent)渗透业务系统。
- 我的再投资:① 主力 Java 中台架构深化(分布式事务、一致性、高可用);② 邻接延伸云原生(我已有 K8s 控制器经验,是差异化);③ 增长方向 AI 工程化补深(诚实标:在学不是专家)。
- 方法论:技术投资分层下注——主力深化 + 邻接延伸 + 增长试水,不追热点 all in。
七、薪资谈判题(HR 轮,Q48–Q50)
Q48|你的薪资期望是多少?(该岗 18-30K,5-10 年,人力公司代招)
参考答案:
- 先澄清再谈:这个岗位我想先确认实际用人单位和用工性质(直签还是驻场),不同性质我对薪资的预期不一样——直签核心岗我看中上部,驻场外包要综合稳定性看。
- 锚定区间:在 18-30K 区间,结合岗位是”架构设计 + 开发”的资深档,我的预期在中上部,但具体可结合你们薪酬结构(是否含奖金/13/15 薪、调薪机制)一起谈。
- 务实弹性:如果项目方向、用人单位、发展空间合适,薪资我有弹性;我看重匹配和稳定,不卡死一个数。
- 反吹嘘:不漫天要价,给区间 + 表达弹性,并把用工性质作为前置条件谈清。
Q49|你为什么从上一家离开?
参考答案:
- 正向理由:我在卓朗/紫金山偏平台和科研课题,沉淀很多架构经验;现在希望回到能持续做核心业务架构设计、把分布式系统的硬骨头落地并长期打磨的环境。
- 不踩前东家:客观讲阶段和诉求变化,不抱怨。
- 稳定性信号:每段都做满完整周期(紫金山、卓朗、青燕祥云),换是为更合适方向不是逃避。
- 方法论:讲”奔向什么”而非”逃离什么”,主动给稳定性证据。
Q50|你约 10 年、带过组,这岗 5-10 年偏架构 + 开发,会不会不稳定?人力代招你介意吗?
参考答案:
- 关于稳定性:这岗位是”架构设计 + 开发”的资深档,正是我的主场——5-10 年的要求我刚好匹配,不存在大材小用;我享受做架构和写核心代码,不是非纯管理不可(紫金山我一直 Java 为主核心编码,手感在)。
- 关于人力代招:我不介意通过人力公司入职,但我会把用工性质问清楚——直签还是驻场外包、实际项目和用人单位。信息透明、方向合适,我能长期做;这是务实不是挑剔。
- 给的价值:我来能直接扛架构设计 + 落地分布式事务/分库分表/高并发,还能帮团队把架构规范、CR、故障排查带起来——用一个资深开发的预算多拿一份架构兜底。
- 稳定承诺:我换工作看重事合不合适,前几段都做满完整周期;这个架构方向是我想长期做的,不是过渡。
- 反吹嘘 + 真诚:我不空口保证稳定,而是讲清”这件事正是我想做的 + 我能创造超岗位预期的价值 + 用工性质透明我就能安心做”。
附:自检与使用建议
设计原则/模式覆盖清单(专项自检表)
| 原则 / 模式 | 对应题号 | 真实源码/项目锚点 |
|---|---|---|
| 责任链(Chain of Responsibility) | Q8, Q29, Q31 | Gateway FilterChain、Sentinel SlotChain、backbone Commons Chain EditFinishStep/DownloadLockStep、RPA 流程引擎 |
| 代理(Proxy) | Q3, Q8 | OpenFeign 动态代理、缓存幂等/布隆前置 |
| 模板方法 | Q3, Q5, Q29 | 重试骨架、缓存互斥重建、Commons Chain Filter |
| 策略 + 简单工厂 / 服务定位器 | Q12, Q30, Q43 | sword PluginLocator(集合注入式 SPI) |
| 命令(Command) | Q29, Q31, Q35 | SaveTask implements Runnable、RPA 任务封装 |
| 状态机 / 状态模式 | Q3, Q20, Q22, Q31, Q32, Q35 | 租户生命周期、防超卖三态、RPA 任务、Saga |
| 观察者(Observer) | Q5, Q35 | binlog 监听刷缓存、事务消息下游订阅 |
| 享元 / 池化 | Q35(锚点 PereDoc) | 池化缓冲、零拷贝 |
| 装饰器 | Q5, Q8 | 多级缓存逐级回源、Gateway filter |
| 面向接口 / DIP / 开闭 OCP | Q2, Q12, Q17, Q24, Q26, Q30, Q43 | 一致性方案抽象、扩展点、IDeviceDriverService 契约 |
| 单一职责 SRP / 关注点分离 SoC | Q12, Q17, Q43 | 巨型服务拆分、能力分层、数据归属单一服务 |
| DRY / 封装变化 / 最少知识 | Q3, Q5, Q21, Q28, Q44 | 可靠消费框架、缓存/锁统一组件、持久层拦截器 |
| Fail Fast / Tell-Don’t-Ask | Q20, Q30, Q32 | 非法状态跃迁直接拒、PluginLocator 找不到抛异常、预占占不住即拒 |
| KISS / YAGNI(防过度设计) | Q4, Q7, Q14, Q15, Q17, Q22, Q41, Q43 | 能不分库就不分、先粗后细拆服务、最终一致优先、先行级隔离 |
| 生产者-消费者 / 无锁并发 | Q31 | RPA 无锁队列 |
| 【反向题·坏设计】 | Q11(本地锁当分布式锁 + 缓存先删后写)、Q12(分布式单体:共享库 + 同步成环 + 巨型服务) | 重构为分布式锁/乐观锁 + Cache Aside;数据解耦 + 异步事件 + 拆服务 |
设计原则/模式专项题:Q3、Q5、Q8、Q29、Q30、Q31、Q35 共 7 道(要求 ≥6 ✓);反向坏设计题:Q11、Q12 共 2 道(要求 ≥2 ✓);另 Q43/Q44 为设计判断题。
4A 覆盖自检
- BA:Q13、Q17、Q20、Q22(≥2 ✓)| AA:Q13、Q14、Q15、Q17、Q18、Q22(≥2 ✓)| DA:Q13、Q16、Q19、Q20、Q21、Q22(≥2 ✓)| TA:Q13、Q15、Q16、Q18、Q19、Q21(≥2 ✓)
- 综合贯穿题:Q13(一题打通 BA→AA→DA→TA ✓)
TOGAF 覆盖自检
- ADM 各阶段:Q23 ✓|架构原则制定(四要素):Q24 ✓|架构治理(分级/ARB):Q25 ✓|需求管理(轴心):Q26 ✓|架构资产沉淀(ABB/SBB/参考架构):Q27 ✓|方法论真实落地:Q37 ✓
诚实边界自检(反吹嘘)
- 主力 Java 十年、Go/K8s 为”者优先”加分(Q47);Seata/TCC”用过懂原理、更多自研柔性事务”(Q2、Q22、Q35);Paxos/Raft/共识”懂原理、没自研”(Q1);异地多活/单元化”懂架构、没生产做过完整单元化”(Q15);全链路压测”参与不主导”(Q19);国企生态”了解能适应、非深谙”(Q46);带团队 6-8 人直接管理、约 20 人含协作(Q38);TPS/QPS/P95 均标内部压测口径(Q31、Q33)。
题量分布核对(7 类,合计 = 50)
技术深度 12(Q1-12)+ 架构设计 10(Q13-22)+ TOGAF 5(Q23-27)+ 项目追问 10(Q28-37)+ 管理决策 8(Q38-45)+ 行业认知 2(Q46-47)+ 薪资谈判 3(Q48-50)= 50 ✓
使用建议
- 这是架构岗(5-10 年 + 架构设计),答题站”系统全局 + 取舍 + 方法论”高度,分布式事务(Q2/Q22/Q35)、分库分表(Q4/Q33)、高可用(Q6/Q15)、4A/TOGAF(Q13/Q23-27/Q37)是主场。
- 设计模式题(Q29/Q30/Q31/Q35)配源码锚点讲,体现”会设计、能讲清为什么 + 取舍”。
- 招聘性质是最大变量——人力公司代招/可能外派,Q46/Q48/Q50 务必先把用工性质、实际用人单位、薪资结构问清,再谈技术与薪资。
- overqualified 顾虑比天地智云岗小(5-10 年正好匹配),但要主动澄清”享受架构 + 开发、不是非管理不可”(Q50)。
评论:
技术文章推送
手机、电脑实用软件分享
微信公众号:AndrewYG的算法世界