经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Elasticsearch » 查看文章
Elasticsearch使用实战以及代码详解
来源:cnblogs  作者:waynaqua  时间:2024/3/1 8:46:10  对本文有异议

Elasticsearch 是一个使用 Java 语言编写、遵守 Apache 协议、支持 RESTful 风格的分布式全文搜索和分析引擎,它基于 Lucene 库构建,并提供多种语言的 API。Elasticsearch 可以对任何类型的数据进行索引、查询和聚合分析,无论是文本、数字、地理空间、结构化还是非结构化的。Elasticsearch 的核心功能是搜索,它可以对数据进行分词匹配、相关性评分、高亮显示等操作,返回相关度高的结果列表。Elasticsearch 也可以用作数据分析,它可以对数据进行统计、分类、聚类等操作,返回聚合结果或图表。

本文将用我开源的 waynboot-mall 项目作于代码讲解,Elasticsearch 版本是 7.10.1。

waynboot-mall 是一套全部开源的微商城项目,包含三个项目:运营后台、H5 商城和后端接口。实现了一套完整的商城业务,有首页展示、商品分类、商品详情、sku 详情、商品搜索、加入购物车、结算下单、支付宝/微信支付、订单列表、商品评论等一系列功能。

本文大纲如下,

image

应用场景

Elasticsearch 的典型应用场景有以下几种:

  • 全文搜索:Elasticsearch 提供了全文搜索的功能,适用于电商商品搜索、App 搜索、企业内部信息搜索、IT 系统搜索等。例如我们可以为每一个商品作为文档保存进 Elasticsearch,然后使用 Elasticsearch 的查询语言来对文档进行分词匹配、相关性评分、高亮显示等操作,返回相关度高的结果列表。
  • 日志分析:Elasticsearch 可以用来收集、存储和分析海量的日志数据,如项目日志、Nginx log、MySQL Log 等,往往很难从繁杂的日志中获取有价值的信息。Elasticsearch 能够借助 Beats、Logstash 等工具快速对接各种常见的数据源,并通过集成的 Kibana 高效地完成日志的可视化分析,让日志产生价值。
  • 运维监控:Elasticsearch 也可以用来监控和管理 IT 系统的运行状态和性能指标,如 CPU、内存、磁盘、网络等。可以使用 Beats、Logstash 将这些数据实时采集并索引到 Elasticsearch 中,然后通过 Kibana 构建自定义的仪表盘和告警规则,实现实时的运维监控和预警。
  • 数据可视化:Elasticsearch 与 Kibana 的结合提供了强大的数据可视化能力,可以使用 Kibana 来创建各种类型的图表和仪表盘,展示 Elasticsearch 中存储或聚合的数据,如直方图、饼图、地图、时间线等。还可以使用 Kibana 的 Canvas 功能来制作动态的数据展示页面,或者使用 Kibana 的 Lens 功能来进行交互式的数据探索。

waynboot-mall 商城选择使用 Elasticsearch 作为搜索引擎,负责对商品数据进行索引和检索,选择 Elasticsearch 的原因有以下几点,

  1. Elasticsearch 是一个开源的分布式搜索引擎,基于 Lucene 开发,支持全文检索、结构化检索、地理位置检索等多种类型的检索,功能丰富。
  2. Elasticsearch 本身具有高性能和高可用性的设计,可以通过集群和分片机制实现水平扩展,支持海量数据的存储和处理,适合大规模的商城搜索场景。
  3. Elasticsearch 网上社区活跃,现有互联网上有大量的使用文档和案例,方便入门使用和问题排查。
  4. Elasticsearch 有众多分词器插件,关于中文分词器的使用非常成熟,拿来即用,支持自定义字典等。

waynboot 项目使用的 Elasticsearch 插件

Elasticsearch 的插件非常丰富,我给大家介绍其中 waynboot 项目使用的 Elasticsearch 插件。

IK Analyzer

IK Analyzer 是一个开源的中文分词器,由阿里巴巴集团发布。它采用了细粒度切分和歧义处理等技术,能够较好地处理各种中文文本。IK Analyzer 支持普通模式、搜索模式和拼音模式三种分词方式,并可以根据需要自定义字典。

Pinyin Analyzer

Pinyin Analyzer 插件是一个用于将中文字符转换为拼音的插件,它集成了 NLP 工具(nlp-lang)。该插件包含了分析器:pinyin,分词器:pinyin 和 token-filter:pinyin。该插件还提供了一些可选的参数,可以控制拼音的输出格式,例如是否保留首字母,是否保留全拼,是否保留非中文字符等。

目录结构

在 waynboot-mall 项目中,给 Elasticsearch 定义了专门的数据访问层 waynboot-data-elastic,该层目录结构如下,

  1. |-- waynboot-data // 数据访问层
  2. | |-- waynboot-data-elastic // Elasticsearch访问配置模块
  3. | |-- config
  4. | |-- constant
  5. | |-- mananger

包目录说明如下,

  • config:Elasticsearch 相关的配置类,包含 ElasticConfig 连接配置类 以及 ElasticClientConfig 客户端配置相关类,ElasticClientConfig 类可以设置访问密码。
  • constants:Elasticsearch 访问层的相关常量类,这里面定义了商品同步数据的索引名称等信息。
  • mananger:Elasticsearch 访问层的相关操作类,定义了 ElasticDocument 文档操作类,用于操作 Elasticsearch。

代码实战

在 waynboot-mall 项目中,Elasticsearch 主要用于支持首页商品的分词搜索、分页排序等功能。Elasticsearch 版本是 7.0,以下实战讲解都是在 7.0 版本基础上进行。

要使用 Elasticsearch ik 分词器进行中文分词搜索,首先需要安装相应的插件 elasticsearch-analysis-ik,然后在创建索引时指定使用中文分词器作为字段的 analyzer 属性。

在日常对 Elasticsearch 的操作中,我们可以通过 rest api 的方式进行操作。

Elasticsearch rest api 操作

如下我们可以创建一个索引名称为 goods,包含两个属性 title、content。并且 这两个属性都使用 ik 分词器。注意这里我用的 Elasticsearch 提供 Rest api 方式创建索引。

  1. PUT /goods
  2. {
  3. "settings": {
  4. "index": {
  5. "number_of_shards": 1,
  6. "number_of_replicas": 0
  7. }
  8. },
  9. "mappings": {
  10. "properties": {
  11. "title": {
  12. "type": "text",
  13. "analyzer": "ik_max_word"
  14. },
  15. "content": {
  16. "type": "text",
  17. "analyzer": "ik_max_word"
  18. }
  19. }
  20. }
  21. }

创建索引后,就可以向索引中添加两条数据,例如:

  1. POST /books/_doc/1
  2. {
  3. "title": "格林童话",
  4. "content": "这本书介绍了很多童话故事,有白雪公主、狮子王、美人鱼等。"
  5. }
  6. POST /books/_doc/2
  7. {
  8. "title": "中国童话故事",
  9. "content": "这本书介绍了很多中国童话故事。"
  10. }

然后我们就可以使用 match 语法来进行中文分词检索,这里我查询 goods 索引中,title 属性是 "动画" 的记录。如下,

  1. GET /books/_search
  2. {
  3. "query":{
  4. "match":{
  5. "title": "童话"
  6. }
  7. }
  8. }

查询结果如下,

  1. {
  2. "took": 0,
  3. "timed_out": false,
  4. "_shards": {
  5. "total": 1,
  6. "successful": 1,
  7. "skipped": 0,
  8. "failed": 0
  9. },
  10. "hits": {
  11. "total": {
  12. "value": 2,
  13. "relation": "eq"
  14. },
  15. "max_score": 0.11190013,
  16. "hits": [
  17. {
  18. "_index": "books",
  19. "_type": "_doc",
  20. "_id": "1",
  21. "_score": 0.11190013,
  22. "_source": {
  23. "title": "格林童话",
  24. "content": "这本书介绍了很多童话故事,有白雪公主、狮子王、美人鱼等。"
  25. }
  26. },
  27. {
  28. "_index": "books",
  29. "_type": "_doc",
  30. "_id": "2",
  31. "_score": 0.099543065,
  32. "_source": {
  33. "title": "中国童话故事",
  34. "content": "这本书介绍了很多中国童话故事。"
  35. }
  36. }
  37. ]
  38. }
  39. }

可以看到,查询结果中匹配了标题包含“童话”的文档,这说明 Elasticsearch 使用了中文分词器对查询字符串和文档进行了分词,并根据相关性得分返回了结果。

全文搜索以及筛选排序

在 waynboot-mall 项目中,商城首页顶部提供了商品搜索栏,用户可以输入商品名称搜索自己想要的商品,搜索结果展示后,还可以进行热门、新品过滤以及价格、销量等进行排序。

image

可以看到搜索功能还是比较复杂的,在 waynboot-mall 项目中,这些逻辑全部在 Elasticsearch 内部进行处理,代码如下,

  1. @RestController
  2. @AllArgsConstructor
  3. @RequestMapping("search")
  4. public class SearchController extends BaseController {
  5. private IGoodsService iGoodsService;
  6. private ElasticDocument elasticDocument;
  7. @GetMapping("result")
  8. public R result(SearchVO searchVO) throws IOException {
  9. // 获取筛选、排序条件
  10. Long memberId = MobileSecurityUtils.getUserId();
  11. String keyword = searchVO.getKeyword();
  12. Boolean filterNew = searchVO.getFilterNew();
  13. Boolean filterHot = searchVO.getFilterHot();
  14. Boolean isNew = searchVO.getIsNew();
  15. Boolean isHot = searchVO.getIsHot();
  16. Boolean isPrice = searchVO.getIsPrice();
  17. Boolean isSales = searchVO.getIsSales();
  18. String orderBy = searchVO.getOrderBy();
  19. SearchHistory searchHistory = new SearchHistory();
  20. if (memberId != null && StringUtils.isNotEmpty(keyword)) {
  21. searchHistory.setCreateTime(LocalDateTime.now());
  22. searchHistory.setUserId(memberId);
  23. searchHistory.setKeyword(keyword);
  24. }
  25. Page<SearchVO> page = getPage();
  26. // 查询包含关键字、已上架商品
  27. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  28. BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
  29. MatchQueryBuilder matchFiler = QueryBuilders.matchQuery("isOnSale", true);
  30. MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("name", keyword);
  31. MatchPhraseQueryBuilder matchPhraseQueryBuilder = QueryBuilders.matchPhraseQuery("keyword", keyword);
  32. boolQueryBuilder.filter(matchFiler).should(matchQuery).should(matchPhraseQueryBuilder).minimumShouldMatch(1);
  33. searchSourceBuilder.timeout(new TimeValue(10, TimeUnit.SECONDS));
  34. // 按是否新品排序
  35. if (isNew) {
  36. searchSourceBuilder.sort(new FieldSortBuilder("isNew").order(SortOrder.DESC));
  37. }
  38. // 按是否热品排序
  39. if (isHot) {
  40. searchSourceBuilder.sort(new FieldSortBuilder("isHot").order(SortOrder.DESC));
  41. }
  42. // 按价格高低排序
  43. if (isPrice) {
  44. searchSourceBuilder.sort(new FieldSortBuilder("retailPrice").order("asc".equals(orderBy) ? SortOrder.ASC : SortOrder.DESC));
  45. }
  46. // 按销量排序
  47. if (isSales) {
  48. searchSourceBuilder.sort(new FieldSortBuilder("sales").order(SortOrder.DESC));
  49. }
  50. // 筛选新品
  51. if (filterNew) {
  52. MatchQueryBuilder filterQuery = QueryBuilders.matchQuery("isNew", true);
  53. boolQueryBuilder.filter(filterQuery);
  54. }
  55. // 筛选热品
  56. if (filterHot) {
  57. MatchQueryBuilder filterQuery = QueryBuilders.matchQuery("isHot", true);
  58. boolQueryBuilder.filter(filterQuery);
  59. }
  60. // 组装Elasticsearch查询条件
  61. searchSourceBuilder.query(boolQueryBuilder);
  62. // Elasticsearch分页相关
  63. searchSourceBuilder.from((int) (page.getCurrent() - 1) * (int) page.getSize());
  64. searchSourceBuilder.size((int) page.getSize());
  65. // 执行Elasticsearch查询
  66. List<JSONObject> list = elasticDocument.search("goods", searchSourceBuilder, JSONObject.class);
  67. List<Integer> goodsIdList = list.stream().map(jsonObject -> (Integer) jsonObject.get("id")).collect(Collectors.toList());
  68. if (goodsIdList.isEmpty()) {
  69. return R.success().add("goods", Collections.emptyList());
  70. }
  71. // 根据Elasticsearch中返回商品ID查询商品详情并保持es中的排序
  72. List<Goods> goodsList = iGoodsService.searchResult(goodsIdList);
  73. Map<Integer, Goods> goodsMap = goodsList.stream().collect(Collectors.toMap(goods -> Math.toIntExact(goods.getId()), o -> o));
  74. List<Goods> returnGoodsList = new ArrayList<>(goodsList.size());
  75. for (Integer goodsId : goodsIdList) {
  76. returnGoodsList.add(goodsMap.get(goodsId));
  77. }
  78. if (CollectionUtils.isNotEmpty(goodsList)) {
  79. AsyncManager.me().execute(new TimerTask() {
  80. @Override
  81. public void run() {
  82. searchHistory.setHasGoods(true);
  83. iSearchHistoryService.save(searchHistory);
  84. }
  85. });
  86. }
  87. return R.success().add("goods", returnGoodsList);
  88. }
  89. }

这里对上面商城的搜索代码给大家做一个讲解:

  • 第一步:获取筛选、排序条件
  • 第二步:获取查询条件-用户搜索关键字、商品已上架
  • 第三步:获取排序条件-按是否新品排序、按是否热品排序、按价格高低排序、按销量排序
  • 第四步:获取过滤条件-筛选新品、筛选热品
  • 第五步:组装 Elasticsearch 查询条件以及分页条件
  • 第六步:执行 Elasticsearch 查询操作
  • 第七步:获取 Elasticsearch 中返回的商品 ID ,并根据商品 id 查询商品详情,最后商品保持 es 中的排序

总结一下

本文给大家讲解了 waynboot-mall 项目中对于 elasticsearch 的使用以及代码实战讲解。希望能帮助大家更好理解 elasticsearch,大家在自己的项目中如果要引入 elasticsearch,可以直接参照本文的示例代码即可使用。

想要获取 waynboot-mall 项目源码的同学,可以关注我公众号【程序员 wayn】,发送 waynboot-mall 即可领取。

如果觉得这篇文章写的不错的话,不妨点赞加关注,我会更新更多技术干货、项目教学、经验分享的文章。

原文链接:https://www.cnblogs.com/waynaqua/p/18042746

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号