核心是可控并发而非是否并发,需在通知入口前置限流(如令牌桶)、线程池配背压队列、db层熔断降级、集群下用redis分布式限流并设本地保底。

处理大批量异步通知时,核心不是“要不要并发”,而是“怎么可控地并发”——尤其当下游是脆弱的数据库集群(如主从延迟高、连接池小、写入吞吐低)时,盲目并发极易引发连接池耗尽、慢 SQL 积压、主库 CPU 打满甚至雪崩。Java 并发编程在此场景下,需将限流作为前置守门员,而非事后补救。
限流必须前置到通知触发入口
异步通知往往来自 MQ 消费、定时任务扫描或事件驱动回调。若在消费端直接开多线程拉取+批量写 DB,等于把压力全甩给数据库。正确做法是:在通知生成或投递阶段就施加限流,让流量平滑进入处理链路。
- 用 令牌桶(Guava RateLimiter 或 Resilience4j Bulkhead + RateLimiter) 控制每秒最大通知触发数,例如设置 200 QPS,确保 DB 写入节奏可控
- 对不同优先级通知区分限流策略:订单履约类通知走严格令牌桶(100 QPS),营销推送类走漏桶(匀速放行,防突发尖峰)
- 避免在数据库操作层做限流——那已是最后一道防线,失守即故障;限流点越靠近源头,系统越健壮
线程池 + 队列组合要带背压与拒绝策略
通知处理本质是生产者-消费者模型。单纯用无界队列(如 LinkedBlockingQueue)+ 固定线程池,等于建了个内存水库,一旦消费跟不上,OOM 就是分分钟的事。
- 选用 有界阻塞队列(ArrayBlockingQueue),容量设为 200–500,配合 CallerRunsPolicy 拒绝策略:队列满时由提交线程自己执行任务,天然反压,迫使上游降速
- 线程池最大线程数 ≠ DB 连接池大小。建议设为连接池最大连接数的 1–1.5 倍(如 HikariCP maxPoolSize=20,则线程池 maximumPoolSize=20–30),避免线程争抢连接
- 禁用 Executors.newCachedThreadPool()——它会无限创建线程,极易拖垮 JVM
DB 写入层做细粒度熔断与降级
即使上游限流了,单条通知写 DB 仍可能因慢查询、锁等待、网络抖动失败。必须在 DAO 层嵌入保护机制:
- 每个写操作封装为 CompletableFuture,统一设超时(如 800ms),超时即快速失败,不阻塞线程池
- 结合 Hystrix 或 Resilience4j 的 CircuitBreaker,当连续 10 次 DB 写入失败率超 50%,自动熔断 30 秒,期间返回缓存默认值或记录日志后丢弃
- 关键字段(如用户 ID、订单号)加 本地缓存(Caffeine)+ 短 TTL(1–5 秒),避免重复通知反复查库
分布式场景下用 Redis + Lua 做全局速率控制
单机限流在集群部署时失效。当通知由多个实例消费同一 MQ Topic 时,需跨节点协同限流:
- 用 Redis INCR + EXPIRE 原子指令 实现分布式计数器,例如按“业务类型:日期”维度统计当日已处理通知数
- 更优方案是 Redisson 的 RateLimiter,基于 Redis Lua 脚本实现原子令牌发放,支持公平性与预热,且自动清理过期数据
- 注意:Redis 本身也是依赖项,需配置连接池与超时,避免因 Redis 不可用导致限流失效——此时应 fallback 到本地令牌桶保底
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











