本文整理 DGJ2 商品/物料搜索的 MySQL、MongoDB、Elasticsearch、Redis 和前端增量缓存链路。核心原则:数据库有商品,不代表当前站点的搜索副本有商品;搜索副本有商品,也不代表它具备价格、库存和业务可用性。

适用于商品搜索不到、搜索结果重复、价格/库存/限购展示旧、Mongo 有而 ES 无、ES 开关切换后结果变化、全量/增量任务卡住和前端商品缓存不更新等问题。

1. 业务目标

  • 从一个 sid、SKU、invId 和关键字确定请求实际使用的搜索引擎。
  • 区分全局物料、站点物料、基础字段、站点扩展字段和实时库存。
  • 解释 MySQL、Mongo、ES、Redis、前端本地副本之间的同步关系。
  • 找出数据在哪一层首次出现差异,而不是直接重建所有缓存。
  • 正确处理全量重建、增量水位、任务锁、批量写入和失败重试。
  • 修复后同时验证搜索、精确查询、采购、销售、商城和活动场景。

2. 五层数据模型

flowchart LR
    DB["MySQL业务事实\nGoods/Ext/Price/Inventory"] --> BUILD["缓存构建/消息同步"]
    BUILD --> MONGO["Mongo站点物料集合"]
    BUILD --> ES["ES全局/站点索引"]
    BUILD --> REDIS["Redis辅助集合/水位/开关"]
    MONGO --> API["搜索与业务查询"]
    ES --> API
    REDIS --> API
    MONGO --> FRONT["PC前端增量物料缓存"]
    API --> PAGE["采购/销售/商城/活动页面"]
层回答的问题不能替代
MySQL业务事实是否正确搜索副本是否已同步
Mongo当前站点物料副本是否存在ES 查询是否命中
ES分词/模糊搜索和排序结果实时库存最终事实
Redis开关、集合、水位、任务锁完整物料文档
前端增量客户端何时拿到变化服务端最新状态

3. 核心代码地图

职责文件核心方法
Redis 客户端application/Components/Cache/KzCache.phpgetInstance
Mongo 客户端application/Components/Cache/MongoCache.phpgetInstance
ES 客户端application/Components/ElasticSearchFactory.phpmake
ES 通用封装application/Services/Cache/EsSer.phplist/upsert/batchHandle
Redis 基础物料application/Services/Materiels/MaterielCacheSer.phpsave/getMaterielCacheBySkuId
Mongo 站点物料application/Services/Materiels/MaterielCacheStationSer.phpgoodsList/findSaMaterielList
ES 站点物料application/Services/Materiels/MaterielCacheStationEsSer.phplist/findSaMaterielList
搜索引擎切换application/Services/Materiels/MaterielCacheQuerySwitchSer.phpsetSid/querySaMaterielList
批量缓存更新application/Services/Materiels/MaterielCacheBatchSer.phpbatchUpdateBySkuIds
商品中心通知application/Services/Notify/MaterielNotifySer.php价格/限价同步
Mongo 构建任务application/controllers/tasks/Goods.php多种增量/库存更新
ES 构建任务application/controllers/tasks/GoodsEsCache.php全量/增量/站点库存同步
辅助集合刷新application/controllers/tasks/GoodsCacheFlush.php限购/有价格 SKU
缓存管理application/controllers/inner/CacheManage.php白名单/开关维护
通用物料搜索application/controllers/inner/GoodsSearch.phplist

4. 数据载荷

4.1 基础字段

Mongo/ES 物料副本包含:

  • id/sid/skuId
  • 名称、标题、规格、车型、品牌、分类
  • 单位、包装、条码、产品码、OE 码
  • 状态、采购状态、销售模式
  • 采购价、销售价、最低价
  • 税率、保质期、属性等

4.2 站点扩展字段

字段业务含义
qty站点库存汇总副本
fineQty特定库区/精品库存口径
disable站点是否停用物料
description站点描述
buyLimit是否限制采购
noPricingFlag是否没有价格
is_hot限价/热点类标识
modiflyTime基础/扩展变化时间戳(历史拼写)
modiflyQtyTime库存变化时间戳

搜索副本的 qty 是展示和筛选副本,不应代替 SCM_INVENTORY_REAL_TIME 做最终库存修复依据。

5. Mongo 集合命名

MaterielCacheStationSer::setSid 使用逻辑集合:

KzEnums::GOODS_LIST_MONGODE_COLLECTION_SID + sid

每个服务站有独立集合。集合内既可包含快准商品,也可包含站点第三方商品,具体用文档的 sid、id 和状态区分。

Mongo 客户端当前使用 secondaryPreferred,只读查询可能命中从节点,因此刚写入后短时间内出现读延迟时要考虑复制滞后。

6. ES 索引命名

MaterielCacheStationEsSer::setSid 的规则:

sid索引含义
0dgj_goods_kz快准物料全局主索引
非 0dgj_goods_{sid}服务站独立物料索引

文档 type 为 goods_info。代码带 type 参数,说明兼容的是较老 ES API;升级 ES 客户端或集群前必须验证 mapping/type 兼容。

flowchart TD
    GOODS["快准/第三方商品"] --> MAIN["dgj_goods_kz"]
    MAIN --> S1["dgj_goods_sidA"]
    MAIN --> S2["dgj_goods_sidB"]
    INV["实时库存/站点价格/限制"] --> S1
    INV --> S2

7. Redis 角色

Redis 不只做结果缓存,还承担开关、集合、同步水位和任务锁。

逻辑键作用
KEY_IS_OPEN_ES是否开启 ES 写入/搜索总开关
HASH_ES_SEARCH_BLACKLIST指定站点关闭 ES 搜索
HASH_ES_SEARCH_WHITELIST历史 ES 搜索白名单配置
KEY_GOODS_UPDATE_TIME商品缓存更新时间
KEY_GOODS_SID_UPDATE_TIMEMongo 站点商品同步水位
KEY_GOODS_QTY_SID_UPDATE_TIMEMongo 站点库存同步水位
KEY_GOODS_ES_UPDATE_TIMEES 主索引增量水位
KEY_GOODS_QTY_SID_ES_UPDATE_TIMEES 站点库存增量水位
KEY_EXIST_SYNC_GOODS_TASKMongo/商品同步任务锁
KEY_EXIST_SYNC_GOODS_ES_TASKES 同步任务锁
KEY_SYNC_GOODS_*_ERROR_TIMES上次任务未结束计数
KEY_BUY_LIMIT_SKUIDS:{sid}限购 SKU 逗号集合
KEY_HAS_PRICE_SKUIDS:{sid}有价格 SKU 逗号集合
HASH_MATERIEL_SKUID:{sku}Redis 物料基础 Hash
MATERIEL_ALL_UPDATE_LIMIT*前端全量物料更新限流

这些是逻辑 key;连接层和枚举都可能带前缀,排查时不要手工重复拼接。

8. 搜索引擎切换

销售开单通过 MaterielCacheQuerySwitchSer::setSid 选择引擎:

flowchart TD
    REQ["销售商品搜索 sid"] --> OPEN{"KEY_IS_OPEN_ES有值?"}
    OPEN -- 否 --> M["Mongo MaterielCacheQuerySer"]
    OPEN -- 是 --> BLACK{"sid在ES搜索黑名单?"}
    BLACK -- 是 --> M
    BLACK -- 否 --> E["ES MaterielCacheQueryEsSer"]
    M --> OUT["count + list"]
    E --> OUT

注意:当前切换逻辑直接使用总开关和黑名单,未读取 HASH_ES_SEARCH_WHITELIST。看到白名单 key 不代表该调用链实际使用它。

9. Mongo 搜索规则

销售搜索 findSaMaterielList:

  1. 关键字通过 RegexUtil::getQuoteRegex 转安全正则。
  2. 匹配预拼接的 search 字段。
  3. 要求 disable='0'。
  4. 可限制微仓 SKU 或 invId。
  5. 只允许正常、临时供给、停止供给、新品等状态集合。
  6. 按 page/limit 做 skip/limit。

Mongo search 字段由名称、编号、品牌、分类、规格、SKU、车型、产品码、拼音、OE 等拼接,最多保留 300 字符。长字段尾部被截断时,某些关键词可能无法命中。

10. ES 搜索规则

MaterielCacheStationEsSer::list 根据关键词类型分两支。

10.1 英文、数字、斜杠类

  • multi_match 搜索全部搜索字段,minimum_should_match=100%。
  • 对 SKU、number、productCode、def、spec、name 增加 wildcard。
  • 不含 / 时使用 *keyword*。
  • 含 / 时按 / 分段,每段都必须匹配。
  • 外层 should 至少命中 1 个。

10.2 中文/分词类

  • 先用 ik_max_word analyzer 分词。
  • token 超过 2 个时 minimum_should_match=90%。
  • 否则要求 100%。
  • 使用 multi_match 的 must 条件。
flowchart TD
    K["search keyword"] --> ASCII{"仅英文/数字/斜杠?"}
    ASCII -- 是 --> MM["multi_match 100%"]
    MM --> WC["关键字段wildcard"]
    ASCII -- 否 --> IK["ik_max_word分词"]
    IK --> TOK{"token > 2?"}
    TOK -- 是 --> P90["minimum_should_match 90%"]
    TOK -- 否 --> P100["minimum_should_match 100%"]
    WC --> FILTER["状态/disable/站点条件"]
    P90 --> FILTER
    P100 --> FILTER

11. Mongo 与 ES 结果差异来源

维度MongoES
关键字拼接字段正则IK 分词 + multi_match + wildcard
字段长度search 最多 300 字符各字段独立索引
大小写正则配置决定搜索前转小写、analyzer/mapping 决定
斜杠Regex 整体匹配分段 wildcard 且要求全部段
排序Mongo sortES sort/relevance
一致性副本读可能有延迟refresh 与增量任务延迟
字段类型保存时大量转 stringmapping 决定 keyword/text/numeric

切换引擎后排序、模糊程度和总数可能变化。上线不能只验证“有结果”,还要验证业务期望的前 N 条排序。

12. 精确查询与全文搜索

查询推荐定位
按 invIdMongo findMateriel / ES findById / MySQL
按多个 invIdfindMaterielByInvIds
按多个 SKUfindMaterielBySkuIds
按商品编号GoodsSearch::getGoodsListByNumber 或业务库
关键字销售搜索QuerySwitch -> ES/Mongo
品牌分类聚合ES collapse 或业务表

精确查询能命中而全文搜索不命中,通常是搜索字段、mapping、analyzer、状态过滤或 disable 问题;两者都不命中才优先怀疑副本缺记录。

13. 物料写入 Mongo

saveMaterielToMongo:

  1. 要求 id 非空。
  2. 只保留定义过的基础、搜索和扩展字段。
  3. 数量/限制字段转 int,其余多数字段转 string。
  4. 拼接 search 字段。
  5. 写 modiflyTime。
  6. 不存在则 insert,存在则 $set update。
  7. 站点 sid 非 0 时更新站点商品更新时间。
sequenceDiagram
    participant B as 业务变更
    participant S as MaterielCacheStationSer
    participant M as Mongo集合
    participant T as GoodsService更新时间
    B->>S: sid + materiel
    S->>S: 过滤字段/转类型/拼search
    S->>M: findOne(id)
    alt 不存在
        S->>M: insertOne
    else 已存在
        S->>M: updateOne $set
    end
    S->>T: updateTime(sid)

14. 库存更新时间分离

Mongo 更新方法区分:

  • 普通字段:更新 modiflyTime。
  • 库存字段:传 is_update_qty=true 时更新 modiflyQtyTime。

前端增量同步和后台增量任务可能分别依赖这两个水位。把库存变化误写成普通变化,或反过来,会导致增量列表和全量阈值行为不一致。

15. 物料写入 ES

ES 支持:

  • 单文档 upsert/update/delete。
  • 条件 update/delete。
  • bulk create/update/delete/index。
  • 索引创建、删除、mapping/settings。
  • scroll 分页查询。

MaterielCacheStationEsSer 的 bulk body 阈值为 2000 个数组元素;一个 ES bulk 文档通常包含“动作行+数据行”,所以 2000 元素并不等于 2000 个商品。

16. ES mapping

mapping 必须与查询写法匹配:

  • skuId.keyword 等精确字段要存在 keyword 子字段。
  • name/brand/category 等全文字段要配置 analyzer。
  • qty/fineQty 若要数值排序必须是 numeric。
  • status/disable 在当前代码中常以 string term 查询。
  • 新增 oe_code/product_model/item_attribute 等字段需同步 mapping 和构建字段。

只更新 PHP 字段列表,不更新线上 mapping,可能导致 bulk 拒绝、字段动态映射错误或查询不命中。

17. Mongo 索引

GoodsCache::addMongoIndex 当前示例为每站集合创建 skuId:1 索引,但方法循环中存在历史 die,一次运行可能只处理首个站点。批量建索引前应检查实际代码和已存在索引,不要假设任务覆盖全部站点。

推荐按真实查询核对:

id 唯一/普通索引
skuId 索引
status + disable 组合
modiflyTime
modiflyQtyTime
search 正则是否可利用索引(通常有限)

索引设计需以 explain 和集合规模验证。

18. Redis 物料基础 Hash

MaterielCacheSer 使用:

HASH_MATERIEL_SKUID + skuId

把物料数组写为 Redis Hash,读取时 HGETALL。此层是按 SKU 的基础信息快捷缓存,不等于站点 Mongo/ES 文档,也不包含完整站点价格库存语义。

19. 限购 SKU 集合

BuyLimitSkuCacheSer 把站点限购 SKU 数组用逗号拼接成一个 Redis String。

KEY_BUY_LIMIT_SKUIDS + sid = "sku1,sku2,..."

注意:flushBuyLimitSkuCache 只有数组非空时才 set。若新结果为空,旧 key 不会在该方法中主动删除,可能留下历史限购集合。排查“全部取消后仍限购”时必须验证这一分支。

20. 有价格 SKU 集合

HasPriceSkuCacheSer 同样用逗号 String 保存站点有价格 SKU。非空才 set,空集合也不会主动删除旧值。

GoodsCacheFlush::flushBuyLimitAndPriceCache 会从 Mongo 重新读取 buyLimit/noPricingFlag 后刷新两个集合。

flowchart LR
    ITEM["商品中心有价格SKU"] --> DIFF["与本地HasPrice集合求差"]
    MONGO["Mongo noPricingFlag"] --> DIFF
    DIFF --> UP1["批量更新Mongo"]
    DIFF --> UP2["条件更新ES"]
    DIFF --> UP3["刷新Redis SKU集合"]

21. 商品中心通知

MaterielNotifySer 处理价格和限价同步:

  1. 从商品中心查询目标 SKU 集合。
  2. 从 Redis 集合读取本地状态;为空时回查 Mongo。
  3. 计算新增和取消差集。
  4. 每 2000 SKU 组装 Mongo 批量更新。
  5. ES 总开关开启时更新 ES。
  6. 刷新 Redis 集合。

ES 更新失败会短暂重试,索引不存在时可被视为站点锁定而忽略;多次失败后告警。业务表/Mongo 成功但 ES 失败会形成暂时不一致。

22. 全量与增量同步

模式来源目标适用
主商品全量Goods/MySQL全局 Mongo/ES首次构建、字段大变更
主商品增量modifyTime >= 水位全局及受影响站点日常商品变更
站点库存增量实时库存变化站点 Mongo/ES qty日常库存变化
价格/限制消息商品中心事件Mongo/ES/Redis实时业务标识
前端增量Mongo modiflyTimePC 本地物料客户端刷新

23. 增量水位

正确增量步骤:

sequenceDiagram
    participant R as Redis水位
    participant T as 同步任务
    participant DB as MySQL
    participant C as Mongo/ES
    T->>R: 读取last_success_time
    T->>T: 固定本次截止时间end_time
    T->>DB: 查询(last_success_time, end_time]
    loop 分批
        T->>C: upsert/delete
        C-->>T: 批量结果
    end
    T->>R: 全部成功后写end_time

水位不能在批处理开始时提前推进,否则中途失败会永久漏数据。需要从代码和日志确认各任务实际写水位时机。

24. ES 任务锁

GoodsEsCache::syncBackupGoodsIdsBySid:

  1. 检查 ES 是否开启。
  2. 查 KEY_EXIST_SYNC_GOODS_ES_TASK。
  3. 已锁时增加错误次数。
  4. 连续超过阈值会清锁、重置计数并告警。
  5. 未锁则 set 锁。
  6. 执行同步。
  7. 完成后 del 锁。

风险:进程崩溃时锁没有显式 TTL,需要靠后续连续检测清理;直接删锁可能导致上一任务仍运行时并发同步。

25. ES 主索引增量

flushBackupGoodsInfo 的主要逻辑:

  • 读取 KEY_GOODS_ES_UPDATE_TIME,无水位时要求先完成基础构建。
  • 全量模式可删除主索引后重建。
  • 增量模式按 Goods modifyTime 查询。
  • 主索引 sid=0,站点索引按库存流水和第三方商品更新。
  • 快准主商品在主索引中站点字段置默认值。
  • 删除商品会同步删除受影响站点文档。
  • 每批组装 bulk body 后提交。

全量删除索引是高风险动作,必须确认重建任务可完成、搜索可降级到 Mongo,并准备失败恢复。

26. 前端增量物料

MaterielFrontCacheSer::getChangeByTime:

  1. 按 modiflyTime > 客户端时间 查 Mongo 更新项。
  2. 普通站点阈值 2000;部分特殊站点阈值更低。
  3. 超过阈值时返回 updateAll=1,要求前端全量更新。
  4. 全量更新受 Redis 计数和超时限流。
  5. 再从 MySQL 查询已删除商品。
  6. 按快准/第三方拆分 update/delete。
flowchart TD
    REQ["客户端time"] --> Q["Mongo modiflyTime > time"]
    Q --> N{"变更数 > 阈值?"}
    N -- 否 --> DEL["MySQL补删除列表"]
    DEL --> INC["返回增量update/delete"]
    N -- 是 --> LIMIT{"全量更新限流允许?"}
    LIMIT -- 是 --> ALL["updateAll=1"]
    LIMIT -- 否 --> WAIT["暂不触发全量"]

27. 搜索 API

通用内部入口 inner/GoodsSearch::list 接收:

{
  "search": "刹车片",
  "isPage": 1,
  "page": 1,
  "limit": 20,
  "fields": "id,skuId,name,brandName",
  "orderBy": ""
}

该入口调用 MaterielSer::getMaterielBySearch,不一定等同于销售开单 QuerySwitch。排查时必须从实际 Controller/Service 追踪,不要仅凭接口名判断走 ES。

28. 业务可用性过滤

搜索文档存在后还可能被这些条件排除:

条件典型字段/来源
已删除isDelete
站点停用disable
供给状态status/buyStatus
采购限购buyLimit + Redis SKU 集合
无价格noPricingFlag + HasPrice 集合
库存不足qty/fineQty 或实时库存查询
微仓范围move_sku
指定商品范围invIds
活动状态活动商品表和活动时间,不在基础搜索副本内

“搜不到”和“搜到但不能下单”应分开记录。

29. 按 SKU 的排查事实卡

环境:
请求接口/页面:
request_id:
sid:
uid:
skuId:
invId:
关键字:
期望搜索引擎:
KEY_IS_OPEN_ES:
ES黑名单命中:
MySQL商品状态:
Mongo文档/更新时间:
ES文档/索引:
Redis限购/有价格集合:
实时库存:
最后同步任务时间:

30. MySQL 只读核对

SELECT id, sid, skuId, name, number, brandId, categoryId,
       status, buyStatus, isDelete, modifyTime
FROM t_bs_goods
WHERE skuId = :sku_id
ORDER BY sid, id;

扩展、价格和站点启停:

SELECT *
FROM t_bs_goods_ext
WHERE invId = :inv_id;

SELECT *
FROM t_bs_goods_purprice
WHERE sid = :sid AND invId = :inv_id;

SELECT *
FROM t_bs_goods_sto
WHERE sid = :sid AND invId = :inv_id;

字段名以目标环境 DDL 为准。生产排查禁止使用 SELECT * 导出大范围数据;示例仅表示关联方向。

31. Mongo 只读核对

使用受控客户端查询目标站点集合:

db.<station_goods_collection>.find(
  { skuId: "<SKU>" },
  {
    id: 1, sid: 1, skuId: 1, name: 1,
    status: 1, disable: 1, qty: 1, fineQty: 1,
    buyLimit: 1, noPricingFlag: 1,
    modiflyTime: 1, modiflyQtyTime: 1
  }
)

不要在公共文档记录 Mongo 地址、账号或密码。

32. ES 只读核对

目标索引按 sid 计算:

GET dgj_goods_<sid>/goods_info/<inv_id>

或按 SKU:

GET dgj_goods_<sid>/goods_info/_search
{
  "query": {
    "term": {
      "skuId.keyword": "<SKU>"
    }
  },
  "_source": [
    "id", "sid", "skuId", "name", "status", "disable",
    "qty", "fineQty", "buyLimit", "noPricingFlag", "modiflyTime"
  ]
}

具体 URL 和 type 语法以现网 ES 版本为准。

33. Redis 只读核对

只读检查逻辑:

GET KEY_IS_OPEN_ES
HGET HASH_ES_SEARCH_BLACKLIST <sid>
GET KEY_GOODS_ES_UPDATE_TIME
GET KEY_GOODS_QTY_SID_ES_UPDATE_TIME
GET KEY_EXIST_SYNC_GOODS_ES_TASK
GET KEY_SYNC_GOODS_ES_ERROR_TIMES
GET KEY_BUY_LIMIT_SKUIDS<sid>
GET KEY_HAS_PRICE_SKUIDS<sid>

逗号 String 很大时不要在共享终端完整打印,可先查长度或在安全脚本中判断目标 SKU 是否存在。

34. 常用代码搜索

# 从页面定位实际搜索实现
rg -n "querySaMaterielList|getMaterielBySearch|findSaMaterielList|GoodsSearch" application

# 查ES/Mongo开关
rg -n "KEY_IS_OPEN_ES|HASH_ES_SEARCH_BLACKLIST|HASH_ES_SEARCH_WHITELIST" application

# 查目标字段在哪些同步链更新
rg -n "noPricingFlag|buyLimit|is_hot|fineQty|modiflyQtyTime" application/Services application/controllers/tasks

# 查全量/增量任务和水位
rg -n "KEY_GOODS_.*UPDATE_TIME|syncBackupGoods|flushBackupGoods|updateTime" application/controllers/tasks application/Services

# 查缓存写入和失效
rg -n "updateMaterielToMongo|saveMaterielToMongo|updateByQuery|flush.*SkuCache" application

# 查搜索字段和mapping
rg -n "SEARCH_FIELDS|getMappings|putMappings|ik_max_word|minimum_should_match" application/Services/Materiels application/Services/Cache

35. 故障一:MySQL 有,Mongo/ES 都没有

flowchart TD
    A["DB有/副本都无"] --> B{"商品modifyTime晚于水位?"}
    B -- 否 --> R1["水位提前/历史漏同步"]
    B -- 是 --> C{"同步任务运行?"}
    C -- 否 --> R2["任务锁/调度/开关"]
    C -- 是 --> D{"商品状态被删除过滤?"}
    D -- 是 --> R3["确认应删除副本"]
    D -- 否 --> E{"bulk有错误?"}
    E -- 是 --> R4["按失败批次补偿"]
    E -- 否 --> R5["核对sid与索引/集合"]

不要先全量重建;先找出水位、任务、过滤或 bulk 的首个断点。

36. 故障二:Mongo 有,ES 没有

  1. 查 KEY_IS_OPEN_ES。
  2. 查站点是否存在 ES 索引。
  3. 查 mapping 是否接受目标字段。
  4. 查商品/价格/库存更新链是否只更新 Mongo。
  5. 查 ES 任务锁和错误计数。
  6. 查 bulk response 是否 errors=true,不能只看 HTTP 成功。
  7. 查目标站点是否因锁定而允许忽略 index not found。
  8. 先单文档补偿并验证,再决定是否重跑批次。

37. 故障三:ES 有,Mongo 没有

如果站点在 ES 黑名单或总开关关闭,请求会走 Mongo,于是用户搜不到。

检查:

  • Mongo 构建任务水位和锁。
  • 集合名是否为当前 sid。
  • 是否只执行过 ES 全量任务。
  • Mongo 文档是否被误删。
  • 商品中心通知是否只更新 ES 条件文档。
  • Mongo 从节点读是否暂时延迟。

38. 故障四:精确 SKU 能查,关键字搜不到

引擎首查
Mongosearch 拼接内容、300 字截断、Regex 转义
ESanalyzer token、mapping、keyword/text、minimum_should_match
共通status、disable、move_sku、invIds 过滤

用同一关键词直接调用 analyzer 和副本查询,确认是索引内容问题还是查询 DSL 问题。

39. 故障五:库存或价格展示旧

  1. 先查实时库存/价格业务表,确认事实值。
  2. 查 Mongo qty/fineQty/noPricingFlag。
  3. 查 ES 同字段。
  4. 查库存/价格增量水位。
  5. 查通知或任务日志。
  6. 查页面是否额外读取 Redis SKU 集合。
  7. 查前端是否仍使用旧增量时间。

库存修复只改搜索副本会再次被同步覆盖;必须先修业务事实或同步链。

40. 故障六:全部取消限购/价格后仍旧

当前两个 Redis 集合刷新方法在新集合为空时不会主动删除旧 key。

排查:

  1. Mongo 所有目标商品标识是否已清零。
  2. ES 是否已清零。
  3. Redis String 是否仍保留旧 SKU。
  4. 业务读取方优先用 Redis 还是 Mongo。
  5. 评估通过标准服务补充“空集合删除 key”的修复和回归。

不要手工删除全部站点缓存;先限定目标 sid 和逻辑 key。

41. 故障七:任务一直提示上次未结束

  1. 查任务进程是否仍存在。
  2. 查锁 key 是否有 TTL;当前 ES 锁逻辑主要靠错误次数清理。
  3. 查最后一批日志和水位。
  4. 查 ES/Mongo 是否仍有写入流量。
  5. 确认旧进程已停止后再清目标锁。
  6. 从上次成功水位重跑,不从当前时间跳过。

42. 故障八:全量重建后短时间搜索为空

全量 ES 可能先删索引再重建。窗口期内:

  • ES 查询可能 index not found 或返回空。
  • QuerySwitch 不会因查询异常自动切 Mongo,除非上层有兜底。
  • 站点不在黑名单仍会走 ES。

高风险全量任务应采用新索引构建完成后切 alias 的方式;当前代码是否支持 alias 需单独评估,不能在现网直接假设。

43. 修复策略

差异优先修复
DB 错修业务数据和正常业务链
DB 对、Mongo 错目标站点/商品定向重建 Mongo
Mongo 对、ES 错定向 upsert ES,保留水位证据
副本对、Redis 集合错定向刷新/删除目标 key
服务端对、前端错推进增量时间或触发受控全量
大范围漏同步修水位/任务后按时间窗补偿

任何修复都要保留修复前后快照、目标数量、任务日志和验证结果。

44. 数据修复门槛

  • 已确认实际搜索引擎。
  • 已确认 sid、SKU、invId 的对应关系。
  • 已找出第一处不一致层。
  • 已固定增量起止水位。
  • 已统计影响商品数和站点数。
  • 已确认全量/增量任务不会并发覆盖。
  • 已准备降级搜索或维护窗口。
  • 已验证目标 mapping/索引/集合存在。

禁止:

  • 未确认旧任务状态直接删任务锁。
  • 先推进水位再补数据。
  • 为单商品问题删除整个 ES 索引。
  • 全库 FLUSHDB。
  • 只改搜索副本而不修业务事实。
  • 在公共工单粘贴连接凭证或完整缓存数据。

45. 回归矩阵

维度用例
引擎ES 开、ES 关、站点黑名单、索引不存在
关键词中文、SKU、编号、拼音、OE、带 /、大小写、特殊字符
查询精确 ID、多个 SKU、全文、品牌分类、空关键字
商品快准、第三方、正常、停用、删除、临采、停止供给、新品
站点有价格、无价格、限购、取消限购、库存 0/正/负
同步单条更新、批量更新、增量、全量、删除、任务中断重跑
一致性DB/Mongo/ES/Redis/前端五层比对
分页第一页、深分页、排序、总数一致性
性能wildcard、正则、批量 SKU、大站点索引
降级ES 异常时 Mongo 方案、恢复后结果对比

46. 改动风险分级

级别改动风险
高QuerySwitch/ES 总开关影响全站销售商品搜索
高ES mapping/全量删索引搜索中断或批量写失败
高增量水位/任务锁永久漏同步或并发重复写
高Mongo/ES 字段口径采购销售价格库存判断变化
中搜索 analyzer/DSL命中率和排序变化
中Redis 限购/有价格集合商品可采购/可售范围变化
中前端全量限流大量客户端同时拉全量
低单站单商品定向补偿仍需防止后续任务覆盖

47. 监控建议

最少监控:

  • ES/Mongo 查询成功率和 P95/P99。
  • ES index not found、bulk errors 数量。
  • 每次任务扫描数、成功数、删除数、失败数、耗时。
  • 当前水位距现在的延迟。
  • 任务锁持续时间和连续冲突次数。
  • DB 与 Mongo/ES 抽样文档差异。
  • 每站索引/集合文档数异常变化。
  • 前端 updateAll 触发和限流次数。

48. 证据来源

结论代码证据
ES/Mongo 切换条件MaterielCacheQuerySwitchSer::setSid
ES 索引命名MaterielCacheStationEsSer::setSid
ES IK/ASCII 搜索MaterielCacheStationEsSer::list
Mongo 拼接 searchMaterielCacheStationSer::formatCacheData
Mongo 搜索过滤MaterielCacheStationSer::findSaMaterielList
Mongo 从节点优先application/Components/Cache/MongoCache.php
Redis 前缀与连接KzCache.php、RedisKeys.php
ES 批量与 scrollapplication/Services/Cache/EsSer.php
价格标识三副本同步MaterielNotifySer::syncMaterielHasPriceBySids
2000 SKU Mongo 批量MaterielCacheBatchSer
空集合不清旧 keyBuyLimitSkuCacheSer/HasPriceSkuCacheSer
前端增量与全量限流MaterielFrontCacheSer::getChangeByTime
ES 任务锁与错误计数GoodsEsCache::syncBackupGoodsIdsBySid
Mongo 索引任务有历史 dieGoodsCache::addMongoIndex

49. 待环境确认项

  • 线上 ES 具体版本、mapping、分片副本、refresh interval 和 alias 使用情况。
  • 每个站点 Mongo 集合的真实索引、文档量和复制延迟。
  • Goods/库存任务的 crontab 频率、互斥方式和最近成功水位。
  • ES 查询异常是否在更上层自动降级 Mongo。
  • HASH_ES_SEARCH_WHITELIST 当前是否有其他入口仍在使用。
  • Redis 客户端配置前缀与枚举 dgj: 组合后的真实 key。
  • qty/fineQty 与实时库存各库区的线上口径。
  • 前端本地物料存储实现、增量时间保存和全量失败重试。
  • 搜索排序的产品验收标准和线上慢查询阈值。

以上环境项必须用只读检查、任务日志和真实请求验证后再补充,不将推测写成现网事实。

请求-日志-数据变更追踪卡

多入口请求链路

场景调用方与入口请求载荷/上下文Controller/ConsumerService/Provider汇合点最终业务事实
商品搜索PC/App/Inner GoodsSearchsid、关键词、品牌/车型、分页inner/GoodsSearch.phpMateriel query switch/ES ServiceSKU/物料 ID按开关走 ES、Mongo/Redis 或 DB
商品变更刷新商品中心事件/业务修改SKU、变更类型、更新时间Goods task/NotifyMaterielNotifySer、Cache ServiceSKU + update time缓存与索引追随商品主表
批量重建CLI/CacheManage站点、SKU 范围、批次GoodsEsCache/GoodsCacheFlush/CacheManageBatch/ES Servicetask batch批量清理或重建索引
库存展示搜索列表/商品详情sid、SKU、仓库查询入口Cache + SCM_INVENTORY_REAL_TIME库存四维键搜索结果附加当前可售库存

日志证据矩阵

| 链路段 | 日志来源 | 可检索锚点 | 成功信号 | 失败信号 | 与下一段关联方式 | | --- | --- | --- | --- | --- | --- | --- | | 查询路由 | QuerySwitch/GoodsSearch | request_id、sid、query、ES 开关/名单 | 明确记录或可代码判定数据源 | 环境开关判断错、无降级 | 同一请求在目标数据源检索 | | ES/缓存查询 | ES/Mongo/Redis Client | index/key、SKU、耗时 | 命中且数据版本合理 | timeout、mapping、反序列化失败 | SKU 与 DB 主表比较 | | 增量刷新 | Goods task/Notify | message ID、SKU、KEY_GOODS_UPDATE_TIME | 目标 key/doc 更新并 ACK | 消费失败、更新时间倒退 | SKU + update time 回查索引 | | 批量重建 | Task 日志 | task、批次、游标、成功失败数 | 总数闭合,可断点继续 | 清空后未重建、部分分片失败 | 批次 SKU 清单抽样校验 |

环节数据变更台账

步骤代码位置事务读取事实写入表/缓存/MQ字段或数量变化回查证据
OLTP 变更商品/库存 Service业务事务商品/实时库存旧值商品/库存表、通知 MQDB old -> new,生成更新时间SKU、表更新时间、消息键
选择查询源MaterielCacheQuerySwitchSer只读ES 开关、黑白名单、站点无请求路由 DB/cache/ESKEY_IS_OPEN_ES、名单成员、request ID
增量同步MaterielNotifySer/Goods task消费事务DB 新事实、消息版本Redis/Mongo/ESdoc/key old -> new;旧事件不得覆盖新版本SKU、update time、ACK
批量重建CacheBatch/EsSer分批外部写DB 分页结果index/cachedelete/reindex;每批成功数累计batch、游标、源/目标总数
读时合并GoodsSearch/InventoryQuery只读商品索引、实时库存无返回快照,不写业务表SKU 同时查 ES 文档和实时库存

子模块追踪:search-route Mongo、ES 与数据库查询路由

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
路由搜索/精确查询sid、query/SKU、engine switchapplication/Services/Materiels/MaterielCacheQuerySwitchSer.phpES/Mongo 开关、查询类型、降级条件查询只读 不写;选择 DB/Mongo/ESrequest ID + query + engine先确认实际路由,再比较对应数据源
降级引擎失败/空结果engine error、fallbackapplication/controllers/inner/GoodsSearch.php主库、索引水位、业务过滤查询只读 不写request ID + fallback result空结果区分索引缺失与过滤;合同待环境确认

子模块追踪:mongo-index 物料 Mongo 缓存与索引

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
同步全量/增量物料task、sid、SKU/invIdapplication/Services/Materiels/MaterielCacheSer.phpMySQL 商品/站点扩展与旧文档事务外 upsert old doc -> source snapshottask + collection + SKU + count失败键保留,不推进其水位
核对MySQL 有而 Mongo 无sid、SKU、collectionapplication/Components/Cache/MongoCache.php源行、文档、索引、updatedAt查询只读 不写SKU + source/cache time只重建缺失 SKU;集合环境待确认

子模块追踪:es-index 物料 ES 文档与 mapping

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
写索引ES 全量/增量index、task、SKUapplication/Services/Cache/EsSer.php -> application/Components/ElasticSearchFactory.php源物料、mapping、旧文档、任务锁事务外 bulk old doc -> new doc,DB 不变task + index + bulk errorsmapping 冲突隔离失败文档
搜索核对SKU 可查但关键词无query、index/versionapplication/Services/Materiels/MaterielCacheStationEsSer.phpanalyzer、mapping、业务过滤查询只读 不写request ID + query/index + hitsmapping 变更需重建,不以改单条文档长期修复

子模块追踪:redis-material Redis 物料、限购与价格集合

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
刷新商品/价格/限购变更sid、SKU、key/versionapplication/Components/Cache/KzCache.phpDB 主事实、旧 Hash/Setcommit 后事务外 cache old -> latest/deletedevent/task + key + SKU全部取消也要清旧集合;TTL 待实测
回查页面值旧sid、SKU、cache keyapplication/Services/Materiels/MaterielCacheStationSer.phpRedis、源表、更新时间查询只读 不写request ID + key + hit/time源正确只补缓存,不改主表

子模块追踪:goods-notify 商品中心增量通知

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
消费SKU/价格/状态事件message ID、event、SKU、versionapplication/Services/Notify/MaterielNotifySer.php本地映射、版本和站点范围单消息本地事务 old -> new/待刷新标记message ID + event + SKU/version老版本不覆盖新值;未知事件告警
传播提交后刷新三类派生层SKU、event IDapplication/controllers/tasks/Goods.php已提交商品和目标更新时间事务外刷新 Mongo/ES/Redisevent + SKU + target results只重试失败目标,成功目标不重做

子模块追踪:full-sync 全量同步与重建

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
构建管理任务重建索引task、scope、new targetapplication/controllers/tasks/GoodsCacheFlush.php主库全量、目标版本、锁、水位事务外 empty target -> full snapshot,主库不变task + batch/total/errors未完成不切流;保留旧索引回滚
切换新版本验收后切流target、sample SKUsapplication/controllers/inner/CacheManage.php行数、样本、过滤、差异alias/config old -> new,DB 不写task + version + diff空搜索立即回切并补失败批

子模块追踪:increment-sync 增量水位与任务锁

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
扫描定时增量同步task、lock、from/to watermarkapplication/controllers/tasks/GoodsEsCache.php成功水位、锁和候选事务外同步;仅成功批 watermark old -> newtask + range + errors失败不得越过;确认进程后再清过期锁
重跑上次未结束/漏数据owner、watermark、SKUapplication/Services/Materiels/MaterielCacheBatchSer.php进程、TTL、源/目标时间查询只读;从旧水位幂等重跑task + owner + watermark禁止直接跳水位;upsert 不重复文档

子模块追踪:search-api 搜索 API 与业务可用性过滤

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
搜索关键字/SKU APIsid、query、filters/pageapplication/controllers/inner/GoodsSearch.php -> application/Services/Materiels/MaterielCacheQuerySwitchSer.php引擎命中、启用、价格库存限购、站点可见查询只读 不写;命中后业务过滤request ID + engine + raw/final hitsraw 有 final 无是过滤,不直接重建索引
复核页面与精确查询不同sid、SKU、engine docapplication/Services/Materiels/MaterielCacheStationSer.phpMySQL/Mongo/ES/Redis 快照查询只读 不写SKU + each-layer time固定同一 sid 与口径比较

子模块追踪:search-repair 缓存索引差异修复

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
判定各层不一致sid、SKU、version/timeapplication/Services/Materiels/MaterielCacheSer.php主库、派生文档和过滤比对查询只读 不写;确定目标层task + SKU + layer diff主库错先修来源,不以缓存覆盖主库
修复单 SKU/小批重建batch、SKU list、targetsapplication/controllers/inner/CacheManage.php前快照、任务锁、目标层事务外 stale -> source snapshot,主库不变batch + SKU + results小批回读;只重试失败层