Elasticsearch的安装和使用——Docker

Elasticsearch的安装和使用——Docker

Elasticsearch官方使用参考手册:https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html

ELK简介

E表示Elasticsearch 是位于 Elastic Stack 核心的分布式搜索和分析引擎.为所有类型的数据提供近乎实时的搜索和分析。无论您拥有结构化或非结构化文本、数字数据还是地理空间数据,Elasticsearch 都能以支持快速搜索的方式高效地存储和索引它。

L表示Logstash是一款数据转换工具Logstash。由于Elasticsearch只接收JSON格式的数据,像数据库、系统日志、网页数据这些乱七八糟的数据就无法放入Elasticsearch,就诞生了一款数据转换工具Logstash。Logstash致力于将不同来源的数据进行集中、转换和过滤,转换成Elasticsearch需要的数据。

K表示Kibana是一款可视化操作工具。为Elasticsearch提供可视化操作界面,使您能够以交互方式探索、可视化和分享对数据的见解,并管理和监控堆栈。

Docker安装Elasticsearch

第一步:开启Docker

systemctl start docker

第二步:拉取Elasticsearch的镜像

docker pull Elasticsearch:7.17.3

第三步:拉取Kibana镜像

docker pull kibana:7.17.3

第四步:启动Elasticsearch和Kinaba(启动方式一)

docker run -d -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" --name=es elasticsearch:7.17.3
docker run -d -p 5601:5601 -e ELASTICSEARCH_HOSTS="http://192.168.213.142:9200" --name=kibana kibana:7.17.3

第五步、编写docker-compose启动(启动方式二)

# 注意version要和docker-compose的版本对应
version: '3.8'
services:
  elasticsearch:
    image: elasticsearch:7.17.3
    ports:
      - 9200:9200
      - 9300:9300
    environment:
      - 'discovery.type=single-node'
    container_name: elasticsearch
  kinaba:
    image: kibana:7.17.3
    ports:
      - 5601:5601
    environment:
      - ELASTICSEARCH_HOSTS=["http://elasticsearch:9200"]
    container_name: kibana

第六步:启动docker-compose

docker-compose up

基本概念

Elasticsearch数据库备注
IndexDatabase一个index就相当于是一个数据库,表示所有文档的集合
TypeTable一个type就相当于是数据库中的一个表,type(在7.0之后type为固定值_doc)
DocumentRow一个document就相当于是数据库表中的一个行
FieldColumn一个Field就相当于是数据库表中的一个列
MappingSchema(大纲)一个Mapping就相当于是数据库表中的一个约束(表结构),包含了该索引所有的属性和属性对应的数据类型

相关命令

查看所有的索引

# 查看所有的索引
GET _cat/indices

在这里插入图片描述

查看对应索引的所有的数据

# 查看对应索引的所有的数据
# 语法:GET 索引/_search
GET kibana_sample_data_flights/_search

在这里插入图片描述
响应中最重要的部分是hits,它包含了total字段来表示匹配到的文档总数,hits数组还包含了匹配到的前10条数据。hits数组中的每个结果都包含_index、_type和文档的_id字段,被加入到_source字段中这意味着在搜索结果中我们将可以直接使用全部文档。这不像其他搜索引擎只返回文档ID,需要你单独去获取文档。
每个节点都有一个_score字段,这是相关性得分(relevance score),它衡量了文档与查询的匹配程度。默认的,返回的结果中关联性最大的文档排在首位;这意味着,它是按照_score降序排列的。这种情况下,我们没有指定任何查询,所以所有文档的相关性是一样的,因此所有结果的_score都是取得一个中间值1max_score指的是所有文档匹配查询中_score的最大值。在查询时,必须加上track_total_hits,不然就只显示10000

# 查看对应索引的所有的数据
# 语法:GET 索引/_search
GET kibana_sample_data_flights/_search
{
  "track_total_hits":true
  
}

查看对应索引的数据总量

# 查看对应索引的数据总量
GET kibana_sample_data_flights/_count

# 按条件查询数据总量
GET user/_count
{
  "query": {
    "match": {
      "about": "陆军上将"
    }
  }
}

在这里插入图片描述

查看某一个Document(某一条数据)的详细数据

# 查看某一个Document(某一条数据)的详细数据
# 语法:GET Document所在的索引/_doc/Document对应的id
GET kibana_sample_data_flights/_doc/mpxoJ4oB0aXjbqrzJSyK

查看对应索引的全部mapping(属性)

# 查看对应索引的全部mapping(属性)
# 语法:GET 索引/_mapping
GET kibana_sample_data_flights/_mapping
# 或者 
GET kibana_sample_data_flights
命令说明
_doc可以对具体的数据进行查询、修改、添加、删除的操作,如果向索引中添加一条id已经存在的数据会将原来数据覆盖,否则直接添加
_create可以对具体的数据进行添加,如果向索引中添加一条id已经存在的数据会报错添加不成功,否则直接添加
_update可以对具体的数据进行修改,如果数据的属性已经存在则修改,否则直接给数据添加一个属性(数据添加或者修改属性:要么直接用_doc覆盖,要么直接用_update添加)
_search对整个索引进行搜索
_mapping对整个索引进行获取属性

CRUD增删改查

# 添加数据,如果索引存在直接向索引添加数据,如果索引不存在则会创建索引,并向索引中添加数据
# 语法:POST 索引/_doc/id
POST user/_doc/1
{
  "firstname":"Douglas",
  "lastname":"MacArthur"
}

# 添加数据,如果索引存在直接向索引添加数据,如果索引中存在一条相同id的数据则会报错,如果索引不存在则会创建索引,并向索引中添加数据
# 语法:POST 索引/_create/id
POST user/_create/2
{
  "firstname":"Douglas",
  "lastname":"Jack",
  "age":47
}

# 修改数据,必须是在索引和数据都存在的情况下,如果数据的属性已经存在则修改,否则直接给数据添加一个属性
# 数据添加或者修改属性:要么直接用_doc覆盖,要么直接用_update添加
# 语法:POST 索引/_create/id
POST user/_update/2
{
  "doc":{
    "age":53,
    "address":"American"
  }
}

# 删除索引中的某条数据,不管数据存不存在都会删除
# 语法:POST 索引/_doc/id
DELETE user/_doc/90

#删除索引
DELETE user

# 获取索引中的某一条数据
GET user/_doc/2

批量添加

# 批量添加
# _bulk的操作,行为和请求体数据 都是不能换行的。
POST user/_bulk
{"index":{"_id":1}}
{"firstname" : "Douglas","lastname" : "MacArthur","chineseName" : "道格拉斯·麦克阿瑟","age":48,"address":"American","graduationSchool" : {"englishName" : "The United States Military Academy(West Point)","chinese" : "美国陆军学院(西点军校)"},"about" : "1899年,麦克阿瑟考入西点军校。第一次世界大战期间晋升为上校并前往法国参战。1919年6月,被任命为西点军校校长。20世纪20-30年代担任陆军参谋长。第二次世界大战时期历任美国远东军司令,西南太平洋战区盟军司令。1944年麦克阿瑟被授予陆军五星上将军衔。1945年9月2日以盟军最高统帅的身份主持了对日本的受降仪式并代表同盟国签字。二战后出任驻日盟军最高司令长官、远东军司令等职,对日本实行了一系列改革,之后出任“联合国军”总司令。1952年,参与美国共和党总统初选,但未胜出。1964年4月5日,麦克阿瑟因胆结石去世,终年84岁。","services":"陆军"}
{"index":{"_id":2}}
{"firstname" : "William Daniel","lastname" : "Leahy","chineseName":"威廉·丹尼尔·莱希","age" : 32,"address" : "American", "graduationSchool" : {"englishName" : "The United States Annapolis Naval Academy","chinese" : "美国亚那波里斯海军学院"},"about" : "威廉·丹尼尔·莱希1897年毕业於美国亚那波里斯海军学院,第一次世界大战期间指挥一艘海军运输舰,同当时任海军部助理部长的富兰克林·罗斯福结成生死之交。历任军械局局长(1927~1931)、航行局局长(1933~1935)、美国海军作战部长(1937年-1939年)、1936年晋升为海军上将。1939年因年迈退休。数月后,被罗斯福总统任命为波多黎各总督(1939年-1940年)、接著出任驻维希法国大使(1941年-1942年),美国参战后,担任新设置的总统参谋长职务(1942年-1949年)并主持美国参谋长联席会议,和英帝国总参谋长阿兰布鲁克子爵一起决定盟国大战略。1944年成为美国历史上首位获五星上将军衔的军官。次年随罗斯福参加雅尔塔会议。1945年4月罗斯福去世后,他在杜鲁门总统任内继任原职。1949年退休,写有战争回忆录《身历其境》(I Was There,1950)。","services":"海军"}
{"index":{"_id":3}}
{"firstname" : "Dwight David","lastname" : "Eisenhower","chineseName" : "德怀特·戴维·艾森豪威尔","age" : 53,"address" : "American","graduationSchool" : {"englishName" : "The United States Military Academy(West Point)","chinese" : "美国陆军学院(西点军校)"},"about" : "艾森豪威尔在1915年毕业于西点军校。1944年任欧洲盟军最高司令,晋升为五星上将。1952年竞选总统获胜,成为第34任美国总统,1956年再次竞选获胜,蝉联总统。1950年前后任美国哥伦比亚大学校长。1969年3月28日,艾森豪威尔在华盛顿因心脏病去世,享年78岁。","services":"陆军"}
{"index":{"_id":4}}
{"firstname" : "James Alward","lastname" : "Van Fleet","chineseName":"詹姆斯·奥尔沃德·范佛里特","age" : 47,"address" : "American","graduationSchool":{"englishName":"The United States Military Academy(West Point)","chinese":"美国陆军学院(西点军校)"},"about":"1915年美国西点军校毕业,晋升中尉。1916年参加墨西哥边界之战。1918年参加第一次世界大战。第二次世界大战期间任第4师第8步兵团团长、第23军第90师师长、第三军军长。1947年任第一集团军副司令。同年任美驻欧洲司令部副总司令。1948年在希腊任联合军事先头增援集团军司令。1951年在朝鲜战争中任美国第八集团军司令。1953年以上将军衔退休","services":"陆军"}
{"index":{"_id":5}}
{"firstname" : "Mark Wayne","lastname" : "Clark","chineseName":"马克·韦恩·克拉克","age" : 49,"address" : "American","graduationSchool":{"englishName":"The United States Military Academy(West Point)","chinese":"美国陆军学院(西点军校)"},"about":"美国四星上将,第二次世界大战期间的美国第五集团军司令与朝鲜战争时的联合国军指挥官。","services":"陆军"}
{"index":{"_id":6}}
{"firstname" : "Matthew Bunker","lastname" : "Ridgway","chineseName":"马修·邦克·李奇微","age" : 47,"address" : "American","graduationSchool":{"englishName":"The United States Military Academy(West Point)","chinese":"美国陆军学院(西点军校)"},"about":"美国著名军事家、陆军上将。1895年,李奇微出生于美国弗吉尼亚州门罗堡,曾经参加过朝鲜战争。1993年7月26日,李奇微去世。","services":"陆军"}

高级条件查询

某一个字段中包含了其中的一个关键词的查询
# 查询姓名包含怀或者特的人
# 某一个字段中包含了多个关键词中的一个关键词的查询
GET user/_search
{
  "query": {
    "match": {
      "chineseName": "怀 特"
    }
  }
}
某一个字段的包含了所有关键词的查询

# 查询姓名包含怀和特的人
# 某一个字段的包含了所有关键词的查询
GET user/_search
{
  "query": {
    "match": {
      "chineseName": {
        "query": "怀 特",
        "operator": "and"
      }
    }
  }
}
将关键词作为一个短语查询

# 查询姓名包含怀和者特的人
# 将关键词作为一个短语查询
GET user/_search
{
  "query": {
    "match_phrase": {
      "chineseName": "怀 特"
    }
  }
}
某一个字段的范围查询
# 查询年龄在47到50岁之间的人
# 某一个字段的范围查询
GET user/_search
{
  "query": {
    "range": {
      "age": {
        "gte": 47,
        "lte": 50
      }
    }
  }
}
两个字段中有一个包含了关键词的查询
# 查询毕业院校或者about中包含了"美国"的人
# 两个字段中有一个包含了关键词的查询
GET user/_search
{
  "query": {
    "multi_match": {
      "query": "美国",
      "fields": ["graduationSchool.chinese","about"]
    }
  }
}
多条件查询,必须满足所有的查询条件
# 查询年龄在49岁到53岁之间并且about中包含了总统的人
# 多条件查询,必须满足所有的查询条件
GET user/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "range": {
            "age": {
              "gte": 49,
              "lte": 53
            }
          }
        },
        {
          "match": {
            "about": "总统"
          }
        }
      ]
    }
  }
}
高级复合查询——should中的条件是或者关系,must中的条件是并且关系
# 高级复合查询
# should中的条件是或者关系,must中的条件是并且关系
GET user/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "range": {
            "age": {
              "gte": 49,
              "lte": 53
            }
          }
        }
      ],
      "must": [
        {
          "match": {
            "chineseName": {
              "query": "德怀特",
              "operator": "and"
            }
          }
        },
        {
          "match_phrase": {
            "about": "1915"
          }
        }
      ]
    }
  }
}
# 也可以这样写
GET user/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "range": {
            "age": {
              "gte": 49,
              "lte": 53
            }
          }
        },
        {
          "must": [
            {
              "match": {
                "chineseName": {
                  "query": "德怀特",
                  "operator": "and"
                }
              }
            },
            {
              "match_phrase": {
                "about": "1915"
              }
            }
          ]
        }
      ]
    }
  }
}

查询英文不区分大小写的原因:Elasticsearch在构建倒排索引的时候会将所有的英文字母转为小写,在进行查询的时候,分词器会将所有的关键词分词并转为小写去倒排索引中进行匹配

查询指定的Field
# 查询指定的Field
GET user/_search
{
  "_source":["about","graduationSchool.chinese"],
  "query": {
    "match": {
      "chineseName": "怀特"
    }
  }
}

keyword数据类型

keyword表示不分词,原始数据长啥样,匹配的时候就得写啥样,大小写、长度、内容必须完全一致

completion数据类型

自动补全(前缀匹配)对于一个搜索引擎或者电商网站来说都是使用最为频繁的一个功能,所以对性能要求是极高,所以ES6针对这种情况专门设计了一种数据 completion, 会将用户搜索过所有的数据都存储到内存中,下次再进行前缀匹配的直接从内存中找;它还能做到预先加载。要想做前缀匹配功能数据类型必须是completion。

在ES中有这样一个规则,索引一旦有数据,是不能调整 mapping的。因此我们只能通过删除原来的mapping,重新定义新的mapping的形式来修改mapping,由于mapping的定义方式比较复杂,我们可以如下步骤定义新的mapping:

1. 先规划好索引中将要存放的数据,然后根据规划随便写入一个样例数据,目的是让借助于ES本身的能力来自动帮我们 生成一个mapping;
2. 通过 GET example/_mapping 来获取样例数据自动生产的mapping, 然后拷贝"mappings" : {}这一部分的内容,将对应字段的type修改为completion
3. 通过DELETE example删除之前的索引;
4. 通过put example{mappings" : {}}将修改好的 "mappings" : {}这一部分的内容重新设置;
5. 导入正规数据; 

在这里插入图片描述
重新设置mapping的语法

PUT user
{
  "mappings" : {
      "properties" : {
        "about" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "address" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "age" : {
          "type" : "long"
        },
        "chineseName" : {
          "type" : "completion",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "firstname" : {
          "type" : "completion",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "graduationSchool" : {
          "properties" : {
            "chinese" : {
              "type" : "completion",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            },
            "englishName" : {
              "type" : "completion",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            }
          }
        },
        "lastname" : {
          "type" : "completion",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "services" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        }
      }
    }
}

使用自动补全(匹配前缀查询功能)

# 使用自动补全(匹配前缀查询功能)
# false表示返回结果中无_source内容:"_source": false,
# _source也可以指定只显示所指定的字段:"_source": "name",
# 或者使用数组表示指定多个字段:"_source": ["name","level"], 
GET user/_search
{
  "_source": false,
  "suggest": {
    "lastName_suggest":{	#lastName_suggest为自定义名称
      "prefix":"美国",
      "completion":{
        "field":"graduationSchool.chinese"
      }
    }
  }
}

自动补全(前缀匹配)即从索引库中匹配perfix:"value"前缀以value开头的倒排索引,再根据倒排索引匹配到对应的document

Analysis分词与Analyzer分词器

Analysis(只是一个概念),文本分析就是把全文本转换成一系列单词(term/token)的过程,也叫分词。Analysis 是通过分词器(Analyzer) 来实现的,可使用 ES 内置的分析器或者按需定制化分析器。数据写入的时候会将词条进行分词,数据查询的时候也会使用相同的分词器对所查询语句进行分词。最终分词的结果会存在于倒排索引库的字典中

ES内置分词器:

 - Standard Analyzer - 默认分词器,按词切分,小写处理
 - Simple Analyzer - 按照非字母切分(符号被过滤),小写处理
 - Stop Analyzer - 小写处理,停用词过滤(the ,a,is)
 - Whitespace Analyzer - 按照空格切分,不转小写
 - Keyword Analyzer - 不分词,直接将输入当做输出
 - Pattern Analyzer - 正则表达式,默认 \W+
 - Language - 提供了 30 多种常见语言的分词器
 - Customer Analyzer - 自定义分词器
分词器的组成
Character Filters

Character Filters:针对原始文本处理,比如去除 html 标签

Tokenizer

Tokenizer:按照规则切分为单词,比如按照空格切分、按标点符号切分

Token Filters

Token Filters:将切分的单词进行加工,比如大写转小写,删除 stopwords(停用词),增加同义语
在这里插入图片描述

# 使用ES内置分词器
GET _analyze
{
  "analyzer": "standard",
  "text": "2 Hello WORLD,The world is beautiful!"
}

ES内置的分词器并不能很好的支持中文的分词,所以我么需要安装中文分词器。常见的中文分词器:IK分词器拼音分词器

IK分词器

IK分词器支持自定义词库,支持热更新分词字典。安装步骤如下:

第一步:下载zip包,下载路径为:https://github.com/medcl/elasticsearch-analysis-ik/releases

第二步:将下载好的zip包解压后放在Elasticsearch的plugins目录下(/usr/share/elasticsearch/plugins)

# 方式一:通过docker cp命令
docker cp /data/elasticsearch/analyzer/ik elasticsearch:/usr/share/elasticsearch/plugins
# 方式二:通过数据卷
# 在原来的docker-compose.yml文件中加入
volumes:
  - /data/elasticsearch/analyzer:/usr/share/elasticsearch/plugins

第三步:删除原来的elasticsearch容器和kibana容器,执行docker-compose up命令

docker rm elasticsearch容器ID kibana容器ID
docker-compose up

IK分词插件对应的分词器有以下几种:ik_smartik_max_word

# 中文分词演示
GET _analyze
{
  "analyzer": "ik_smart",
  "text": "Docker菜鸟教程"
}
拼音分词器

拼音分词器的安装步骤:

第一步:下载ZIP包,下载路径为:https://github.com/medcl/elasticsearch-analysis-pinyin/releases

第二步:将下载好的zip包解压后放在Elasticsearch的plugins目录下(/usr/share/elasticsearch/plugins)

# 方式一:通过docker cp命令
docker cp /data/elasticsearch/analyzer/pinyin elasticsearch:/usr/share/elasticsearch/plugins
# 方式二:通过数据卷
# 在原来的docker-compose.yml文件中加入
volumes:
  - /data/elasticsearch/analyzer:/usr/share/elasticsearch/plugins

第三步:删除原来的elasticsearch容器和kibana容器,执行docker-compose up命令

docker rm elasticsearch容器ID kibana容器ID
docker-compose up
# pinyin分词器演示
GET _analyze
{
  "analyzer": "pinyin",
  "text": "Docker菜鸟教程"
}
在索引上使用分词器
# 在mapping中为需要使用分词器的field设置分词器
# analyzer表示该field所要使用的分词器
# search_analyzer:keyword表示在查询该field的时候查询的关键词不被分词处理,如果不写默认采用该字段所使用的分词器对查询的关键词进行分词
PUT user/_mapping
{
	"properties" : {
    "about" : {
      "type" : "text",
      "analyzer":"ik_max_word",
      "search_analyzer": "keyword", 
      "fields" : {
        "keyword" : {
          "type" : "keyword",
          "ignore_above" : 256
        }
      }
    },
    "address" : {
      "type" : "text",
      "analyzer":"ik_max_word",
      "search_analyzer": "keyword",
      "fields" : {
        "keyword" : {
          "type" : "keyword",
          "ignore_above" : 256
        }
      }
    }
}
自定义分词器

第一步:根据规则导入一个样例数据,目的是让elasticsearch帮我们生成mapping

PUT example
{
	"field_1":"value",
	"field_2":"value",
	"field_3":"value",
	"field_4":"value"
}

第二步:查看mapping,并复制修改mapping

GET example/_mapping

第三步:删除样例数据生成的index

DELETE example

第四步:自定义分词器

# 自定义analyzer中的属性都是和分词器分词的顺序对应:先走Character Filters,再走Tokenizer,最后走Token Filter
# char_filter:规则决定Character Filters组件对原始文本处理时处理的规则--原始文本如何处理
# tokenizer:规则决定Tokenizer组件分词的规则--如何分词
# filter:规则决定Token Filter对分词后的词语处理的规则--分词后如何处理(这里采用的是自定义规则)

# type:表示该分词器使用的基础分词器(分完词后再进行拼音分词)
# keep_full_pinyin:type后面的属性是对基础分词器的属性的设置
PUT user
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer":{
          "char_filter":"html_strip",
          "tokenizer":"keyword",
          "filter":"my_filter"
        }
      },
      "filter": {
        "my_filter":{
          "type": "pinyin",
          "keep_full_pinyin": false,
          "keep_joined_full_pinyin": true,
          "keep_none_chinese": false,
          "keep_original": true
        }
      } 
    }
  }
}
# 或者
PUT user/_settings
{
  "analysis": {
    "analyzer": {
      "my_analyzer":{
        "char_filter":"html_strip",
        "tokenizer":"keyword",
        "filter":"my_filter"
      }
    },
    "filter": {
      "my_filter":{
        "type": "pinyin",
        "keep_full_pinyin": false,
        "keep_joined_full_pinyin": true,
        "keep_none_chinese": false,
        "keep_original": true
      }
    } 
  }
}

char_filter可以选择的规则有以下这些规则
在这里插入图片描述
tokenizer可以选择的规则有以下这些规则
在这里插入图片描述

第五步:验证自定义分词器效果

GET user/_analyze
{
  "analyzer": "my_analyzer", 
  "text":"Elasticsearch 平台 — 大规模查找实时答案"
}

第六步:PUT examplet{“mappings”:{}}添加mapping

PUT user/_mapping
{
  "properties" : {
    "about" : {
      "type" : "text",
      "fields" : {
        "keyword" : {
          "type" : "keyword",
          "ignore_above" : 256
        }
      }
    },
    "address" : {
      "type" : "text",
      "fields" : {
        "keyword" : {
          "type" : "keyword",
          "ignore_above" : 256
        }
      }
    },
    "age" : {
      "type" : "long"
    },
    "chineseName" : {
      "type" : "completion",
      "analyzer":"my_analyzer",
      "search_analyzer": "keyword", 
      "fields" : {
        "keyword" : {
          "type" : "keyword",
          "ignore_above" : 256
        }
      }
    },
    "firstname" : {
      "type" : "completion",
      "analyzer":"my_analyzer",
      "search_analyzer": "keyword", 
      "fields" : {
        "keyword" : {
          "type" : "keyword",
          "ignore_above" : 256
        }
      }
    },
    "graduationSchool" : {
      "properties" : {
        "chinese" : {
          "type" : "completion",
          "analyzer":"my_analyzer",
          "search_analyzer": "keyword", 
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "englishName" : {
          "type" : "completion",
          "analyzer":"my_analyzer",
          "search_analyzer": "keyword", 
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        }
      }
    },
    "lastname" : {
      "type" : "completion",
      "analyzer":"my_analyzer",
      "search_analyzer": "keyword", 
      "fields" : {
        "keyword" : {
          "type" : "keyword",
          "ignore_above" : 256
        }
      }
    },
    "services" : {
      "type" : "text",
      "fields" : {
        "keyword" : {
          "type" : "keyword",
          "ignore_above" : 256
        }
      }
    }
  }
}

# 第七步:添加数据-(批量添加、logstash添加)
# _bulk的操作,行为和请求体数据 都是不能换行的。
POST user/_bulk
{"index":{"_id":1}}
{"firstname" : "Douglas","lastname" : "MacArthur","chineseName" : "道格拉斯·麦克阿瑟","age":48,"address":"American","graduationSchool" : {"englishName" : "The United States Military Academy(West Point)","chinese" : "美国陆军学院(西点军校)"},"about" : "1899年,麦克阿瑟考入西点军校。第一次世界大战期间晋升为上校并前往法国参战。1919年6月,被任命为西点军校校长。20世纪20-30年代担任陆军参谋长。第二次世界大战时期历任美国远东军司令,西南太平洋战区盟军司令。1944年麦克阿瑟被授予陆军五星上将军衔。1945年9月2日以盟军最高统帅的身份主持了对日本的受降仪式并代表同盟国签字。二战后出任驻日盟军最高司令长官、远东军司令等职,对日本实行了一系列改革,之后出任“联合国军”总司令。1952年,参与美国共和党总统初选,但未胜出。1964年4月5日,麦克阿瑟因胆结石去世,终年84岁。","services":"陆军"}
{"index":{"_id":2}}
{"firstname" : "William Daniel","lastname" : "Leahy","chineseName":"威廉·丹尼尔·莱希","age" : 32,"address" : "American", "graduationSchool" : {"englishName" : "The United States Annapolis Naval Academy","chinese" : "美国亚那波里斯海军学院"},"about" : "威廉·丹尼尔·莱希1897年毕业於美国亚那波里斯海军学院,第一次世界大战期间指挥一艘海军运输舰,同当时任海军部助理部长的富兰克林·罗斯福结成生死之交。历任军械局局长(1927~1931)、航行局局长(1933~1935)、美国海军作战部长(1937年-1939年)、1936年晋升为海军上将。1939年因年迈退休。数月后,被罗斯福总统任命为波多黎各总督(1939年-1940年)、接著出任驻维希法国大使(1941年-1942年),美国参战后,担任新设置的总统参谋长职务(1942年-1949年)并主持美国参谋长联席会议,和英帝国总参谋长阿兰布鲁克子爵一起决定盟国大战略。1944年成为美国历史上首位获五星上将军衔的军官。次年随罗斯福参加雅尔塔会议。1945年4月罗斯福去世后,他在杜鲁门总统任内继任原职。1949年退休,写有战争回忆录《身历其境》(I Was There,1950)。","services":"海军"}
{"index":{"_id":3}}
{"firstname" : "Dwight David","lastname" : "Eisenhower","chineseName" : "德怀特·戴维·艾森豪威尔","age" : 53,"address" : "American","graduationSchool" : {"englishName" : "The United States Military Academy(West Point)","chinese" : "美国陆军学院(西点军校)"},"about" : "艾森豪威尔在1915年毕业于西点军校。1944年任欧洲盟军最高司令,晋升为五星上将。1952年竞选总统获胜,成为第34任美国总统,1956年再次竞选获胜,蝉联总统。1950年前后任美国哥伦比亚大学校长。1969年3月28日,艾森豪威尔在华盛顿因心脏病去世,享年78岁。","services":"陆军"}
{"index":{"_id":4}}
{"firstname" : "James Alward","lastname" : "Van Fleet","chineseName":"詹姆斯·奥尔沃德·范佛里特","age" : 47,"address" : "American","graduationSchool":{"englishName":"The United States Military Academy(West Point)","chinese":"美国陆军学院(西点军校)"},"about":"1915年美国西点军校毕业,晋升中尉。1916年参加墨西哥边界之战。1918年参加第一次世界大战。第二次世界大战期间任第4师第8步兵团团长、第23军第90师师长、第三军军长。1947年任第一集团军副司令。同年任美驻欧洲司令部副总司令。1948年在希腊任联合军事先头增援集团军司令。1951年在朝鲜战争中任美国第八集团军司令。1953年以上将军衔退休","services":"陆军"}
{"index":{"_id":5}}
{"firstname" : "Mark Wayne","lastname" : "Clark","chineseName":"马克·韦恩·克拉克","age" : 49,"address" : "American","graduationSchool":{"englishName":"The United States Military Academy(West Point)","chinese":"美国陆军学院(西点军校)"},"about":"美国四星上将,第二次世界大战期间的美国第五集团军司令与朝鲜战争时的联合国军指挥官。","services":"陆军"}
{"index":{"_id":6}}
{"firstname" : "Matthew Bunker","lastname" : "Ridgway","chineseName":"马修·邦克·李奇微","age" : 47,"address" : "American","graduationSchool":{"englishName":"The United States Military Academy(West Point)","chinese":"美国陆军学院(西点军校)"},"about":"美国著名军事家、陆军上将。1895年,李奇微出生于美国弗吉尼亚州门罗堡,曾经参加过朝鲜战争。1993年7月26日,李奇微去世。","services":"陆军"}

第七步:验证

GET user/_search
{
  "suggest": {
    "chineseName_suggest": {
      "prefix":"dan",
      "completion":{
        "field":"chineseName"
      }
    }
  }
}

Logstash导入MySQL中的数据

第一步:在logstash的confing目录下创建一个.conf文件,写入以下内容:

input {
  jdbc {
    jdbc_driver_class => "com.mysql.jdbc.Driver"
    jdbc_connection_string => "jdbc:mysql://localhost:3306/es?useSSL=false&serverTimezone=UTC"
    jdbc_user => es
    jdbc_password => "123456"
    #启用追踪,如果为true,则需要指定tracking_column
    use_column_value => false
    #指定追踪的字段,
    tracking_column => "id"
    #追踪字段的类型,目前只有数字(numeric)和时间类型(timestamp),默认是数字类型
    tracking_column_type => "numeric"
    #记录最后一次运行的结果
    record_last_run => true
    #上面运行结果的保存位置
    last_run_metadata_path => "mysql-position.txt"
    statement => "SELECT * FROM news where tags is not null"
    #表示每天的 17:57分执行
    schedule => " 0 57 17 * * *"
  }
}

filter {
  mutate {
    split => { "tags" => ","}
  }
}
output {
  elasticsearch {
    #每一个document对应的_id的生成规则
    document_id => "%{id}"
    #每一个document对应的type
    document_type => "_doc"
    #生成的index名称
    index => "news"
    hosts => ["http://localhost:9200"]
  }
  stdout{
    codec => rubydebug
  }
}

第二步:将MySQL的驱动jar包放入logstash的/logstash-core/lib/jars目录下
第三步:在logstash的bin目录下执行logstash命令

logstash -f ../confing/example.conf

定制化高亮显示

需要在查询语句中添加highlight属性规则

GET user/_search
{
    "query": {
      "match": {
        "about": {
          "query": "陆军上将",
          "operator": "and"
        }
      }
    },
    "highlight": {
      "fields": {
        "about":{
          "pre_tags": "<span>",
          "post_tags": "</span>"
        }
      }
    },
    "from": 0,
    "size": 10
}

GET user/_search
{
  "query": {
    "match_phrase": {
      "about": "陆军上将"
    }
  },
  "highlight": {
    "fields": {
      "about": {
        "pre_tags": "<em>"
        , "post_tags": "</em>"
      }
    }
  }
}

在这里插入图片描述

提高搜索的准确度–自定义词库

因为在我们实际的业务中,有很多专业的业务词库并没有被IK收录,那么就会导致一个问题,数据在索引进ES的时候,不会将业务的专有词库作为一个名词,会拆分为一个个的字,导致搜索的准确度会受到很大的影响,所以我们需要在IK中添加一些针对业务的专有词库。

配置本地自定义词库–静态添加

第一步:在ik分词器的confing目录中创建一个自定义词库文件夹analysis

第二步:在analysis文件下创建一个.dic文件custom_analysis.dic

第三步:在custom_analysis.dic添加词语(custom_analysis.dic文件中的词语都不会再被分词),在custom_analysis.dic中加入自己的业务词库(一个词是一行)
在这里插入图片描述
第四步:编辑ik分词器confing目录下的IKAnalyzer.cfg.xml文件,将自己定义的词库配置进ik分词的词库
将自己的文件路径配置进去:<entry key="ext_dict">analysis/custom_analysis.dic</entry>

<?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">analysis/custom_analysis.dic</entry>
         <!--用户可以在这里配置自己的扩展停止词字典-->
        <entry key="ext_stopwords"></entry>
        <!--用户可以在这里配置远程扩展字典 -->
        <!-- <entry key="remote_ext_dict">words_location</entry> -->
        <!--用户可以在这里配置远程扩展停止词字典-->
        <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

第五步:验证自定义词库

GET user/_analyze
{
  "analyzer": "ik_max_word",
  "text": ["词库中的词"]
}

无论是何种方式,只能影响后续数据索引进 ES的分词,改变不了之前已经写入到ES中的数据的分词;那么我们需要将这些文档重新索引,执行如下的命令:

第六步:更新索引库–重新索引

# 自定义词库
POST user/_update_by_query
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "about": "将"
          }
        },
        {
          "match": {
            "about": "军"
          }
        }
        
      ]
    }
  }
}

第七步:重启Elasticsearch

这种静态添加的方式存在弊端在于,每次添加新的业务词库,都需要重启ES服务,所以就有了动态添加的方式。

配置远程自定义词库–动态添加

第一步:需要一个服务器,可以是nginx,也可以自己搭建;但是根据IK分词器的要求,这个服务器需要在请求头中返回 LastModified、ETag. 现在我们以 nginx 为例来说明:

第二步:在 nginx的/html 目录下新建一个创建一个analysis.txt文件,要注意该文件的编码是 utf-8

第三步:在analysis.txt文件中加入业务词库,一词一行

第四步:将analysis.txt 文件的访问路径配置到 ik分词器的/config/IKAnalyzer.cfg.xml文件中,如下所示:

<?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">words_location</entry> -->
	<!-- http://192.168.213.142/analysis.txt最终会访问到nginx的/html/analysis.txt文件 -->
	<entry key="remote_ext_dict">http://192.168.213.142/analysis.txt</entry>
	<!--用户可以在这里配置远程扩展停止词字典-->
	<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

第五步:验证自定义词库

GET user/_analyze
{
  "analyzer": "ik_max_word",
  "text": ["词库中的词"]
}

无论是何种方式,只能影响后续数据索引进 ES的分词,改变不了之前已经写入到ES中的数据的分词;那么我们需要将这些文档重新索引,执行如下的命令:

第六步:更新索引库–重新索引

# 自定义词库
POST user/_update_by_query
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "about": "将"
          }
        },
        {
          "match": {
            "about": "军"
          }
        }
        
      ]
    }
  }
}

这种动态添加的好处在于,每次添加新的业务词库,不需要重新启动服务器

添加近义词

在实际的业务中,很多时候在搜索的时候,有些内容可能不同的人表述方式不同,或者在专业或者非专业领域表述的方式不同,但是我们要达到相同的搜索效果,所以需要添加近义词,例如:

1. 例如美团搜索 住宿,会检索出 酒店、民宿、宾馆等。
2. 例如搜索 娱乐,会检索出 游乐场、影院、沐足等。

第一步:在Elasticsearch的confing目录下创建一个analysis/synonym.txt近义词文件

第二步:在 synonym.txt 文件中加入如下内容:

酒店,民宿,宾馆=>住宿
炒菜具,烹饪具,锅具,锅铲
娱乐,游乐场,影院,沐足
旅游景点,黄山,泰山,华山

对于第一行,在数据写入到 ES,将"酒店,民宿,宾馆"都映射为"住宿",这样导致的后果就是可以通过"住宿"可以收到"酒店,民宿,宾馆",但是单独去搜索"酒店",蒸具,"酒店"就搜不到了。

对于第二行,“炒菜具,烹饪具,锅具,锅铲"是"炒菜具"的同义词,在搜索"炒菜具"的时候,可以把"炒菜具,烹饪具,锅具,锅铲"都搜索出来;搜"烹饪具"可以搜索到"烹饪具,锅具,锅铲”;搜索"锅具",可以搜索到"锅具,锅铲",搜"锅铲"就只有"锅铲"。

第三步:自定义分词器,将近义词词库与分词器绑定

# synonyms_path会默认去Elasticsearch的confing目录下寻找该文件
PUT example/_settings
{
	"analysis": {
      "analyzer": {
        "example_analyzer": {
          "tokenizer": "ik_max_word",
          "filter": "example_synonym"
        }
      },
      "filter": {
        "example_synonym": {
          "type": "synonym",
          "synonyms_path": "analysis/synonym.txt"
        }
      }
    }
}

第四步:构建mapping,在mapping中指定对应的field使用的分词器。

# 可将第三步和第四步进行合并
PUT example
{
  "settings": {
    "analysis": {
      "analyzer": {
        "example_analyzer": {
          "tokenizer": "ik_max_word",
          "filter": "example_synonym"
        }
      },
      "filter": {
        "example_synonym": {
          "type": "synonym",
          "synonyms_path": "analysis/synonym.txt"
        }
      }
    }
  }, 
  "mappings": {
    "properties" : {
      "id" : {
        "type" : "long"
      },
      "price" : {
        "type" : "float"
      },
      "small_pic" : {
        "type" : "keyword"
      },
      "title" : {
        "type" : "text",
        "analyzer": "example_analyzer",
        "search_analyzer": "ik_smart"
      }
    }
  }
}

第五步:写入数据。然后可以执行近义词的搜索。

GET spu/_analyze
{
  "analyzer": "insert_analyzer",
  "text": ["柳岩"]
}

第六步,每次添加新的近义词,都需要重新启动ES服务,还需要对后写入的数据重新索引;

Spring Boot整合Elasticsearch

第一步:导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.elasticsearch.client/elasticsearch-rest-high-level-client -->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.8.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.elasticsearch/elasticsearch -->
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>7.8.1</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.73</version>
</dependency>

第二步:在容器中注入RestHighLevelClient

@Configuration
public class ESConfing extends AbstractElasticsearchConfiguration {
    @Override
    @Bean
    public RestHighLevelClient elasticsearchClient() {
        final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
                .connectedTo("192.168.11.132:9200")
                .build();
        return RestClients.create(clientConfiguration).rest();
    }
}

第四步:使用相关的API

@RestController
@RequestMapping("/user")
@CrossOrigin("*")
public class CompletionController {
    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @RequestMapping("/firstNameCompletion")
    public ResultPageBean<SearchResultModel> getFirstNameCompletion(String prefix) throws IOException {
        Request request = new Request("GET","user/_search");
        request.setJsonEntity(String.format("{" +
                "  \"_source\": false, " +
                "  \"suggest\": {" +
                "    \"chineseName_suggest\": {" +
                "      \"prefix\":\"%s\"," +
                "      \"completion\":{" +
                "        \"field\":\"chineseName\"" +
                "      }" +
                "    }" +
                "  }" +
                "}",prefix));

        RestClient lowLevelClient = restHighLevelClient.getLowLevelClient();
        Response response = lowLevelClient.performRequest(request);
        HttpEntity responseEntity = response.getEntity();
        String result = EntityUtils.toString(responseEntity);
        JSONArray options = JSONObject.parseObject(result).getJSONObject("suggest").getJSONArray("chineseName_suggest").getJSONObject(0).getJSONArray("options");

        List<SearchResultModel> resultList = options.stream().map(ele -> {
            String text = ((JSONObject) ele).getString("text");
            return new SearchResultModel(text);
        }).collect(Collectors.toList());

        ResultPageBean<SearchResultModel> stringResultPageBean = new ResultPageBean<SearchResultModel>(200,10,resultList);
        return stringResultPageBean;
    }

    @GetMapping("/allUserInfo")
    public ResultPageBean<JSONObject> getAllUserInfo() throws IOException {
        Request request = new Request("GET","/user/_search");
        Response response = restHighLevelClient.getLowLevelClient().performRequest(request);
        HttpEntity entity = response.getEntity();
        String resutl = EntityUtils.toString(entity);
        JSONArray hitsArray = JSONObject.parseObject(resutl).getJSONObject("hits").getJSONArray("hits");
        List<JSONObject> sourceList = hitsArray.stream().map(ele -> ((JSONObject) ele).getJSONObject("_source")).collect(Collectors.toList());
        ResultPageBean<JSONObject> jsonObjectResultPageBean = new ResultPageBean<>(200, 10, sourceList);
        return  jsonObjectResultPageBean;
    }
}

参考1

参考2

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值