项目STAR与场景题面试考题
09 - 项目STAR自述与高频场景题 · 面试通关指南
目录
一、四大项目 STAR 完整自述
项目一:医院后台管理系统(医博系统,2020.06 ~ 2022.08)
STAR 自述模板:
S(背景):我毕业后第一份工作是在医博系统技术(深圳),参与开发面向中小型私立医院的数字化管理后台,系统涵盖挂号预约、就诊结算积分核算、药品管理、患者病历管理等模块,服务于多家连锁医院的日常运营。
T(挑战):主要面临两个核心挑战:一是患者数据(身份证、手机号、病历)的安全合规问题,系统需要通过医疗数据安全审计;二是医生排班、号源等热点数据频繁被查询,数据库压力大,查询响应慢。
A(行动):
- 设计并实现了基于 AOP 注解驱动的动态脱敏拦截器,对标注了
@SensitiveField注解的字段(身份证号保留前 3 后 4 位,手机号保留前 3 后 4 位),在接口响应时自动进行正则替换脱敏,业务代码零改动。- 引入 Redis 缓存排班热点数据,使用 List 结构缓存每个医生的可用号源,结合逻辑过期策略(Lua 脚本)防止热点 Key 缓存击穿。
- 针对大宽表(50 万患者记录)导出的 OOM 问题,将 Apache POI 替换为 EasyExcel SAX 流式分批写盘,分批大小 1000 条,JVM 堆内存使用降低 95%。
- 强化了细粒度的 RBAC 权限控制,基于角色+资源+操作三维度控制接口访问权限,通过拦截器统一鉴权。
R(成果):系统顺利通过国家医疗健康数据安全合规审计;大宽表导出彻底消除 OOM;排班查询响应时间从 800ms 降至 50ms 以内。
面试官必问 Q&A:
Q:你说的动态脱敏拦截器是如何实现的,详细说一下?
“我们在实体类的敏感字段上自定义了一个
@SensitiveField(type = SensitiveType.PHONE)注解。然后编写了一个SensitiveSerializeAspect(AOP 切面)在所有标注了@RequireDesensitize的接口方法返回后触发,使用 Java 反射遍历返回对象的所有字段,如果字段上有@SensitiveField注解,就通过对应的脱敏规则(正则表达式)将字段值替换为脱敏后的值再返回。这样所有接口的脱敏逻辑完全统一收口,新增敏感字段只需加注解,无需改业务代码,完全符合开闭原则。”
Q:号源缓存击穿是如何防止的?
“我们医院系统中,某些热门专家号在开放挂号的瞬间会有数百个请求同时涌来。为防止缓存击穿,我们采用了逻辑过期方案:Redis 中的号源 Key 永远不设置 TTL(不会真正过期),而是在 Value 里存储一个逻辑过期时间戳。每次读取时先判断逻辑时间是否超过当前时间,如果超过,用
SET NX加一把互斥锁,只有抢到锁的线程去数据库重建缓存(异步进行),其他线程直接返回旧的缓存数据。这样既防止了大量请求同时击穿数据库,也避免了用互斥锁时其他请求一直阻塞等待的问题。”
Q:EasyExcel 和 Apache POI 的区别?
“Apache POI 的
XSSFWorkbook会将所有 Sheet 数据全部加载到 JVM 堆内存中,对于 50 万行数据会产生数 GB 的内存膨胀,导致频繁 Full GC 甚至 OOM。EasyExcel 基于 SAX 流式解析机制,写出时以 Row 为单位逐行处理:从数据库分批查(每批 1000 条)→ 逐行写入 Excel 临时文件 → 写完立即释放对象。JVM 中同时存在的对象数量始终很少,内存占用稳定且极低,从根本上解决了 OOM 问题。”
项目二:美团智能化餐饮 SaaS 管家运营引擎(纬创软件,2022.10 ~ 2024.06)
STAR 自述模板:
S(背景):在纬创软件外包至美团的项目中,我参与开发美团餐饮 SaaS 管家核心运营引擎,该系统服务于海量 B 端餐饮商户,承载单日百万级的商户运营请求,支撑商户进行营销活动、对账报表、积分管理等全流程操作,同时对外提供 OpenAPI 供商户系统集成。
T(挑战):两大核心挑战:一是商户高频对账单导出时产生大量慢 SQL(最慢接口 3.2 秒),导致服务频繁超时熔断,CPU 剧烈震荡;二是 OpenAPI 对外开放,面临接口重放攻击和非法调用的安全威胁。
A(行动):
- 主导慢 SQL 专项治理:使用
EXPLAIN分析执行计划,发现大量Using filesort和Using temporary,将 3 表LEFT JOIN的复杂查询拆分为高效单表查询 + Java 内存聚合,建立联合覆盖索引(shop_id, status, create_time),消除全表扫描和磁盘排序。- 设计并落地 HMAC-SHA256 + nonce + 时间戳的 OpenAPI 防重放防刷体系,在 API 网关层实现两道拦截:5 分钟时间戳过期校验 + Redis nonce 去重(5 分钟 TTL 自动回收)。
- 搭建插件总线架构,支持 50+ 业态功能的动态开关,核心业务逻辑和差异化功能完全解耦,新业态接入无需修改主流程代码。
R(成果):Top 10 慢接口平均响应时间从 3.2s 降至 0.4s,服务超时熔断故障率下降 25%;OpenAPI 接口安全性通过第三方安全审计;插件总线上线后,新业态接入时间从 2 周缩短至 3 天。
面试官必问 Q&A:
Q:慢 SQL 优化 3.2s → 0.4s 的完整过程,详细说一下?(这是必考题!)
“当时对账导出接口出现 CPU 毛刺和大量超时告警,我把慢 SQL 从 APM 监控中提取出来,用
EXPLAIN分析。发现的问题:
type = ALL:全表扫描(数据量 200 万行)。Extra = Using filesort:ORDER BY create_time没有命中索引,需要磁盘文件排序。Extra = Using temporary:GROUP BY 相关操作产生了临时表。- SQL 是 3 个
LEFT JOIN的大表连接,驱动表选择也有问题。优化方案:
- 第一步:拆 SQL。坚决禁止多表 JOIN,将联合查询拆为 3 个独立的高效单表查询,每个表各自按主键查,在 Java 层用 Stream + Map 做数据关联(例如
Map<Long, ShopInfo> shopMap按 ID 映射)。- 第二步:建联合覆盖索引。主查询表建立
idx_shop_status_time(shop_id, status, create_time)联合索引,同时把 SELECT 列精简为只查展示字段,使 EXPLAIN 的 Extra 显示Using index(覆盖索引,无需回表)。效果:优化后 EXPLAIN 的 type 从
ALL→ref,Extra 从Using filesort; Using temporary→Using index,接口响应时间从 3.2s 降至 0.4s,数据库 CPU 使用率下降了 60%。”
Q:HMAC + nonce 防重放的完整链路?nonce 存 Redis 会不会撑爆内存?
“防重放体系是这样工作的:客户端调用 API 时,生成一个 UUID 作为
nonce,记录当前毫秒级时间戳timestamp,将所有请求参数按字典序排序后拼接上AppSecret,用 HMAC-SHA256 算出签名,放入 HTTP Header。网关收到请求后,两道校验:
- 时间戳校验:拿服务器时间与 timestamp 比对,差值超过 5 分钟直接拦截(重放攻击的报文时间戳已过期)。
- nonce 防重放:去 Redis 查
SETNX nonce 1 EX 300,若返回 0 说明 5 分钟内该 nonce 已用过,直接拦截;返回 1 则放行,同时记录该 nonce(5 分钟自动过期)。不会撑爆内存:因为 nonce 的 TTL 和时间戳校验窗口都是 5 分钟,Redis 中最多只存 5 分钟内请求的 nonce,以每秒 1000 QPS、每个 nonce UUID(36 字节)+ Key 开销估算,5 分钟 = 300 秒,最多 30 万个 Key,约占 20~30MB,完全可接受。过期的 nonce 被 Redis 自动清除,不会无限增长。”
项目三:招商银行分布式核心流程调度中台(拓保软件,2024.07 ~ 2025.11)
STAR 自述模板:
S(背景):在拓保软件外包至招商银行的项目中,我负责核心流程调度中台的后端研发,该中台作为招行网贷、对公等核心业务线的调度引擎,处理流程规则的流转、路由和调度计算,日均调用量百万级,对响应时间和高可用性要求极高。
T(挑战):规则库数据频繁被各微服务节点高并发查询,直接访问 MySQL 导致数据库不堪重负;同时规则一旦变更,需要实时同步到所有运行中的微服务节点,否则会出现规则不一致导致的计算错误。
A(行动):
- 设计并落地 Caffeine + Redis Cluster 多级缓存体系:热点规则数据先从 Caffeine 本地缓存读取(亚毫秒级响应),Caffeine 未命中再读 Redis Cluster(毫秒级),Redis 未命中才访问 MySQL。
- 引入 Canal 订阅 MySQL Binlog,实时捕获规则库数据变更,推送 Kafka Topic,各节点的消费服务接收变更事件后:① 失效对应 Redis 缓存;② 通过 Redis Pub/Sub 广播通知所有节点主动失效 Caffeine 本地缓存,实现毫秒级多节点缓存同步。
- 基于报文驱动设计了 XML/定长报文解析引擎,通过策略工厂 + 声明式注解映射,支持不同报文格式的自动路由解析。
R(成果):核心读取接口响应延迟降低 40%,90% 以上的数据库读请求被缓存层拦截,MySQL QPS 从高峰 10 万降至 1 万,系统从容支撑了日均百万次的高并发调用。
面试官必问 Q&A:
Q:Canal 是怎么感知到 MySQL 数据变更的?Canal HA 是如何实现的?
“Canal 的底层原理是模拟 MySQL Slave(从库)的复制协议。Canal 将自己伪装成一个 MySQL 从库,向 MySQL 主库发送 dump 协议请求。主库收到后,将 Binlog 二进制日志持续推送给 Canal。Canal 解析这些 ROW 格式的 Binlog(得到行级别的增删改数据),将其封装为标准 JSON 变更事件,推送到 Kafka 对应的 Topic。
Canal HA(高可用):我们部署了两个 Canal 实例,通过 ZooKeeper 进行 Leader 选举。两个实例同时向 ZooKeeper 的同一个临时节点抢注册,成功的成为 Active(工作节点),失败的成为 Standby(备用节点)。Active 节点故障时,ZooKeeper 的临时节点过期删除,Standby 节点监听到后立即抢占并接管,同时从 ZooKeeper 中读取上一次记录的 Binlog Position,从断点位置继续拉取,保证一条 Binlog 事件都不丢失,整个切换过程在毫秒级完成。”
Q:多级缓存架构的完整读写流程是怎样的?
“完整的读流程:请求到达 → 先查 Caffeine 本地缓存(HashMap 级别的纳秒响应,命中率约 80%)→ Caffeine 未命中则查 Redis Cluster(网络 IO,毫秒级,命中率约 18%)→ Redis 未命中才查 MySQL(磁盘 IO,最慢,只承受约 2% 的穿透请求)→ 查到后同时写入 Redis 和 Caffeine,下次访问直接从缓存取。
写流程(数据变更时):业务代码更新 MySQL → Canal 感知到 Binlog 变更 → 推入 Kafka → 消费服务接收消息 → 删除 Redis 对应 Key(缓存失效)→ 发布 Redis Pub/Sub 消息 → 所有微服务节点的订阅监听器收到通知 → 调用
caffeine.invalidate(key)失效本地缓存 → 下次读取时从 MySQL 重建缓存。整个链路延迟在 100ms 以内,对于规则一致性来说完全可以接受。”
项目四:SRM 采购端全链路自动化管理平台(中电金信,2026.01 ~ 至今)
STAR 自述模板:
S(背景):我目前在中电金信负责 SRM 采购端全链路自动化管理平台的核心研发,系统服务于中大型制造业企业,覆盖采购询价、自动寻源匹配、多供应商报价比价、采购订单生成及审批的全链路自动化,并通过防腐层对接企业内部的 ERP、OA 等异构系统。
T(挑战):两大挑战:一是寻源匹配规则复杂(不同物料类别有不同的寻源权重策略),业务扩展频繁,需要高度可扩展的架构;二是需要并发调用多个供应商的报价接口进行实时比价,串行调用会严重超时。
A(行动):
- 基于 策略模式 + Spring Map 依赖注入设计动态寻源算法引擎:将每种物料类别的寻源策略抽象为
SourceStrategy接口的实现类,Spring 容器自动注入Map<String, SourceStrategy>,新增策略只需实现接口加@Component,无需改动主流程代码,完美遵循开闭原则。- 基于 CompletableFuture + 自定义隔离线程池实现多供应商并发报价:10 个供应商接口并发调用,总耗时从串行 5s 降至单次 500ms,
.exceptionally()对单个接口失败进行降级处理,保证整体比价不中断。- 设计 防腐层(Anti-Corruption Layer, ACL) 隔离外部 ERP/OA 系统的不稳定依赖:为每个外部系统定义统一的接口适配器,将外部系统的协议、数据格式差异封装在 ACL 内部,内部业务代码只依赖内部接口定义,外部系统迭代变更时只需修改 ACL 层。
- 基于 责任链模式 + 数据库配置驱动实现动态审批流引擎,审批节点配置化,支持会签、顺签、加签、超时转办等复杂审批场景,修改审批流程无需发版。
R(成果):询价匹配周期从天级压缩至小时级,比价准确率 100%;防腐层成功隔离了 3 个外部系统的接口变更,零业务中断;动态审批流落地后,业务团队自助配置审批流,研发工作量降低 60%。
面试官必问 Q&A:
Q:策略模式 + Spring Map 注入如何实现动态寻源扩展?
“我们定义了一个寻源策略接口
SourceStrategy,有一个核心方法match(SourceContext ctx): List<Supplier>。然后为每种物料类别实现具体的策略类,如MaterialSourceStrategy(原材料)、StandardPartSourceStrategy(标准件)等,每个类上加@Component(value = "MATERIAL")注解,Bean 名称就是策略标识符。在核心的
SourceEngine服务中,我声明了@Autowired Map<String, SourceStrategy> strategyMap,Spring 会自动将所有SourceStrategy实现类按 Bean Name 注入到这个 Map 中。运行时只需strategyMap.get(materialType).match(ctx)就能动态调用对应策略。新增一种物料的寻源规则时,只需新建一个实现类加上对应的
@Component,主流程代码零改动,完全符合开闭原则。”
Q:防腐层(ACL)是如何设计的?解决了什么问题?
“SRM 系统需要对接 SAP ERP(获取物料信息)、OA 系统(提交审批单)和财务系统(生成付款单),这三个都是外部异构系统,有各自的接口协议(SOAP/REST)、数据模型和错误码体系。
没有防腐层的话,业务代码直接依赖外部系统的接口定义和数据模型,一旦外部系统接口变更,业务代码到处改动,维护噩梦。
ACL 的设计:在 SRM 内部定义统一的领域接口,如
ErpMaterialPort(获取物料信息),不依赖任何外部系统的具体实现。然后在 ACL 层为每个外部系统实现一个适配器,如SapErpAdapter implements ErpMaterialPort,在适配器内部处理 SAP 的 SOAP 调用、XML 解析、异常转换、重试等所有外部系统相关的脏活。业务代码只依赖ErpMaterialPort接口,完全感知不到底层是 SAP 还是其他 ERP。当 SAP 接口升级时,只需修改SapErpAdapter,业务代码零改动。”
二、技术底层深度挖掘
Q1:动态审批流引擎如何设计(责任链 + 数据库配置)?
背诵话术:
动态审批流引擎的核心思想是:审批节点配置化,执行逻辑代码化。
数据模型设计:在数据库中维护
approval_flow_template(审批流模板:如”采购合同审批流”)和approval_flow_node(节点定义:包含节点序号、节点类型(顺签/会签/角色审批)、审批角色、超时时间、超时动作(转办/自动通过))。责任链执行:系统启动时根据数据库中的节点配置,动态构建责任链。当一个采购申请触发审批时,从第一个节点开始依次执行:
- 顺签节点:按序号逐人审批,一人通过才流转到下一人。
- 会签节点:多人并行审批,全部通过才流转下一阶段。
- 超时转办:通过定时任务扫描超时未审批的节点,按配置自动转派给替代审批人或自动通过。
修改审批流程时,业务人员直接在管理后台修改数据库中的节点配置,无需发布代码,审批流变更立即生效。
三、高频场景题答辩
场景题 1:SRM 询价 Kafka 消息大量积压,如何处理?
回答思路:
“处理消息积压,我分三步走:
第一步:定位根因。通过 Kafka 监控(Consumer Lag 指标)确认是哪个 Topic 和消费者组积压,日志里查是消费抛错(消费者卡死在重试)、下游处理太慢,还是生产速度远超消费速度。
第二步:扩容消费者:
- 如果分区数充足(如 10 个分区):立即水平扩容消费者实例数到与分区数持平,每个分区都有消费者并行消费,是最快最有效的手段。
- 如果分区数不足(如只有 2 个分区):扩消费者没用,需上线一个临时中转服务,快速消费旧 Topic 并 ACK,把消息转投到新建的有足够分区数(如 20 个)的临时 Topic,再用 20 个消费者实例消费临时 Topic,快速消化积压。
第三步:临时降级。关闭非核心消费逻辑(如埋点上报、积分回调),只保留核心报价处理,释放 CPU 和 DB IO 资源,加速消费速度。等积压消费完后恢复完整逻辑。”
场景题 2:招行中台下游服务假死导致连接池耗尽,如何快速恢复?
回答思路:
“下游服务’假死’(响应极慢但不报错),如果上游没有防护,会导致 Feign 调用持续等待,Tomcat 线程被占满,连接池耗尽,上游服务也随之崩溃(雪崩)。
我们在招行项目中的防护层次:
- Sentinel 慢调用熔断:为每个 Feign 接口配置慢调用比例熔断规则(如 10s 内平均响应 > 2s 且比例 > 30%,熔断 30s),熔断后所有请求直接在本地返回 Fallback 降级数据,立即释放线程,不再发起网络请求。这是最核心的防护。
- 线程隔离(舱壁模式):为不同的下游服务分配独立的线程池(如
@FeignClient配置 thread-pool),即使某个下游服务彻底挂死,也只占满它自己的线程池(20 个线程),不影响其他接口和主 Tomcat 容器,实现’舱壁隔离’。- 配置合理超时:
connect-timeout=2s,read-timeout=5s,防止单次调用无限等待。以上三层防护组合,能在下游服务故障时,将影响面控制在最小范围,快速自愈。”
场景题 3:如何设计一个通用的分布式 ID 生成方案?
回答思路:
“分布式 ID 的核心要求:全局唯一、趋势递增(有序)、高性能、高可用。主流方案对比:
雪花算法(Snowflake,推荐):64 位 Long 型 ID = 1 位符号位 + 41 位时间戳(精确到毫秒,可用约 69 年)+ 10 位机器 ID(5 位数据中心 + 5 位机器号)+ 12 位序列号(同毫秒内最多 4096 个 ID)。优点:趋势递增、高性能(单机每秒可生成 400 万 ID)、不依赖外部存储。核心缺点:时钟回拨问题(如果机器时钟被调回,可能生成重复 ID)。
时钟回拨解决方案:
- 等待法:发现当前时间小于上次生成时间时,等待时间追上后再生成,适合回拨时间很短(< 5ms)的场景。
- 备用时间序列:预留额外的若干 bit 作为时钟回拨计数器,回拨发生时递增计数器而不是等待,保证 ID 不重复。
- Leaf-Snowflake(美团 Leaf 方案):启动时从 ZooKeeper 获取机器 ID,并向 NTP 服务校准时间;运行时若发现时钟回拨,抛出异常等待人工处理,或利用 ZooKeeper 的 Epoch 机制降级处理。
我们 SRM 项目的做法:使用 Hutool 或 Mybatis-Plus 自带的雪花算法实现,并在应用启动时通过机器 IP 的最后两段计算
workerId,避免机器 ID 冲突,同时设置了时钟回拨检测告警。”
场景题 4:秒杀系统的完整设计
回答思路:
“秒杀系统的核心挑战是:瞬时高并发(几万 QPS)+ 数据一致性(不能超卖)。我的设计分为以下几层:
第一层:流量收束(前置过滤):
- Nginx 限流:对秒杀接口配置
limit_req,限制单 IP 的请求速率。- 验证码/答题:秒杀前要求用户输入验证码,拦截机器刷接口的流量,同时打散请求的时间分布。
- 按钮防抖:前端禁止重复点击,减少重复请求。
第二层:Sentinel 流量控制:在接入层(网关或应用层)对秒杀接口配置 Sentinel QPS 限流规则(如最多 500 QPS),超出的请求直接返回”系统繁忙,请稍后重试”,不进入后端。
第三层:Redis 原子性预扣库存(核心!):
1
2
3
4
5 -- Lua 脚本保证原子性
local stock = redis.call('GET', KEYS[1])
if tonumber(stock) <= 0 then return -1 end -- 库存不足
redis.call('DECR', KEYS[1])
return 1 -- 扣减成功Redis 单线程执行 Lua 脚本,原子性判断库存并扣减,99% 的无效请求在 Redis 层被过滤。
第四层:Kafka 异步下单(削峰):Redis 扣减成功后,立即返回”下单受理中”给用户,并将下单请求投入 Kafka。后台消费服务从 Kafka 消费,进行数据库库存扣减(带
WHERE stock > 0乐观锁兜底)、创建订单记录、触发支付流程。第五层:结果异步通知:消费服务处理完后,通过 WebSocket 或轮询接口将最终下单结果(成功/失败)推送给用户。”
场景题 5:微服务接口幂等性如何保证?
回答思路:
“幂等性是指同一个请求执行多次和执行一次的结果完全相同,是分布式系统中防止重复请求(网络重试、消息重消费)的关键。主要方案:
方案一:唯一主键/唯一索引(最简单可靠):在数据库表中对业务唯一标识(如
order_no、trade_id)建唯一索引,重复请求插入时数据库抛出唯一键冲突异常,业务代码捕获该异常并返回成功(幂等处理),适合插入类操作。方案二:Token 令牌机制:客户端在真正操作前先请求服务端获取一个唯一 Token(存入 Redis),提交请求时携带该 Token,服务端用
DEL token_key删除 Token(原子操作),删除成功才执行业务,删除失败(Token 已不存在)则判定为重复请求,直接返回成功。适合表单防重复提交。方案三:状态机检查:根据业务状态流转规则判断幂等性。如订单只能从”待支付”流转到”已支付”,重复的”支付”请求检查订单已是”已支付”状态,直接幂等返回,不做重复处理。
方案四:Redis SETNX 防重(适合 Kafka 消费幂等):消费消息前执行
SET msg_id 1 NX EX 7d,成功则处理,失败则说明已处理过,直接 commit offset 跳过。在我们的 SRM 采购系统中,询价回调接口(供应商回调报价结果)采用了唯一索引 + Token 令牌双重保险,确保即使供应商系统超时重试也不会重复处理报价数据。”
场景题 6:如何设计支持亿级数据的定时任务调度系统?
回答思路:
“亿级数据的定时任务,核心挑战是单台机器无法在时间窗口内处理完,必须分布式并行处理。
核心设计:XXL-Job 分片任务 + 分库分表:
- XXL-Job 分片广播:使用 XXL-Job 的”分片广播”任务类型,有多少个执行器实例,任务就分成多少个分片。每个执行器通过
ShardingContext获取自己的shardIndex(当前分片序号)和shardTotal(总分片数),然后只处理id % shardTotal == shardIndex的数据,实现数据的均匀拆分并行处理。- 分页游标处理:每个分片内,使用游标分页(
WHERE id > lastId AND id % shardTotal = shardIndex LIMIT 1000)分批处理数据,避免一次加载大量数据到内存导致 OOM。- 分库分表支持:数据按 ID 分片后,可以分散在不同的库/表中,分片任务对应访问各自负责的库/表,避免跨库查询。
- 任务幂等性:每条记录处理前,通过 Redis SETNX 加任务锁(
task:{taskName}:{recordId}),防止多个执行器实例(负载均衡漂移时)重复处理同一条记录。- 任务进度持久化:每个分片定期将当前处理的游标写入 Redis,任务失败重启后从断点继续,而不是从头开始。”








