news 2026/4/16 12:58:59

电商项目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/16 12:27:59

使用LangChain与Python构建高效Chatbot:从架构设计到性能优化

背景与痛点&#xff1a;传统 Chatbot 的“慢”与“堵” 过去两年&#xff0c;我至少接手过五个 Chatbot 维护项目&#xff0c;它们都有一个共同症状&#xff1a;用户量一上来&#xff0c;响应时间从 1 秒飙到 5 秒以上&#xff0c;CPU 占用率却不高——典型的 I/O 等待型瓶颈。…

作者头像 李华
网站建设 2026/3/18 19:14:20

3分钟上手!无Steam局域网联机工具:宿舍开黑/公司团建神器

3分钟上手&#xff01;无Steam局域网联机工具&#xff1a;宿舍开黑/公司团建神器 【免费下载链接】SteamEmulator MIRROR REPO - Credits : Mr. Goldberg. Steam emulator that emulates Steam online features. Lets you play games that use the Steam multiplayer APIs on a…

作者头像 李华
网站建设 2026/4/11 22:21:47

3步解锁Mac NTFS读写权限:告别跨平台文件传输困扰

3步解锁Mac NTFS读写权限&#xff1a;告别跨平台文件传输困扰 【免费下载链接】Free-NTFS-for-Mac Nigate&#xff0c;一款支持苹果芯片的Free NTFS for Mac小工具软件。NTFS R/W for macOS. Support Intel/Apple Silicon now. 项目地址: https://gitcode.com/gh_mirrors/fr/…

作者头像 李华
网站建设 2026/4/15 9:19:28

字符艺术创作指南:从像素到文字的视觉转译秘术

字符艺术创作指南&#xff1a;从像素到文字的视觉转译秘术 【免费下载链接】ASCII-generator ASCII generator (image to text, image to image, video to video) 项目地址: https://gitcode.com/gh_mirrors/as/ASCII-generator 字符艺术生成是一种将视觉图像转化为文字…

作者头像 李华
网站建设 2026/4/2 4:25:31

RPG Maker MV Decrypter:从入门到精通的7个实战技巧

RPG Maker MV Decrypter&#xff1a;从入门到精通的7个实战技巧 【免费下载链接】RPG-Maker-MV-Decrypter You can decrypt RPG-Maker-MV Resource Files with this project ~ If you dont wanna download it, you can use the Script on my HP: 项目地址: https://gitcode.c…

作者头像 李华
网站建设 2026/4/16 9:07:59

FPGA局部动态重构:从硬件分时复用看系统效率革命

FPGA局部动态重构&#xff1a;硬件分时复用的效率革命 在计算架构不断演进的今天&#xff0c;FPGA&#xff08;现场可编程门阵列&#xff09;凭借其独特的可重构特性&#xff0c;正在从传统的固定功能加速器转变为更加灵活的计算平台。而局部动态重构技术&#xff0c;则进一步释…

作者头像 李华