欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

关于电商搜索中Elasticsearch的正确使用姿势--配置篇

时间:2023-07-28
文章目录

前言什么是ElasticsearchES快在哪里创建索引

索引的基本配置

分片分析器Field分析器应用

copy_tomulti-fields 结语更新 前言

过年放假啦,总算是闲下来了,笔者自从上次文章更新之后经历了许多事情(裁员风波,面试找工作等等),最近总算是安定下来了。

言归正传,笔者在之前接触Elasticsearch很少,在新公司中,接触到了以电商搜索推荐为主的项目,其中就大量运用到了Elasticsearch(以下简称ES),并收获了不少经验。

本篇就来围绕如何在电商搜索中正确高效的使用ES聊聊笔者的一些心得。

什么是Elasticsearch

ES是一款分布式搜索引擎,这里注意点重点:搜索引擎,很显然,ES对于数据检索有非常多的设计和优化,在这方面有着得天独厚的优势。

ES底层基于 Lucene 实现,同时屏蔽了很多Lucene的底层细节,提供了分布式特性,同时对外提供了 Restful API,我们的所有针对ES的操作,可以直接通过发HTTP请求就可以完成了。

ES快在哪里

答案是 全文检索

想象这么一个场景,有1亿篇文章,现在想要通过搜索文章内容找到对应的文章。假设这些数据存储在mysql当中,能够做到吗。

当然是可以的,但是性能上就比较感人了,原因是由于mysql通常的索引底层实现(B+树)无法很好的实现上述匹配度查找,而使用like语法查找则会导致索引失效,同样也会有性能问题(mysql 5.6之后开始支持全文索引,这些内容不在本文的描述范围之内,不过正如佛瑞德·布鲁克斯所说,软件工程没有银弹,不同场景下采用合适的技术才是解决问题更加有效率的方式)。

而ES基于分词 + 倒排索引,在匹配查询这方面性能上更加优异,操作也更加友好(restful,并且为nosql,数据格式十分灵活),关于ES检索的核心实现机制也不在本文的论述范围当中,兴趣的小伙伴可以自行谷歌学习。

而本文主要描述如何在检索项目(如电商搜索)中灵活使用ES实现对应需求

创建索引

首先需要提一句的是,在ES中,万物皆索引。这正是ES将检索功能发挥到极致的体现。由于是不同的数据库,术语的含义也有一些变化,为便于理解,这里使用mysql作为对比。

在ES中,一个索引(Index)即相当于mysql的一个数据库;一个类型(Type)相当于mysql的一张表(ES7以后舍弃了type的概念,一个索引只能有一个type);一个文档(document)相当于mysql的一行记录;一个字段(Field)相当于记录中的一个属性。

ESmysql索引(Index)数据库类型(Type)表文档(document)记录(行)字段(Field)属性(列)

正如mysql中检索后出来的是若干条记录,ES检索出来的则是一条条文档,文档在ES中是被检索的最小单位。一般文档就长这样

{ "_index": "mall", "_type": "_doc", "_id": "10104", "_version": 6, "_score": 1, "_source": { "itemid": 10104, "title": "1500g铁观音赛君王安溪铁观音", "pubtime": 1576152329, "ipname": "铁观音", "brandid": 0, "brandname": "西湖牌", "categoryid": 3, "logic_categoryid": [ "1", "2", "3", ], "commentnum": 2, "qualityscore": 2, "subscribenum": 2 }}

这是通过ES检索后返回的文档,标准的json格式,其中_index, _type就是我们上述讲到的ES的存储结构;_id相当于这条文档的主键,如果不主动设置的话ES会默认分配;

_score是匹配分,ES会对每条查询结果打一个分数,评分机制比较复杂且灵活,只需要记住,分数越高,这条文档的相对于检索条件的匹配度就越高(ES的查询结果默认是按分数进行排序的)。

_version是这条文档的版本号,每次对文档进行修改,版本号都会增加,版本号的作用在于保持数据的一致性。

最后的_source就是文档本身的内容了,也就是我们的业务数据。

简单的介绍就到此为止,业务数据是上面那样,如果什么都不做,直接往ES里塞数据的话,ES会根据文档的属性Field自动进行分词优化以达到快速检索的目的,但是实际上什么都不设置在工程中是几乎不存在的,我们或多或少会根据业务需求对每个字段进行单独设置。

打个比方来说,文档中的title(标题), ipname(ip名称), brandname(品牌名称)正常来说我们都要进行分词,有时候用户检索不一定是中文,还可能是拼音,我们则需要对应的拼音分词(可以通过增加插件);又或者我们的品牌名称是一个生造词,这种时候通过分词器是识别不出这样的词汇的,我们也需要进行特定的设置。

当然,上述情况只是众多需要我们自定义索引结构的理由之一,下面我将通过一份索引配置来引出ES所提供的更多实用的功能。

索引的基本配置 分片

为了便于理解,我们把一份完整的配置拆分成若干单独的配置逐个查看

{ "mall": { "settings": { "index": { "number_of_shards": "5", "number_of_replicas": "1", "analysis": { "filter": {...}, "char_filter": {...}, "analyzer": {...} } } }, "mappings": {...}, }}

其中,number_of_shards表示ES主分片的数量,这里的分片相当于的mysql的分库分表,这里ES自身实现了这个功能。number_of_replicas表示复制分片的数量,复制分片相当于主分片的备份,上述设置表示为该索引设置5个分片,每个分片有一个备份。这也是ES的默认配置。

深入了解分片功能,可以看看这篇文章,本文不做过多赘述。

分析器

接下来我们看看analysis中设置哪些内容

... "analysis": { "filter": {...}, "char_filter": {...}, "analyzer": {...} }...

在上述配置中,主要分位3个部分,filter、char_filter、analyzer,filter,char_filter表示过滤器,analyzer为分析器(其实还有tokenizer分词器,不过这里没有配置),这三者的关系为analyzer包含filter和char_filter,实际的配置中可能更多,但大致都是analyzer为最终的组合结果。

我们来看下analysis部分的完整配置

{ "mall": { "settings": { "index": { "number_of_shards": "5", "number_of_replicas": "1", "analysis": { "filter": { "my_synonym_filter": { "type": "synonym", "synonyms_path": "analysis/synonyms.txt" }, "origin_and_pinyin": { "keep_joined_full_pinyin": "true", "type": "pinyin" } }, "char_filter": { "tsconvert": { "convert_type": "t2s", "type": "stconvert", "keep_both": "false", "delimiter": "#" } }, "analyzer": {...} } } }, "mappings": {} }}

上述配置中,我们在filter中配置了同义词过滤,这使得我们在面对一些生造词的商品也能够使得ES能够正确的进行分词。

my_synonym_filter,这个名字是我们自己起的,在filter中的key名字随便起,我们将在接下来对analyzer中的配置中使用他们,其他配置项也是一样

同理,origin_and_pinyin使得我们能够识别用户的拼音输入(使用的是medcl的elasticsearch-analysis-pinyin插件)

在char_filter中,我们配置了tsconvert,这使得我们能够识别用户输入的繁体字,并将其匹配对应的简体字。采用的插件为elasticsearch-analysis-stconvert。这里我们不会详细解释每一个配置,有兴趣的同学可以自行谷歌对应的插件学习。

接下来,我们来看下analyzer的配置,后续我们针对每个Field实际使用的也是analyzer中的配置

{ "mall": { "settings": { "index": { ... "analysis": { "filter": {...}, "char_filter": {...}, "analyzer": { "ik_syno": { "filter": [ "my_synonym_filter" ], "type": "custom", "tokenizer": "ik_max_word" }, "smart_syno": { "filter": [ "my_synonym_filter" ], "type": "custom", "tokenizer": "ik_smart" }, "ik_max_word_t2s": { "char_filter": [ "tsconvert" ], "tokenizer": "ik_max_word" }, "ik_smart_t2s": { "char_filter": [ "tsconvert" ], "tokenizer": "ik_smart" }, "origin_pinyin_firstletter": { "filter": [ "origin_and_pinyin" ], "tokenizer": "keyword" } } } } }, "mappings": {} }}

这里配置了5个最终的分析器
ik_syno
smart_syno
ik_max_word_t2s
ik_smart_t2s
origin_pinyin_firstletter
当然,名字也是我们自己取的,我们先来看看ik_syno中的配置

"ik_syno": { "filter": [ "my_synonym_filter" ], "type": "custom", "tokenizer": "ik_max_word"}

其中,filter为过滤器配置(笔者看来更像是修改器),这里我们就用上了上面配置好的my_synonym_filter(如果不记得可以返回上文看看),type表示分析器的类型,这里是custom表示自定义类型;tokenizer是分词器,使用的是大名鼎鼎的ik分词器(一款针对中文的分词器,不了解的话可以看看这篇文章)

这样,ik_syno就成为了一个能够进行细粒度中文分词,并且具有自定义同义词过滤修改的分析器。

在看另外一个ik_smart_t2s,就是一个能够识别繁体中文,并且能够进行智能(粗粒度)分词的分析器

其他几个的组合原理基本类似,这里就不赘述了。

Field分析器应用

好了,分析器配完了,想要让他们生效,我们需要将其应用到对应的Field上

{ "mall": { "settings": { "index": { "number_of_shards": "5", "number_of_replicas": "1", "analysis": { "filter": {...}, "char_filter": {...}, "analyzer": {...} } } }, "mappings": { "dynamic": "strict", "properties": { //商品id "itemid": { "type": "long" }, //标题 "title": { "copy_to":[ "full_name", "smart_title" ], "analyzer":"ik_syno", "type":"text", "fields":{ "pinyin":{ "analyzer":"origin_pinyin_firstletter", "type":"text" } } }, //上新时间 "pubtime": { "type": "long" }, //ip名称 "ipname": { "copy_to":[ "full_name" ], "analyzer":"ik_syno", "type":"text", "fields":{ "pinyin":{ "analyzer":"origin_pinyin_firstletter", "type":"text" } } }, //品牌id "brandid": { "type": "integer" }, //品牌名称 "brandname": { "copy_to":[ "full_name" ], "type":"keyword", "fields":{ "pinyin":{ "analyzer":"origin_pinyin_firstletter", "type":"text" } } }, //后台类目id "categoryid": { "type": "long" }, //前台类目id "logic_categoryid": { "type": "keyword" }, //评论数 "commentnum": { "type": "long" }, //用户打分 "qualityscore": { "type": "long" }, //订阅数 "subscribenum": { "type": "long" }, // copy_to 字段 "full_name":{ "analyzer":"ik_syno", "type":"text" } "smart_title":{ "analyzer":"smart_syno", "type":"text" } } } }}

字段类型这类属于比较基本的,如果对ES的字段类型不熟悉,可以参考这篇文章。这里字段比较多,我们重点看看最常被用作检索的title字段是如何配置的

"title": { "type":"text", "analyzer":"ik_syno", "copy_to":[ "full_name", "smart_title" ], "fields":{ "pinyin":{ "analyzer":"origin_pinyin_firstletter", "type":"text" }}}

可以看到,标题作为最常被检索的字段,是很有必要进行分词的,所以类型上我们将其定义为文本text,analyzer使用上之前配置的ik_syno分析器。

copy_to

copy_to是ES提供的一种用于提高检索效率,简化查询语句的功能,他可以将带有 copy_to的字段复制到copy_to所指定的字段上。

观察上面的字段配置,发现有很多字段被copy到了full_name和smart_title字段上,以full_name为例。这样做的好处是我们在查询文档时候,不需要去指定搜索哪个字段,而是只需要去搜索full_name就可以了,这在电商搜索中是很重要的,因为你无法判断用户搜索的是商品的名称,品牌还是类型(也许更多)。

但是这样做会引起另外一个问题,这个我们将会在下一篇文章中提到并解决。

multi-fields

我们还看到字段中配置了field,这是出于我们需要根据不同的目的将同一个字段用不同的方式索引。这就相当于实现了 multi-fields。例如,一个 string 类型字段可以被映射成 text 字段作为 full-text 进行搜索,同时也可以作为 keyword 字段用于排序和聚合。

除此之外,multi-fields的另一个主要作用就是让同一字段使用不同的解析方式,使其能更好的检索。例如在本文中,我们为title字段设置了fields: pinyin,使其能够将不同的词语再分解成拼音,使得我们再检索过程中能够尽量更多的匹配到文档。

结语

笔者以电商索引为例,简单描述了在索引配置中用到的一些实用功能。ES在配置方面为我们提供了灵活丰富的其他功能及拓展,想要将ES的功能发挥到最大,合理的索引配置是必不可少的,笔者也期望和朋友们一起在未来继续探索和挖掘。

更新

检索篇已更新,感兴趣的小伙伴可以看看:传送门

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。