← 学习门户 每日面试题 学习看板

2026-09-21 · 高阶题

高阶题 · 线程池优化 / Vibe Coding / 架构设计

3 题 · 平均 2.83/5 · 高阶题

#1 线程池优化 进阶 practice

题目

线程池优化

我的回答

暂无回答记录

点评

暂无点评

完整讲解

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 | 渠道官方账单文件 |
| 时延 | 秒级 | 次日凌晨 |
| 覆盖度 | 覆盖已处理交易 | 覆盖全部交易(含掉单) |
| 主要作用 | **及时发现** 新差异 | **兜底发现** 漏单/漏对 |
| 修复方式 | 自动修复优先 | 人工审核为主 |

**为什么必须双轨**:
- 只有 T+0:如果 MQ 丢消息、CDC 延迟、渠道回调没到,T+0 完全发现不了
- 只有 T+1:资损已经发生 24h 才被发现,可能已被套利

R2:差错账户设计

```sql
-- 差错表
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. 查代码:发放接口的幂等实现
- 是否有唯一索引?
- 是否用了分布式锁?
- 锁的粒度对不对?
```

**修复方案(三层防护)**:

```java
@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 |

**实时监控告警**:
```sql
-- 每小时跑一次,发现异常立即告警
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 = ?);
```

---

#2 Vibe Coding 进阶 practice

题目

Vibe Coding

我的回答

暂无回答记录

点评

暂无点评

完整讲解

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 | 渠道官方账单文件 |
| 时延 | 秒级 | 次日凌晨 |
| 覆盖度 | 覆盖已处理交易 | 覆盖全部交易(含掉单) |
| 主要作用 | **及时发现** 新差异 | **兜底发现** 漏单/漏对 |
| 修复方式 | 自动修复优先 | 人工审核为主 |

**为什么必须双轨**:
- 只有 T+0:如果 MQ 丢消息、CDC 延迟、渠道回调没到,T+0 完全发现不了
- 只有 T+1:资损已经发生 24h 才被发现,可能已被套利

R2:差错账户设计

```sql
-- 差错表
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. 查代码:发放接口的幂等实现
- 是否有唯一索引?
- 是否用了分布式锁?
- 锁的粒度对不对?
```

**修复方案(三层防护)**:

```java
@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 |

**实时监控告警**:
```sql
-- 每小时跑一次,发现异常立即告警
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 = ?);
```

---

#3 架构设计 高阶 practice

题目

架构设计

我的回答

暂无回答记录

点评

暂无点评

完整讲解

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 | 渠道官方账单文件 |
| 时延 | 秒级 | 次日凌晨 |
| 覆盖度 | 覆盖已处理交易 | 覆盖全部交易(含掉单) |
| 主要作用 | **及时发现** 新差异 | **兜底发现** 漏单/漏对 |
| 修复方式 | 自动修复优先 | 人工审核为主 |

**为什么必须双轨**:
- 只有 T+0:如果 MQ 丢消息、CDC 延迟、渠道回调没到,T+0 完全发现不了
- 只有 T+1:资损已经发生 24h 才被发现,可能已被套利

R2:差错账户设计

```sql
-- 差错表
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. 查代码:发放接口的幂等实现
- 是否有唯一索引?
- 是否用了分布式锁?
- 锁的粒度对不对?
```

**修复方案(三层防护)**:

```java
@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 |

**实时监控告警**:
```sql
-- 每小时跑一次,发现异常立即告警
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 = ?);
```

---

📚 查看全部面试记录