超卖异常是业务失败而非标准异常,应通过Redis预减、数据库条件更新、分布式锁等前置拦截,统一返回Result结构体,仅对Redis连接失败等底层故障做兜底捕获。

在并发秒杀场景中,“超卖异常”本身不是 Java 标准异常(如 RuntimeException),而是业务逻辑失败的一种表现——即库存校验未通过、扣减被拒绝、订单创建被拦截等。真正要做的,不是“捕获超卖异常”,而是**主动识别超卖风险、前置拦截、优雅反馈,并避免把业务失败包装成未定义异常**。
不依赖 try-catch 捕获“超卖异常”
强行用 try...catch 去捕获一个本不该发生的“超卖异常”,说明系统设计已失守:库存控制逻辑没前置、原子性没保障、事务边界混乱。正确做法是让超卖在源头就无法发生,而非等它发生后再“处理异常”。
- Redis 预减库存失败(
DECR返回负值或 nil)→ 直接返回“库存不足”,不进后续流程 - 数据库 UPDATE 带条件(
WHERE stock >= 1)影响行为 0 → 认定扣减失败,返回明确业务码(如ERR_STOCK_UNAVAILABLE),不抛运行时异常 - 分布式锁获取超时或被拒绝 → 返回“抢购拥挤,请重试”,而非
LockTimeoutException向上透传
用统一业务结果封装替代异常流
秒杀接口应统一返回 Result<t></t> 结构,其中 code 和 msg 明确表达业务状态,而不是靠抛异常来跳转逻辑。
- 成功下单:code=200, msg="抢购成功"
- 库存耗尽:code=409, msg="抱歉,库存已抢光"
- 重复抢购:code=403, msg="您已参与本场秒杀"
- 限流拦截:code=429, msg="请求过于频繁"
这样前端可精准提示,日志可按 code 聚合分析,监控能识别真实失败率——而不会因某段代码意外抛出 NullPointerException 或 RedisConnectionFailureException 干扰业务指标。
立即学习“Java免费学习笔记(深入)”;
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
只对真正不可控的底层异常做兜底
仅对非业务逻辑导致的、必须中断流程的故障做异常捕获和降级,例如:
-
JedisConnectionException:Redis 短暂不可用 → 查本地内存标记 + 返回缓存库存值 + 异步告警 -
DataAccessException(如 MySQL 连接池耗尽):启用熔断,自动切到“排队中”页面,异步写入 MQ 待重试 -
RejectedExecutionException(线程池满):触发限流降级,拒绝新请求并返回友好提示
这些异常不等于“超卖”,而是系统稳定性问题;它们需要记录堆栈、触发告警,但绝不用于控制库存逻辑分支。
日志与监控中显式标记超卖拦截点
在关键判断处打结构化日志,便于排查是否防护生效:
- “[MIAOSHA] Redis stock key: goods:1001 = 0 → rejected”
- “[MIAOSHA] DB update affected 0 rows for goods_id=1001 → inventory race avoided”
- “[MIAOSHA] User 500231 duplicated attempt → blocked by user_id bloom filter”
这类日志不依赖异常机制,却比任何 catch (Exception e) 更真实反映超卖防控效果。

















