谷粒商城:14.全文检索ElasticSearch

一、ElasticSearch简介

https://www.elastic.co/cn/what-is/elasticsearch

全文搜索属于最常见的需求,开源的 Elasticsearch 是目前全文搜索引擎的首选。

它可以快速地储存、搜索和分析海量数据。维基百科、Stack Overflow、Github 都采用它。

在这里插入图片描述

Elastic 的底层是开源库 Lucene。但是,你没法直接用 Lucene,必须自己写代码去调用它的

接口。Elastic 是 Lucene 的封装,提供了 REST API 的操作接口,开箱即用。

REST API:天然的跨平台。

官方文档:

https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html

官方中文:

https://www.elastic.co/guide/cn/elasticsearch/guide/current/foreword_id.html

社区中文:

https://es.xiaoleilu.com/index.html

http://doc.codingdict.com/elasticsearch/0/

二、基本概念

在这里插入图片描述

2.1. Index(索引)

类比MySQL,Index相当于数据库。

动词,相当于 MySQL 中的 insert;

名词,相当于 MySQL 中的 Database

2.2. Type(类型)

类比MySQL,Type相当于数据表。

在 Index(索引)中,可以定义一个或多个类型。

类似于 MySQL 中的 Table;每一种类型的数据放在一起;

2.3. Document(文档)

类比MySQL,Document相当于数据。

保存在某个索引(Index)下,某种类型(Type)的一个数据(Document),文档是 JSON 格式的,Document 就像是 MySQL 中的某个 Table 里面的内容;

2.4. 倒排索引机制

在这里插入图片描述

三、Docker安装Es

3.1. 下载镜像文件

elasticsearch:存储和检索数据

kibana:可视化检索数据

  1. 使用vagrant upcmd命令启动虚拟机在这里插入图片描述
  2. 使用vagrant sshcmd命令连接虚拟机在这里插入图片描述
  3. 使用sudo docker pull elasticsearch:7.4.2cmd命令下载elasticsearch镜像,并使用sudo docker images查看所有下载镜像在这里插入图片描述
  4. 使用sudo docker pull kibana:7.4.2cmd命令下载kibana镜像,并使用sudo docker images查看所有下载镜像在这里插入图片描述
  5. 使用free -m查看虚拟机剩余内存在这里插入图片描述

3.2. 创建实例

3.2.1. ElasticSearch

sudo mkdir -p /mydata/elasticsearch/config :新建配置文件夹

sudo mkdir -p /mydata/elasticsearch/data :新建数据文件夹

新建文件夹的作用是将docker中的对应文件夹挂载到虚拟机中,便于查看。

可以使用su root切换到root用户,密码也是vagrant

echo “http.host: 0.0.0.0” >> /mydata/elasticsearch/config/elasticsearch.yml

书写配置命令后,elasticsearch可以被远程任意机器访问。

在这里插入图片描述

chmod -R 777 /mydata/elasticsearch/ 保证权限

docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \

-e “discovery.type=single-node” \

-e ES_JAVA_OPTS="-Xms64m -Xmx128m" \

-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \

-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \

-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \

-d elasticsearch:7.4.2

在这里插入图片描述

使用docker ps查看

在这里插入图片描述

以后再外面装好插件重启即可;

特别注意:

-e ES_JAVA_OPTS="-Xms64m -Xmx256m" \ 测试环境下,设置 ES 的初始内存和最大内存,否则导致过大启动不了 ES

启动测试http://192.168.56.10:9200/

在这里插入图片描述

在这里插入图片描述

3.2.2. Kibana

docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.56.10:9200 -p 5601:5601 -d kibana:7.4.2

http://192.168.56.10:9200 一定改为自己虚拟机的地址

在这里插入图片描述

启动测试http://192.168.56.10:5601/

在这里插入图片描述

在这里插入图片描述

四、初步检索

4.1. _cat

GET /_cat/nodes:查看所有节点在这里插入图片描述

GET /_cat/health:查看 es 健康状况在这里插入图片描述

GET /_cat/master:查看主节点在这里插入图片描述

GET /_cat/indices:查看所有索引 show databases;在这里插入图片描述

4.2. 索引一个文档(保存)

索引一个文档即保存一个目录。

保存一个数据,保存在哪个索引的哪个类型下,指定用哪个唯一标识

PUT customer/external/1;在 customer 索引下的 external 类型下保存 1 号数据为

PUT /customer/external/1

{
    "name":"John Doe"
}
  • PUT请求

    在这里插入图片描述

  • POST请求

    在这里插入图片描述

PUT 和 POST 都可以,

POST 新增。如果不指定 id,会自动生成 id。指定 id 就会修改这个数据,并新增版本号

PUT 可以新增可以修改。PUT 必须指定 id;由于 PUT 需要指定 id,我们一般都用来做修改操作,不指定 id 会报错。

返回数据分析

  • 带有_的都是元数据,存储基本信息
  • _index:数据在哪个索引(数据库)下
  • _type:数据在哪个类型(数据表)下
  • _id:数据id,唯一标识
  • _version:数据版本
  • result:结果,由于是第一次保存,所以是create

4.3. 查询文档

GET customer/external/1

在这里插入图片描述

{
    "_index": "customer", //在哪个索引
    "_type": "external", //在哪个类型
    "_id": "1",           //记录 id
    "_version": 2,            //版本号
    "_seq_no": 1,            //并发控制字段,每次更新就会+1,用来做乐观锁
    "_primary_term": 1,      //同上,主分片重新分配,如重启,就会变化
    "found": true,
	"_source": {             //真正的内容
		"name": "John Doe"
	}
}

更新控制

更新携带 ?if_seq_no=0&if_primary_term=1

通过_seq_no_primary_term做控制

4.4. 更新文档

POST customer/external/1/_update

通过_update更新数据的话,会对比源数据,如果与元数据相同,就不会更新,result为noop,version、seq_no都不变

{
    "doc":{
        "name":"John Doew"
    }
}

在这里插入图片描述

或者

POST customer/external/1
{
    "name":"John Doe2"
}

在这里插入图片描述

或者

PUT customer/external/1
{
    "name":"John Doe"
}

在这里插入图片描述

  • 不同:POST 操作会对比源文档数据,如果相同不会有什么操作,文档 version 不增加

    PUT 操作总会将数据重新保存并增加 version 版本;

    带_update 对比元数据如果一样就不进行任何操作。看场景;

    对于大并发更新,不带 update;

    对于大并发查询偶尔更新,带 update;对比更新,重新计算分配规则。

  • 更新同时增加属性

    POST customer/external/1/_update
    {
        "doc":{
            "name":"Jane Doe",
            "age":20
        }
    }
    

    在这里插入图片描述

    PUT 和 POST 不带_update 也可以

    在这里插入图片描述

4.5. 删除文档&索引

删除某一条数据:DELETE customer/external/1

在这里插入图片描述

在这里插入图片描述

删除某个索引:DELETE customer

在这里插入图片描述

4.6. bulk 批量 API

POST customer/external/_bulk
{"index":{"_id":"1"}}
{"name": "John Doe" }
{"index":{"_id":"2"}}
{"name": "Jane Doe" }

使用Kibana进行测试

在这里插入图片描述

语法格式:

{ action: { metadata }}
{ request body}
{ action: { metadata }}
{ request body }

复杂实例:

POST /_bulk
{ "delete": { "_index": "website", "_type": "blog", "_id": "123" }}
{ "create": { "_index": "website", "_type": "blog", "_id": "123" }}
{ "title":	"My first blog post" }
{ "index":	{ "_index": "website", "_type": "blog" }}
{ "title":	"My second blog post" }
{ "update": { "_index": "website", "_type": "blog", "_id": "123"}}
{ "doc" : {"title" : "My updated blog post"} }

在这里插入图片描述

bulk API 以此按顺序执行所有的 action(动作)。如果一个单个的动作因任何原因而失败, 它将继续处理它后面剩余的动作。当 bulk API 返回时,它将提供每个动作的状态(与发送的顺序相同),所以您可以检查是否一个指定的动作是不是失败了。

4.7. 样本测试数据

一份顾客银行账户信息的虚构的 JSON 文档样本。每个文档都有下列的 schema(模式):

{
    "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"
}

https://github.com/elastic/elasticsearch/blob/master/docs/src/test/resources/accounts.json?raw=true 导入测试数据

POST bank/account/_bulk

在这里插入图片描述

测试数据

五、进阶检索

5.1. SearchAPI

ES 支持两种基本方式检索 :

  • 一个是通过使用 REST request URI 发送搜索参数(uri+检索参数)
  • 另一个是通过使用 REST request body 来发送它们(uri+请求体)

检索信息

一切检索从_search 开始

GET bank/_search 检索 bank 下所有信息,包括 type 和 docs

GET bank/_search?q=*&sort=account_number:asc 请求参数方式检索

在这里插入图片描述

响应结果解释:

  • took - Elasticsearch 执行搜索的时间(毫秒)
  • time_out - 告诉我们搜索是否超时
  • _shards - 告诉我们多少个分片被搜索了,以及统计了成功/失败的搜索分片
  • hits - 搜索结果
  • hits.total - 搜索结果
  • hits.hits - 实际的搜索结果数组(默认为前 10 的文档)
  • sort - 结果的排序 key(键)(没有则按 score 排序)
  • score 和 max_score –相关性得分和最高得分(全文检索用)

uri+请求体进行检索

GET bank/_search
{
    "query":{
        "match_all":{

        }
    },
    "sort":[
        {
            "account_number":{
                "order":"desc"
            }
        }
    ]
}

在这里插入图片描述

HTTP 客户端工具(POSTMAN),get 请求不能携带请求体,我们变为 post 也是一样的我们 POST 一个 JSON 风格的查询请求体到 _search API

需要了解,一旦搜索的结果被返回,Elasticsearch 就完成了这次请求,并且不会维护任何

服务端的资源或者结果的 cursor(游标)

5.2. Query DSL

5.2.1. 基本语法格式

Elasticsearch 提供了一个可以执行查询的 Json 风格的 DSLdomain-specific language 领域特定语言)。这个被称为Query DSL。该查询语言非常全面,并且刚开始的时候感觉有点复杂, 真正学好它的方法是从一些基础的示例开始的。

  • 一个查询语句 的典型结构

    {
    	"QUERY_NAME":{ 
    		"ARGUMENT": "VALUE",
        	"ARGUMENT1": "VALUE1",...
        }
    }
    
  • 如果是针对某个字段,那么它的结构如下:

    {
        "QUERY_NAME":{
            "FIELD_NAME":{
                "ARGUMENT":"VALUE",
                "ARGUMENT1":"VALUE1"...
            }
        }
    }
    
  • 实例:

    GET bank/_search
    {
      "query": {
        "match_all": {}
      },
      "sort": [
        {
          "balance": {
            "order": "desc"
          }
        }
      ],
      "from": 0,
      "size": 5
    }
    
  1. query 定义如何查询,
  2. match_all 查询类型【代表查询所有的所有】,es 中可以在 query 中组合非常多的查询类型完成复杂查询
  3. 除了 query 参数之外,我们也可以传递其它的参数以改变查询结果。如 sort,size
  4. from+size 限定,完成分页功能
  5. sort 排序,多字段排序,会在前序字段相等时后续字段内部排序,否则以前序为准

5.2.2. 返回部分字段

GET bank/_search
{
  "_source": ["balance","firstname"]
}

使用"_source"规定返回的字段名称

5.2.3. match【匹配查询】

  • 基本类型(非字符串),精确匹配

    GET bank/_search
    {
      "query": {
        "match": {
          "account_number": "20"
        }
      }
    }
    

    match 返回 account_number=20 的

  • 字符串,全文检索

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

    最终查询出 address 中包含 mill 单词的所有记录

    match 当搜索字符串类型的时候,会进行全文检索,并且每条记录有相关性得分。

    字符串,多个单词(分词+全文检索)

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

    最终查询出 address 中包含 mill 或者 road 或者 mill road 的所有记录,并给出相关性得分

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

5.2.4. match_phrase【短语匹配】

将需要匹配的值当成一个整体单词(不分词)进行检索

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

查出 address 中包含 mill road 的所有记录,并给出相关性得分

在这里插入图片描述

5.2.5. multi_match【多字段匹配】

GET bank/_search
{
    "query":{
        "multi_match":{
            "query":"mill",
            "fields":[
                "state",
                "address"
            ]
        }
    }
}

查询的是state或者address中包含mill字段的

在这里插入图片描述

5.2.6. bool【复合查询】

bool 用来做复合查询:

复合语句可以合并 任何 其它查询语句,包括复合语句,了解这一点是很重要的。这就意味着,复合语句之间可以互相嵌套,可以表达非常复杂的逻辑。

  • must:必须达到 must 列举的所有条件

    GET bank/_search
    {
        "query":{
            "bool":{
                "must":[
                    {
                        "match":{
                            "address":"mill"
                        }
                    },
                    {
                        "match":{
                            "gender":"M"
                        }
                    }
                ]
            }
        }
    }
    

    在这里插入图片描述

  • should:应该达到 should 列举的条件,如果达到会增加相关文档的评分,并不会改变查询的结果。如果 query 中只有 should 且只有一种匹配规则,那么 should 的条件就会被作为默认匹配条件而去改变查询结果

    GET bank/_search
    {
        "query":{
            "bool":{
                "must":[
                    {
                        "match":{
                            "address":"mill"
                        }
                    },
                    {
                        "match":{
                            "gender":"M"
                        }
                    }
                ],
                "should":[
                    {
                        "match":{
                            "address":"lane"
                        }
                    }
                ]
            }
        }
    }
    

    在这里插入图片描述

  • must_not 必须不是指定的情况

    GET bank/_search
    {
        "query":{
            "bool":{
                "must":[
                    {
                        "match":{
                            "address":"mill"
                        }
                    },
                    {
                        "match":{
                            "gender":"M"
                        }
                    }
                ],
                "should":[
                    {
                        "match":{
                            "address":"lane"
                        }
                    }
                ],
                "must_not":[
                    {
                        "match":{
                            "email":"baluba.com"
                        }
                    }
                ]
            }
        }
    }
    

    在这里插入图片描述

    address 包含 mill,并且 gender 是 M,如果 address 里面有 lane 最好不过,但是 email 必

    须不包含 baluba.com

    在这里插入图片描述

5.2.7. filter【结果过滤】

并不是所有的查询都需要产生分数,特别是那些仅用于 “filtering”(过滤)的文档。为了不计算分数 Elasticsearch 会自动检查场景并且优化查询的执行。

过滤不会计算相关性得分,只会筛选出满足条件的数据。

GET bank/_search
{
    "query":{
        "bool":{
            "must":[
                {
                    "match":{
                        "address":"mill"
                    }
                }
            ],
            "filter":{
                "range":{
                    "balance":{
                        "gte":10000,
                        "lte":20000
                    }
                }
            }
        }
    }
}

在这里插入图片描述

5.2.8. term

和 match 一样。匹配某个属性的值。

全文检索字段用 match其他非 text 字段匹配用 term。

在这里插入图片描述

GET bank/_search
{
    "query":{
        "bool":{
            "must":[
                {
                    "term":{
                        "age":{
                            "value":"28"
                        }
                    }
                },
                {
                    "match":{
                        "address":"990 Mill Road"
                    }
                }
            ]
        }
    }
}

在这里插入图片描述

5.2.9. aggregations(执行聚合)

聚合提供了从数据中分组和提取数据的能力。最简单的聚合方法大致等于 SQL GROUP

BYSQL 聚合函数。在 Elasticsearch 中,您有执行搜索返回 hits(命中结果),并且同时返回聚合结果,把一个响应中的所有 hits(命中结果)分隔开的能力。这是非常强大且有效的, 您可以执行查询和多个聚合,并且在一次使用中得到各自的(任何一个的)返回结果,使用一次简洁和简化的 API 来避免网络往返。

  • 搜索address中包含mill的所有人的年龄分布以及平均年龄,但不显示这些人的详情。

    GET bank/_search
    {
        "query":{
            "match":{
                "address":"mill"
            }
        },
        "aggs":{
            "group_by_state":{
                "terms":{
                    "field":"age"
                }
            },
            "avg_age":{
                "avg":{
                    "field":"age"
                }
            }
        },
        "size":0
    }
    

    size:0 不显示搜索数据

    aggs:执行聚合。聚合语法如下

    "aggs": {
    	"aggs_name 这次聚合的名字,方便展示在结果集中": {
    		"AGG_TYPE 聚合的类型(avg,term,terms)": {}
    	}
    },
    

    在这里插入图片描述

  • 复杂:

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

    GET bank/account/_search
    {
        "query":{
            "match_all":{
    
            }
        },
        "aggs":{
            "age_avg":{
                "terms":{
                    "field":"age",
                    "size":1000
                },
                "aggs":{
                    "banlances_avg":{
                        "avg":{
                            "field":"balance"
                        }
                    }
                }
            }
        },
        "size":1000
    }
    

    在这里插入图片描述

  • 复杂

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

    GET bank/account/_search
    {
        "query":{
            "match_all":{
    
            }
        },
        "aggs":{
            "age_agg":{
                "terms":{
                    "field":"age",
                    "size":100
                },
                "aggs":{
                    "gender_agg":{
                        "terms":{
                            "field":"gender.keyword",
                            "size":100
                        },
                        "aggs":{
                            "balance_avg":{
                                "avg":{
                                    "field":"balance"
                                }
                            }
                        }
                    },
                    "balance_avg":{
                        "avg":{
                            "field":"balance"
                        }
                    }
                }
            }
        },
        "size":1000
    }
    

在这里插入图片描述

5.3. Mapping

5.3.1. 字段类型

  1. 核心类型

    在这里插入图片描述

  2. 复杂类型

    在这里插入图片描述

  3. 地理类型

    在这里插入图片描述

  4. 特定类型

    在这里插入图片描述

  5. 多字段

    在这里插入图片描述

5.3.2. 映射

Mapping(映射)

Mapping 是用来定义一个文档(document),以及它所包含的属性(field)是如何存储和索引的。比如,使用 mapping 来定义:

  • 哪些字符串属性应该被看做全文本属性(full text fields)。
  • 哪些属性包含数字,日期或者地理位置。
  • 文档中的所有属性是否都能被索引(_all 配置)。
  • 日期的格式。
  • 自定义映射规则来执行动态添加属性。
  1. 查看 mapping 信息

    GET bank/_mapping

  2. 修改 mapping 信息:

    https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html

    自动猜测的映射类型

    在这里插入图片描述

5.3.3. 新版本改变

Es7 及以上移除了 type 的概念。

  • 关系型数据库中两个数据表示是独立的,即使他们里面有相同名称的列也不影响使用, 但ES 中不是这样的。elasticsearch 是基于 Lucene 开发的搜索引擎,而 ES 中不同 type 下名称相同的 filed 最终在 Lucene 中的处理方式是一样的。
    • 两个不同 type 下的两个user_name,在 ES 同一个索引下其实被认为是同一个 filed, 你必须在两个不同的 type 中定义相同的 filed 映射。否则,不同 type 中的相同字段名称就会在处理中出现冲突的情况,导致 Lucene 处理效率下降。
    • 去掉 type 就是为了提高 ES 处理数据的效率。

Elasticsearch 7.x

  • URL 中的 type 参数为可选。比如,索引一个文档不再要求提供文档类型。

Elasticsearch 8.x

  • 不再支持 URL 中的 type 参数。

    解决:

    1. 将索引从多类型迁移到单类型,每种类型文档一个独立索引
    2. 将已存在的索引下的类型数据,全部迁移到指定位置即可。详见数据迁移
  1. 创建映射

    创建索引并指定映射

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

    在这里插入图片描述

  2. 添加新的字段映射

    index控制字段是否被检索到

    PUT /my-index/_mapping
    {
        "properties":{
            "employee-id":{
                "type":"keyword",
                "index":false
            }
        }
    }
    

    在这里插入图片描述

  3. 更新映射

    对于已经存在的映射字段,我们不能更新。更新必须创建新的索引进行数据迁移。

  4. 数据迁移

    先创建出 new_twitter 的正确映射。然后使用如下方式进行数据迁移

    POST _reindex   [固定写法]
    {
        "source":{
            "index":"twitter"
        },
        "dest":{
            "index":"new_twitter"
        }
    }
    

    将旧索引的type下的数据进行迁移

    POST _reindex
    {
        "source":{
            "index":"twitter",
            "type":"tweet"
        },
        "dest":{
            "index":"tweets"
        }
    }
    

5.4. 分词

一个 tokenizer(分词器)接收一个字符流,将之分割为独立的 tokens(词元,通常是独立的单词),然后输出 tokens 流。

例如,whitespace tokenizer 遇到空白字符时分割文本。它会将文本 “Quick brown fox!” 分割为[Quick, brown, fox!]。

tokenizer(分词器)还负责记录各个 term(词条)的顺序或 position 位置(用于 phrase 短语和 word proximity 词近邻查询),以及 term(词条)所代表的原始 word(单词)的 start

(起始)和 end(结束)的 character offsets(字符偏移量)(用于高亮显示搜索的内容)。

Elasticsearch 提供了很多内置的分词器,可以用来构建custom analyzers(自定义分词器)。

5.4.1. 安装ik分词器

**注意:**不能用默认 elasticsearch-plugin install xxx.zip 进行自动安装

https://github.com/medcl/elasticsearch-analysis-ik/releases?after=v7.5.2 对应 es 版本安装

  • 进入 es 容器内部 plugins 目录

    1. docker exec -it 容器 id /bin/bash

      在这里插入图片描述

    2. wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip

    在这里插入图片描述

  • 安装wget

    在这里插入图片描述

  • 到linux映射的plugins中

    在这里插入图片描述

  • 修改vagrant ssh连接

    使用命令vi /etc/ssh/sshd_config在这里插入图片描述

    输入i进入插入模式

    在这里插入图片描述

    将PasswordAuthentication改为yes在这里插入图片描述

    Esc后使用:wq保存并退出在这里插入图片描述

    重启ssh服务

    使用命令service sshd restart重启服务

    在这里插入图片描述

    使用Xshell新建虚拟机连接

    在这里插入图片描述

    登录在这里插入图片描述

    默认密码vagrant在这里插入图片描述

    登陆成功在这里插入图片描述

  • 下载ik分词器对应版本https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip在这里插入图片描述

  • 使用Xshell文件传输将文件传输到linux虚拟机中

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

  • unzip 下载的文件

    在这里插入图片描述

  • 安装unzip

    在这里插入图片描述

  • rm –rf *.zip

  • mv elasticsearch/ ik

  • 暂时先将文件直接解压后传到对应目录下在这里插入图片描述

    在这里插入图片描述

  • 设置ik文件夹权限

    chmod -R 777 ik/

    在这里插入图片描述

  • 可以确认是否安装好了分词器

    cd …/bin

    elasticsearch-plugin list:即可列出系统的分词器

    在这里插入图片描述

5.4.2. 测试分词器

  1. 退出容器

    exit;在这里插入图片描述

  2. 重启elasticsearch

    在这里插入图片描述

  3. 使用默认

    POST _analyze
    {
        "text":"我是中国人"
    }
    

    在这里插入图片描述

  4. 使用分词器

    POST _analyze
    {
        "analyzer":"ik_smart",
        "text":"我是中国人"
    }
    

    在这里插入图片描述

  5. 另外一个分词器ik_max_word

    POST _analyze
    {
        "analyzer":"ik_max_word",
        "text":"我是中国人"
    }
    

    在这里插入图片描述

能够看出不同的分词器,分词有明显的区别,所以以后定义一个索引不能再使用默认的 mapping 了,要手工建立 mapping, 因为要选择分词器。

5.4.3. 自定义词库

5.4.3.1. 扩大虚拟机内存

将虚拟机内存扩大至3G左右

在这里插入图片描述

5.4.3.2. 修改ES内存

之前测试的时候设置ES内存为128,太小,需要修改。

  1. 查看所有容器docker ps

    在这里插入图片描述

  2. 停止ES容器docker stop 8165

    在这里插入图片描述

  3. 删除原有ESdocker rm 8165

    在这里插入图片描述

    在这里插入图片描述

  4. 重新创建ES

    因为之前数据都放与linux外的文件夹做了映射,所以只要新建的时候仍然绑定映射关系,数据就不会丢失。

    在这里插入图片描述

    使用命令创建ES,指定内存为512

    docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
    -e "discovery.type=single-node" \
    -e ES_JAVA_OPTS="-Xms64m -Xmx512m" \
    -v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
    -v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
    -v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
    -d elasticsearch:7.4.2
    

    在这里插入图片描述

    在这里插入图片描述

5.4.3.3. 安装创建Nginx

  1. 在mydata文件夹下新建nginx文件夹,将所有ngnix相关配置、数据、文件放到该目录下。

    在这里插入图片描述

  2. 随便启动一个 nginx 实例,只是为了复制出配置

    docker run -p 80:80 --name nginx -d nginx:1.10

    在这里插入图片描述

    docker ps查看

    在这里插入图片描述

  • 将容器内的配置文件拷贝到当前目录:docker container cp nginx:/etc/nginx .

    别忘了后面的点

    在这里插入图片描述

  • 停止并删除Ngnix

    在这里插入图片描述

  • 修改文件名称:mv nginx conf

    在这里插入图片描述

  • 新建nginx文件夹,并把这个 conf 移动到/mydata/nginx 下

    在这里插入图片描述

  • 至此,创建出nginx文件夹,并包含nginx配置

    在这里插入图片描述

  • 创建新的 nginx;执行以下命令

    docker run -p 80:80 --name nginx \
    -v /mydata/nginx/html:/usr/share/nginx/html \
    -v /mydata/nginx/logs:/var/log/nginx \
    -v /mydata/nginx/conf:/etc/nginx \
    -d nginx:1.10
    

在这里插入图片描述

  • 给 nginx 的 html 下面放的所有资源可以直接访问;

  • 测试:进入html文件夹,新建index.html,vi进入编辑模式,编写后即可通过http://192.168.56.10/进行访问

5.4.3.4. 将ES所需资源放到Nginx中

  1. 在html文件夹下新建es文件夹在这里插入图片描述

  2. 进入es,新建fenci.txt并进行编辑在这里插入图片描述

  3. fenci.txt中输入希望识别的单词后Esc,:wq保存在这里插入图片描述

  4. 页面访问测试

    在这里插入图片描述

    测试可以访问,分词完成

5.4.3.5. 修改ik分词器配置

  1. 修改/usr/share/elasticsearch/plugins/ik/config/中的 IKAnalyzer.cfg.xml

    在这里插入图片描述

    在这里插入图片描述

  2. 输入i进入编辑模式,放开配置远程扩展,将配置的分词文件地址放在里面。

    在这里插入图片描述

  3. Esc后:wq保存并退出

  4. 重启es在这里插入图片描述

5.4.3.6. 测试

在这里插入图片描述

成功识别出乔碧萝单词。

5.4.3.7. 设置ES自动重启

使用命令docker update elasticsearch --restart=always

在这里插入图片描述

/usr/share/elasticsearch/plugins/ik/config

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <comment>IK Analyzer 扩展配置</comment>
    <!--用户可以在这里配置自己的扩展字典 -->
    <entry key="ext_dict"></entry>
    <!--用户可以在这里配置自己的扩展停止词字典-->
    <entry key="ext_stopwords"></entry>
    <!--用户可以在这里配置远程扩展字典 -->
    <entry key="remote_ext_dict">http://192.168.128.130/fenci/myword.txt</entry>
    <!--用户可以在这里配置远程扩展停止词字典-->
    <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
  • 更新完成后,es 只会对新增的数据用新词分词。历史数据是不会重新分词的。如果想要历史数据重新分词。需要执行:

    POST my_index/_update_by_query?conflicts=proceed

  • 再想增加分词,只需要在fenci.txt中增加单词即可。

六、Elasticsearch-Rest-Client

6.1 Java操作ES的方法

6.1.1. 操作9300端口

9300是一个TCP端口

spring-data-elasticsearch:transport-api.jar;

  • springboot 版本不同, transport-api.jar 不同,不能适配 es 版本
  • 7.x 已经不建议使用,8 以后就要废弃

6.1.2. 操作9200端口

9300是一个HTTP端口

  • JestClient:非官方,更新慢
  • RestTemplate:模拟发 HTTP 请求,ES 很多操作需要自己封装,麻烦
  • HttpClient:同上
  • Elasticsearch-Rest-Client:官方 RestClient,封装了 ES 操作,API 层次分明,上手简单

6.1.3. 结论

最 终 选 择 Elasticsearch-Rest-Client(elasticsearch-rest-high-level-client) https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html

6.2. 开启ES项目

新建module

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

6.3. 导入依赖

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.4.2</version>
</dependency>

在这里插入图片描述

6.4. 修改elasticsearch版本

由于SpringBoot设置了elasticsearch版本管理,因此需要修改elasticsearch版本

在这里插入图片描述

6.5. 配置

  1. 新建配置文件

    在这里插入图片描述

  2. 导入common包

    在这里插入图片描述

  3. 配置注册中心

    在这里插入图片描述

    在这里插入图片描述

  4. 编写配置,给容器中注入一个RestHighLevelClient

    package com.atguigu.gulimall.search.config;
    
    import org.apache.http.HttpHost;
    import org.elasticsearch.client.RestClient;
    import org.elasticsearch.client.RestClientBuilder;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class GulimallElasticSearchConfig {
        @Bean
        RestHighLevelClient client() {
            RestClientBuilder builder = RestClient.builder(new HttpHost("192.168.56.10", 9200,
                    "http"));
            return new RestHighLevelClient(builder);
        }
    }
    

    在这里插入图片描述

6.6. 排除数据源配置

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

在这里插入图片描述

6.7. 测试是否连接成功

package com.atguigu.gulimall.search;

import org.elasticsearch.client.RestHighLevelClient;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class GulimallSearchApplicationTests {

    @Autowired
    private RestHighLevelClient client;

    @Test
    public void contextLoads() {
        System.out.println(client);
    }
}

在这里插入图片描述

在这里插入图片描述

6.8. 添加默认设置

public static final RequestOptions COMMON_OPTIONS;

static {
    RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
//        builder.addHeader("Authorization", "Bearer " + TOKEN);
//        builder.setHttpAsyncResponseConsumerFactory(
//                new HttpAsyncResponseConsumerFactory
//                        .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));
    COMMON_OPTIONS = builder.build();
}

在这里插入图片描述

6.9. 测试保存

Index

@Data
class User{
    private String userName;
    private String gender;
    private Integer age;

}

/**
 * 测试存储数据到es
 * 更新也可以
 */
@Test
public void indexData() throws IOException {
    IndexRequest indexRequest = new IndexRequest("users");
    indexRequest.id("1");//数据的id
//        indexRequest.source("userName","zhangsan","age",18,"gender","男");
    User user = new User();
    user.setUserName("zhangsan");
    user.setAge(18);
    user.setGender("男");
    String jsonString = JSON.toJSONString(user);
    indexRequest.source(jsonString, XContentType.JSON);//要保存的内容
    //执行操作
    IndexResponse index = client.index(indexRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
    //提取有用的响应数据
    System.out.println(index);
}

在这里插入图片描述

6.10. 测试复杂检索

@ToString
@Data
static class  Accout {

    private int account_number;
    private int balance;
    private String firstname;
    private String lastname;
    private int age;
    private String gender;
    private String address;
    private String employer;
    private String email;
    private String city;
    private String state;

}

//    @Test
//    public void testDefaultUriBuilderFactory(){
//        DefaultUriBuilderFactory builderFactory = new DefaultUriBuilderFactory();
//
//        //http://search.gulimall.com/list.html
//        UriBuilder builder = builderFactory.builder()
//                .scheme("http")
//                .host("search.gulimall.com")
//                .path("/list.html")
//                .queryParam("keyword", "手机")
//                ;
//
//        String s = builder.build().toString();
//        System.out.println(s);
//    }

/**
 * (1)、方便检索{
 *      skuId:1
 *      spuId:11
 *      skuTitle:华为xx
 *      price:998
 *      saleCount:99
 *      attrs:[
 *          {尺寸:5寸},
 *          {CPU:高通945},
 *          {分辨率:全高清}
 *      ]
 *  }
 * 冗余:
 *  100万*20=1000000*2KB=2000MB=2G 20
 * (2)、
 *    sku索引{
 *     skuId:1
 *     spuId:11
 *     xxxxx
 *    }
 *
 *    attr索引{
 *        spuId:11,
 *        attrs:[
 *              {尺寸:5寸},
 *              {CPU:高通945},
 *              {分辨率:全高清}
 *      ]
 *    }
 *
 *   搜索 小米; 粮食,手机,电器。
 *   10000个,4000个spu
 *   分步,4000个spu对应的所有可能属性;
 *   esClient: spuId:[4000个spuid] 4000*8=32000byte=32kb
 *
 *   32kb*10000=32000mb;=32GB
 *
 *
 * @throws IOException
 */


@Test
public void searchData() throws IOException {
    //1、创建检索请求
    SearchRequest searchRequest = new SearchRequest();
    //指定索引
    searchRequest.indices("bank");
    //指定DSL,检索条件
    //SearchSourceBuilder sourceBuilde 封装的条件
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    //1.1)、构造检索条件
//        sourceBuilder.query();
//        sourceBuilder.from();
//        sourceBuilder.size();
//        sourceBuilder.aggregation()
    sourceBuilder.query(QueryBuilders.matchQuery("address","mill"));

    //1.2)、按照年龄的值分布进行聚合
    TermsAggregationBuilder ageAgg = AggregationBuilders.terms("ageAgg").field("age").size(10);
    sourceBuilder.aggregation(ageAgg);

    //1.3)、计算平均薪资
    AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
    sourceBuilder.aggregation(balanceAvg);

    System.out.println("检索条件"+sourceBuilder.toString());
    searchRequest.source(sourceBuilder);


    //2、执行检索;
    SearchResponse searchResponse = client.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);

    //3、分析结果 searchResponse
    System.out.println(searchResponse.toString());
//        Map map = JSON.parseObject(searchResponse.toString(), Map.class);
    //3.1)、获取所有查到的数据
    SearchHits hits = searchResponse.getHits();
    SearchHit[] searchHits = hits.getHits();
    for (SearchHit hit : searchHits) {
        /**
         * "_index": "bank",
         * 			"_type": "account",
         * 			"_id": "345",
         * 			"_score": 5.4032025,
         * 			"_source":
         */
//            hit.getIndex();hit.getType();hit.getId();
        String string = hit.getSourceAsString();
        Accout accout = JSON.parseObject(string, Accout.class);
        System.out.println("accout:"+accout);
    }

    //3.2)、获取这次检索到的分析信息;
    Aggregations aggregations = searchResponse.getAggregations();
//        for (Aggregation aggregation : aggregations.asList()) {
//            System.out.println("当前聚合:"+aggregation.getName());
            aggregation.get
//
//        }
    Terms ageAgg1 = aggregations.get("ageAgg");
    for (Terms.Bucket bucket : ageAgg1.getBuckets()) {
        String keyAsString = bucket.getKeyAsString();
        System.out.println("年龄:"+keyAsString+"==>"+bucket.getDocCount());
    }

    Avg balanceAvg1 = aggregations.get("balanceAvg");
    System.out.println("平均薪资:"+balanceAvg1.getValue());

//        Aggregation balanceAvg2 = aggregations.get("balanceAvg");


}

在这里插入图片描述

6.11. 完整测试

package com.atguigu.gulimall.search;

import com.atguigu.gulimall.search.config.GulimallElasticSearchConfig;
import lombok.Data;
import com.alibaba.fastjson.JSON;
import lombok.ToString;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.Avg;
import org.elasticsearch.search.aggregations.metrics.AvgAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;

@RunWith(SpringRunner.class)
@SpringBootTest
public class GulimallSearchApplicationTests {

    @ToString
    @Data
    static class Accout {

        private int account_number;
        private int balance;
        private String firstname;
        private String lastname;
        private int age;
        private String gender;
        private String address;
        private String employer;
        private String email;
        private String city;
        private String state;

    }

//    @Test
//    public void testDefaultUriBuilderFactory(){
//        DefaultUriBuilderFactory builderFactory = new DefaultUriBuilderFactory();
//
//        //http://search.gulimall.com/list.html
//        UriBuilder builder = builderFactory.builder()
//                .scheme("http")
//                .host("search.gulimall.com")
//                .path("/list.html")
//                .queryParam("keyword", "手机")
//                ;
//
//        String s = builder.build().toString();
//        System.out.println(s);
//    }

    /**
     * (1)、方便检索{
     * skuId:1
     * spuId:11
     * skuTitle:华为xx
     * price:998
     * saleCount:99
     * attrs:[
     * {尺寸:5寸},
     * {CPU:高通945},
     * {分辨率:全高清}
     * ]
     * }
     * 冗余:
     * 100万*20=1000000*2KB=2000MB=2G 20
     * (2)、
     * sku索引{
     * skuId:1
     * spuId:11
     * xxxxx
     * }
     * <p>
     * attr索引{
     * spuId:11,
     * attrs:[
     * {尺寸:5寸},
     * {CPU:高通945},
     * {分辨率:全高清}
     * ]
     * }
     * <p>
     * 搜索 小米; 粮食,手机,电器。
     * 10000个,4000个spu
     * 分步,4000个spu对应的所有可能属性;
     * esClient: spuId:[4000个spuid] 4000*8=32000byte=32kb
     * <p>
     * 32kb*10000=32000mb;=32GB
     *
     * @throws IOException
     */


    @Test
    public void searchData() throws IOException {
        //1、创建检索请求
        SearchRequest searchRequest = new SearchRequest();
        //指定索引
        searchRequest.indices("bank");
        //指定DSL,检索条件
        //SearchSourceBuilder sourceBuilde 封装的条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //1.1)、构造检索条件
//        sourceBuilder.query();
//        sourceBuilder.from();
//        sourceBuilder.size();
//        sourceBuilder.aggregation()
        sourceBuilder.query(QueryBuilders.matchQuery("address", "mill"));

        //1.2)、按照年龄的值分布进行聚合
        TermsAggregationBuilder ageAgg = AggregationBuilders.terms("ageAgg").field("age").size(10);
        sourceBuilder.aggregation(ageAgg);

        //1.3)、计算平均薪资
        AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
        sourceBuilder.aggregation(balanceAvg);

        System.out.println("检索条件" + sourceBuilder.toString());
        searchRequest.source(sourceBuilder);


        //2、执行检索;
        SearchResponse searchResponse = client.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);

        //3、分析结果 searchResponse
        System.out.println(searchResponse.toString());
//        Map map = JSON.parseObject(searchResponse.toString(), Map.class);
        //3.1)、获取所有查到的数据
        SearchHits hits = searchResponse.getHits();
        SearchHit[] searchHits = hits.getHits();
        for (SearchHit hit : searchHits) {
            /**
             * "_index": "bank",
             * 			"_type": "account",
             * 			"_id": "345",
             * 			"_score": 5.4032025,
             * 			"_source":
             */
//            hit.getIndex();hit.getType();hit.getId();
            String string = hit.getSourceAsString();
            Accout accout = JSON.parseObject(string, Accout.class);
            System.out.println("accout:" + accout);
        }

        //3.2)、获取这次检索到的分析信息;
        Aggregations aggregations = searchResponse.getAggregations();
//        for (Aggregation aggregation : aggregations.asList()) {
//            System.out.println("当前聚合:"+aggregation.getName());
            aggregation.get
//
//        }
        Terms ageAgg1 = aggregations.get("ageAgg");
        for (Terms.Bucket bucket : ageAgg1.getBuckets()) {
            String keyAsString = bucket.getKeyAsString();
            System.out.println("年龄:" + keyAsString + "==>" + bucket.getDocCount());
        }

        Avg balanceAvg1 = aggregations.get("balanceAvg");
        System.out.println("平均薪资:" + balanceAvg1.getValue());

//        Aggregation balanceAvg2 = aggregations.get("balanceAvg");


    }

    @Data
    class User {
        private String userName;
        private String gender;
        private Integer age;

    }

    /**
     * 测试存储数据到es
     * 更新也可以
     */
    @Test
    public void indexData() throws IOException {
        IndexRequest indexRequest = new IndexRequest("users");
        indexRequest.id("1");//数据的id
//        indexRequest.source("userName","zhangsan","age",18,"gender","男");
        User user = new User();
        user.setUserName("zhangsan");
        user.setAge(18);
        user.setGender("男");
        String jsonString = JSON.toJSONString(user);
        indexRequest.source(jsonString, XContentType.JSON);//要保存的内容
        //执行操作
        IndexResponse index = client.index(indexRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
        //提取有用的响应数据
        System.out.println(index);
    }

    @Autowired
    private RestHighLevelClient client;

    @Test
    public void contextLoads() {
        System.out.println(client);
    }
}
  • 0
    点赞
  • 0
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

打赏
文章很值,打赏犒劳作者一下
相关推荐
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页

打赏

KaiSarH

如果觉得文章不错,可以支持下~

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者