线程池配合分布式缓存实现热点变量异步预加载,核心是提前加载高频数据,避免请求时压垮db或阻塞主线程;需控制调度、防重复、可感知失败,并结合监控识别热点、分片加载、绑定生命周期、固定线程池+有界队列+callerrunspolicy、分布式锁防重、空值兜底、状态反馈及本地缓存协同,形成闭环预热链路。

线程池配合分布式缓存做热点变量的异步预加载,核心是“不等请求来,提前把热数据塞进缓存”,同时避免压垮数据库或阻塞主线程。关键不在并发多高,而在于调度可控、资源不溢出、数据不重复、失败可感知。
明确预加载触发时机与数据范围
预加载不是全量刷缓存,而是聚焦真正高频访问的变量。比如秒杀商品库存、实时榜单TOP100、全局活动开关等。
- 从监控系统识别热点:基于Redis慢日志、QPS突增告警、应用层埋点(如某key 1分钟内被读取超5000次)自动标记为待预热目标
- 限定数据粒度:避免加载“全部用户配置”,改为按业务域分片加载,例如
hot_config:activity_v2_202605、hot_rank:hourly_08 - 绑定生命周期:预加载任务应关联业务周期,如大促前30分钟触发,有效期设为2小时,避免长期驻留无效数据
用固定大小线程池执行预加载任务
不用Executors.newCachedThreadPool()——它会无节制创建线程,容易耗尽连接或打满CPU;也不用单线程——太慢。推荐固定大小+有界队列的组合。
- 线程数建议 = CPU核数 × 1.5(IO密集型场景),例如8核机器设12线程,避免上下文切换开销过大
- 使用
LinkedBlockingQueue或ArrayBlockingQueue,容量设为200~500,防止突发大量预热请求堆积OOM - 拒绝策略选
CallerRunsPolicy:当队列满时,由提交线程自己执行任务,天然实现背压,防止雪崩
预加载逻辑需自带防重、降级与状态反馈
同一热点变量可能被多个服务或多次定时任务触发预热,必须避免重复拉库、重复写缓存。
- 加分布式锁:用Redis Lua脚本实现原子性判断+上锁,锁过期时间设为预估加载耗时×2(如30秒)
- 空值/异常兜底:若DB查不到或超时,写入短TTL空值(如
SET hot_key "null" EX 5),防止缓存穿透 - 记录执行结果:将成功/失败/跳过状态写入轻量日志表或上报Metrics,便于追踪“哪些key没刷上”
与本地缓存协同形成两级预热链路
仅靠Redis预热还不够快。对极高频读取的变量(如每毫秒调用的开关标识),应在JVM内存中也同步加载一份。
- 预加载任务完成Redis写入后,顺带更新Caffeine本地缓存:
localCache.put(key, value) - 本地缓存设最大size=1000 + expireAfterWrite(10, MINUTES),避免内存膨胀
- 读取路径统一走
Local → Redis → DB三级,预热覆盖越深,首字节延迟越低
不复杂但容易忽略:预加载不是一次性的“启动脚本”,而是带健康检查的闭环流程——有触发、有执行、有锁控、有结果、有回滚。真正落地时,往往比写个定时任务多花20%代码,但换来的是高峰期间数据库QPS下降70%、接口P99延迟稳定在8ms以内。











