news 2026/4/23 13:06:51

电商项目ElasticSearch高性能搜索实战全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
电商项目ElasticSearch高性能搜索实战全解析

一、ElasticSearch回顾与应用场景

1.1 ElasticSearch简介

ElasticSearch(简称ES)是一个分布式、RESTful风格的搜索和数据分析引擎,采用Java开发,是当前最流行的开源企业级搜索引擎。它具有近实时搜索、稳定、可靠、快速、安装使用方便等特点,支持Java、.NET、PHP、Python、Ruby等多种语言的客户端。

官方网站:https://www.elastic.co/

1.2 主要应用场景

  • 站内搜索

  • 日志管理与分析

  • 大数据分析

  • 应用性能监控

  • 机器学习

1.3 业务场景:图灵商城商品搜索

在实际电商项目中,商品搜索需要支持多种查询条件:

  • 根据关键字查询

  • 根据品牌查询

  • 商品类别筛选

  • 商品属性信息筛选

  • 价格区间筛选

  • 是否有库存

  • 多种排序方式(销量、价格、上架时间等)

二、文档建模与索引设计

2.1 商品文档结构分析

从文档中的示例数据可以看出,商品文档包含以下字段:

商品基本信息:

  • id:商品ID

  • name:商品名称

  • keywords:关键词

  • subTitle:副标题

  • price:价格

  • promotionPrice:促销价

  • originalPrice:原价

  • pic:图片地址

销售信息:

  • sale:销量

  • hasStock:是否有库存

  • salecount:销售数量

  • putawayDate:上架日期

品牌分类信息:

  • brandId:品牌ID

  • brandName:品牌名称

  • brandImg:品牌图片

  • categoryId:分类ID

  • categoryName:分类名称

商品属性:

  • attrs:属性数组,包含attrId、attrName、attrValue

2.2 建模分析

  1. 分词字段处理

    • name、keywords、subTitle字段需要使用中文分词器(ik_max_word)

  2. 精确匹配字段

    • categoryName、brandName等字段类型设置为keyword

  3. 关联关系处理

    • 商品属性attrs采用nested类型,因为属性与商品存在关联关系且不频繁更新

2.3 索引映射定义

json

{ "mappings": { "properties": { "id": { "type": "long" }, "name": { "type": "text", "analyzer": "ik_max_word" }, "keywords": { "type": "text", "analyzer": "ik_max_word" }, "subTitle": { "type": "text", "analyzer": "ik_max_word" }, "salecount": { "type": "long" }, "putawayDate": { "type": "date" }, "price": { "type": "double" }, "promotionPrice": { "type": "keyword" }, "originalPrice": { "type": "keyword" }, "pic": { "type": "keyword" }, "sale": { "type": "long" }, "hasStock": { "type": "boolean" }, "brandId": { "type": "long" }, "brandName": { "type": "keyword" }, "brandImg": { "type": "keyword" }, "categoryId": { "type": "long" }, "categoryName": { "type": "keyword" }, "attrs": { "type": "nested", "properties": { "attrId": { "type": "long" }, "attrName": { "type": "keyword" }, "attrValue": { "type": "keyword" } } } } } }

2.4 索引文档与数据同步

文档中展示了多个商品数据的索引示例,数据同步可以使用canal等工具实现。以下是部分示例数据:

json

PUT /product_db/_doc/1 { "id": "26", "name": "小米 11 手机", "keywords": "小米手机", "subTitle": "AI智慧全面屏 6GB +64GB 亮黑色 全网通版 移动联通电信4G手机 双卡双待", "price": "3999", "promotionPrice": "2999", "originalPrice": "5999", "pic": "http://macro-oss.oss-cn-shenzhen.aliyuncs.com/mall/images/20180615/xiaomi.jpg", "sale": 999, "hasStock": true, "salecount": 999, "putawayDate": "2021-04-01", "brandId": 6, "brandName": "小米", "brandImg": "http://macro-oss.oss-cn-shenzhen.aliyuncs.com/mall/images/20190129/1e34aef2a409119018a4c6258e39ecfb_222_222.png", "categoryId": 19, "categoryName": "手机通讯", "attrs": [ { "attrId": 1, "attrName": "cpu", "attrValue": "2核" }, { "attrId": 2, "attrName": "颜色", "attrValue": "黑色" } ] }

三、DSL查询语句构建

3.1 基础搜索查询

文档中提供了两种查询示例:

示例1:基础条件查询

json

POST /product_db/_doc/_search { "from": 0, "size": 8, "query": { "bool": { "must": [ { "match": { "name": { "query": "手机" } } } ] } }, "filter": [ { "term": { "hasStock": { "value": true } } }, { "range": { "price": { "from": "1", "to": "5000" } } } ], "sort": [{ "salecount": { "order": "asc" } }] }

示例2:多字段搜索与聚合

json

GET product_db/_search { "from": 0, "size": 20, "query": { "bool": { "must": [ { "multi_match": { "query": "手机", "fields": ["name", "keywords", "subTitle"] } } ], "filter": [ { "term": { "hasStock": "true" } }, { "range": { "price": { "gte": 2000, "lte": 5000 } } } ] } } }

3.2 聚合分析

搜索查询中包含了多种聚合分析:

  1. 品牌聚合:按brandId分组,统计品牌信息

  2. 分类聚合:按categoryId分组,统计分类信息

  3. 属性聚合:使用nested类型对商品属性进行聚合分析

  4. 高亮显示:对匹配的关键词进行高亮标记

四、Java代码实现商品搜索功能

4.1 环境准备

引入Spring Boot Elasticsearch依赖:

xml

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency>

4.2 核心搜索服务实现

4.2.1 主搜索方法

java

@Override public ESResponseResult search(ESRequestParam param) { try { // 1、构建检索对象-封装请求相关参数信息 SearchRequest searchRequest = startBuildRequestParam(param); // 2、进行检索操作 SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT); // 3、分析响应数据,封装成指定的格式 ESResponseResult responseResult = startBuildResponseResult(response, param); return responseResult; } catch (Exception e) { e.printStackTrace(); } return null; }
4.2.2 请求参数构建

java

private SearchRequest startBuildRequestParam(ESRequestParam param) { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder(); // 1、关键字查询 if (!StringUtils.isEmpty(param.getKeyword())) { boolQueryBuilder.must(QueryBuilders.multiMatchQuery( param.getKeyword(), "name", "keywords", "subTitle")); } // 2、根据类目ID过滤 if (null != param.getCategoryId()) { boolQueryBuilder.filter(QueryBuilders.termQuery("categoryId", param.getCategoryId())); } // 3、根据品牌ID过滤 if (null != param.getBrandId() && param.getBrandId().size() > 0) { boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId", param.getBrandId())); } // 4、根据属性过滤 if (param.getAttrs() != null && param.getAttrs().size() > 0) { param.getAttrs().forEach(item -> { String[] s = item.split("_"); String attrId = s[0]; String[] attrValues = s[1].split(":"); BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); boolQuery.must(QueryBuilders.termQuery("attrs.attrId", attrId)); boolQuery.must(QueryBuilders.termsQuery("attrs.attrValue", attrValues)); NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery( "attrs", boolQuery, ScoreMode.None); boolQueryBuilder.filter(nestedQueryBuilder); }); } // 5、是否有库存过滤 if (null != param.getHasStock()) { boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock", param.getHasStock() == 1)); } // 6、价格区间过滤 if (!StringUtils.isEmpty(param.getPrice())) { RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("price"); String[] price = param.getPrice().split("_"); if (price.length == 2) { rangeQueryBuilder.gte(price[0]).lte(price[1]); } else if (price.length == 1) { if (param.getPrice().startsWith("_")) { rangeQueryBuilder.lte(price[1]); } if (param.getPrice().endsWith("_")) { rangeQueryBuilder.gte(price[0]); } } boolQueryBuilder.filter(rangeQueryBuilder); } searchSourceBuilder.query(boolQueryBuilder); // 排序处理 if (!StringUtils.isEmpty(param.getSort())) { String sort = param.getSort(); String[] sortFields = sort.split("_"); if (!StringUtils.isEmpty(sortFields[0])) { SortOrder sortOrder = "asc".equalsIgnoreCase(sortFields[1]) ? SortOrder.ASC : SortOrder.DESC; searchSourceBuilder.sort(sortFields[0], sortOrder); } } // 分页处理 searchSourceBuilder.from((param.getPageNum() - 1) * SearchConstant.PAGE_SIZE); searchSourceBuilder.size(SearchConstant.PAGE_SIZE); // 高亮显示 if (!StringUtils.isEmpty(param.getKeyword())) { HighlightBuilder highlightBuilder = new HighlightBuilder(); highlightBuilder.field("name"); highlightBuilder.preTags("<b style='color:red'>"); highlightBuilder.postTags("</b>"); searchSourceBuilder.highlighter(highlightBuilder); } // 聚合分析 // 品牌聚合 TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg"); brand_agg.field("brandId").size(50); brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg") .field("brandName").size(1)); brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg") .field("brandImg").size(1)); searchSourceBuilder.aggregation(brand_agg); // 分类聚合 TermsAggregationBuilder category_agg = AggregationBuilders.terms("category_agg"); category_agg.field("categoryId").size(50); category_agg.subAggregation(AggregationBuilders.terms("category_name_agg") .field("categoryName").size(1)); searchSourceBuilder.aggregation(category_agg); // 属性聚合 NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs"); TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg") .field("attrs.attrId"); attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg") .field("attrs.attrName")); attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg") .field("attrs.attrValue")); attr_agg.subAggregation(attr_id_agg); searchSourceBuilder.aggregation(attr_agg); return new SearchRequest(new String[]{"product_db"}, searchSourceBuilder); }
4.2.3 响应结果处理

java

private ESResponseResult startBuildResponseResult(SearchResponse response, ESRequestParam param) { ESResponseResult result = new ESResponseResult(); // 1、获取商品信息 SearchHit[] hits = response.getHits().getHits(); List<ESResponseResult.ProductVo> productVos = new ArrayList<>(); for (SearchHit hit : hits) { ESResponseResult.ProductVo productVo = new ESResponseResult.ProductVo(); String sourceAsString = hit.getSourceAsString(); productVo = JSON.parseObject(sourceAsString, ESResponseResult.ProductVo.class); // 设置高亮 if (hit.getHighlightFields().get("name") != null) { String name = hit.getHighlightFields().get("name").getFragments()[0].string(); productVo.setName(name); } productVos.add(productVo); } result.setProducts(productVos); // 2、获取品牌聚合信息 ParsedLongTerms brandAgg = response.getAggregations().get("brand_agg"); List<ESResponseResult.BrandVo> brandVos = new ArrayList<>(); for (Terms.Bucket bucket : brandAgg.getBuckets()) { ESResponseResult.BrandVo brandVo = new ESResponseResult.BrandVo(); brandVo.setBrandId(Long.parseLong(bucket.getKeyAsString())); ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brand_name_agg"); brandVo.setBrandName(brandNameAgg.getBuckets().get(0).getKeyAsString()); ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brand_img_agg"); brandVo.setBrandImg(brandImgAgg.getBuckets().get(0).getKeyAsString()); brandVos.add(brandVo); } result.setBrands(brandVos); // 3、获取分类聚合信息 ParsedLongTerms categoryAgg = response.getAggregations().get("category_agg"); List<ESResponseResult.CategoryVo> categoryVos = new ArrayList<>(); for (Terms.Bucket bucket : categoryAgg.getBuckets()) { ESResponseResult.CategoryVo categoryVo = new ESResponseResult.CategoryVo(); categoryVo.setCategoryId(Long.parseLong(bucket.getKeyAsString())); ParsedStringTerms categoryNameAgg = bucket.getAggregations().get("category_name_agg"); categoryVo.setCategoryName(categoryNameAgg.getBuckets().get(0).getKeyAsString()); categoryVos.add(categoryVo); } result.setCategories(categoryVos); // 4、获取属性聚合信息 List<ESResponseResult.AttrVo> attrVos = new ArrayList<>(); ParsedNested attrsAgg = response.getAggregations().get("attr_agg"); ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attr_id_agg"); for (Terms.Bucket bucket : attrIdAgg.getBuckets()) { ESResponseResult.AttrVo attrVo = new ESResponseResult.AttrVo(); attrVo.setAttrId(bucket.getKeyAsNumber().longValue()); ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attr_name_agg"); attrVo.setAttrName(attrNameAgg.getBuckets().get(0).getKeyAsString()); ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attr_value_agg"); List<String> attrValues = attrValueAgg.getBuckets().stream() .map(item -> item.getKeyAsString()) .collect(Collectors.toList()); attrVo.setAttrValue(attrValues); attrVos.add(attrVo); } result.setAttrs(attrVos); // 5、分页信息 result.setPageNum(param.getPageNum()); long total = response.getHits().getTotalHits().value; result.setTotal(total); int totalPages = (int) total % SearchConstant.PAGE_SIZE == 0 ? (int) total / SearchConstant.PAGE_SIZE : ((int) total / SearchConstant.PAGE_SIZE + 1); result.setTotalPages(totalPages); List<Integer> pageNaws = new ArrayList<>(); for (int i = 1; i <= totalPages; i++) { pageNaws.add(i); } result.setPageNaws(pageNaws); return result; }

4.3 测试示例

文档中提供了测试URL示例:

text

http://localhost:8054/searchList?price=1_5000&keyword=手机&sort=salecount_asc&hasStock=1&pageNum=1&pageSize=20&categoryId=19&attrs=2_蓝色&attrs=1_2核

五、总结

通过本文的完整解析,我们了解了电商项目中ElasticSearch高性能搜索的完整实现流程:

  1. 数据建模:合理设计文档结构,区分需要分词的字段和精确匹配的字段

  2. 索引设计:使用合适的字段类型,对于关联数据使用nested类型

  3. 查询构建:灵活运用bool查询、多字段匹配、范围查询等DSL语法

  4. 聚合分析:实现品牌、分类、属性等多维度聚合统计

  5. Java集成:通过RestHighLevelClient实现完整的搜索服务

这种架构设计能够支持电商平台复杂的产品搜索需求,提供高性能、高可用的搜索服务,为用户提供良好的购物体验。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/21 0:45:22

告别卡顿与重复操作:WaveTools鸣潮配置工具提升游戏效率全指南

告别卡顿与重复操作&#xff1a;WaveTools鸣潮配置工具提升游戏效率全指南 【免费下载链接】WaveTools &#x1f9f0;鸣潮工具箱 项目地址: https://gitcode.com/gh_mirrors/wa/WaveTools 当你在《鸣潮》的战斗中因帧率骤降错失连招时机&#xff0c;或是切换小号时被反复…

作者头像 李华
网站建设 2026/4/20 3:35:24

游戏优化工具高帧率设置指南:解决鸣潮玩家三大帧率痛点

游戏优化工具高帧率设置指南&#xff1a;解决鸣潮玩家三大帧率痛点 【免费下载链接】WaveTools &#x1f9f0;鸣潮工具箱 项目地址: https://gitcode.com/gh_mirrors/wa/WaveTools 你是否曾在《鸣潮》的战斗中遭遇画面卡顿&#xff1f;是否觉得技能释放时不够流畅&#…

作者头像 李华
网站建设 2026/4/23 1:45:34

Mac窗口管理效率优化:Topit工具的技术实现与效能分析

Mac窗口管理效率优化&#xff1a;Topit工具的技术实现与效能分析 【免费下载链接】Topit Pin any window to the top of your screen / 在Mac上将你的任何窗口强制置顶 项目地址: https://gitcode.com/gh_mirrors/to/Topit 在macOS多任务处理环境中&#xff0c;窗口管理…

作者头像 李华
网站建设 2026/4/23 12:31:08

颠覆式效率工具RunAsDate:时间管理与开发测试的革命性突破

颠覆式效率工具RunAsDate&#xff1a;时间管理与开发测试的革命性突破 【免费下载链接】RunAsDate 类型于 RunAsDate 软件&#xff0c;C#实现代码 项目地址: https://gitcode.com/malaohu/RunAsDate 在软件开发与测试领域&#xff0c;时间测试工具往往是提升效率的关键。…

作者头像 李华