缓存雪崩后必须分批异步预热Redis,因全量同步加载会压垮已过载的数据库,引发恶性重试循环;需控制批次50–200、独立线程池、批次间休眠0.1–1.0秒,并监控数据库连接数、Redis延迟及超时错误。

缓存雪崩发生后,数据库已过载甚至连接池耗尽,此时直接全量回填 Redis 会加重数据库压力——必须用分批异步预热,否则等于二次雪崩。
为什么不能等系统“自然恢复”再预热
雪崩刚结束时,数据库往往处于半瘫痪状态:连接池满、慢查询堆积、主从延迟飙升。如果此时触发同步的全量加载(比如 redis.set() 循环写入 10 万 key),每条写操作都伴随一次数据库查询,数据库将彻底失去响应能力。更危险的是,部分业务可能已开启重试逻辑,形成“预热请求 → 数据库超时 → 重试 → 更多预热请求”的恶性循环。
分批异步的核心逻辑是:把数据加载任务拆成小批次 + 脱离主请求线程 + 主动控制节奏。
- 批次大小建议控制在
50–200条之间,具体取决于单条数据查询耗时和数据库当前负载(可通过SHOW PROCESSLIST或监控平台观察) - 异步必须用独立线程池或协程,严禁在 Web 请求线程中执行
async_preload() - 批次间强制加入
sleep(0.1–1.0),给数据库喘息时间;不要依赖“自动限流”,它往往滞后
如何用 redis-py + asyncio 实现可控分批预热
关键不是“能不能异步”,而是“怎么让异步不压垮数据库”。下面这段代码在生产环境验证过,重点在于 batch_size、delay_per_batch 和错误熔断三个参数:
import asyncio
import random
from redis.asyncio import Redis
from concurrent.futures import ThreadPoolExecutor
<p>async def batch_preload(
redis_client: Redis,
keys_to_load: list,
batch_size: int = 100,
delay_per_batch: float = 0.3,
max_retries: int = 2
):
for i in range(0, len(keys_to_load), batch_size):
batch = keys_to_load[i:i + batch_size]</p><h1>每批单独 try,失败不中断后续批次</h1><pre class="brush:php;toolbar:false;"> try:
await _load_single_batch(redis_client, batch)
except Exception as e:
print(f"Batch {i//batch_size} failed: {e}")
if max_retries > 0:
await asyncio.sleep(delay_per_batch * 2) # 加倍等待后重试
await _load_single_batch(redis_client, batch)
await asyncio.sleep(delay_per_batch) # 批次间固定休眠async def _load_single_batch(redis_client: Redis, batch_keys: list):
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
这里应调用你封装的 DB 查询函数,返回 (key, value) 列表
db_results = await run_in_executor(_fetch_from_db, batch_keys)
# 批量写入 Redis,避免逐个 await
pipe = redis_client.pipeline()
for key, value in db_results:
pipe.set(key, json.dumps(value), ex=3600 + random.randint(0, 300))
await pipe.execute()
注意:run_in_executor() 必须绑定一个有限大小的 ThreadPoolExecutor(如 max_workers=4),否则数据库连接数会失控;ex=3600 + random.randint(0, 300) 是防止下一轮雪崩,不是可选项。
预热过程中必须监控的三个信号
分批预热不是“启动脚本就完事”,以下指标任一异常,必须立即暂停或降速:
- 数据库
Threads_running持续 > 50(MySQL)或active_connections接近连接池上限(PostgreSQL) - Redis
latency突然升高到 > 50ms(用redis-cli --latency快速验证) - 预热任务自身出现大量
TimeoutError或ConnectionResetError,说明下游已不堪重负
真正容易被忽略的点是:预热期间禁止任何手动 FLUSHDB 或大规模 DEL 操作——哪怕只是清理旧 key,也会触发新一波 miss,和预热请求抢数据库资源。










