cover_image

用 Elasticsearch 搞定营销活动商品搜索,Join 选型的必要性探讨

铭毅天下 铭毅天下Elasticsearch
2025年03月26日 01:39

1. 引出问题:前端搜索累了,后端 ES 上场

最近有个球友星球提问,问了个挺常见但又有点棘手的问题。他说他们有个商品系统,商品都存在 Elasticsearch(简称 ES)里,现在要把这些商品塞进各种营销活动里。


图片

 ——https://t.zsxq.com/boPTY

比如“双11大促”“618狂欢”之类的,一个活动可能涉及成千上万的商品。

之前他们的做法是把所有商品数据一股脑儿拉到前端,然后前端自己搜,想找啥找啥。

但问题来了:活动商品太多,前端扛不住了,加载慢、搜索卡,用户体验直线下降

现在他们想改成后端用 ES 直接搜,把重活交给服务器干。

朋友还问了一句:“这种场景能用 ES 的索引 join 吗?一般咋搞?”

听完我心里一琢磨,这事儿不简单,但也不是没招儿。

咱们今天就来聊聊怎么用 ES 优雅地解决这个问题。

图片

2. 方案探讨:怎么把商品和活动绑一块儿?

在 ES 里处理这种“商品和营销活动”的关系,得先想清楚数据咋存、咋查。

球友提到 join,咱们先看看这招行不行,再聊聊其他招数。

用 join 行不行?

ES 确实有 join 功能,能实现类似数据库的父子关系

干货 | Elasticsearch多表关联设计指南

比如可以建两个索引,一个存商品,一个存活动,然后用 parent-child 把它们连起来。听起来挺美,但实际用起来有坑:

  •  :join 查询比普通查询费劲,数据多的时候性能掉得厉害。

  • 麻烦 :得提前定义好父子关系,写数据时还得多操心。

  • 不灵活 :分布式环境下,查多了分片多,效率更低。

想象一下,一个活动几万商品,查的时候还得跨索引拼数据,服务器不得累吐血?
 
所以,**join这招不太香,咱们得另想办法** 。

其他招数有哪些?

在 ES 里,这种问题一般靠“数据建模”解决,也就是调整数据存的方式,让查询更顺手。

干货 | Elasticsearch 数据建模指南

以下是几个常见的方案:

招数 1:嵌套字段(Nested Fields)

  • 咋干 :在商品索引里加个嵌套字段,把活动信息塞进去。比如:
    {
      "product_id""123",
      "name""手机",
      "price"2000,
      "activities": [
        { "activity_id""act001""activity_name""双11促销" },
        { "activity_id""act002""activity_name""新年特惠" }
      ]
    }
  • 好处 :查的时候直接用 nested 查询,过滤出某个活动的商品,简单粗暴。
  •  :活动信息变了,得更新整个商品文档,数据多的话存起来也占地方。
  • 适合 :活动不多,关系不咋变的场景。

招数 2:反向建模(活动-商品索引)

  • 咋干 :另起一个索引,按活动存数据,每条记录是一个活动和商品的组合。比如:
    {
      "activity_id""act001",
      "activity_name""双11促销",
      "product_id""123",
      "product_name""手机",
      "price"2000
    }
  • 好处 :查某个活动下的商品贼快,直接按 activity_id 过滤就行。
  •  :数据会重复存(需要理解一下空间换时间),占地方,得保证写入时同步好。
  • 适合 :活动商品巨多,查询主要看活动的场景。

招数 3:宽表模式(Flattened Data)

  • 咋干 :把活动信息展平塞进商品索引,用数组存。比如:
    {
      "product_id""123",
      "name""手机",
      "price"2000,
      "activity_ids": ["act001""act002"],
      "activity_names": ["双11促销""新年特惠"]
    }
  • 好处 :查起来简单,terms 一句搞定,性能也不错。
  •  :活动信息复杂的话不好维护,更新也麻烦。
  • 适合 :活动信息简单,更新不频繁的场景。

3. 推荐方案:反向建模最靠谱

球友说他们有些活动商品特别多,动辄几万条,那我觉得反向建模(活动-商品索引)是最佳选择。为什么呢?

  • 查得快 :按活动 ID 过滤,ES 天生擅长这种操作,再多商品也不怕。
  • 扩展强 :活动规模大了也没压力,分布式环境下扛得住。
  • 业务匹配 :从前端搜改成后端搜,正好让 ES 发挥搜索优势。

如果活动信息简单、更新少,也可以试试宽表模式 ,实现起来更省事儿。不过考虑到“商品特别多”这点,反向建模更稳。


4. 实现建议:DSL 代码实操一把

光说不练假把式,咱们直接上代码,看看咋用 ES 实现。如下内容在 Elasticsearch 8.15 版本完全验证ok

图片

建索引

先建一个“活动-商品”索引,映射(mapping)长这样:

PUT /activity_products
{
  "mappings": {
    "properties": {
      "activity_id": { "type""keyword" },
      "activity_name": { "type""text" },
      "product_id": { "type""keyword" },
      "product_name": { "type""text" },
      "price": { "type""float" }
    }
  }
}

写数据

批量塞点数据进去:

POST /activity_products/_bulk
"index": {} }
"activity_id""act001""activity_name""双11促销""product_id""123""product_name""小米手机14""price"3999 }
"index": {} }
"activity_id""act002""activity_name""新年特惠""product_id""123""product_name""小米手机14""price"3999 }
"index": {} }
"activity_id""act001""activity_name""双11促销""product_id""456""product_name""华为耳机Pro""price"499 }
"index": {} }
"activity_id""act003""activity_name""618年中大促""product_id""456""product_name""华为耳机Pro""price"499 }
"index": {} }
"activity_id""act001""activity_name""双11促销""product_id""789""product_name""苹果笔记本M2""price"9999 }
"index": {} }
"activity_id""act002""activity_name""新年特惠""product_id""789""product_name""苹果笔记本M2""price"9999 }
"index": {} }
"activity_id""act004""activity_name""黑色星期五""product_id""789""product_name""苹果笔记本M2""price"9999 }
"index": {} }
"activity_id""act003""activity_name""618年中大促""product_id""101""product_name""索尼电视65寸""price"5999 }
"index": {} }
"activity_id""act002""activity_name""新年特惠""product_id""101""product_name""索尼电视65寸""price"5999 }
"index": {} }
"activity_id""act001""activity_name""双11促销""product_id""202""product_name""戴森吹风机""price"2999 }
"index": {} }
"activity_id""act003""activity_name""618年中大促""product_id""202""product_name""戴森吹风机""price"2999 }
"index": {} }
"activity_id""act004""activity_name""黑色星期五""product_id""202""product_name""戴森吹风机""price"2999 }
"index": {} }
"activity_id""act002""activity_name""新年特惠""product_id""456""product_name""华为耳机Pro""price"499 }
"index": {} }
"activity_id""act003""activity_name""618年中大促""product_id""123""product_name""小米手机14""price"3999 }
"index": {} }
"activity_id""act004""activity_name""黑色星期五""product_id""101""product_name""索尼电视65寸""price"5999 }

查数据

想找“双11促销”里的商品,用这个 DSL:

GET /activity_products/_search
{
  "query": {
    "term": {
      "activity_id""act001"
    }
  },
  "size"10,
  "sort": [
    { "price""asc" }
  ]
}

返回结果会按价格排序,最多 10 条。
 

图片

分页优化

商品太多咋办?用 search_after 分页:

GET /activity_products/_search
{
"query": {
    "term": {
      "activity_id""act001"
    }
  },
"size"10,
"sort": [
    { "price""asc" },
    { "product_id""asc" }
  ],
"search_after": [2000"123"]
}

search_after 用上一页的最后一条记录定位下一页,效率比传统 from/size 高。

干货 | 全方位深度解读 Elasticsearch 分页查询

额外小贴士

  • 缓存 :热门活动查得多?加个 Redis 缓存,少烦 ES。
  • 精简字段 :索引里只存关键信息,其他细节可以用商品 ID 再查数据库。

5.小结

后端ES搜起来,省心又高效这事儿总结下来,ES的join不太适合这种“活动商品多”的场景。

更好的办法是调整数据模型,推荐用** 反向建模建一个“活动-商品”索引,查询快、扩展强。如果活动简单,也可以试试 宽表模式**。

具体咋选,看你数据量和业务需求。
 
大家看完估计能松口气了,前端搜索的担子终于可以卸给后端 ES。
 
想试试代码或者有啥细节问题,欢迎随时留言交流。



  1. 《一本书讲透 Elasticsearch》被清华、北大等多所知名高校图书馆收录

  2. 干货 | Elasticsearch 数据建模指南

  3. Elasticsearch Nested 选型,先看这一篇!

  4. 干货 | 拆解一个 Elasticsearch Nested 类型复杂查询问题

图片

短时间快习得多干货!

和全球2000+ Elastic 爱好者一起精进!

elastic6.cn——ElasticStack进阶助手


图片

抢先一步学习进阶干货

死磕Elasticsearch · 目录
上一篇多个 Elasticsearch 集群要一起监控,怎么办?下一篇Elasticsearch 大揭秘:漫画带你轻松学搜索引擎
继续滑动看下一个
铭毅天下Elasticsearch
向上滑动看下一个