电商项目——全文检索-ElasticSearch——第一章——中篇

it2024-01-12  106

电商项目——全文检索-ElasticSearch——第一章——中篇 电商项目——商城业务-商品上架——第二章——中篇 电商项目——商城业务-首页——第三章——中篇 电商项目——性能压测——第四章——中篇 电商项目——缓存——第五章——中篇

文章目录

1:简介2:Docker安装ES-存储和检索数据3:Docker安装Kibana-可视化检索数据4:入门-_cat5:入门-put&post新增数据6:入门-get查询数据&乐观锁字段7:入门-put&post修改数据8:入门-删除数据&bulk批量操作导入样本测试数据9:进阶-两种查询方式9.1 SearchAPI 10:进阶-QueryDSL基本使用&match_all11:进阶-match全文检索12:进阶-match_phrase短语匹配13:进阶-multi_match多字段匹配14:进阶-bool复合查询15:进阶-filter过滤16:进阶-term查询17:进阶-aggregations聚合分析18:映射-mapping创建19:添加新的字段映射20:修改映射&数据迁移21:分词-分词&安装ik分词21.1 扩展自定义词库 22:SpringBoot整合hign-level-client23:整合-测试保存24:整合-测试复杂检索

1:简介

电商项目所有的检索功能都是用ElasticSearch构建起来的,全文检索的数据都是来自mysql,以后我们要把mysql的数据给ES存储一份,这样ES才可以将数据检索出来 lasticSearch——ElasticSearch和Kibana介绍

Elasticsearch是一个分布式的、RESTful的搜索和分析引擎,能够解决越来越多的用例。作为弹性堆栈的核心,它集中存储数据,以便您可以发现预期的数据和发现意外的数据。

ElassticSearch的数据默认放在内存中(进行分析检索是非常快的)

2:Docker安装ES-存储和检索数据

Docker下安装Redis和Mysql,elasticsearch,Kibana,nginx——简洁篇 我在使用Vagrant中拉取ES镜像报如下错误

结果分析发现是 /var/lib/docker/overlay2/路径下的联合目录挂载文件系统,满了

& df -hl #查看存储分布情况

以下的博客对我理解上面的问题有一定的帮助 docker的overlay2笔记 /var/lib/docker/overlay2 占用很大,清理Docker占用的磁盘空间,迁移 /var/lib/docker 目录 sda, sdb, sdc, sda1, sda2在Linux中都代表什么

最终的解决办法,我就只可重新安装一遍虚拟机

如下是在docker中查找镜像的位置和容器所在位置的命令 docker查看镜像仓库的位置

3:Docker安装Kibana-可视化检索数据

Docker下安装Redis和Mysql,elasticsearch,Kibana,nginx——简洁篇

4:入门-_cat

ES作为存储分析检索的一个引擎,我们先来体会它的存储功能,先来对它进行增删改查,在进行这个之前,我们先来体会一下_cat,我们说对所有的ES操作,ES都封装成了API发送请求进行,我们就使用postman进行如下测试

GET:http://192.168.56.10:9200/_cat/nodes 127.0.0.1 60 95 0 0.00 0.04 0.05 dilm * 5f28b7a757f5 GET:http://192.168.56.10:9200/_cat/health 1603399251 20:40:51 elasticsearch green 1 1 0 0 0 0 0 0 - 100.0% GET:http://192.168.56.10:9200/_cat/master 08ZJHzRhQzipECG4ADz0mg 127.0.0.1 127.0.0.1 5f28b7a757f5 GET:http://192.168.56.10:9200/_cat/indices #装好了kibnana,也会给es中存储一些kibana一些节点信息 green open .kibana_task_manager_1 1CyCbE1ETFOFC7se-K1T9Q 1 0 2 0 38.2kb 38.2kb green open .apm-agent-configuration wQZkBe1bRgu7bJW28RDNhg 1 0 0 0 283b 283b green open .kibana_1 qKYKuXT_S86Zdtlj6H3u8A 1 0 5 0 18.3kb 18.3kb

5:入门-put&post新增数据

#发送多次是一个更新操作 PUT:http://192.168.56.10:9200/customer/external/1 { "name":"zlj" } { "_index": "customer", #在哪个索引下(数据库)被创建 "_type": "external", #在哪个类型下(表)被创建 "_id": "1", #数据id "_version": 1, #版本号(不断叠加过程) "result": "created", "_shards": { # 分片(集群用到) "total": 2, "successful": 1, "failed": 0 }, "_seq_no": 0, "_primary_term": 1 } PUT:http://192.168.56.10:9200/customer/external/1 { "name":"zlj" } { "_index": "customer",#在哪个索引下(数据库)被创建 "_type": "external", #在哪个类型下(表)被创建 "_id": "1", #数据id "_version": 2, #版本号(不断叠加过程) "result": "updated", "_shards": { # 分片(集群用到) "total": 2, "successful": 1, "failed": 0 }, "_seq_no": 1, "_primary_term": 1 }

我们在使用_cat查找索引发现,customer被成功添加

GET:http://192.168.56.10:9200/_cat/indices green open .kibana_task_manager_1 1CyCbE1ETFOFC7se-K1T9Q 1 0 2 0 38.2kb 38.2kb green open .apm-agent-configuration wQZkBe1bRgu7bJW28RDNhg 1 0 0 0 283b 283b green open .kibana_1 qKYKuXT_S86Zdtlj6H3u8A 1 0 5 0 18.3kb 18.3kb yellow open customer w3rhRLUsTWWx_lC_XN08LQ 1 1 1 0 3.3kb 3.3kb

#不指定id,会自动生成id。指定了id就会修改这个数据 POST:http://192.168.56.10:9200/customer/external { "name":"zlj" } { "_index": "customer", #在哪个索引下(数据库)被创建 "_type": "external", #在哪个类型下(表)被创建 "_id": "sWomU3UBynfGwnzRowT3", #数据id "_version": 1, #版本号(不断叠加过程) "result": "created", # 分片(集群用到) "_shards": { "total": 2, "successful": 1, "failed": 0 }, "_seq_no": 3, "_primary_term": 1 }

6:入门-get查询数据&乐观锁字段

GET:http://192.168.56.10:9200/customer/external/1 { "_index": "customer",#在哪个索引下(数据库)被创建 "_type": "external", #在哪个类型下(表)被创建 "_id": "1", #数据id "_version": 3, #版本是3说明更新过两次,第一次创建是1 "_seq_no": 2, #并发控制字段,每次更新就会+1,用来做乐观锁 "_primary_term": 1, #同上,主分片重新分配,如重启就会发生变化 "found": true, "_source": { "name": "zlj" } }

比如A看到这个数据是1,B,C,D看到的也都是1,这个时候,A想把1改成2,B想把1改成3,C想把1改成4,D想把1改成5,这个时候如果B先到达es,A后到达es(A的本意是想把1改成2的,如果是别的值就算了),这个时候我们就可以使用乐观锁,比如B先到达es,把值改了,这个时候_seq_no+1,A想进来修改就先判断这个值,如果_seq_no值是1才进行修改,如果是其他值就不会进行修改,我们来模拟上面操作 我们来进行修改下面name=zlj的数据

GET:http://192.168.56.10:9200/customer/external/1 { "_index": "customer", "_type": "external", "_id": "1", "_version": 3, "_seq_no": 2, "_primary_term": 1, "found": true, "_source": { "name": "zlj" } }

我门创建如下两个请求,按顺序执行,并带上?if_seq_no=2&if_primary_term=1字段(值和查询出的数据响应结果对应)

PUT:http://192.168.56.10:9200/customer/external/1 { "name":"1" } { "_index": "customer", "_type": "external", "_id": "1", "_version": 4, "result": "updated", "_shards": { "total": 2, "successful": 1, "failed": 0 }, "_seq_no": 5, #不断叠加,下次还想要乐观锁进行修改,就要使用这个数据 "_primary_term": 1 } PUT:http://192.168.56.10:9200/customer/external/1?if_seq_no=2&if_primary_term=1 { "name":"2" } { "error": { "root_cause": [ { "type": "version_conflict_engine_exception", "reason": "[1]: version conflict, required seqNo [2], primary term [1]. current document has seqNo [5] and primary term [1]", "index_uuid": "w3rhRLUsTWWx_lC_XN08LQ", "shard": "0", "index": "customer" } ], "type": "version_conflict_engine_exception", "reason": "[1]: version conflict, required seqNo [2], primary term [1]. current document has seqNo [5] and primary term [1]", "index_uuid": "w3rhRLUsTWWx_lC_XN08LQ", "shard": "0", "index": "customer" }, "status": 409 } #因为我们执行第一个请求后 "_seq_no": 5,发生了改变,所以报 "reason": "[1]: version conflict, required seqNo [2], primary term [1]. current document has seqNo [5] and primary term [1]", #我们想要修改成功执行如下命令即可 PUT:http://192.168.56.10:9200/customer/external/1?if_seq_no=5&if_primary_term=1 { "name":"2" } { "_index": "customer", "_type": "external", "_id": "1", "_version": 5, "result": "updated", "_shards": { "total": 2, "successful": 1, "failed": 0 }, "_seq_no": 6,#不断叠加,下次还想要乐观锁进行修改,就要使用这个数据 "_primary_term": 1 }

7:入门-put&post修改数据

#使用_update POST:http://192.168.56.10:9200/customer/external/1/_update { "doc":{ "name":"zlj" } } { "_index": "customer", "_type": "external", "_id": "1", "_version": 6, "result": "updated", "_shards": { "total": 2, "successful": 1, "failed": 0 }, "_seq_no": 7, "_primary_term": 1 } #我们再次执行发现响应发生如下变化 POST:http://192.168.56.10:9200/customer/external/1/_update { "doc":{ "name":"zlj" } } { "_index": "customer", "_type": "external", "_id": "1", "_version": 6, "result": "noop", "_shards": { "total": 0, "successful": 0, "failed": 0 }, "_seq_no": 7, "_primary_term": 1 } #使用_update就是对比原来数据,与原来一样就什么都不做,序列号(_seq_no)不变的 和版本号(version)不会往上加,操作也就是noop不做任何操作, POST:http://192.168.56.10:9200/customer/external/1/ { "name":"zlj" } { "_index": "customer", "_type": "external", "_id": "1", "_version": 7, "result": "updated", "_shards": { "total": 2, "successful": 1, "failed": 0 }, "_seq_no": 8, "_primary_term": 1 } #我们再次执行发现响应发生如下变化 POST:http://192.168.56.10:9200/customer/external/1/ { "name":"zlj" } { "_index": "customer", "_type": "external", "_id": "1", "_version": 8, "result": "updated", "_shards": { "total": 2, "successful": 1, "failed": 0 }, "_seq_no": 9, "_primary_term": 1 } #不使用_update就不会检查原数据序列号(_seq_no)变的和版本号(version)会往上加, 操作也就是updated不断更新,使用PUT跟使用POST不带_dpdate一样

使用post不带_update,post带_updat,put都可以

POST:http://192.168.56.10:9200/customer/external/1/ { "name":"zlj", "age":20 } { "_index": "customer", "_type": "external", "_id": "1", "_version": 9, "result": "updated", "_shards": { "total": 2, "successful": 1, "failed": 0 }, "_seq_no": 10, "_primary_term": 1 } POST:http://192.168.56.10:9200/customer/external/1/_update { "doc": { "name":"zlj", "age":20 } } { "_index": "customer", "_type": "external", "_id": "1", "_version": 11, "result": "updated", "_shards": { "total": 2, "successful": 1, "failed": 0 }, "_seq_no": 12, "_primary_term": 1 }

8:入门-删除数据&bulk批量操作导入样本测试数据

DELETE:http://192.168.56.10:9200/customer/external/1/ { "_index": "customer", "_type": "external", "_id": "1", "_version": 12, "result": "deleted", "_shards": { "total": 2, "successful": 1, "failed": 0 }, "_seq_no": 13, "_primary_term": 1 }

我们现在批量的给索引中导入一些数据,进行复杂的检索功能 bulk批量API 我们使用kibana进行测试

#post新增不要带id,put新增要带id POST /customer/external/_bulk {"index":{"_id":"1"}} {"name":"zlj"} {"index":{"_id":"2"}} {"name":"zzz"} { "took" : 5, "errors" : false, "items" : [ { "index" : { "_index" : "customer", "_type" : "external", "_id" : "1", "_version" : 2, "result" : "updated", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 16, "_primary_term" : 1, "status" : 200 } }, { "index" : { "_index" : "customer", "_type" : "external", "_id" : "2", "_version" : 2, "result" : "updated", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 17, "_primary_term" : 1, "status" : 200 } } ] }

我们批量插入如下数据,方便以后进行测试 https://github.com/elastic/elasticsearch/blob/master/docs/src/test/resources/accounts.json

POST /bank/account/_bulk {//......}

9:进阶-两种查询方式

所有的学习都要参照官方文档

9.1 SearchAPI

1)检索信息 在kibana中进行测试

GET /bank/_search { "query": { "match_all": {} }, "sort": [ { "account_number": "asc" } ] } #响应的hits部分包括匹配搜索条件的前10个文档: { "took" : 25, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 1000, "relation" : "eq" }, "max_score" : null, "hits" : [ { "_index" : "bank", "_type" : "account", "_id" : "0", "_score" : null, "_source" : { "account_number" : 0, "balance" : 16623, "firstname" : "Bradshaw", "lastname" : "Mckenzie", "age" : 29, "gender" : "F", "address" : "244 Columbus Place", "employer" : "Euron", "email" : "bradshawmckenzie@euron.com", "city" : "Hobucken", "state" : "CO" }, "sort" : [ 0 ] }, { "_index" : "bank", "_type" : "account", "_id" : "1", "_score" : null, "_source" : { "account_number" : 1, "balance" : 39225, "firstname" : "Amber", "lastname" : "Duke", "age" : 32, "gender" : "M", "address" : "880 Holmes Lane", "employer" : "Pyrami", "email" : "amberduke@pyrami.com", "city" : "Brogan", "state" : "IL" }, "sort" : [ 1 ] }, //................. ] } } #要翻页搜索结果,请在请求中指定from和size参数。 #每个搜索请求都是自包含的:Elasticsearch不会跨请求维护任何状态信息 GET /bank/_search { "query": { "match_all": {} }, "sort": [ { "account_number": "asc" } ], "from": 10, "size": 10 }

10:进阶-QueryDSL基本使用&match_all

GET /bank/_search { # 一个查询语句的典型结构 "query": { "match_phrase": { "balance": "26135" } }, # 针对某个字段的结构 "sort": [ { "balance": { "order": "desc" } } ] }

返回部分字段的体验

GET /bank/_search { "query": { "match_all": {} }, "sort": [ { "balance": { "order": "desc" } } ], "from": 9, "size": 10, "_source": ["age","balance"] }

11:进阶-match全文检索

全文检索最终按照评分进行排序,会对检索条件进行分词匹配

要在字段中搜索特定的术语,可以使用match查询。例如,下面的请求搜索地址字段以查找地址包含mill或lane的客户:

GET /bank/_search { "query": { "match": { "address": "mill lane" } } }

12:进阶-match_phrase短语匹配

要执行短语搜索而不是匹配单个术语,可以使用match_phrase而不是match。例如,下面的请求只匹配包含短语mill lane的地址:

GET /bank/_search { "query": { "match_phrase": { "address": "mill lane" } } }

13:进阶-multi_match多字段匹配

GET /bank/_search { "query": { "multi_match": { "query": "671", "fields": ["balance","address"] } } }

14:进阶-bool复合查询

要构造更复杂的查询,可以使用bool查询组合多个查询条件。您可以根据需要(must match)、需要(should match)或不需要(must not match)指定标准。 例如,以下请求在银行索引中搜索属于40岁客户的账户,但不包括居住在爱达荷州的客户(ID):

GET /bank/_search { "query": { "bool": { "must": [ { "match": { "age": "40" } } ], "must_not": [ { "match": { "state": "ID" } } ] } } }

bool查询中的每个must、should和must_not元素被称为查询子句。**文档满足每个must或should子句中的标准的程度将决定文档的相关性得分。得分越高,文档越符合您的搜索条件。**默认情况下,Elasticsearch返回按相关度评分排序的文档。

must_not子句中的条件被视为筛选器。它影响结果中是否包含文档,但不影响如何对文档进行评分。我们还可以显式地指定任意过滤器,以基于结构化数据包含或排除文档。

例如,下面的请求使用范围筛选器将结果限制为余额在$20,000到$30,000之间的账户。

GET /bank/_search { "query": { "bool": { "must": { "match_all": {} }, "filter": { "range": { "balance": { "gte": 20000, "lte": 30000 } } } } } }

15:进阶-filter过滤

第14章节已经介绍了

16:进阶-term查询

精确字段用term,全文检索用query

返回在所提供的字段中包含确切术语的文档。"que可以使用term query根据精确的值(如价格、产品ID或用户名)来查找文档。默认情况下,Elasticsearch在分析时更改文本字段的值。这可能使查找文本字段值的精确匹配变得困难。要搜索文本字段值,请使用匹配查询。 GET /bank/_search { "query": { "term": { "age": "24" } } } GET /bank/_search { "query": { "term": { "address": "583 Ainslie Street" } } }

17:进阶-aggregations聚合分析

聚合框架帮助提供基于搜索查询的聚合数据。它基于称为聚合(aggregations)的简单构建块,可以组合这些构建块来构建复杂的数据摘要。聚合可以看作是在一组文档上构建分析信息的工作单元。执行的上下文定义了这个文档集是什么(例如,一个顶级聚合在被执行的查询/搜索请求的过滤器的上下文中执行)。有许多不同类型的聚合,每一种都有自己的目的和输出。为了更好地理解这些类型,通常更容易把它们分为四个主要家族: Bucketing 构建bucket的聚合家族,其中每个bucket与一个键和一个文档标准关联。在执行聚合时,将对上下文中的每个文档评估所有bucket标准,当一个标准匹配时,将认为文档“属于”相关bucket。在聚合过程结束时,我们将得到一个bucket列表——每个Bucketing都有一组“属于”它的文档。Metric 对一组文档进行跟踪和计算指标的聚合。Matrix 对多个字段进行操作并根据从请求的文档字段中提取的值生成矩阵结果的聚合家族。与度量和桶聚合不同,这个聚合系列还不支持脚本。Pipeline 聚合其他聚合的输出及其相关指标的聚合 由于每个bucket有效地定义了一个文档集(属于该bucket的所有文档),因此可以在bucket级别关联聚合,这些聚合将在该bucket的上下文中执行。这就是聚合真正发挥作用的地方:聚合可以嵌套! 聚合结构 "aggregations" : { "<aggregation_name>" : { "<aggregation_type>" : { <aggregation_body> } [,"meta" : { [<meta_data_body>] } ]? [,"aggregations" : { [<sub_aggregation>]+ } ]? } [,"<aggregation_name_2>" : { ... } ]* }

示例1:搜索address中包含mill的所有人的年龄以及平均年龄#,以及平均薪资

#搜索address中包含mill的所有人的年龄以及平均年龄#,以及平均薪资 GET /bank/_search { "query": { "match": { "address": "mill" } } , "aggs": { "ageAgg": { "terms": { "field": "age", "size": 10 } }, "ageAvg": { "avg": { "field": "age" } }, "balanceAvg": { "avg": { "field": "balance" } } }, "size": 0 }

示例2: 按照年龄聚合,并且请求这些年龄段的这些人的平均薪资

# 按照年龄聚合,并且请求这些年龄段的这些人的平均薪资 GET /bank/_search { "query": { "match_all": {} }, "aggs": { "ageAgg": { "terms": { "field": "age", "size": 1000 }, "aggs": { "balanceAvg": { "avg": { "field": "balance" } } } } } }

示例3:查出所有年龄分布,并且这些年龄段中的M的平均薪资和F的平均薪资以及这个年龄段的总体平均薪资

#查出所有年龄分布,并且这些年龄段中的M的平均薪资和F的平均薪资以及这个年龄段的总体平均薪资 GET /bank/_search { "query": { "match_all": {} }, "aggs": { "ageAgg": { "terms": { "field": "age", "size": 10 }, "aggs": { "balanceAgg": { "terms": { "field": "balance", "size": 10 }, "aggs": { "balanceAvg": { "avg": { "field": "balance" } } } }, "balanceAvgTotal": { "avg": { "field": "balance" } } } } } }

18:映射-mapping创建

可以使用create index API创建具有显式映射的新索引。格式

PUT /my-index-000001 { "mappings": { "properties": { "age": { "type": "integer" }, "email": { "type": "keyword" }, "name": { "type": "text" } } } }

19:添加新的字段映射

20:修改映射&数据迁移

除了受支持的映射参数外,您不能更改现有字段的映射或字段类型。更改现有字段可能会使已经建立索引的数据无效。如果需要更改字段在其他索引中的映射,请创建具有正确映射的新索引,并将数据重新索引到该索引中。重命名字段会使已在旧字段名称下建立索引的数据无效。相反,添加一个别名字段来创建一个备用字段名。对于已经存在的映射字段,我们不可以再更新。更新必须创建新的索引进行数据迁移

示例:创建一个新的索引更改年龄的类型,并且移交给bank的索引

#创建一个新索引 PUT /newbank { "mappings": { "properties": { "account_number": { "type": "long" }, "address": { "type": "text" }, "age": { "type": "integer" }, "balance": { "type": "long" }, "city": { "type": "keyword" }, "email": { "type": "keyword" }, "employer": { "type": "keyword" }, "firstname": { "type": "text" }, "gender": { "type": "keyword" }, "lastname": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "state": { "type": "keyword" } } } } #执行如下命令进行数据迁移

21:分词-分词&安装ik分词

ES中的标准分词器介绍 开启密码登录在虚拟机中

ES官网中默认的分词器都是只支持英文的,,如果要想支持中文,我们要安装ik分词器

21.1 扩展自定义词库

我们一些流行用语,ik检测不出来,我就要使用扩展的自定义词库来进行鉴别 最简单的方法就是修改ik分词器的IKAnalyzer.cfg.xml配置文件,指定远程词库,向远程词库发送请求,要到最新的词,作为新的词源进行分解,所以我们有两种方式 第一种:自己写一个项目来接受请求,对词源进行分解 第二种:安装nginx,把最新词库放在nginx中,在配置文件中向nginx发送请求,得到最新的词源 安装nginx Nginx——Nginx简介 Docker下安装Redis和Mysql,elasticsearch,Kibana,nginx——简洁篇 nginx默认访问html文件中的index.html,输入虚拟机ip地址即可(nginx找资源都是在html的路径下找) 我的nginx访问失败,如下博客对我有帮助 关于Nginx启动成功,浏览器不能访问的解决办法 centos出现“FirewallD is not running”怎么办 -bash: netstat: command not found 扩展自定义词库步骤 第一步:在nginx中编写一个默认的fenci_ik.txt分词库(一些流行用语)

vi fenci_ik.txt

第二步:修改ik分词器的配置

22:SpringBoot整合hign-level-client

SpringBoot整合ES

23:整合-测试保存

SpringBoot整合ES

24:整合-测试复杂检索

@RunWith(SpringRunner.class) @SpringBootTest class MallSearchApplicationTests { @Autowired private RestHighLevelClient restHighLevelClient; /** * 检索数据 */ public void searchData() throws IOException { //:1:创建检索请求 SearchRequest searchRequest = new SearchRequest(); //指定索引 searchRequest.indices("bank"); //指定dsl,检索条件 SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); //对应es中的query,aggs,size,from,sort 所有根操作都可以找到 // searchSourceBuilder.query(); // searchSourceBuilder.aggregation(); // searchSourceBuilder.size(); // searchSourceBuilder.from(); // searchSourceBuilder.sort() searchSourceBuilder.query(QueryBuilders.matchQuery("address","mill")); //1:按照年龄值分布进行聚合 TermsAggregationBuilder size = AggregationBuilders.terms("ageAgg").field("age").size(10); searchSourceBuilder.aggregation(size); searchRequest.source(searchSourceBuilder); //2:执行检索 SearchResponse searchResponse = restHighLevelClient.search(searchRequest, MallESConfig.COMMON_OPTIONS); //提取有用的响应数据 SearchHits hits = searchResponse.getHits(); SearchHit[] searchHits = hits.getHits(); for (SearchHit hit:searchHits){ //获取数据 } System.out.println(searchResponse); }
最新回复(0)