📌 高并发系统设计 : 基于Redis与MQ的秒杀系统架构:库存预减、防超卖与限流防刷全流程解析
📋 目录
一、工具类功能
一组 Supplier 并行提交到指定线程池,按提交顺序聚合结果,支持整体超时与单任务异常隔离,把「一次请求要打好几个下游」收成一次调用。
二、所需环境
JDK 17或以上
三、方法与调用示例(按需调用)
🔢 方法序号:1
🎯 方法用途:
- 把一组
Supplier并行提交到你指定的线程池,按提交顺序把结果收回来 - 一次请求要打多个下游(订单、用户、库存)时用,不用自己逐个
supplyAsync再拼allOf
🧩 方法定义 :public static <T> List<T> supplyAll(List<Supplier<T>> tasks, Executor executor)
🗣️ 入参解释 :public static <T> List<T> supplyAll(任务列表, 线程池)
📤 返回值解释 :List<T>,与提交顺序一致的结果列表,不可变
💡 边界口径:
- 严格模式:任一任务失败就整体以
CompletionException失败,cause 是原始异常 - 任一任务失败时仍会等其他任务跑完才返回,避免别的线程里已发生的副作用失去归属
executor为null直接拒绝,不允许用ForkJoinPool.commonPool()跑阻塞任务- 任务列表会先整体校验再提交,含
null元素抛IllegalArgumentException,不会留下没人管的任务
🧪 方法示例:
supplyAll(List.of(订单任务, 用户任务, 库存任务), executor)拿到[订单SO20261005001, 用户-张三, 库存-剩8件],耗时 83 毫秒而串行要 120 毫秒- 三个任务按 60/40/20 毫秒提交,最先完成的库存仍排在第三位,拿到
[订单SO20261005001, 用户-张三, 库存-剩8件] supplyAll(tasks, null)抛IllegalArgumentException,消息是「线程池不能为null(禁止使用 ForkJoinPool.commonPool() 跑阻塞任务)」- 任务列表里放一个
null元素,抛IllegalArgumentException: 任务列表含null元素,无法执行
🔢 方法序号:2
🎯 方法用途:
- 并行执行整组任务并给一个整体超时,到点就不再等下去
- 下游有慢风险时用,避免一个卡住的接口把这次请求拖成长尾
🧩 方法定义 :public static <T> List<T> supplyAll(List<Supplier<T>> tasks, Duration timeout, Executor executor)
🗣️ 入参解释 :public static <T> List<T> supplyAll(任务列表, 整体超时, 线程池)
📤 返回值解释 :List<T>,按提交顺序的结果列表
💡 边界口径:
- 超时后取消所有还没出结果的 future,让下游依赖与内存及时释放,但正在跑的任务不会被打断
- 超时以
CompletionException失败,cause 是TimeoutException,消息写明等待多久 timeout必须大于 0,给Duration.ZERO抛IllegalArgumentException- 真要可控中断,任务内部得自己响应
Thread.currentThread().isInterrupted()
🧪 方法示例:
supplyAll(tasks, Duration.ofSeconds(1), executor)拿到[订单SO20261005001, 用户-张三, 库存-剩8件],与不带超时版本同序- 整体超时给 100 毫秒而慢任务要跑更久时抛
CompletionException,cause 是TimeoutException,消息是「并行任务在 100 毫秒内未全部完成」 - 严格模式抛错前等满了
75毫秒,因为是慢任务跑完才返回 - 等待线程被中断时抛
CompletionException,cause 拿到java.lang.InterruptedException supplyAll(tasks, Duration.ZERO, executor)抛IllegalArgumentException,消息带上「当前为PT0S」
🔢 方法序号:3
🎯 方法用途:
- 隔离模式并行执行:任务失败不抛异常,按下标记进
ParallelResult - 允许部分下游挂掉的聚合(订单查到就先展示,优惠券查不到当没有)用它
🧩 方法定义 :public static <T> ParallelResult<T> supplyAllIsolated(List<Supplier<T>> tasks, Executor executor)
🗣️ 入参解释 :public static <T> ParallelResult<T> supplyAllIsolated(任务列表, 线程池)
📤 返回值解释 :ParallelResult<T>,带下标的逐项结果,可查 isAllSuccess()、failureCount()、resultAt(int)
💡 边界口径:
- 只给「成功列表 + 异常列表」排不了障,所以每一项都保留下标,
resultAt(int)取回成功值或失败原因 - 任务失败不抛异常,由调用方看
isAllSuccess()决定是否降级 successes()与failures()返回的是不可变视图- 线程池与任务列表的校验口径同方法1
🧪 方法示例:
- 中间那个下游抛「优惠券服务无响应」,
isAllSuccess()拿到false - 同一份结果的
failureCount()拿到1 - 同一份结果的
successes()拿到[订单SO20261005001, 库存-剩8件],失败项没有把整组带走 resultAt(1)的isSuccess()拿到false,error是优惠券服务无响应resultAt(0)的value拿到订单SO20261005001failures()拿到[优惠券服务无响应],异常对象本身还在,能继续取 cause
🔢 方法序号:4
🎯 方法用途:
- 隔离模式带整体超时:等到点就把没结果的项按真实原因记账
- 既不能整体失败、又不能无限等时用这一版
🧩 方法定义 :public static <T> ParallelResult<T> supplyAllIsolated(List<Supplier<T>> tasks, Duration timeout, Executor executor)
🗣️ 入参解释 :public static <T> ParallelResult<T> supplyAllIsolated(任务列表, 整体超时, 线程池)
📤 返回值解释 :ParallelResult<T>,超时项也带真实原因的逐项结果
💡 边界口径:
- 整体超时、任务失败、等待线程被中断是三种不同原因,逐项记的 error 各不相同,不谎报成同一种
- 超时只让调用方不再等,任务本身继续跑到自然结束
- 已完成的任务结果不会被超时连累,照常留在对应下标上
🧪 方法示例:
- 整体超时 200 毫秒时
resultAt(0)拿到用户-张三,先完成的结果没被连累 - 同一份结果的
resultAt(1)的 error 类型拿到TimeoutException,消息是「并行任务在 200 毫秒内未全部完成」 - 等待线程被中断时,未完成项的 error 拿到
java.lang.InterruptedException,不会被写成超时
🔢 方法序号:5
🎯 方法用途:
- 给一批输入并行跑同一段处理逻辑,输出顺序与入参一致
- 批量查详情、批量调外部接口这类 map 型任务用它,比
parallelStream更可控
🧩 方法定义 :public static <I, O> List<O> mapAll(List<I> inputs, Function<I, O> mapper, Executor executor)
🗣️ 入参解释 :public static <I, O> List<O> mapAll(输入集合, 单元处理逻辑, 线程池)
📤 返回值解释 :List<O>,与入参同序的输出列表
💡 边界口径:
- 顺序按入参下标对齐,跟哪个先完成无关
- 任一元素处理失败即整体以
CompletionException失败,要部分成功就用方法3 的隔离模式 - 输入集合含
null元素抛IllegalArgumentException,线程池为null同样拒绝
🧪 方法示例:
mapAll(List.of("SO-3", "SO-1", "SO-2"), this::queryDetail, executor)拿到[SO-3=已支付, SO-1=已支付, SO-2=已支付],与入参顺序一致- 三个订单号耗时不同,返回值仍按入参下标对齐,拿到
[SO-3=已支付, SO-1=已支付, SO-2=已支付] - 输入里放一个
null,抛IllegalArgumentException: 输入集合含null元素,无法作为任务参数
🔢 方法序号:6
🎯 方法用途:
- 批量并行处理的同时给一个整体超时
- 输入条数不确定、又怕个别元素卡住整批时用这一版
🧩 方法定义 :public static <I, O> List<O> mapAll(List<I> inputs, Function<I, O> mapper, Duration timeout, Executor executor)
🗣️ 入参解释 :public static <I, O> List<O> mapAll(输入集合, 单元处理逻辑, 整体超时, 线程池)
📤 返回值解释 :List<O>,与入参同序的输出列表
💡 边界口径:
- 超时口径同方法2:到点不再等,但不打断正在执行的任务
- 超时以
CompletionException失败,cause 是TimeoutException timeout必须大于 0,mapper为null抛IllegalArgumentException
🧪 方法示例:
mapAll(orders, this::queryDetail, Duration.ofSeconds(1), executor)拿到[SO-3=已支付, SO-1=已支付, SO-2=已支付]- 超时时间给得短于最慢元素时抛
CompletionException,cause 是TimeoutException,消息写明等待毫秒数
四、工具类源码(可直接复制)
📦 源码文件