最近有个球友星球提问,问了个挺常见但又有点棘手的问题。他说他们有个商品系统,商品都存在 Elasticsearch(简称 ES)里,现在要把这些商品塞进各种营销活动里。
——https://t.zsxq.com/boPTY
比如“双11大促”“618狂欢”之类的,一个活动可能涉及成千上万的商品。
之前他们的做法是把所有商品数据一股脑儿拉到前端,然后前端自己搜,想找啥找啥。
但问题来了:活动商品太多,前端扛不住了,加载慢、搜索卡,用户体验直线下降。
现在他们想改成后端用 ES 直接搜,把重活交给服务器干。
朋友还问了一句:“这种场景能用 ES 的索引 join
吗?一般咋搞?”
听完我心里一琢磨,这事儿不简单,但也不是没招儿。
咱们今天就来聊聊怎么用 ES 优雅地解决这个问题。
在 ES 里处理这种“商品和营销活动”的关系,得先想清楚数据咋存、咋查。
球友提到 join
,咱们先看看这招行不行,再聊聊其他招数。
join
行不行?ES 确实有 join
功能,能实现类似数据库的父子关系。
比如可以建两个索引,一个存商品,一个存活动,然后用 parent-child
把它们连起来。听起来挺美,但实际用起来有坑:
慢 :join
查询比普通查询费劲,数据多的时候性能掉得厉害。
麻烦 :得提前定义好父子关系,写数据时还得多操心。
不灵活 :分布式环境下,查多了分片多,效率更低。
想象一下,一个活动几万商品,查的时候还得跨索引拼数据,服务器不得累吐血?
所以,**join
这招不太香,咱们得另想办法** 。
在 ES 里,这种问题一般靠“数据建模”解决,也就是调整数据存的方式,让查询更顺手。
以下是几个常见的方案:
{
"product_id": "123",
"name": "手机",
"price": 2000,
"activities": [
{ "activity_id": "act001", "activity_name": "双11促销" },
{ "activity_id": "act002", "activity_name": "新年特惠" }
]
}
nested
查询,过滤出某个活动的商品,简单粗暴。{
"activity_id": "act001",
"activity_name": "双11促销",
"product_id": "123",
"product_name": "手机",
"price": 2000
}
activity_id
过滤就行。{
"product_id": "123",
"name": "手机",
"price": 2000,
"activity_ids": ["act001", "act002"],
"activity_names": ["双11促销", "新年特惠"]
}
terms
一句搞定,性能也不错。球友说他们有些活动商品特别多,动辄几万条,那我觉得反向建模(活动-商品索引)是最佳选择。为什么呢?
如果活动信息简单、更新少,也可以试试宽表模式 ,实现起来更省事儿。不过考虑到“商品特别多”这点,反向建模更稳。
光说不练假把式,咱们直接上代码,看看咋用 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 分页查询
后端ES搜起来,省心又高效这事儿总结下来,ES的join
不太适合这种“活动商品多”的场景。
更好的办法是调整数据模型,推荐用** 反向建模建一个“活动-商品”索引,查询快、扩展强。如果活动简单,也可以试试 宽表模式**。
具体咋选,看你数据量和业务需求。
大家看完估计能松口气了,前端搜索的担子终于可以卸给后端 ES。
想试试代码或者有啥细节问题,欢迎随时留言交流。
更短时间更快习得更多干货!
和全球超2000+ Elastic 爱好者一起精进!
elastic6.cn——ElasticStack进阶助手
抢先一步学习进阶干货!