关于后端:Elasticsearch使用实战以及代码详解

2次阅读

共计 7391 个字符,预计需要花费 19 分钟才能阅读完成。

Elasticsearch 是一个应用 Java 语言编写、恪守 Apache 协定、反对 RESTful 格调的分布式全文搜寻和剖析引擎,它基于 Lucene 库构建,并提供多种语言的 API。Elasticsearch 能够对任何类型的数据进行索引、查问和聚合剖析,无论是文本、数字、天文空间、结构化还是非结构化的。Elasticsearch 的外围性能是搜寻,它能够对数据进行分词匹配、相关性评分、高亮显示等操作,返回相关度高的后果列表。Elasticsearch 也能够用作数据分析,它能够对数据进行统计、分类、聚类等操作,返回聚合后果或图表。

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

waynboot-mall 是一套全副开源的微商城我的项目,蕴含三个我的项目: 经营后盾、H5 商城和后端接口 。实现了一套残缺的商城业务,有首页展现、商品分类、商品详情、sku 详情、商品搜寻、退出购物车、结算下单、支付宝 / 微信领取、订单列表、商品评论等一系列性能。

本文纲要如下,

利用场景

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,该层目录构造如下,

    |-- waynboot-data                    // 数据拜访层
    |   |-- waynboot-data-elastic        // Elasticsearch 拜访配置模块
    |       |-- config
    |       |-- constant
    |       |-- 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 形式创立索引。

    PUT /goods
    {
        "settings": {
            "index": {
                "number_of_shards": 1,
                "number_of_replicas": 0
            }
        },
        "mappings": {
            "properties": {
                "title": {
                    "type": "text",
                    "analyzer": "ik_max_word"
                },
                "content": {
                    "type": "text",
                    "analyzer": "ik_max_word"
                }
            }
        }
    }

创立索引后,就能够向索引中增加两条数据,例如:

    POST /books/_doc/1
    {
        "title": "格林童话",
        "content": "这本书介绍了很多童话故事,有白雪公主、狮子王、美人鱼等。"
    }

    POST /books/_doc/2
    {
        "title": "中国童话故事",
        "content": "这本书介绍了很多中国童话故事。"
    }

而后咱们就能够应用 match 语法来进行中文分词检索,这里我查问 goods 索引中,title 属性是 “ 动画 ” 的记录。如下,

    GET /books/_search
    {
        "query":{
            "match":{"title": "童话"}
        }
    }

查问后果如下,

    {
        "took": 0,
        "timed_out": false,
        "_shards": {
            "total": 1,
            "successful": 1,
            "skipped": 0,
            "failed": 0
        },
        "hits": {
            "total": {
                "value": 2,
                "relation": "eq"
            },
            "max_score": 0.11190013,
            "hits": [
                {
                    "_index": "books",
                    "_type": "_doc",
                    "_id": "1",
                    "_score": 0.11190013,
                    "_source": {
                        "title": "格林童话",
                        "content": "这本书介绍了很多童话故事,有白雪公主、狮子王、美人鱼等。"
                    }
                },
                {
                    "_index": "books",
                    "_type": "_doc",
                    "_id": "2",
                    "_score": 0.099543065,
                    "_source": {
                        "title": "中国童话故事",
                        "content": "这本书介绍了很多中国童话故事。"
                    }
                }
            ]
        }
    }

能够看到,查问后果中匹配了题目蕴含“童话”的文档,这阐明 Elasticsearch 应用了中文分词器对查问字符串和文档进行了分词,并依据相关性得分返回了后果。

全文搜寻以及筛选排序

在 waynboot-mall 我的项目中,商城首页顶部提供了商品搜寻栏,用户能够输出商品名称搜寻本人想要的商品,搜寻后果展现后,还能够进行热门、新品过滤以及价格、销量等进行排序。

能够看到搜寻性能还是比较复杂的,在 waynboot-mall 我的项目中,这些逻辑全副在 Elasticsearch 外部进行解决,代码如下,

    @RestController
    @AllArgsConstructor
    @RequestMapping("search")
    public class SearchController extends BaseController {
        private IGoodsService iGoodsService;
        private ElasticDocument elasticDocument;

        @GetMapping("result")
        public R result(SearchVO searchVO) throws IOException {
            // 获取筛选、排序条件
            Long memberId = MobileSecurityUtils.getUserId();
            String keyword = searchVO.getKeyword();
            Boolean filterNew = searchVO.getFilterNew();
            Boolean filterHot = searchVO.getFilterHot();
            Boolean isNew = searchVO.getIsNew();
            Boolean isHot = searchVO.getIsHot();
            Boolean isPrice = searchVO.getIsPrice();
            Boolean isSales = searchVO.getIsSales();
            String orderBy = searchVO.getOrderBy();
            SearchHistory searchHistory = new SearchHistory();
            if (memberId != null && StringUtils.isNotEmpty(keyword)) {searchHistory.setCreateTime(LocalDateTime.now());
                searchHistory.setUserId(memberId);
                searchHistory.setKeyword(keyword);
            }
            Page<SearchVO> page = getPage();
            // 查问蕴含关键字、已上架商品
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            MatchQueryBuilder matchFiler = QueryBuilders.matchQuery("isOnSale", true);
            MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("name", keyword);
            MatchPhraseQueryBuilder matchPhraseQueryBuilder = QueryBuilders.matchPhraseQuery("keyword", keyword);
            boolQueryBuilder.filter(matchFiler).should(matchQuery).should(matchPhraseQueryBuilder).minimumShouldMatch(1);
            searchSourceBuilder.timeout(new TimeValue(10, TimeUnit.SECONDS));
            // 按是否新品排序
            if (isNew) {searchSourceBuilder.sort(new FieldSortBuilder("isNew").order(SortOrder.DESC));
            }
            // 按是否热品排序
            if (isHot) {searchSourceBuilder.sort(new FieldSortBuilder("isHot").order(SortOrder.DESC));
            }
            // 按价格高下排序
            if (isPrice) {searchSourceBuilder.sort(new FieldSortBuilder("retailPrice").order("asc".equals(orderBy) ? SortOrder.ASC : SortOrder.DESC));
            }
            // 按销量排序
            if (isSales) {searchSourceBuilder.sort(new FieldSortBuilder("sales").order(SortOrder.DESC));
            }
            // 筛选新品
            if (filterNew) {MatchQueryBuilder filterQuery = QueryBuilders.matchQuery("isNew", true);
                boolQueryBuilder.filter(filterQuery);
            }
            // 筛选热品
            if (filterHot) {MatchQueryBuilder filterQuery = QueryBuilders.matchQuery("isHot", true);
                boolQueryBuilder.filter(filterQuery);
            }

            // 组装 Elasticsearch 查问条件
            searchSourceBuilder.query(boolQueryBuilder);
            // Elasticsearch 分页相干
            searchSourceBuilder.from((int) (page.getCurrent() - 1) * (int) page.getSize());
            searchSourceBuilder.size((int) page.getSize());
            // 执行 Elasticsearch 查问
            List<JSONObject> list = elasticDocument.search("goods", searchSourceBuilder, JSONObject.class);
            List<Integer> goodsIdList = list.stream().map(jsonObject -> (Integer) jsonObject.get("id")).collect(Collectors.toList());
            if (goodsIdList.isEmpty()) {return R.success().add("goods", Collections.emptyList());
            }
            // 依据 Elasticsearch 中返回商品 ID 查问商品详情并放弃 es 中的排序
            List<Goods> goodsList = iGoodsService.searchResult(goodsIdList);
            Map<Integer, Goods> goodsMap = goodsList.stream().collect(Collectors.toMap(goods -> Math.toIntExact(goods.getId()), o -> o));
            List<Goods> returnGoodsList = new ArrayList<>(goodsList.size());
            for (Integer goodsId : goodsIdList) {returnGoodsList.add(goodsMap.get(goodsId));
            }
            if (CollectionUtils.isNotEmpty(goodsList)) {AsyncManager.me().execute(new TimerTask() {
                    @Override
                    public void run() {searchHistory.setHasGoods(true);
                        iSearchHistoryService.save(searchHistory);
                    }
                });
            }
            return R.success().add("goods", returnGoodsList);
        }
    }

这里对下面商城的搜寻代码给大家做一个解说:

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

总结一下

本文给大家解说了 waynboot-mall 我的项目中对于 elasticsearch 的应用以及代码实战解说。心愿能帮忙大家更好了解 elasticsearch,大家在本人的我的项目中如果要引入 elasticsearch,能够间接参照本文的示例代码即可应用。

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

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

正文完
 0