缓存与 Redis 使用说明
本仓库中 Redis 主要用于性能型缓存,当前落地的业务缓存包括:
- 文本向量缓存(embedding 缓存)
- 翻译结果缓存(Qwen-MT 等机器翻译)
底层连接配置统一来自 config/env_config.py 的 REDIS_CONFIG:
- Host/Port:
REDIS_HOST/REDIS_PORT(默认localhost:6479) - Password:
REDIS_PASSWORD - Socket & 超时:
REDIS_SOCKET_TIMEOUT/REDIS_SOCKET_CONNECT_TIMEOUT/REDIS_RETRY_ON_TIMEOUT - 通用缓存 TTL:
REDIS_CACHE_EXPIRE_DAYS(默认360*2天,代码注释为 “6 months”)
1. 缓存总览表
| 模块 / 场景 | Key 模板 | Value 内容示例 | 过期策略 | 备注 |
|---|---|---|---|---|
| 向量缓存(text/image embedding) | 文本:`{EMBEDDING_CACHE_PREFIX}:embed:norm{0 | 1}:{text};图片:{EMBEDDING_CACHE_PREFIX}:image:embed:norm{0 |
1}:{url_or_path}` | BF16 bytes(每维 2 字节大端存储),读取后恢复为 np.float32 |
| 翻译结果缓存(translator service) | trans:{model}:{target_lang}:{source_text[:4]}{sha256(source_text)} |
机翻后的单条字符串 | TTL=services.translation.cache.ttl_seconds 秒;可配置滑动过期 |
见 translation/service.py + config/config.yaml |
下面按模块详细说明。
2. 向量缓存(embeddings/text_encoder.py / embeddings/image_encoder.py / embeddings/server.py)
- 代码位置:
- 调用侧:
embeddings/text_encoder.py、embeddings/image_encoder.py - 服务侧:
embeddings/server.py - 公共 Redis/BF16 编解码:
embeddings/redis_embedding_cache.py、embeddings/bf16.py
- 调用侧:
- 用途:缓存文本/图片向量结果,避免重复推理、重复下载图片、重复占用模型并发槽位。
2.0 当前缓存链路总览
- 现在是双层缓存:
- 调用侧缓存:
TextEmbeddingEncoder/CLIPImageEncoder在发 HTTP 请求前先查 Redis; - 服务侧缓存:
/embed/text//embed/image在进入后端推理前再查 Redis。
- 调用侧缓存:
- 这次改动里,BF16 存储格式本身没有变化:
- 写入:
float32 -> BF16 -> bytes -> Redis - 读取:
Redis bytes -> BF16 -> float32(np.float32)
- 写入:
- 这次新增的核心变化是:
- 缓存 key 加入 normalize 维度,避免
normalize=true与normalize=false命中同一条缓存; - 服务端也启用同一套 Redis 向量缓存,而不是只有 client 侧缓存;
- 拆分 text / image 服务 后,图片压力不会再拖慢文本服务;
- 服务侧 full-cache-hit 可直接返回,不会再进入模型限流槽位。
- 缓存 key 加入 normalize 维度,避免
2.1 Key 设计
- 统一 helper:
embeddings/cache_keys.py - 文本主 key(TEI/BGE):
build_text_cache_key(text, normalize=...) - 多模态图片(CN-CLIP
/embed/image):build_image_cache_key(url, normalize=..., model_name=...),其中model_name来自services.embedding.image_backends.*.model_name(与embeddings.config.CONFIG.MULTIMODAL_MODEL_NAME一致) - 多模态文本(CN-CLIP
/embed/clip_text):build_clip_text_cache_key(text, normalize=..., model_name=...) - 模板:
TEI 文本: {EMBEDDING_CACHE_PREFIX}:embed:norm{0|1}:{text}
CN-CLIP 图片: {EMBEDDING_CACHE_PREFIX}:image:embed:{model_name}:txt:norm{0|1}:{url_or_path}
CN-CLIP 文本塔: {EMBEDDING_CACHE_PREFIX}:clip_text:embed:{model_name}:img:norm{0|1}:{text}
- 字段说明:
EMBEDDING_CACHE_PREFIX:来自REDIS_CONFIG["embedding_cache_prefix"],默认值为"embedding",可通过环境变量REDIS_EMBEDDING_CACHE_PREFIX覆盖;model_name:如CN-CLIP/ViT-H-14;切换模型时自动使用新 key 空间,避免混用旧维度向量;norm1/norm0:分别表示normalize=true/normalize=false;text/url_or_path:经strip后,若 Unicode 长度 ≤CACHE_KEY_RAW_BODY_MAX_CHARS(默认 256,见embeddings/cache_keys.py)则原样写入键尾;更长则改为h:sha256:<64 hex>(对 UTF-8 字节做 SHA-256)。TEI 文本与多模态共用_stable_body_for_cache_key。
补充说明:
- TEI 文本 raw key 仍为
embed:norm{0|1}:...(尾部负载规则同上)。 - 多模态键名中
txt/img段为项目内约定(与embeddings/cache_keys.py一致),用于区分图片 lane 与 clip 文本 lane。
2.2 Value 与类型
- 类型:BF16 bytes(bfloat16),每一维用 2 字节无符号整数表示,按大端序列化。
- 写入流程:FP32 向量 → BF16 → bytes → Redis
- 读取流程:Redis bytes → BF16 → FP32(
np.float32)向量 - 典型示例:BGE-M3 1024 维向量在 Redis value 大小约为 (1024*2=2048) bytes(不含 Redis 元数据开销)。
2.3 过期策略
- 初始化:
self.expire_time = timedelta(days=REDIS_CONFIG.get("cache_expire_days", 180)).env中可通过REDIS_CACHE_EXPIRE_DAYS配置,默认360*2(代码注释标注为 6 个月)。
- 写入:
redis.setex(cache_key, self.expire_time, serialized_data)
- 访问(滑动过期):
- 命中缓存后,会调用
redis.expire(cache_key, self.expire_time)延长 TTL。
- 命中缓存后,会调用
2.4 特殊处理
- 若缓存中的向量 为空 / shape 异常 / 含 NaN/Inf,会:
- 直接丢弃该缓存(并尝试
deletekey); - 回退为重新调用向量服务。
- 直接丢弃该缓存(并尝试
2.5 服务拆分与缓存/限流关系
- 当前默认部署形态:
- 文本 embedding 服务:
6005 - 图片 embedding 服务:
6008
- 文本 embedding 服务:
- 调用方配置:
EMBEDDING_TEXT_SERVICE_URLEMBEDDING_IMAGE_SERVICE_URL
- 服务端配置:
TEXT_MAX_INFLIGHTIMAGE_MAX_INFLIGHTEMBEDDING_SERVICE_KIND=all|text|image
- 关键行为:
- full-cache-hit 请求在服务端会直接返回,不占用
TEXT_MAX_INFLIGHT/IMAGE_MAX_INFLIGHT; - cache miss 才会进入对应模型 lane;
- 因此高频重复图片请求不会再因为严格的图片限流而大量阻塞。
- full-cache-hit 请求在服务端会直接返回,不占用
3. 翻译结果缓存(translation/service.py)
- 代码位置:
translation/service.py - 用途:统一缓存所有 translation capability 的翻译结果。
- 配置入口:
config/config.yaml -> services.translation.cacheconfig/config.yaml -> services.translation.capabilities.*.use_cache
3.1 Key 设计
- 内部构造函数:
TranslationCache.build_key(...) - 模板:
trans:{model}:{target_lang}:{source_text[:4]}{sha256(source_text)}
其中:
model:capability 名称,如qwen-mt、llm、opus-mt-zh-entarget_lang:目标语言,如en/zhsource_text[:4]:原文前 4 个字符sha256(source_text):对完整原文做 SHA-256
3.2 Value 与类型
- 类型:UTF-8 字符串,即翻译后的文本结果。
- 存取逻辑:
- 读取:
redis.get(key)返回str或None; - 写入:
redis.setex(key, expire_seconds, translation)。
- 读取:
3.3 过期策略
- 配置来源:
config/config.yaml -> services.translation.cache,经get_translation_cache_config()解析:
services:
translation:
cache:
ttl_seconds: 62208000 # 默认约 720 天
sliding_expiration: true
capabilities:
qwen-mt:
use_cache: true
llm:
use_cache: true
deepl:
use_cache: true
nllb-200-distilled-600m:
use_cache: true
opus-mt-zh-en:
use_cache: true
opus-mt-en-zh:
use_cache: true
- 运行时行为:
- translator service 启动时初始化共享 Redis cache;
- 读缓存:
- 命中后,若
sliding_expiration=True,会调用redis.expire(key, expire_seconds); - 写缓存:
- 使用
redis.setex(key, expire_seconds, translation)。
3.4 关联模块
api/translator_app.py通过TranslationService统一复用同一套缓存逻辑;- 所有翻译后端都通过
TranslationService接入缓存。
4. 商品内容理解缓存(已迁出)
本仓库原先存在一套用于 qanchors / enriched_* 生成的 Redis 缓存实现,但对应内容理解服务已经迁移到独立项目,当前仓库代码中不再读写这类缓存,也不再把它作为运行时能力的一部分维护。
5. Redis 运维脚本工具
scripts/redis/ 下提供三个脚本,用于查看缓存数量、内存占用与健康状态。连接配置均来自 config/env_config.py 的 REDIS_CONFIG,运行前需在项目根目录执行(或保证 PYTHONPATH 含项目根),以便加载配置。
5.1 redis_cache_health_check.py(缓存健康巡检)
功能:按业务缓存类型(embedding / translation)做健康巡检,不扫全库。
- 对每类缓存:SCAN 匹配对应 key 前缀,统计匹配 key 数量(受
--max-scan上限约束); - TTL 分布:对采样 key 统计
no-expire-or-expired/0-1h/1h-1d/1d-30d/>30d; - 近期活跃 key:从采样中选出
OBJECT IDLETIME <= 600s的 key,用于判断是否有新写入; - 样本 key 与 value 预览:对 embedding 显示 ndarray 信息,对 translation 显示译文片段。
适用场景:日常查看两类缓存是否在增长、TTL 是否合理、是否有近期写入;与「缓存总览表」中的 key 设计一一对应。
用法示例:
# 默认:检查 embedding / translation 两类
python scripts/redis/redis_cache_health_check.py
# 只检查某一类
python scripts/redis/redis_cache_health_check.py --type embedding
python scripts/redis/redis_cache_health_check.py --type translation
# 按自定义 pattern 检查(不按业务类型)
python scripts/redis/redis_cache_health_check.py --pattern "mycache:*"
# 调整采样与扫描规模
python scripts/redis/redis_cache_health_check.py --sample-size 100 --max-scan 50000 --db 0
常用参数:
| 参数 | 说明 | 默认 |
|---|---|---|
--type |
缓存类型:embedding / translation,可多选 |
两类都检查 |
--pattern |
自定义 key pattern(如 mycache:*),指定后忽略 --type |
- |
--db |
Redis 数据库编号 | 0 |
--sample-size |
每类采样的 key 数量 | 50 |
--max-scan |
每类最多 SCAN 的 key 数量上限 | 20000 |
5.2 redis_cache_prefix_stats.py(按前缀统计条数与内存)
功能:全局视角,扫描当前 DB 下所有 key,按 key 的前缀(第一个冒号前)分类,统计每类 key 的条数与内存占用量(含占比),并输出每类示例 key 与 Redis 总内存信息。
- 使用
SCAN扫全库,按前缀聚合; - 内存优先用 Redis
MEMORY USAGE,不可用时用 key+value 长度估算;key 过多时按--sample-size采样后按均值推算总内存; - 输出:前缀、条数、内存及计算方式、占比、简要说明(如「翻译缓存」「向量化缓存」);末尾附 Redis 总内存与 maxmemory(若配置)。
适用场景:了解「哪一类前缀占了多少条、多少内存」,做容量规划或清理决策;支持多 DB(--all-db)或指定前缀(--prefix)缩小范围。
用法示例:
# 默认 DB 0,全库按前缀统计
python scripts/redis/redis_cache_prefix_stats.py
# 统计所有有数据的 DB
python scripts/redis/redis_cache_prefix_stats.py --all-db
# 指定 DB
python scripts/redis/redis_cache_prefix_stats.py --db 1
# 只统计指定前缀(可多个)
python scripts/redis/redis_cache_prefix_stats.py --prefix trans embedding
# 全 DB + 指定前缀
python scripts/redis/redis_cache_prefix_stats.py --all-db --prefix trans embedding
常用参数:
| 参数 | 说明 | 默认 |
|---|---|---|
--prefix |
只统计这些前缀下的 key(如 trans embedding) |
全库按前缀统计 |
--db |
数据库编号(0–15) | 0 |
--all-db |
对所有有数据的 DB 分别执行 | 否 |
--sample-size |
单前缀 key 过多时,用于内存采样的数量 | 100 |
--real |
对单前缀 key 数 ≤10000 时计算全部 key 真实内存(较慢) | 否 |
5.3 redis_memory_heavy_keys.py(大 key / 内存占用排查)
功能:找出当前 DB 中占用内存最多的 key,并分析「按 key 估算的总内存」与「Redis 实际使用内存」的差异原因。
- 全库
SCAN获取 key 列表,对 key 采样(默认最多 1000)调用MEMORY USAGE(或估算)得到单 key 内存; - 按内存排序,输出占用最高的 N 个 key(
--top,默认 50); - 输出:前缀分布、采样统计、估算总内存 vs 实际内存、差异说明(碎片、内部结构等);并检测超大 value(>1MB)、key 类型分布。
适用场景:内存异常升高时定位大 key;理解为何「按 key 加总」与 used_memory 不一致。
用法示例:
# 显示占用内存最多的 50 个 key(默认)
python scripts/redis/redis_memory_heavy_keys.py
# 显示前 100 个
python scripts/redis/redis_memory_heavy_keys.py --top 100
常用参数:
| 参数 | 说明 | 默认 |
|---|---|---|
--top |
显示内存占用最高的 N 个 key | 50 |
5.4 三个脚本的选用建议
| 需求 | 推荐脚本 |
|---|---|
| 看两类业务缓存(embedding/translation)的数量、TTL、近期写入、样本 value | redis_cache_health_check.py |
| 看全库或某前缀的 key 条数与内存占比 | redis_cache_prefix_stats.py |
| 找占用内存最多的大 key、分析内存差异 | redis_memory_heavy_keys.py |
6. 其他 Redis 相关代码(测试与替身)
除上述运维脚本外,以下代码使用 Redis 或其替身,但不定义新的业务缓存 key 协议:
- 单元测试:
tests/test_embedding_pipeline.py中的_FakeRedis用于 mock embedding 缓存;tests/test_translator_failure_semantics.py中的_RecordingRedis用于验证翻译失败时不写缓存。
7. 添加新缓存时的建议
新增 Redis 缓存时,建议遵循以下约定:
- 配置集中:
- key 前缀、TTL 优先放在
config/env_config.py(通用)或config/config.yaml -> services.<capability>.cache; - 避免在业务代码中硬编码 TTL。
- key 前缀、TTL 优先放在
- 命名规范:
- 统一使用
prefix:维度1:维度2:...的扁平 key 结构; - 对长文本/value 使用
md5/sha256做哈希,避免过长 key。
- 统一使用
- 文档同步:
- 新增缓存后,应在本文件中补充一行总览表 + 详细小节;
- 若缓存与外部系统/历史实现兼容(如 Java 侧翻译缓存),需在说明中显式标注。