← 每日面试题 学习看板

Java 后端模拟面试题

面试题/高阶题目/2026-09-21-线程池-Vibe-架构.md

Java 后端模拟面试题

日期:2026-09-21

总题数:3 题

方向覆盖:线程池优化、Vibe Coding、架构设计


R1 线程池优化 | 进阶

题目

场景:支付回调线程池 corePoolSize=50 / maxPoolSize=100 / LinkedBlockingQueue(2000) / CallerRunsPolicy。线上突发下游风控接口 RT 从 50ms 飙到 3s,队列迅速被打满,CallerRuns 反压到 Tomcat worker,导致全站支付接口 RT 全部升高到 5s。

3 个问题:

  1. 为什么下游一个慢接口会拖垮全站支付?从线程池反压链路上讲清楚。
  2. 如何给这类线程池做任务级超时(不是线程超时)?给出可落地方案。
  3. 如果要引入熔断降级,熔断器放在哪一层?熔断指标选 RT 还是错误率?为什么?

原始回答

首先第一个问题,下游一个慢接口拖垮全站支付的核心原因是,它出现异常后,将拒绝策略选择抛售给主线程。这时候由于 Tomcat 自身或者是外部某一个外部容器的自身的线程池被占满,导致对外提供其他能力的接口无法正常返回。所有的外部容器的线程全部被回调任务的线程占领,导致整个线程无法正常使用,所以拖慢主业务流程的线程。 进行三方调用的时候,设置任务请求超时。在线程池的调用远程 HTTP 请求的时候,这块请求基本上设,怎么可能设到 3 秒呢?基本上是 500 毫秒就到头了,500 毫秒就结束。然后如果有问题,然后进行一次重试,顶天是两次重试或者三次重试就到头了,不会说让它一直等待。三次重试没有结果就抛出异常或者记录日志,或者是调降级服务,给出一些默认的结果,然后直接执行任务返回。另外就是回调任务应该是走自己的异步线程线程池,而不是说去占用主线程的线程池。然后它的前置的设置的处理。不应该压到主线程上,因为它本身是一个异步的回调的服务,压到主线程上意义不大。你可以自定义线程池,或者是抛出异常,然后外部进行处理。抛出异常,外部进行处理,比如说抛个异常,然后记录扔到 MQ,后续再异步的扫表,扫 MQ 信息,去把这个所有的回调执行完。 熔断的话,熔断肯定是放在三方调用那,而不是放在整个的回调服务那。熔断之后把它的三方进行降级,降级之后直接返回结果,然后把熔断记录,每个回调记录一下,这些记录是没有处理的,后续异步通过计划任务也好,通过其他的方式也好,把这些任务统一的处理掉,扫表处理掉,最终保证业务的正常运行。 熔断选择指标的话,推荐还是选错误率吧。RT 的话不稳定,因为容易出现波动。RT 这你定多少 RT?超过一个 RT,它突然出现问题了,某一个波动一下,你整个就熔断了吗?这个是很不合理的。当整个服务出现了一定比例的,比如说 10%、15%。这个比例之后,证明这个服务的可用性发生了明显的问题。这时候我们再去进行整个的熔断。但是一般也不会是 15 这个比例,应该会更高一些,20% 左右,20 至 30。

点评

优点:

不足:

完整讲解

一、原理分析:为什么一个下游慢接口能拖垮全站

反压链路全貌:

用户请求 → Tomcat worker (200) → 支付回调入口
                              ↓
                    提交到业务线程池 pool:
                    core=50, max=100, queue=2000, CallerRuns
                              ↓
                    50 个核心线程都去调风控 API
                              ↓
                    风控 RT 从 50ms → 3s
                              ↓
                    50 个线程全部阻塞在 socket.read()
                              ↓
                    新任务进入 → 走 LinkedBlockingQueue 堆积
                              ↓
                    2000 个队列位置被打满(不到 10 秒)
                              ↓
                    扩容到 maxPool=100,100 个线程也阻塞
                              ↓
                    再进来的任务 → 触发 CallerRunsPolicy
                              ↓
                    【关键】CallerRuns = 谁提交谁跑
                              ↓
                    提交方是 Tomcat worker 线程!
                              ↓
                    worker 线程被强制跑回调任务,阻塞 3 秒
                              ↓
                    Tomcat 200 个 worker 逐渐耗尽
                              ↓
                    全站接口排队等 worker → RT 全线飙升

三个致命的设计错配:

设计点问题后果
LinkedBlockingQueue(2000) 容量大队列不打满,maxPool 永远不扩容,50 个核心线程长期阻塞反应滞后,堆积到爆才扩容
CallerRunsPolicy反压策略选错场景污染 Tomcat 主线程
任务里直接调下游同步接口没有超时兜底一个下游慢就所有任务卡住

核心认知:拒绝策略决定的是"任务满的时候谁买单",CallerRuns 买单的人是提交方,如果提交方是宝贵的资源(Tomcat worker、WebFlux event loop),那就是自杀。

二、方案设计

2.1 线程池参数正确姿势
// 回调场景:允许异步补偿,绝不能污染上游
ThreadPoolExecutor pool = new ThreadPoolExecutor(
    32,                              // core: 按机器核数 * 2
    64,                              // max: 不超过 core * 4
    60, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(500),  // 队列小一点,尽早暴露问题
    new ThreadPoolExecutor.AbortPolicy(),  // 关键:快速失败,不反压
    new ThreadFactoryBuilder()
        .setNameFormat("pay-callback-%d")
        .build()
);

// 提交任务的代码
try {
    pool.submit(callbackTask);
} catch (RejectedExecutionException e) {
    // 队列满了怎么办?
    mqProducer.send("callback-retry-topic", taskPayload);  // 扔 MQ 异步补偿
    metrics.increment("callback.rejected");                // 埋点
}
2.2 任务级超时(不是 RPC 超时)

三种方案对比:

方案代码特点缺点
RPC 超时httpClient.setReadTimeout(500)简单只解决下游不响应,不解决任务本身复杂耗时
Future.get(timeout)见下方代码调用方主动放弃任务还在跑,资源占用不释放
CompletableFuture.orTimeout见下方代码优雅 + 支持异常降级需要 11+
// 方案 2:Future.get(timeout) —— 老代码兼容
Future<CallbackResult> f = pool.submit(() -> {
    RiskResult r = riskClient.check(order);  // 下游 3s
    return process(r);
});
try {
    CallbackResult result = f.get(2, TimeUnit.SECONDS);
    // 用 result
} catch (TimeoutException e) {
    f.cancel(true);  // 尽力中断,不保证生效
    fallbackService.defaultResult();  // 降级
}

// 方案 3:CompletableFuture.orTimeout —— 推荐
CompletableFuture<CallbackResult> future = CompletableFuture
    .supplyAsync(() -> process(riskClient.check(order)), pool)
    .orTimeout(2, TimeUnit.SECONDS)                          // 2s 内没完成就 TimeoutException
    .exceptionally(ex -> {
        metrics.increment("callback.timeout");
        return FallbackResult.DEFAULT;                        // 异常降级
    });

CallbackResult result = future.join();  // 阻塞拿结果

关键区别:

2.3 熔断降级 —— 三指标 + 状态机
// 用 Resilience4j 为例
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
    .failureRateThreshold(50)              // 错误率 > 50% 熔断
    .slowCallRateThreshold(80)             // 慢调用占比 > 80% 熔断
    .slowCallDurationThreshold(Duration.ofSeconds(2))  // 单请求 > 2s 算慢
    .waitDurationInOpenState(Duration.ofSeconds(30))   // 熔断 30s
    .slidingWindowType(SlidingWindowType.COUNT_BASED)
    .slidingWindowSize(50)                 // 最近 50 个请求
    .minimumNumberOfCalls(20)              // 至少 20 个请求才判断,避免小样本
    .build();

CircuitBreaker cb = CircuitBreaker.of("risk-client", config);

// 使用
RiskResult result = circuitBreakerExecute(cb, () -> {
    try {
        return riskClient.check(order);
    } catch (Exception e) {
        throw new CallException(e);  // 转成熔断能识别的异常
    }
}, order -> {
    // fallback
    metrics.increment("risk.fallback");
    return RiskResult.APPROVE_WITH_REVIEW;  // 转人工审核
});

熔断状态机:

    错误率>50% 或 慢调用>80%
[Closed] ────────────────────────→ [Open]
    ↑                                 │
    │   半开试探通过                    │ 30s 后
    │                                 ↓
[Half-Open] ←───────────────────────┘
    │
    │ 半开探测失败
    ↓
[Open]

三、代码示例:完整生产版本

@Service
public class PaymentCallbackService {

    private final ThreadPoolExecutor pool = new ThreadPoolExecutor(
        32, 64, 60, TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(500),
        new ThreadFactoryBuilder().setNameFormat("pay-cb-%d").build(),
        new ThreadPoolExecutor.AbortPolicy()
    );

    private final CircuitBreaker cb = CircuitBreaker.of("risk", config);
    private final MqProducer mq;
    private final Metrics metrics;

    public CompletableFuture<CallbackResult> handleAsync(CallbackPayload payload) {
        try {
            return CompletableFuture
                .supplyAsync(() -> doProcess(payload), pool)
                .orTimeout(3, TimeUnit.SECONDS)
                .exceptionally(ex -> {
                    metrics.increment("callback.failed");
                    mq.send("callback-retry", payload);  // 失败补偿
                    return CallbackResult.PENDING;
                });
        } catch (RejectedExecutionException e) {
            mq.send("callback-retry", payload);
            metrics.increment("callback.rejected");
            return CompletableFuture.completedFuture(CallbackResult.PENDING);
        }
    }

    private CallbackResult doProcess(CallbackPayload p) {
        RiskResult risk = Call.decorateChecked(cb, () -> riskClient.check(p))
            .recover(ex -> RiskResult.APPROVE_WITH_REVIEW);  // 熔断降级
        return orderService.update(p, risk);
    }
}

四、对比分析

4.1 拒绝策略选择
策略行为适用场景反例
CallerRuns提交方跑提交方是空闲业务线程且任务不能丢提交方是 Tomcat worker(本题)
Abort抛异常任务可重试/可补偿(本题推荐)任务丢了不可恢复
Discard静默丢弃埋点类可丢任务支付、扣款
DiscardOldest丢最老的实时性 > 完整性(如埋点流)顺序性任务
自定义落库/MQ金融场景最佳实践—
4.2 熔断 vs 限流 vs 降级
概念保护对象触发条件动作
限流自己QPS 超过阈值拒绝多余请求
熔断下游错误率/RT 超过阈值停止调用下游
降级用户体验熔断/异常时返回默认值/缓存/兜底

三者关系:熔断 + 降级通常成对使用(熔断挡住流量,降级给出兜底),限流独立。


R2 Vibe Coding | 进阶

题目

场景:团队 6 人,3 人主用 Cursor/Claude Code。3 个月后:

3 个问题:

  1. AI 让"代码产出量"暴涨但"代码可信度"下降,根因是流程哪个环节失效?
  2. 如何量化 AI 生成代码的"可信度"?给出 3 个可落地的度量指标。
  3. 设计一个 AI 代码准入流水线,从 PR 提交到合并,哪些 gate 是硬门禁?

原始回答

从返回的给出的结果看,首先,单测通过率降低。这里边单测,并且很多大量测试只是 mock 掉被 mock 对象,表明单元测试根本没有实际的价值。这里边首先第一个就是单测存在大量的不合理问题,设计不合理,没能覆盖标准,没能覆盖到边界,测试设置不合理。其次,Review 时间缩短了,不合理对吧?Review 时间在 AI 介入之后,Review 时间其实应该大量的增加,而不是缩短,因为编程时间本身变短了,这时候需要更多的 Review 去观测整个设计方案。另外就是大量的代码变更。这个本身是不合理的。在 AI Vibe Coding 的阶段,其实应该给它设定增减范围,尽量只能修改自己相关的内容,每次修改前应该进行设定,而不是大范围的去修改,有和没的都进行修改。线上事故方面,SQL 的审查,这个就是在 code review 方面没有做详细的审核,这个方面需要单独的进行去审核关注。

点评

优点:

不足:

这是这道题典型的"只分析症状不下诊断、只讲感受不给方案"。面试里这样答基本挂了,因为 Vibe Coding 主题考察的是你作为技术负责人能不能建立治理机制,不是让你描述现象。

完整讲解

R1 根因:三个环节的失效

环节传统流程AI 时代为什么失效
开发阶段编码 + 单测 + 自测AI 让代码生成极快,测试没有跟上速度,变成"应付 CI"的僵尸测试
Review 阶段人肉 Review 30min 看设计PR 变大 + Review 疲劳 → 变成"扫一眼 CI 绿灯就 approve",Reviewer 变成了橡皮章
测试/发布阶段集成测试 + 灰度AI 生成的代码"看起来很对",骗过了静态测试,只有到线上真实流量才炸

最核心的一句话:AI 把"写代码"从瓶颈变成了非瓶颈,但整个研发流程的其他环节没有同步加速,导致"产出-审查"严重失衡。

R2 三个可落地的量化指标

指标定义采集方式参考阈值
有效测试率断言数 / 测试方法数,且排除全 mock 的测试静态扫描:@Mock 数量 ≥ 5 且无真实断言 → 判为无效< 30% 触发告警
PR 变更熵修改文件数 × 平均每文件行数Git diff 统计单 PR 变更 > 500 行强制拆分
线上故障归因每月 P2+ 故障中"AI 生成代码引入"的占比故障复盘时打标签> 20% 需专项治理

加分指标:

R3 AI 代码准入流水线

开发者提交 PR
     │
     ▼
┌───────────────────────────┐
│ Gate 1: AI 标注 & 元数据    │  软门禁
│ - PR 描述必须标注 AI 占比   │  - 未标注可合并但打标签
└───────────────────────────┘
     │
     ▼
┌───────────────────────────┐
│ Gate 2: 静态扫描 & 安全     │  ★ 硬门禁
│ - SQL AST 分析(危险 SQL)  │  - Block 合并
│ - 敏感信息扫描              │
│ - 依赖漏洞                  │
└───────────────────────────┘
     │
     ▼
┌───────────────────────────┐
│ Gate 3: PR 大小检查         │  ★ 硬门禁
│ - 单 PR > 500 行强制拆分    │  - Block 合并
│ - 单文件 > 200 行改动预警    │
└───────────────────────────┘
     │
     ▼
┌───────────────────────────┐
│ Gate 4: 测试质量门禁        │  ★ 硬门禁
│ - 新增代码必须有新增测试    │  - Block 合并
│ - 测试有效率高(非全 mock)  │  - Block 合并
│ - 覆盖率不下降(增量>70%)   │
└───────────────────────────┘
     │
     ▼
┌───────────────────────────┐
│ Gate 5: AI Reviewer        │  软门禁(辅助)
│ - Claude/GPT 二次 review    │  - 意见仅供参考
│ - 生成风险清单              │
└───────────────────────────┘
     │
     ▼
┌───────────────────────────┐
│ Gate 6: 人工 Review         │  ★ 硬门禁
│ - 至少 1 人 approve          │  - Block 合并
│ - AI 生成 > 50% 的 PR       │  - 需 2 人 approve
│   需 2 人 approve           │
└───────────────────────────┘
     │
     ▼
   Merge

硬门禁的核心思路:凡是"人容易忽略但机器能检查的",一律机器拦;凡是"必须人来判断意图"的(业务正确性、架构合理性),机器给提示但人来决定。


R3 架构设计 | 高阶

题目

场景:日均订单量 800 万,微信/支付宝/银联 3 个渠道 + 自建优惠券系统。当前对账发现:

3 个问题:

  1. 设计一套 T+1 全量对账 + T+0 实时差异监控 的双轨对账方案
  2. 差错账户如何设计?自动修复的边界在哪里,什么必须走人工?
  3. 优惠券重复领取这类幂等失效的根因定位思路?给出修复方案 + 兜底监控

原始回答

T 加一的对账核心是要保证数据的完全的准确,将当天产生的问题进行排查,能明确上一日产生了哪些问题,有什么不一致的。所以 T 加一的对账基本上是核心数据是对比,然后对比整个总数的差异情况,增量在多少,一天的对比差异在多少,最终得出一个当日的差异。然后 T 加零实时监控的话,就是对比一定时间范围内,然后对账系统的数据跟实时系统的数据存在哪些不符合预期的数据。G 加一基本上是用作归档,把归档数据进行一个统计,统计和当前实时数据进行对比,看具体差距有多少。然后得出差异差异量,然后进行去看具体的哪存在问题,是中台对比不成,还是说哪存在了数据上的差异,我就去寻找差异信息的具体条数。这个其实是相当于对存量数据的历史归档数据区进行搜索和查询。比如说 7+1 的数据,之前的数据都已经归档了,在归档数据内进行当天已经确定结。 差错账户设计,差错账户具体指什么?这块没有描述清楚,暂时不确认。但是自动修复的边界在哪里?比如说其实是渠道成功了,我方订单状态未处理,这种可以自动修复。但是我方退款成功了,渠道显示失败了,然后超时后又成功了,这种其实也是可以自动处理的。优惠券回滚失败,用户重复领取,这个需要人工介入去查看具体什么原因导致的重复领取,是否是幂等性的,是否幂等方面没做处理,这块需要人工介入查看。核心就是最后的状态能修复到一致的,简单的通过逻辑判断能修复到一致的,就可以自动修复。但是需要有多种情况去复查的,这种就必须走人工。 重复领取密等失失败,我觉得核心原因是在于用户发放定发放优惠券的数量状态,这块其实可以在每个阶段发放的时候,做一个唯一校验也好,做一个状态机也好,就是他接收了这个类似的优惠券吗?如果接收了就不可以再接收,发放的时候做这方面判定,然后做一个唯一标识,就发放的过程中做唯一标识,防止重复领取、重放。 兜底监控的话,其实可以放优惠券放出比例、放出数和当年活跃用户数,然后人均优惠券数这种监控去进行判定,具体哪些数据存在问题。 果的数据的对比。然后 T+0 的实时数据的话,就是对当天内产生变动的数据来进行处理。

点评

优点:

不足:

总体评价:这道题属于高阶题,需要展现"能把方案设计到可落地"的能力。目前回答停留在"知道大概方向"的层面,缺少工程细节。

完整讲解

R1:双轨对账方案设计

┌─────────────────────────────────────────────────────────┐
│                    T+0 实时差异监控                       │
│                                                         │
│  ┌──────────┐    ┌──────────┐    ┌──────────────────┐   │
│  │ 支付回调  │───→│  CDC 变更 │───→│  实时对账引擎     │   │
│  │ 消息流    │    │  捕获     │    │  (Flink/自研)     │   │
│  └──────────┘    └──────────┘    └────────┬─────────┘   │
│                                           │             │
│                                           ▼             │
│                                 ┌──────────────────┐    │
│                                 │  差异判定 & 告警    │    │
│                                 │  - 状态不一致      │    │
│                                 │  - 金额不一致      │    │
│                                 └────────┬─────────┘    │
│                                          │              │
│                                          ▼              │
│                              写差错表 + 触发告警/自动修复  │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│                    T+1 全量对账                          │
│                                                         │
│  每日 02:00 开始                                         │
│  ┌──────────────────────────────────────┐                │
│  │ 1. 拉取各渠道账单文件(T-1 日数据)    │                │
│  │    - 微信/支付宝/银联 官方对账文件      │                │
│  └──────────────────────────────────────┘                │
│                      │                                  │
│                      ▼                                  │
│  ┌──────────────────────────────────────┐                │
│  │ 2. 与本地订单表全量比对                │                │
│  │    - 按 order_id 逐笔匹配             │                │
│  │    - 比对:金额、状态、时间            │                │
│  └──────────────────────────────────────┘                │
│                      │                                  │
│                      ▼                                  │
│  ┌──────────────────────────────────────┐                │
│  │ 3. 输出差异报表                       │                │
│  │    - 渠道成功我方失败                   │                │
│  │    - 我方成功渠道失败                   │                │
│  │    - 金额不一致                       │                │
│  └──────────────────────────────────────┘                │
│                      │                                  │
│                      ▼                                  │
│  ┌──────────────────────────────────────┐                │
│  │ 4. 差异进入差错表                     │                │
│  │    - 分级:自动修复 / 人工审核          │                │
│  └──────────────────────────────────────┘                │
└─────────────────────────────────────────────────────────┘

T+0 vs T+1 对比:

维度T+0 实时T+1 全量
数据源业务事件流 + CDC渠道官方账单文件
时延秒级次日凌晨
覆盖度覆盖已处理交易覆盖全部交易(含掉单)
主要作用及时发现 新差异兜底发现 漏单/漏对
修复方式自动修复优先人工审核为主

为什么必须双轨:

R2:差错账户设计

-- 差错表
CREATE TABLE reconciliation_error (
    id              BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_id        VARCHAR(64) NOT NULL,
    channel         VARCHAR(16) NOT NULL,       -- wechat/alipay/unionpay
    error_type      VARCHAR(32) NOT NULL,       -- CHANNEL_SUCCESS_LOCAL_FAIL / REFUND_TIMEOUT / COUPON_DUPLICATE
    error_level     TINYINT NOT NULL,           -- 1:自动修复 2:人工审核 3:资损升级
    channel_data    JSON,                        -- 渠道侧数据
    local_data      JSON,                        -- 我方数据
    status          TINYINT DEFAULT 0,           -- 0:待处理 1:自动处理中 2:已自动修复 3:待人工 4:已人工处理 5:已关闭
    process_log     JSON,                        -- 处理流水
    created_at      DATETIME,
    resolved_at     DATETIME
);

-- 差异处理表(记录每次修复动作)
CREATE TABLE error_resolution_log (
    id              BIGINT PRIMARY KEY AUTO_INCREMENT,
    error_id        BIGINT NOT NULL,
    action          VARCHAR(64),                -- AUTO_RETRY_REFUND / MANUAL_APPROVE / MANUAL_REJECT
    operator        VARCHAR(32),                -- system / user_id
    before_state    JSON,
    after_state     JSON,
    result          TINYINT,                    -- 0:失败 1:成功
    created_at      DATETIME
);

自动修复 vs 人工审核边界:

场景自动/人工理由
渠道成功、我方未终态自动补一次订单终态即可,无资损风险
我方退款成功、渠道退款超时自动渠道会重试到成功,只需等确认
我方退款成功、渠道最终失败自动重试 → 人工先重试 3 次,仍失败人工介入
金额不一致人工涉及资损,必须人工核对
优惠券重复领取人工涉及资损,需评估是否追回
渠道账单缺失某笔人工渠道可能漏出账,需联系渠道

核心原则:

  1. 无资损 + 状态可逆 → 自动修复
  2. 有资损 or 不可逆 → 人工审核
  3. 自动修复失败 N 次 → 升级人工

自动修复流程:

差异写入差错表 (status=0, level=1)
    ↓
定时任务扫描 level=1 的待处理记录
    ↓
执行修复动作(补订单终态 / 触发退款 / 等渠道回调)
    ↓
修复结果写回 resolution_log
    ↓
成功 → status=2, 关闭
失败 → 重试计数 +1
      - 重试 < 3 → 继续等待下次扫描
      - 重试 ≥ 3 → status=3, 升级人工

R3:优惠券重复领取 - 幂等失效根因

三种典型根因:

根因表现修复方案
幂等 Key 设计错误用 order_id 做幂等,但一个 order 可以领多次用 user_id + coupon_template_id + time_window 做幂等
并发控制缺失快速点击导致重复数据库唯一索引 + 分布式锁双重保障
事务边界错误库存扣减和发放不在同一事务本地事务 + 本地消息表保证最终一致

根因定位流程:

1. 拿到重复领取的用户 ID 和优惠券 ID
   ↓
2. 查数据库:这几笔发放记录的时间戳
   - 如果间隔 < 100ms → 并发问题
   - 如果间隔 > 1s → 幂等 Key 或事务问题
   ↓
3. 查日志:是否走了同一个请求 ID
   - 是同一个请求 → 幂等 Key 失效
   - 不同请求 ID → 并发问题
   ↓
4. 查代码:发放接口的幂等实现
   - 是否有唯一索引?
   - 是否用了分布式锁?
   - 锁的粒度对不对?

修复方案(三层防护):

@Service
public class CouponService {
    
    /**
     * 第一层:分布式锁 - 防并发
     */
    @RedisLock(key = "coupon:receive:{userId}:{couponId}", expire = 5)
    public Coupon receiveCoupon(Long userId, Long couponId) {
        return doReceive(userId, couponId);
    }
    
    /**
     * 第二层:数据库唯一索引 - 防重复
     */
    private Coupon doReceive(Long userId, Long couponId) {
        // 唯一索引:user_id + coupon_template_id
        // 如果重复,会抛 DuplicateKeyException
        try {
            couponRecordMapper.insert(new CouponRecord(userId, couponId, ...));
        } catch (DuplicateKeyException e) {
            return getCouponRecord(userId, couponId);  // 返回已有记录
        }
        return issueCoupon(userId, couponId);
    }
    
    /**
     * 第三层:状态机校验 - 防状态异常
     */
    private Coupon issueCoupon(Long userId, Long couponId) {
        CouponTemplate template = templateMapper.selectById(couponId);
        // 校验用户是否已达到领取上限
        if (userCouponMapper.countByUser(userId, couponId) >= template.getMaxPerUser()) {
            throw new BusinessException("已达领取上限");
        }
        // 校验库存
        if (templateMapper.decrementStock(couponId) == 0) {
            throw new BusinessException("库存不足");
        }
        return couponRecordMapper.selectByUser(userId, couponId);
    }
}

兜底监控:

指标告警阈值说明
单用户领券数> 模板上限的 1.5 倍可能幂等失效
单用户每秒领券次数> 5可能并发问题
总发放数 vs 总库存发放 > 库存 95%可能超发
人均券数 / 活跃用户环比上涨 > 20%业务指标异常
同一 coupon_template_id 领取失败率> 10%可能有 bug

实时监控告警:

-- 每小时跑一次,发现异常立即告警
SELECT user_id, COUNT(*) as cnt 
FROM coupon_record 
WHERE coupon_template_id = ? 
  AND created_at > NOW() - INTERVAL 1 HOUR
GROUP BY user_id 
HAVING cnt > (SELECT max_per_user FROM coupon_template WHERE id = ?);

面试总结

各轮次表现汇总

轮次方向难度表现得分
R1线程池优化进阶反压链路抓得准,但任务级超时概念混淆7/10
R2Vibe Coding进阶现象分析有感觉,但缺量化指标和流水线设计5/10
R3架构设计高阶有方向感但缺工程细节,差错账户概念不清5/10

强项

  1. 反压链路的直觉不错:能讲清 CallerRuns 污染 Tomcat 的机制
  2. 风险意识强:能识别 AI 时代的 Review 疲劳问题、知道资损要人工介入
  3. 兜底思维:多次提到"重试"、"异步补偿"、"扫表修复",有工程兜底意识

待提升点

  1. 概念精度:任务级超时 vs RPC 超时、差错账户、幂等失效的三种根因——需要精准区分
  2. 量化能力:Vibe Coding 那题完全没给量化指标,这是技术负责人必备的能力
  3. 方案落地:架构题讲原则多、讲实现少,缺代码级细节和流程图

建议


会话全文记录

2026-09-21

面试官:今天 3 题,方向覆盖线程池优化、Vibe Coding、架构设计。建议按 R1 → R2 → R3 顺序。

候选人:开始

面试官:[R1 题目 - 线程池反压 + 任务级超时 + 熔断降级]

候选人:

R1 答:CallerRuns 反压到 Tomcat worker 导致全站 RT 上升。下游调用设 500ms 超时,重试 2-3 次。回调任务走独立线程池不占主线程。熔断放在三方调用那,不放整个回调服务。熔断选错误率不选 RT,错误率 20-30% 触发熔断。

面试官:[R1 点评]

候选人:讲解

面试官:[R1 完整讲解 - 反压链路图 + 线程池参数 + 任务级超时三方案 + 熔断三指标状态机 + 完整代码]

候选人:继续

面试官:[R2 题目 - AI 代码可信度治理]

候选人:

R2 答:单测大量 mock 掉被测对象说明测试不合理。Review 时间应该增加而不是缩短。PR 大小要约束,AI 应该只修改自己相关内容。线上事故是 SQL 审查不足,需要单独审核。

面试官:[R2 点评]

候选人:继续

面试官:[R2 完整讲解 - 三环节失效分析 + 三个量化指标 + 六层准入流水线]

候选人:继续

面试官:[R3 题目 - 支付对账 + 差错账户 + 幂等失效]

候选人:

R3 答:T+1 核心保证数据准确,对比总数差异。T+0 实时监控对比时间范围内数据。差错账户没概念,但自动修复边界能判断:简单一致性问题自动修,涉及资损走人工。优惠券重复领取根因是唯一性校验和状态机缺失。兜底监控人均券数、发放比例。

面试官:[R3 点评]

面试官:[R3 完整讲解 - 双轨对账方案图 + 差错账户表结构 + 自动修复流程 + 幂等三种根因 + 三层防护代码 + 兜底监控]

面试官:[面试总结 - 汇总表 + 强项 + 待提升 + 建议]


文档生成时间:2026-09-21