Elasticsearch 【搜索结果去重与合并】的解决方案

在电商、商品搜索或内容平台中,搜索结果去重与合并展示(如将多个 SKU 合并为一个 SPU 展示,或将多个套餐合并为一个商品)是提升用户体验的关键功能。

例如:

用户搜索“iPhone 15”,不希望看到 10 个不同的 SKU(不同颜色、容量)重复出现,而是希望看到 1 个商品卡片,下方展示可选规格。

本文将为你设计一套完整的 Elasticsearch 搜索结果去重与合并(如套餐合并)的解决方案,涵盖数据建模、查询策略、聚合逻辑、前端渲染等环节。


一、核心需求

需求说明
结果去重相同商品(SPU)只展示一次
规格聚合展示该商品下的价格区间、库存状态、图片等
最优排序按销量最高、价格最低等策略选择主展示 SKU
支持筛选用户选择“仅显示有货”时,过滤无库存 SPU
高亮与相关性保留关键词高亮,不影响搜索评分
性能可接受大数据量下响应时间 < 500ms

二、去重方案选型对比

方案优点缺点适用场景
collapse(折叠)ES 原生支持,简单易用不聚合子文档信息,功能有限基础去重
terms + top_hits 聚合灵活,可自定义聚合逻辑查询复杂,性能较低需要丰富子信息
应用层合并完全可控,逻辑灵活增加网络开销,实现复杂多源数据合并
双索引(SPU + SKU)职责分离,性能好维护两套数据同步大型电商平台

推荐组合方案terms + top_hits 聚合(搜索页) + collapse(列表页) + 双索引架构(最优)


三、方案一:使用 collapse 实现基础去重(推荐用于列表页)

1. 数据准备

确保 SKU 文档中包含 spu_id 字段:

{
  "sku_id": "sku_1001",
  "spu_id": "spu_2001",
  "title": "iPhone 15 Pro 256GB 白色",
  "price": 899900,
  "stock_status": "in_stock",
  "image": "https://...",
  "sales_count": 1500
}

2. 使用 collapsespu_id 折叠

GET /sku-catalog/_search
{
  "query": {
    "match": { "title": "iPhone 15" }
  },
  "collapse": {
    "field": "spu_id",
    "inner_hits": {
      "name": "top_sku",
      "size": 1,
      "sort": [
        { "sales_count": "desc" }  // 每个 SPU 展示销量最高的 SKU
      ],
      "_source": ["sku_id", "price", "image", "stock_status"]
    }
  },
  "sort": [
    { "sales_count": "desc" }  // 整体按销量排序
  ],
  "size": 20
}

3. 返回结果示例

"hits": {
  "hits": [
    {
      "id": "spu_2001",
      "fields": { "spu_id": "spu_2001" },
      "inner_hits": {
        "top_sku": {
          "hits": {
            "hits": [
              {
                "_source": {
                  "sku_id": "sku_1001",
                  "price": 899900,
                  "image": "https://...",
                  "stock_status": "in_stock"
                }
              }
            ]
          }
        }
      }
    }
  ]
}

✅ 优点

  • 原生支持,语法简洁。
  • 支持 inner_hits 展示代表 SKU。
  • 可结合 sort 控制排序。

❌ 局限

  • 无法展示所有规格(如颜色、容量列表)。
  • 不支持复杂聚合(如价格区间)。

四、方案二:使用 terms + top_hits 聚合(推荐用于搜索页)

1. 查询:按 spu_id 分组,聚合规格信息

GET /sku-catalog/_search
{
  "size": 0,
  "query": {
    "match": { "title": "iPhone 15" }
  },
  "aggs": {
    "products": {
      "terms": {
        "field": "spu_id",
        "size": 20,
        "order": { "max_sales": "desc" }
      },
      "aggs": {
        "max_sales": {
          "max": { "field": "sales_count" }  // 用于排序
        },
        "price_stats": {
          "stats": { "field": "price" }  // 价格区间
        },
        "in_stock_count": {
          "filter": { "term": { "stock_status": "in_stock" } }
        },
        "top_sku": {
          "top_hits": {
            "size": 1,
            "sort": [{ "sales_count": "desc" }],
            "_source": ["image", "price", "sku_id"]
          }
        },
        "spec_agg": {
          "nested": { "path": "specifications" },
          "aggs": {
            "colors": {
              "filter": { "term": { "specifications.name": "颜色" } },
              "aggs": {
                "values": { "terms": { "field": "specifications.value" } }
              }
            },
            "capacities": {
              "filter": { "term": { "specifications.name": "容量" } },
              "aggs": {
                "values": { "terms": { "field": "specifications.value" } }
              }
            }
          }
        }
      }
    }
  }
}

2. 应用层处理聚合结果

{
  "key": "spu_2001",
  "doc_count": 3,
  "price_stats": { "min": 899900, "max": 999900 },
  "in_stock_count": { "doc_count": 2 },
  "top_sku": {
    "hits": {
      "hits": [ { "_source": { "image": "...", "price": 899900 } } ]
    }
  },
  "spec_agg": {
    "colors": { "values": { "buckets": [ { "key": "白色", "doc_count": 2 }, { "key": "黑色", "doc_count": 1 } ] } },
    "capacities": { "buckets": [ { "key": "256GB" }, { "key": "512GB" } ] }
  }
}

✅ 优点

  • 可聚合价格区间、库存数量、规格列表。
  • 支持复杂排序与筛选。
  • 适合构建完整的商品卡片。

❌ 缺点

  • 性能开销大,尤其在 nested 字段上。
  • 返回的是 aggregations,不是 hits,需应用层拼装。

五、方案三:双索引架构(SPU + SKU)—— 最佳实践

架构设计

两个索引:
- sku-catalog-*:存储所有 SKU,用于库存、价格、规格查询
- spu-catalog-*:存储 SPU 汇总信息,用于搜索展示

1. SPU 索引 Mapping 示例

PUT /spu-catalog-2024-10
{
  "mappings": {
    "properties": {
      "spu_id": { "type": "keyword" },
      "title": { "type": "text", "analyzer": "ik_max_word" },
      "brand": { "type": "keyword" },
      "category_path": { "type": "keyword" },
      "price_min": { "type": "scaled_float", "scaling_factor": 100 },
      "price_max": { "type": "scaled_float", "scaling_factor": 100 },
      "stock_status": { "type": "keyword" },  // in_stock, out_of_stock
      "sales_count": { "type": "long" },
      "image": { "type": "keyword" },  // 主图
      "spec_summary": {  // 规格摘要
        "type": "text",
        "fields": {
          "keyword": { "type": "keyword" }  // "颜色:白/黑, 容量:256/512"
        }
      },
      "sku_count": { "type": "integer" }  // 可选 SKU 数量
    }
  }
}

2. 数据同步逻辑

  • 当 SKU 变更时(新增、价格、库存),触发更新 SPU 汇总:
    • 重新计算 price_min/max
    • 更新 stock_status(任一有货则 in_stock
    • 汇总 spec_summary
    • 选择 sales_count 最高的 SKU 作为主图

可通过 Kafka 事件驱动自动更新。

3. 搜索查询(直接查 SPU)

GET /spu-read/_search
{
  "query": {
    "bool": {
      "must": [ { "match": { "title": "iPhone 15" } } ],
      "filter": [ { "term": { "stock_status": "in_stock" } } ]
    }
  },
  "sort": [ { "sales_count": "desc" } ],
  "highlight": { "fields": { "title": {} } }
}

✅ 优势

  • 查询性能极高(无聚合、无折叠)。
  • 易于排序、过滤、高亮。
  • 支持分页、滚动。

❌ 缺点

  • 需维护两套数据同步。
  • 存在短暂延迟(最终一致性)。

六、去重与合并的前端渲染建议

字段渲染方式
主标题SPU 标题 + 高亮
价格¥8999 ~ ¥9999 或 “起售价 ¥8999”
库存状态“有货” / “仅剩 X 件” / “无货”
规格标签“颜色:白、黑” “容量:256GB、512GB”
主图销量最高的 SKU 图片
角标“爆款”、“新品”、“限时折扣”

七、性能优化建议

场景优化方案
collapse 性能避免 nested 字段参与排序
top_hits 性能减少 size,仅取必要字段
双索引延迟使用 refresh=wait_for 保证强一致性(关键页面)
内存占用关闭不必要的字段 doc_valuesindex

八、总结:三种方案对比与选型建议

方案适用场景推荐指数
collapse列表页、简单去重⭐⭐⭐⭐
terms + top_hits搜索页、需丰富聚合信息⭐⭐⭐⭐
双索引(SPU + SKU)大型电商、高性能要求⭐⭐⭐⭐⭐

推荐架构

  • 搜索页 → 查询 spu-catalog-*(双索引)
  • 商品详情页 → 查询 sku-catalog-*(具体规格)
  • 后台管理 → 同时操作两个索引

九、扩展场景

场景实现方式
套餐合并将“手机+耳机”作为一个虚拟 SPU,单独建索引
品牌聚合搜索“苹果”时,合并 iPhone、Mac、iPad
搜索去重 + 展开先折叠展示,点击后展开所有 SKU
个性化去重按用户偏好(如常买品牌)调整排序
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值