大家好,我是小米,今年 31 岁,一个喜欢写代码、讲故事、分享技术的程序员。最近我们商城要做一个大升级:在商品列表的左侧,加一个 商品类目树。
说起来,可能你觉得类目树很常见,淘宝、京东、拼多多都有,点开左侧的"手机数码"、"服饰鞋包"、"家用电器",右边商品就会动态刷新。但真正自己去做的时候,才发现这背后其实是个大坑:
- 商品数据量大,几百万级别;
- 类目树要展示每个节点下的商品数量,还要能实时变化;
- 用户在筛选条件下(比如品牌、价格区间)点击类目,商品数也要立刻跟着刷新。
一句话:既要快,又要准,还要实时!
于是,我们决定用 Spring + Elasticsearch 的聚合 来搞定。接下来,我就带大家走一遍我们的踩坑之旅和最终的技术方案。
最初的想法:MySQL + group by
一开始,需求刚提出来,我们的直觉是:
既然要统计类目下的商品数,那用 MySQL group by 不就行了?
比如下面这条 SQL:
乍一看没问题,但当我们实际跑到线上时,MySQL 就直接跪了:
- 我们的商品表有几百万条数据,每次查询要扫全表;
- 用户还可能加上品牌、价格区间、关键字等条件;
- 一旦有多个并发请求,数据库直接爆炸。
于是,方案一票否决。
为什么是 Elasticsearch
我们换个思路。既然要做实时统计和聚合,那 Elasticsearch 正好是擅长的领域:
- 倒排索引:查询速度快;
- 聚合(Aggregation) :专门干统计分析的活;
- 水平扩展:数据量大也能 hold 住。
所以,最终我们决定用 Spring Boot + Elasticsearch 来做。
类目树的数据结构
在讲技术实现之前,先聊聊类目树的数据长什么样。
比如我们的商城类目结构:
- 手机数码
-
- 手机通讯
-
-
- 智能手机
- 老年机
-
-
- 笔记本电脑
- 家用电器
-
- 冰箱
-
- 洗衣机
每个节点都要展示:
- 类目名称
- 商品数量(动态计算)
所以,我们需要在 Elasticsearch 里把商品的类目信息存进去。一般存法是:
这里的 category_path 非常关键,它表示商品所在的完整类目路径。这样我们才能按父级类目统计商品数。
Elasticsearch 的聚合查询
接下来就是重头戏:如何统计类目下的商品数。
我们用的是 Terms Aggregation。比如,统计一级类目的商品数,可以这样写:
这个查询的意思是:按 category_path 聚合,统计每个类目下的商品数量。
如果要结合搜索条件,比如用户搜了"iPhone",就可以直接加上:
这样,返回结果里就是:
- 各个类目下 iPhone 的数量;
- 用户再点选类目时,右侧商品列表和左侧数量能保持一致。
Spring Boot 集成 Elasticsearch
聊完了原理,我们来点代码。我们用的是 Spring Data Elasticsearch。
1. 配置依赖
2. 定义实体类
3. 编写聚合查询
这样,我们就能实时拿到每个类目下的商品数了。
性能优化
当然,实际做的时候我们也遇到不少问题。
1. 类目层级太深
如果类目有三级甚至四级,直接聚合会很慢。我们用了 子聚合(Sub Aggregation) 来做。
这样可以逐级展开,性能会好很多。
2. 热点类目查询太频繁
比如"手机数码"点击太多,我们加了一层 缓存:
- 短期内同样的查询直接走 Redis;
- 后台有定时任务刷新数据。
3. 分片 & 副本优化
Elasticsearch 的分片数我们按数据量调优,确保查询分布均匀,避免单点热点。
上线后的效果
类目树功能上线后,用户体验大大提升:
- 点开"手机数码",能秒级展示数量;
- 搜索条件变化时,左侧数量实时刷新;
- 后端 QPS 从原来的几百压到几千,Elasticsearch 扛住了。
而且,最让我自豪的是,之前大家觉得"类目树就是个 UI 样式",现在都觉得背后是门学问,哈哈。
总结
这次的类目树项目,我学到几点:
- 需求再小,背后都有大学问。一个小小的类目树,其实牵扯到搜索、聚合、性能优化。
- 选对技术栈很重要。如果我们还死磕 MySQL,估计项目早黄了。
- 实战是最好的学习。写博客讲出来,也算是对自己知识的一次梳理。
如果你们的项目里,也有类似的 "实时统计"、"大数据聚合" 需求,Elasticsearch 一定要学会。
END
以上就是小米今天的分享。希望能帮到你们,如果你们在做类目树或者 Elasticsearch 聚合时遇到坑,欢迎留言交流!
我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号"软件求生",获取更多技术干货!