## 缓存与 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”) - **翻译缓存 TTL & 前缀**:`REDIS_TRANSLATION_CACHE_EXPIRE_DAYS`、`REDIS_TRANSLATION_CACHE_PREFIX` --- ## 1. 缓存总览表 | 模块 / 场景 | Key 模板 | Value 内容示例 | 过期策略 | 备注 | |------------|----------|----------------|----------|------| | 文本向量缓存(embedding) | `embedding:{language}:{norm_flag}:{query}` | `pickle.dumps(np.ndarray)`,如 1024 维 BGE 向量 | TTL=`REDIS_CONFIG["cache_expire_days"]` 天;访问时滑动过期 | 见 `embeddings/text_encoder.py` | | 翻译结果缓存(Qwen-MT 翻译) | `{cache_prefix}:{model}:{src}:{tgt}:{sha256(payload)}` | 机翻后的单条字符串 | TTL=`services.translation.cache.ttl_seconds` 秒;可配置滑动过期 | 见 `query/qwen_mt_translate.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/text_encoder.py` 中 `TextEmbeddingEncoder` - **用途**:缓存调用向量服务(6005)的文本向量结果,避免重复计算。 ### 2.1 Key 设计 - 函数:`_get_cache_key(query: str, language: str, normalize_embeddings: bool) -> str` - 模板: ```text embedding:{language}:{norm_flag}:{query} ``` - 字段说明: - `language`:当前实现中统一传入 `"generic"`; - `norm_flag`:`"norm1"` 表示归一化向量,`"norm0"` 表示未归一化; - `query`:原始文本(未做哈希),注意长度特别长的 query 会直接出现在 key 中。 ### 2.2 Value 与类型 - 类型:`pickle.dumps(np.ndarray)`,在读取时通过 `pickle.loads` 还原为 `np.ndarray`。 - 典型示例:BGE-M3 1024 维 `float32` 向量。 ### 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); - 回退为重新调用向量服务。 --- ## 3. 翻译结果缓存(query/qwen_mt_translate.py) - **代码位置**:`query/qwen_mt_translate.py` 中 `Translator` 类 - **用途**:缓存 Qwen-MT 翻译(及 translator service 复用的翻译)结果,减少云端请求,遵守限速。 - **配置入口**:`config/config.yaml -> services.translation.cache`,统一由 `config/services_config.get_translation_cache_config()` 解析。 ### 3.1 Key 设计 - 内部构造函数:`_build_cache_key(...)` - 模板: ```text {cache_prefix}:{model}:{src}:{tgt}:{sha256(payload)} ``` 其中: - `cache_prefix`:来自 `services.translation.cache.key_prefix`,默认 `trans:v2`; - `model`:如 `"qwen-mt"`; - `src`:源语言(如 `zh` / `en` / `auto`),是否包含在 key 中由 `key_include_source_lang` 控制; - `tgt`:目标语言,如 `en` / `zh`; - `sha256(payload)`:对以下内容整体做 SHA-256: - `model` - `src` / `tgt` - `context`(受 `key_include_context` 控制) - `prompt`(受 `key_include_prompt` 控制) - 原始 `text` > 注意:所有 key 设计集中在 `_build_cache_key`,**不要在其他位置手动拼翻译缓存 key**。 ### 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()` 解析: ```yaml services: translation: cache: enabled: true key_prefix: "trans:v2" ttl_seconds: 62208000 # 默认约 720 天 sliding_expiration: true key_include_context: true key_include_prompt: true key_include_source_lang: true ``` - 运行时行为: - 创建 `Translator` 时,从 `cache_cfg` 读取: - `self.cache_prefix` - `self.expire_seconds` - `self.cache_sliding_expiration` - `self.cache_include_*` 一系列布尔开关; - **读缓存**: - 命中后,若 `sliding_expiration=True`,会调用 `redis.expire(key, expire_seconds)`; - **写缓存**: - 使用 `redis.setex(key, expire_seconds, translation)`。 ### 3.4 关联模块 - `api/translator_app.py` 会通过 `query.qwen_mt_translate.Translator` 复用同一套缓存逻辑; - 文档说明:`docs/翻译模块说明.md` 中提到“推荐通过 Redis 翻译缓存复用结果”。 --- ## 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_anchor_cache_key(title, target_lang, tenant_id)` - 模板: ```text {ANCHOR_CACHE_PREFIX}:{tenant_or_global}:{target_lang}:{md5(title)} ``` - 字段说明: - `ANCHOR_CACHE_PREFIX`:默认 `"product_anchors"`,可通过 `.env` 中的 `REDIS_ANCHOR_CACHE_PREFIX`(若存在)间接配置到 `REDIS_CONFIG`; - `tenant_or_global`:`tenant_id` 去空白后的字符串,若为空则使用 `"global"`; - `target_lang`:内容理解输出语言,例如 `zh`; - `md5(title)`:对原始商品标题(UTF-8)做 MD5。 ### 4.2 Value 与类型 - 类型:`json.dumps(dict, ensure_ascii=False)`。 - 典型结构(简化): ```json { "id": "123", "lang": "zh", "title_input": "原始标题", "title": "归一化后的商品标题", "category_path": "...", "tags": "...", "target_audience": "...", "usage_scene": "...", "anchor_text": "..., ..." } ``` - 读取时通过 `json.loads(raw)` 还原为 `Dict[str, Any]`。 ### 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.py` 的 `REDIS_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 设计一一对应。 **用法示例**: ```bash # 默认:检查 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`)缩小范围。 **用法示例**: ```bash # 默认 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` 不一致。 **用法示例**: ```bash # 显示占用内存最多的 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..cache`; - 避免在业务代码中硬编码 TTL。 - **命名规范**: - 统一使用 `prefix:维度1:维度2:...` 的扁平 key 结构; - 对长文本/value 使用 `md5`/`sha256` 做哈希,避免过长 key。 - **文档同步**: - 新增缓存后,应在本文件中补充一行总览表 + 详细小节; - 若缓存与外部系统/历史实现兼容(如 Java 侧翻译缓存),需在说明中显式标注。