缓存与Redis使用说明.md 17.5 KB

缓存与 Redis 使用说明

本仓库中 Redis 主要用于性能型缓存,当前落地的业务缓存包括:

  • 文本向量缓存(embedding 缓存)
  • 翻译结果缓存(Qwen-MT 等机器翻译)
  • 商品内容理解缓存(锚文本 / 语义属性 / 标签)

底层连接配置统一来自 config/env_config.pyREDIS_CONFIG

  • Host/PortREDIS_HOST / REDIS_PORT(默认 localhost:6479
  • PasswordREDIS_PASSWORD
  • Socket & 超时REDIS_SOCKET_TIMEOUT / REDIS_SOCKET_CONNECT_TIMEOUT / REDIS_RETRY_ON_TIMEOUT
  • 通用缓存 TTLREDIS_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
商品内容理解缓存(anchors / 语义属性 / tags) {ANCHOR_CACHE_PREFIX}:{tenant_or_global}:{target_lang}:{md5(title)} json.dumps(dict),包含 id/title/category/tags/anchor_text 等 TTL=ANCHOR_CACHE_EXPIRE_DAYS indexer/product_enrich.py

下面按模块详细说明。


2. 向量缓存(embeddings/text_encoder.py / embeddings/image_encoder.py / embeddings/server.py)

  • 代码位置
    • 调用侧:embeddings/text_encoder.pyembeddings/image_encoder.py
    • 服务侧:embeddings/server.py
    • 公共 Redis/BF16 编解码:embeddings/redis_embedding_cache.pyembeddings/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=truenormalize=false 命中同一条缓存;
    • 服务端也启用同一套 Redis 向量缓存,而不是只有 client 侧缓存;
    • 拆分 text / image 服务 后,图片压力不会再拖慢文本服务;
    • 服务侧 full-cache-hit 可直接返回,不会再进入模型限流槽位。

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,会:
    • 直接丢弃该缓存(并尝试 delete key);
    • 回退为重新调用向量服务。

2.5 服务拆分与缓存/限流关系

  • 当前默认部署形态:
    • 文本 embedding 服务:6005
    • 图片 embedding 服务:6008
  • 调用方配置:
    • EMBEDDING_TEXT_SERVICE_URL
    • EMBEDDING_IMAGE_SERVICE_URL
  • 服务端配置:
    • TEXT_MAX_INFLIGHT
    • IMAGE_MAX_INFLIGHT
    • EMBEDDING_SERVICE_KIND=all|text|image
  • 关键行为:
    • full-cache-hit 请求在服务端会直接返回,不占用 TEXT_MAX_INFLIGHT / IMAGE_MAX_INFLIGHT
    • cache miss 才会进入对应模型 lane;
    • 因此高频重复图片请求不会再因为严格的图片限流而大量阻塞。

3. 翻译结果缓存(translation/service.py)

  • 代码位置translation/service.py
  • 用途:统一缓存所有 translation capability 的翻译结果。
  • 配置入口
    • config/config.yaml -> services.translation.cache
    • config/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-mtllmopus-mt-zh-en
  • target_lang:目标语言,如 en / zh
  • source_text[:4]:原文前 4 个字符
  • sha256(source_text):对完整原文做 SHA-256

3.2 Value 与类型

  • 类型:UTF-8 字符串,即翻译后的文本结果。
  • 存取逻辑:
    • 读取:redis.get(key) 返回 strNone
    • 写入: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. 商品内容理解缓存(indexer/product_enrich.py)

  • 代码位置indexer/product_enrich.py
  • 用途:在生成商品锚文本(qanchors)、语义属性、标签等内容理解结果时复用缓存,避免对同一标题重复调用大模型。

4.1 Key 设计

  • 配置项:
    • ANCHOR_CACHE_PREFIX = REDIS_CONFIG.get("anchor_cache_prefix", "product_anchors")
    • ANCHOR_CACHE_EXPIRE_DAYS = int(REDIS_CONFIG.get("anchor_cache_expire_days", 30))
  • Key 构造函数:_make_analysis_cache_key(product, target_lang, analysis_kind)
  • 模板:
{ANCHOR_CACHE_PREFIX}:{analysis_kind}:{prompt_contract_hash}:{target_lang}:{prompt_input_prefix}{md5(prompt_input)}
  • 字段说明:
    • ANCHOR_CACHE_PREFIX:默认 "product_anchors",可通过 .env 中的 REDIS_ANCHOR_CACHE_PREFIX(若存在)间接配置到 REDIS_CONFIG
    • analysis_kind:分析族,目前至少包括 contenttaxonomy,两者缓存隔离;
    • prompt_contract_hash:基于 system prompt、shared instruction、localized headers、result fields、user instruction template、schema cache version 等生成的短 hash;只要提示词或输出契约变化,缓存会自动失效;
    • target_lang:内容理解输出语言,例如 zh
    • prompt_input_prefix + md5(prompt_input):对真正送入 prompt 的商品文本做前缀 + MD5;当前 prompt 输入来自 titlebriefdescription 的规范化拼接结果。

设计原则:

  • 只让实际影响 LLM 输出的输入参与 key;
  • 不让 tenant_idspu_id 这类“结果归属信息”污染缓存;
  • prompt 或 schema 变更时,不依赖人工清理 Redis,也能自然切换到新 key。

4.2 Value 与类型

  • 类型:json.dumps(dict, ensure_ascii=False)
  • 典型结构(简化):
{
  "id": "123",
  "lang": "zh",
  "title_input": "原始标题",
  "title": "归一化后的商品标题",
  "category_path": "...",
  "tags": "...",
  "target_audience": "...",
  "usage_scene": "...",
  "anchor_text": "..., ..."
}
  • 读取时通过 json.loads(raw) 还原为 Dict[str, Any]
  • contenttaxonomy 的 value 结构会随各自 schema 不同而不同,但都会先通过统一的 normalize 逻辑再写缓存。

4.3 过期策略

  • TTL:ttl = ANCHOR_CACHE_EXPIRE_DAYS * 24 * 3600 秒(默认 30 天);
  • 写入:redis.setex(key, ttl, json.dumps(result, ensure_ascii=False))
  • 读取:仅做 redis.get(key)不做滑动过期

4.4 调用流程中的位置

  • 单条调用(索引阶段常见)时,analyze_products() 会先尝试命中缓存:
    • 若命中,直接返回缓存结果;
    • 若 miss,调用 LLM,解析结果后再写入缓存。

5. Redis 运维脚本工具

scripts/redis/ 下提供三个脚本,用于查看缓存数量、内存占用与健康状态。连接配置均来自 config/env_config.pyREDIS_CONFIG,运行前需在项目根目录执行(或保证 PYTHONPATH 含项目根),以便加载配置。

5.1 redis_cache_health_check.py(缓存健康巡检)

功能:按业务缓存类型(embedding / translation / anchors)做健康巡检,不扫全库。

  • 对每类缓存: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 显示译文片段,对 anchors 显示 JSON 摘要。

适用场景:日常查看三类缓存是否在增长、TTL 是否合理、是否有近期写入;与「缓存总览表」中的 key 设计一一对应。

用法示例

# 默认:检查 embedding / translation / anchors 三类
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 anchors

# 按自定义 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 / anchors,可多选 三类都检查
--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 product_anchors

# 全 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/anchors)的数量、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。
  • 命名规范
    • 统一使用 prefix:维度1:维度2:... 的扁平 key 结构;
    • 对长文本/value 使用 md5/sha256 做哈希,避免过长 key。
  • 文档同步
    • 新增缓存后,应在本文件中补充一行总览表 + 详细小节;
    • 若缓存与外部系统/历史实现兼容(如 Java 侧翻译缓存),需在说明中显式标注。