字节二面挂了!被问“1000 万短信 1 小时发完,怎么设计线程池?”

张开发
2026/4/19 23:00:04 15 分钟阅读

分享文章

字节二面挂了!被问“1000 万短信 1 小时发完,怎么设计线程池?”
千万级推送不仅考参数调优更考架构防御本文拆解 1000 万短信 1 小时发完的真实现场从 黄金公式到 动态监控调优再到防止 OOM 的 “生产级”拒绝策略。文末附带 P7 级面试套路模板助你扫平 线程池深坑。“我们要发 618 营销短信1000 万条要求 1 小时内发完。你打算怎么设计线程池核心参数给多少拒绝策略选哪个”结果你想都没想“简单啊算一下 1 小时 3600 秒每秒发 2800 条。直接搞个FixedThreadPool线程数开到 500队列给大点不就行了”我冷笑一声连追三问“FixedThreadPool默认队列是LinkedBlockingQueue长度是Integer.MAX_VALUE千万级数据还没发完内存就 OOM 了怎么办”“如果短信网关限流了你的任务积压在队列里应用重启任务全丢了怎么办”“你怎么证明你配的线程数是最优的是拍脑袋想的还是有数据支撑”第一点在大厂规范里严禁使用Executors.newFixedThreadPool或newCachedThreadPool。OOM 隐患默认的无界队列能塞 个任务。在 1000 万数据的冲击下还没等到线程处理你的 JVM 堆内存就先爆了。资源耗尽CachedThreadPool允许创建的线程数也是无限大瞬间的高并发能直接把 CPU 100% 跑满。所以生产环境必须手动创建ThreadPoolExecutor且必须配合有界队列。线程池调优的三种修为修为 1利用“黄金公式”计算初始值面试官问你线程数给多少千万别直接说 200 或 500。你要先问“这任务是 CPU 密集型还是 IO 密集型”短信推送涉及网络调用属于典型的IO 密集型。根据经验公式N(cpu)CPU 核心数U(cpu)目标 CPU 利用率W/C等待时间与计算时间的比值实战落地对于千万级推送通常 W/C 很大建议初始线程数设置为 起步并根据压测调整。修为 2动态调优 全链路监控参数是“死”的流量是“活”的。大厂 P7 的标准做法是动态线程池。参数动态化核心参数CoreSize、MaxSize、QueueSize不要写死在代码里接入配置中心如 Apollo、Nacos。监控预警监控队列剩余容量、线程池活跃度。当队列超过 80% 满时自动触发告警或动态扩容。Fox 提示业内著名的开源项目 Hippo4J 或 DynamicTp 就是干这个的面试时提一句加分不少。修为 3拒绝策略的“终极防线”当 1000 万数据涌入线程池满了拒绝策略选哪个AbortPolicy默认直接抛异常千万别选数据直接丢了。CallerRunsPolicy推荐让提交任务的线程比如主线程自己去执行。 这其实是一种“天然的背压”。主线程去发短信了它就没空再去数据库捞新任务从而减缓了任务产生速度给线程池喘息的机会。很多同学应该还记得我写过CallerRunsPolicy回退给调用者执行是个坑因为它会阻塞主线程。但是在千万级推送这种“离线批量场景”下这个“坑”反而成了神技。在线 Web 场景避坑如果是处理用户请求绝对不能用它否则 Tomcat 线程被占满整个网站直接卡死。离线批量场景神器我们从 DB 里捞千万级数据往线程池塞。如果池子满了触发CallerRunsPolicy让“捞数据的线程”自己去发短信。高阶奥义天然背压Backpressure。当“生产者”被迫去干“消费者”的活儿时它就没空去 DB 捞新数据了。这会自动减缓任务产生的速度给线程池喘息的时间彻底规避 OOM 风险。三、 最后的“防杠”指南万一服务挂了怎么办面试官看你答得不错通常会祭出最后一招“任务在内存队列里机器宕机了100 万条短信没发出去怎么补救”本地持久化在任务入队前先在数据库/Redis 记录一个“发送中”的状态。Ack 机制线程处理完后回调更新状态为“已完成”。离线补偿启动一个定时任务TN专门扫描那些处于“发送中”超过 10 分钟的任务重新投递。

更多文章