ElasticSearch详细笔记

什么是ElasticSearch

Elasticsearch(简称ES)是一个基于Apache Lucene(TM)的开源搜索引擎,无论在开源还是专有领域,Lucene 可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。注意,Lucene 只是一个库。想要发挥其强大的作用,你需使用 Java 并要将其集成到你的应用中。

重要特性:

  • 分布式的实时文件存储,每个字段都被索引并可被搜索
  • 实时分析的分布式搜索引擎
  • 可以扩展到上百台服务器,处理PB级结构化或非结构化数据

基本概念&倒排索引

需要了解ElasticSearch中的一些基本概念。

- 索引(indices)
-- Databases 数据库 - 类型(type)
-- Table 数据表 - 文档(Document)
-- Row 行 - 字段(Field)
-- Columns 列

ElasticSearch中的倒排索引

ElasticSearch在插入数据的同时还会为这些数据维护了一张倒排索引表,通过这个倒排索引可以大大的提高搜索的性能

倒排索引(Inverted Index)也叫反向索引,有反向索引必有正向索引。简单来讲,正向索引是通过key找value,反向索引则是通过value找key。

举个例子:

  1. comment 表有 idcontent 两个字段,现在向 comment 表插入如下一条数据:

    id:1
    content:今天天气很好

    ElasticSearch 会把 content 的内容进行分词,可以分成三个词:今天天气很好。倒排索引表就如下:

    今天		[1]
    天气 [1]
    很好 [1]

    表示 "今天"、"天气"、"很好"这三个词在 1号记录中存在。

  2. 再向 comment 表中插入一条数据:

    id: 2
    content: 今天天气好冷

    继续将 content 的内容进行分词,得到:今天天气好冷。将这三个词添加到倒排索引表中

    今天		[1,2]
    天气 [1,2]
    很好 [1]
    好冷 [2]

    "今天""天气" 这两个词在 1号2号记录中都存在。

    "很好"1号 记录存在

    "好冷"2号记录存在

  3. 现在查询记录,检索条件:今天好冷

    通过 "今天好冷" 这个字符串进行检索记录,这种就属于通过value查找key

    ElasticSearch 首先将 "今天好冷"进行分词为:"今天""好冷"两个词。

    然后在倒排索引表中查询,发现 "今天" 这个词命中了 1号2号 记录,再看 "好冷"这个词命中了 2号记录。

    这里有个评分机制,2号记录经过对比发现命中 2次1号记录命中 1次。因此 2号记录的评分就比 1号 记录高。查询出来的结果顺序就是:

    id: 2 		content: 今天天气好冷
    id: 1 content: 今天天气很好

这就是倒排索引的基本逻辑,通过 value 查找 key。实际上,ElasticSearch引擎创建的倒排索引比这个复杂得多。

安装ElasticSearch&Kibana

Docker安装ElasticSearch

  1. 下载镜像

    docker pull elasticsearch:7.4.2		#存储和检索数据
  2. 创建实例需要挂载目录

    mkdir -p /mydata/elasticsearch/config
    mkdir -p /mydata/elasticsearch/data
    echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml chmod -R 777 /mydata/elasticsearch/
  3. 创建运行实例

    docker run --name es -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

    -e "discovery.type=single-node":单实例模式

    -e ES_JAVA_OPTS="-Xms64m -Xmx512m":设置运行的初始内存和最大内存

    -v:将本地文件映射到容器中对应文件

  4. 浏览器访问主机 9200 端口

Docker安装Kibana

Kibana 是一个基于 Node.js 的 Elasticsearch 索引库数据统计工具,可以利用 Elasticsearch 的聚合功能,生成各种图表,如柱形图,线状图,饼图等。

而且还提供了操作 Elasticsearch 索引数据的控制台,并且提供了一定的API提示,非常有利于我们学习 Elasticsearch 的语法。

安装步骤:

  1. 下载镜像

    docker pull kibana:7.4.2
  2. 查看 ElasticSearch 实例地址

    docker inspect es

    es:运行的 elasticsearch 容器实例名

  3. 创建运行实例

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

    172.17.0.3:地址填写上一步查询到的地址

  4. 浏览器访问主机 5601 端口

    注意:kibana启动可能有点慢,需要等待一会

ES基本操作

_cat

elasticsearch 提供 _cat API 来查看ElasticSearch状态

#0. 查看_cat支持的命令
GET /_cat #1. 查看所有节点
GET /_cat/nodes #2. 查看es健康状态
GET /_cat/health #3. 查看主节点
GET /_cat/master #4. 查看所有索引
GET /_cat/indices

例子:http://192.168.23.6:9200/_cat/indices

新增数据

elasticsearch 中保存的都是 json 格式的数据

现在向 es 添加一条数据 { "msg": "Hello ElasticSearch" }

  1. 访问 kibana,选择 Dev Tools

  2. 在这个界面操作 es


  • PUT 方式

    PUT /news/comment/1
    {
    "msg":"Hello ElasticSearch"
    }

    执行结果:

  • POST 方式

    POST /news/comment/1
    {
    "name":"Hello ElasticSearch"
    }

    执行结果:

可以理解为向 news 数据库的 comment 表中添加了一条记录,不过这里叫做索引和类型

分析结果:

_index: 索引,对应就是数据库名
_type: 类型,对应就是数据表
_id: 数据的id
_version: 版本号,通过操作数据版本号会不断增加
_result: created表示创建了一条数据,如果重新put一条数据,则该状态会变为updated,并且版本号也会发生变化。
_shards: 分片信息
_seq_no: 序列号
_primary_term:
  • PUT可以新增也可以修改。PUT必须指定id;
  • POST添加数据的时候不指定id,会自动的生成id,并且类型是新增
  • 由于PUT需要指定id,我们一般用来做修改操作,不指定id会报错。

查询数据

查看使用GET请求方式检索数据

GET /news/comment/1

可以理解为向 news 数据库的 comment 表中查询一条id1的记录

_source:保存的数据

更新数据

  • POST 方式

    POST /news/comment/1/_update
    {
    "doc": {
    "msg": "Hello ES"
    }
    }

    POST /customer/external/1
    {
    "msg": "Hello ES"
    }

    使用 _update 需要加 doc

    区别:

    • 使用 _update修改数据,版本号不会增加
  • PUT 方式

    PUT /news/comment/1
    {
    "msg": "Hello ES"
    }

删除数据

删除一条数据

DELETE /news/comment/1

删除一个索引

DELETE /customer

bulk批量API

bulk相当于数据库里的bash操作, 其支持的操作类型包括:index, create, update, delete

  • bulk 语法:

    { action: { metadata } }
    { requstbody }

批量新增 index

POST /news/comment/_bulk
{ "index": {"_id": 2} }
{ "msg": "zhangsan" }
{ "index": {"_id": 3} }
{ "msg": "lisi" }
{ "index": {"_id": 4} }
{ "msg": "wangwu" }

执行结果:

  • 参数解析:

    { "index": {"_id": 2} }

    { "msg": "zhangsan" }

    这两行为一次操作,第一行指定了数据的id(还可以指定 indextype;以下划线开头)

    第二行是保存的数据体

  • 结果解析:

    "took": 31:请求执行时间(毫秒)

    "error": false :请求是否出错,返回flase表示没有出错

    "items":操作过的文档的具体信息

    "static":响应状态码

批量新增 create

POST /news/comment/_bulk
{ "create": {"_id": 2} }
{ "msg": "zhangsan" }
{ "create": {"_id": 3} }
{ "msg": "lisi" }
{ "create": {"_id": 4} }
{ "msg": "wangwu" }

执行结果:

新增失败了,因为id重复问题

  • create方式新增,如果id已存在了就会报错
  • index方式新增,如果id存在不会报错,并且 version 增加

批量更新 update

POST /news/comment/_bulk
{ "update": {"_id": 2} }
{ "doc":{"msg": "zhangsan.cn"} }
{ "update": {"_id": 3} }
{ "doc":{"msg": "lisi.cn"} }
{ "update": {"_id": 4} }
{ "doc":{"msg": "wangwu.cn"} }

执行结果:

更新操作需要多加一层 doc

  • { "update": {"_id": 2} }

  • { "doc":{"msg": "zhangsan.cn"} }

​ 第一行update为更新操作,并指定了更新数据的id

​ 第二行 doc里面是更新的新数据。

批量删除 delete

批量删除不需要请求体(数据体)

POST /news/comment/_bulk
{ "delete": {"_id": 2} }
{ "delete": {"_id": 3} }
{ "delete": {"_id": 4} }

执行结果:

进阶检索

学习之前先为es添加一些测试数据,这里使用官方提供的测试数据 https://gitee.com/depthch/elasticsearch/blob/master/doc/test/resourses/accounts.json

  1. 打开上面链接将里面的数据复制

  2. 在 kibana 中使用 bulk 批量添加数据

    在索引为 bank,类型 account中批量插入了数据

ES支持两种基本方式检索

  • 通过REST request uri 发送搜索参数 (uri +检索参数)
  • 通过REST request body 来发送它们(uri+请求体)

_search

请求方式:uri + 检索参数

1、检索 bank 下所有信息

GET /bank/_search

响应结果解析:

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

2、检索 bank 下所有信息,并按照 account_number升序

GET /bank/_search?q=*&sort=account_number:asc

  • q=*:* 是通配符,表示查询所有的数据
  • sort=account_number:asc:按照 account_number 排序,asc 是升序

queryDSL

请求方式:uri + 请求体

基本语法
QUERY_NAME:{
ARGUMENT:VALUE,
ARGUMENT:VALUE,...
}

1、检索 bank 下所有信息,并按照 account_number降序

GET /bank/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"account_number": {
"order": "desc"
}
}
]
}

查询参数解析:

  • match_all查询类型【代表查询所有的所有】,es中可以在query中组合非常多的查询类型完成复杂查询;
  • 除了query参数之外,我们可也传递其他的参数以改变查询结果,如sort,size;
  • from+size限定,完成分页功能;
  • sort排序,多字段排序,会在前序字段相等时后续字段内部排序,否则以前序为准;
_source

_source:返回指定的部分字段

GET bank/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 5,
"sort": [
{
"account_number": {
"order": "desc"
}
}
],
"_source": ["balance","firstname"]
}

_source:指定返回的部分字段

match匹配查询
  • 基本类型(非字符串),精确控制

    GET bank/_search
    {
    "query": {
    "match": {
    "account_number": "20"
    }
    }
    }
  • 字符串,全文检索

    GET bank/_search
    {
    "query": {
    "match": {
    "address": "kings"
    }
    }
    }
match_phrase

match_phrase: 短句匹配,将需要匹配的值当成一整个单词(不分词)进行检索

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

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

match_phrasematch 的区别:

  • match:匹配时会分词,如:mill road,会拆分成:mill、road。然后检索出包含这两个词的记录(包含其中一个词也满足条件)
  • match_phrase:匹配时不会分词,如:mill road,会被当成一个整体来检索记录,必须包含整个整体的记录才会被检索出来
match.keyword

keyword 是精确匹配,就是说某条记录必须完全满足匹配条件才会被检索出来

GET bank/_search
{
"query": {
"match": {
"address.keyword": "990 Mill Road"
}
}
}
multi_math

multi_math:多字段匹配可以在多个字段中去匹配条件

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

state 或者 address 中包含 mill,并且在查询过程中,会对于查询条件进行分词。

bool

bool:用来做复合查询,复合语句可以合并,任何其他查询语句,包括符合语句。这也就意味着,复合语句之间可以互相嵌套,可以表达非常复杂的逻辑。

must

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

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

匹配 genderM并且 address包含 mill的文档

must_not

must_not,必须不匹配must_not所列举的所有条件。

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

匹配 genderM并且 address包含 mill的文档,但是 age 不等于38的数据

should

should:应该达到should列举的条件,如果到达会增加相关文档的评分,并不会改变查询的结果。

如果query中只有should且只有一种匹配规则,那么should的条件就会被作为默认匹配条件二区改变查询结果。

GET bank/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"gender": "M"
}
},
{
"match": {
"address": "mill"
}
}
],
"must_not": [
{
"match": {
"age": "18"
}
}
],
"should": [
{
"match": {
"lastname": "Wallace"
}
}
]
}
}
}

should"应该包含"的意思,不是必须包含,也就是除去其他匹配条件即使 lastName不包含 Wallace也能匹配成功。但是如果有数据的 lastName 包含 Wallace ,那么这条数据的相关性得分会更高,即优先匹配。

Filter

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

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

这里先是查询所有匹配 address 包含 mill 的文档,然后再根据 10000<=balance<=20000进行过滤查询结果

filter在使用过程中,并不会计算相关性得分,即 "_score" : 0.0

term

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

  • 全文检索字段用 match

  • 非text字段匹配用term

GET bank/_search
{
"query": {
"term": {
"address": "mill Road"
}
}
}

使用 term 匹配 text类型数据是匹配不到任何数据的。

Aggregation

聚合提供了从数据中分组和提取数据的能力。最简单的聚合方法大致等于SQL Group by和SQL聚合函数。在elasticsearch中,执行搜索返回this(命中结果),并且同时返回聚合结果,把以响应中的所有hits(命中结果)分隔开的能力。这是非常强大且有效的,你可以执行查询和多个聚合,并且在一次使用中得到各自的(任何一个的)返回结果,使用一次简洁和简化的API啦避免网络往返。

聚合语法:

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

常用聚合类型:

  • avg:求平均值
  • max:求最大值
  • min:求最小值
  • sum:求和
  • filter:过滤聚合。基于一个条件,来对当前的文档进行过滤的聚合。
  • terms:词聚合。基于某个field,该 field 内的每一个【唯一词元】为一个桶,并计算每个桶内文档个数。默认返回顺序是按照文档个数多少排序。

搜索address中包含mill的所有人的年龄分布以及平均年龄

GET bank/_search
{
"query": {
"match": {
"address": "Mill"
}
},
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 10
}
},
"ageAvg": {
"avg": {
"field": "age"
}
}
}
}

查出所有年龄分布,并且求这些年龄段的这些人的平均薪资

GET bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 100
},
"aggs": {
"ageAvg": {
"avg": {
"field": "balance"
}
}
}
}
}
}

聚合是可以嵌套聚合的。

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

GET bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 100
},
"aggs": {
"genderAgg": {
"terms": {
"field": "gender.keyword"
},
"aggs": {
"balanceAvg": {
"avg": {
"field": "balance"
}
}
}
},
"ageBalanceAvg": {
"avg": {
"field": "balance"
}
}
}
}
}
}

先按年龄聚合查出了所有分布情况,再嵌套聚合 按性别聚合查出分布情况,最后再嵌套聚合 按薪资聚合查出平均薪资。

ageBalanceAvg:根据年龄分布计算出平均工资,这个聚合跟性别无关。

mapping

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

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

查看 bank 索引的 mapping 映射信息

GET bank/_mapping

执行结果:

properties 中可以看到每个 field 的字段类型

新版本的改变

ElasticSearch7 去掉了type(表)概念

  1. 关系型数据库中两个数据表示是独立的,即使他们里面有相同名称的列也不影响使用,但ES中不是这样的。elasticsearch是基于Lucene开发的搜索引擎,而ES中不同type下名称相同的filed最终在Lucene中的处理方式是一样的。

    • 两个不同type(表)下的两个名称 user_name(字段),在ES同一个索引下其实被认为是同一个filed,你必须在两个不同的type(表)中定义相同的 filed 映射。否则,不同 type(表)中的相同字段名称就会在处理中出现冲突的情况,导致Lucene处理效率下降。
    • 去掉type(表)就是为了提高ES处理数据的效率。
  2. Elasticsearch 7.x URL中的type参数为可选。比如,索引一个文档不再要求提供文档类型。
  3. Elasticsearch 8.x 不再支持URL中的type参数。
创建映射
PUT /student
{
"mappings": {
"properties": {
"age": {
"type": "integer"
},
"email": {
"type": "keyword"
},
"name": {
"type": "text"
}
}
}
}

执行结果:

创建 student 索引并指定了索引的 mapping 映射信息。

properties:指定映射的字段和字段类型

查看映射
GET /student

执行结果:

添加新的字段映射

student索引添加一个新的字段映射

PUT /student/_mapping
{
"properties": {
"id": {
"type": "keyword",
"index": false
}
}
}

执行结果:

再次查看映射:

"index": false:表明新增的字段不能被检索,只是一个冗余字段。

数据迁移

由于ElasticSearch是不支持修改映射字段的,只能添加映射字段。如果必须修改就需要数据迁移。

需求:将 bank 索引的所有数据迁移到 newbank 索引下,并将 age 字段类型改为 integer。修改 city、email、employer、gender等字段类型改为 keyword

具体步骤:

  1. 查看 bank 的映射信息

    GET /bank

  2. 创建一个跟 bank 索引字段相同 mapping 映射的索引,并且改变字段类型

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

    在指定映射信息时改变了字段的类型。

  3. 可以查看到 newbank 索引的映射信息

    GET /newbank

    执行结果:

    字段类型都已经改变。

  4. bank 索引中的数据迁移到 newbank 索引中

    POST _reindex
    {
    "source": {
    "index": "bank",
    "type": "account"
    },
    "dest": {
    "index": "newbank"
    }
    }

    执行结果:

    source:指定旧索引信息

    • index:指定旧的索引名
    • type:指定 type,如果没有 type 可以不指定。

    dest:指定新索引信息

    • index:指定新的索引名
  5. 查看 newbank 中的数据

    GET /newbank/_search

    执行结果:

    检索了1000条数据,数据迁移成功。发现 type: _doc,我们在创建映射关系时并没有设置 type,这是因为ElasticSearch7 去掉了type(表)概念,但是有个默认的 type 就是 _doc

分词

一个 tokenizer(分词器)接收一个字符流,将之分割为独立的 tokens(词元,通常是独立的单词),然后输出tokens 流。例如:hello world 遇到空白字符时分割文本。它会将文本 "hello world" 分割为 [hello, world]

tokenizer(分词器)还负责记录各个terms(词条)的顺序或position位置(用于phrase短语和word proximity词近邻查询),以及term(词条)所代表的原始word(单词)start(起始)end(结束)character offsets(字符串偏移量) (用于高亮显示搜索的内容)

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

使用分词器

POST _analyze
{
"analyzer": "standard",
"text": "Nothing is impossible!"
}

执行结果:

standard:ElasticSearch默认的分词器,默认就是按空格进行分词的

安装 ik 分词器

所有的语言分词,默认使用的都是“Standard Analyzer”,但是这些分词器针对于中文的分词,并不友好。为此需要安装中文的分词器。

具体步骤:

  1. 查看 ElasticSearch 的版本

    访问es主机的 9200 端口查看es版本

  2. 下载对应版本的 ik 分词器

    在前面安装的elasticsearch时,我们已经将elasticsearch容器的“/usr/share/elasticsearch/plugins”目录,映射到本地主机的“ /mydata/elasticsearch/plugins”目录下.

    2.1)进入 /mydata/elasticsearch/plugins目录

    cd /mydata/elasticsearch/plugins

    2.2)下载 ik 分词器

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

    注意 7.4.2是自己es对应的版本。

  3. 解压下载好的文件

    unzip elasticsearch-analysis-ik-7.4.2.zip -d ik
  4. 重启es

    docker restart es
  5. 查看安装好的ik

    GET _cat/plugins

    执行结果:

    可以看到我们的ik分词器已经配置成功了

使用ik分词器

ik分词器有两种分词模式:ik_max_wordik_smart 模式。

  • ik_max_word

    会将文本做最细粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为“中华人民共和国、中华人民、中华、华人、人民共和国、人民、共和国、大会堂、大会、会堂等词语。

  • ik_smart

    会做最粗粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为中华人民共和国、人民大会堂。

先来看看默认的分词器

GET _analyze
{
"text":"每天都要努力"
}

执行结果:


ik 分词器

GET _analyze
{
"analyzer": "ik_smart",
"text":"每天都要努力"
}

执行结果:

可以看到使用 ik 分词器可以把一些常用的中文词分出来了。

自定义词库

虽然使用 ik 分词器默认的词库已经可以实现常用的中文分词了,但是如果我们要分的词不常用,如:张明想学Java

GET _analyze
{
"analyzer": "ik_smart",
"text":"张明想学Java"
}

执行结果:

可以看到这里把 "张明" 拆成了 "张""明",这并我是预想的效果,"张明" 应该拆成整体。

使用自定义词库,因为要使用 "远程扩展字典",因此就需要一个远程的字典文件。这里可以使用 nginx来配置远程扩展字典文件。文档最后有nginx安装和配置步骤

具体步骤:

  1. 配置好 nginx 后,创建词库文件

    vi /mydata/nginx/html/fenci.txt

    添加如下内容并保存:

    张明
    学java

  2. 修改es-plugins配置文件

    vi /mydata/elasticsearch/plugins/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">http://192.168.16.6/fenci.txt</entry>
    <!--用户可以在这里配置远程扩展停止词字典-->
    <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
    </properties>

    注意:修改第10行需要改成 nginx 服务的地址(这行默认是注释的)。

  3. 修改了配置文件需要重启 es

    docker restart es
  4. 再次使用 ik 分词器

    GET /_analyze
    {
    "analyzer": "ik_max_word",
    "text":"张明想学Java"
    }

//TODO 执行结果

....

SpringBoot 整合 ElasticSearch

SpringBoot可以通过 92009300端口来调用 ElasticSearch,它们之间的区别:

  • 9300:TCP

    SpringBoot提供了 spring-data-elasticsearch:transport-api.jar;来对 ES调用。这种方式有些缺陷:

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

    jestClient:非官方,更新慢;

    RestTemplate:模拟HTTP请求,ES很多操作需要自己封装,麻烦;

    HttpClient:模拟HTTP请求,ES很多操作需要自己封装,麻烦;


    Elasticsearch-Rest-Client:官方RestClient,封装了ES操作,API层次分明,上手简单;

根据上面分析,我们最终选择 Elasticsearch-Rest-Client 来进行调用es

具体步骤:

  1. 添加依赖

    <dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.4.2</version>
    </dependency> <dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>7.4.2</version>
    </dependency> <dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>7.4.2</version>
    </dependency>
  2. 创建一个 ElasticSearch的配置类

    @Configuration
    public class ElasticSearchConfig {
    public static final RequestOptions COMMON_OPTIONS;
    static {
    RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
    COMMON_OPTIONS = builder.build();
    } @Bean
    public RestHighLevelClient esRestClient(){
    RestHighLevelClient client = new RestHighLevelClient(
    RestClient.builder(new HttpHost("192.168.16.6", 9200, "http")));
    return client;
    }
    }

    HttpHost("192.168.16.6", 9200, "http")

    • 192.168.16.6:es服务的地址
    • 9200:es服务9200端口
    • http:使用http协议

    配置类中创建了一个 JavaBen,之后通过这个 JavaBean 来调用 ElasticSearch 的相关 API

保存数据

@RunWith(SpringRunner.class)
@SpringBootTest
public class MallSearchApplicationTests {
@Autowired
RestHighLevelClient client; /**
* 测试保存数据
*/
@Test
public void contextLoads() throws IOException {
IndexRequest request = new IndexRequest("users"); //创建索引对象
request.id("10"); //设置id
//source方法可以直接传入多个键值对值保存
//request.source("name", "lisi", "age", 24, "gender", "男"); User user = new User(); //创建一个实体类user
user.setName("java");
user.setAge(24);
user.setGender("男"); String jsonString = JSON.toJSONString(user); //解析实体转成json字符串 request.source(jsonString, XContentType.JSON); //传入json格式字符串保存
IndexResponse response = client.index(request, ElasticSearchConfig.COMMON_OPTIONS); System.out.println(response); //打印结果
} /**
* 定义 user 实体类
*/
@Data
class User{
private String name;
private int age;
private String gender;
}
}

执行结果:

   IndexResponse[index=users,type=_doc,id=10,version=2,result=updated,seqNo=3,primaryTerm=6,shards={"total":2,"successful":1,"failed":0}]

kibana查看:

数据测试添加成功。

检索数据

搜索address中包含mill的所有人的年龄分布以及平均年龄

QueryDSL 实现:

GET bank/_search
{
"query": {
"match": {
"address": "Mill"
}
},
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 10
}
},
"ageAvg": {
"avg": {
"field": "age"
}
}
}
}

执行结果:

java代码实现:

首先需要生成实体类,因为从es获取的数据在java中最终都会保存为java对象。

需要根据 _source 中的字段生成 java 类 account

测试类代码:

@RunWith(SpringRunner.class)
@SpringBootTest
public class MallSearchApplicationTests { @Autowired
RestHighLevelClient client; /**
* 按照 bank 索引里的 _source 数据字段创建对应的实体类
*/
@Data
@ToString
static class Account{
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 searchData() throws IOException {
//1、创建检索对象
SearchRequest searchRequest = new SearchRequest();
//指定索引
searchRequest.indices("bank");
//指定DSL&检索条件
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); //构建检索条件
//sourceBuilder.query();
//sourceBuilder.from();
//sourceBuilder.size();
//sourceBuilder.aggregation();
sourceBuilder.query(QueryBuilders.matchQuery("address", "mill")); //构建聚合条件: 按照年龄值分布进行聚合
TermsAggregationBuilder ageAgg = AggregationBuilders.terms("ageAgg").field("age").size(10);
sourceBuilder.aggregation(ageAgg); //构建聚合条件: 计算平均工资
AvgAggregationBuilder ageAvgAgg = AggregationBuilders.avg("ageAvgAgg").field("age");
sourceBuilder.aggregation(ageAvgAgg); searchRequest.source(sourceBuilder); SearchResponse searchResponse = client.search(searchRequest, ElasticSearchConfig.COMMON_OPTIONS); //结果分析
SearchHits hits = searchResponse.getHits();
SearchHit[] hitsHits = hits.getHits();
for(SearchHit hit : hitsHits){
String sourceAsString = hit.getSourceAsString(); //将结果转成 javeBean
Account account = JSON.parseObject(sourceAsString, Account.class);
System.out.println("account:" + account);
} //获取检索到的聚合信息
Aggregations aggregations = searchResponse.getAggregations();
Terms ageAgg1 = aggregations.get("ageAgg"); // 打印聚合结果
for (Terms.Bucket bucket : ageAgg1.getBuckets()) {
String keyAsString = bucket.getKeyAsString();
System.out.println("年龄:" + keyAsString + "===> " + bucket.getDocCount());
} Avg balanceAgg1 = aggregations.get("ageAvgAgg");
System.out.println("平均年龄:" + balanceAgg1.getValueAsString());
}
}

执行结果:

这里执行结果跟queryDSL查询是相同的。

附:Docker 安装 Nginx

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

    docker run -p 80:80 --name nginx -d nginx:1.10
  2. 创建目录并且将容器内部配置文件拷贝到外部

    mkdir -p /mydata/nginx/html
    mkdir -p /mydata/nginx/logs
    mkdir -p /mydata/nginx/conf
    docker container cp nginx:/etc/nginx/* /mydata/nginx/conf/
    #由于拷贝完成后会在conf中存在一个nginx文件夹,所以需要将它的内容移动到conf中
    mv /mydata/nginx/conf/nginx/* /mydata/nginx/conf/
    rm -rf /mydata/nginx/conf/nginx
  3. 终止容器&删除原来的容器

    docker stop nginx
    docker rm nginx
  4. 创建新的 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

ElasticSearch详细笔记的更多相关文章

  1. ElasticSearch学习笔记(超详细)

    文章目录 初识ElasticSearch 什么是ElasticSearch ElasticSearch特点 ElasticSearch用途 ElasticSearch底层实现 ElasticSearc ...

  2. ElasticSearch NEST笔记

    ElasticSearch NEST笔记 1. 什么是ElasticSearch? ElasticSearch is a powerful open source search and analyti ...

  3. (转载)Linux下安装配置MySQL+Apache+PHP+WordPress的详细笔记

    Linux下安装配置MySQL+Apache+PHP+WordPress的详细笔记 Linux下配LMAP环境,花了我好几天的时间.之前没有配置过,网上的安装资料比较混乱,加上我用的版本问题,安装过程 ...

  4. Elasticsearch学习笔记一

    Elasticsearch Elasticsearch(以下简称ES)是一款Java语言开发的基于Lucene的高效全文搜索引擎.它提供了一个分布式多用户能力的基于RESTful web接口的全文搜索 ...

  5. xshell远程终端操作Ubuntu server安装LAMP环境之最详细笔记之二PHP开发环境配置

    前言: 昨天学会了安装server,今天试着通过远程终端xshell来安装LAMP,搭配一下开发环境,也有集成环境可以一键安装使用,还是瞎折腾一下,手动一步一步搭建一下这个开发环境. 接上一篇:ubu ...

  6. elasticsearch学习笔记——相关插件和使用场景

    logstash-input-jdbc学习 ES(elasticsearch缩写)的一大优点就是开源,插件众多.所以扩展起来非常的方便,这也造成了它的生态系统越来越强大.这种开源分享的思想真是与天朝格 ...

  7. 清晰易懂!关于PS入门的超详细笔记!

    给大家分享一篇关于PS入门的超详细笔记!原理讲解清晰明了,虽不是新版本解析,但都是新手学习PS必掌懂的一些知识点,灰常的实用,转走收藏学习! 编辑:千锋UI设计 来源:PS学堂

  8. java编写service详细笔记 - centos7.2实战笔记(windows类似就不在重复了)

    java编写service详细笔记 - centos7.2实战笔记(windows类似就不在重复了)  目标效果(命令行启动服务): service xxxxd start #启动服务  servic ...

  9. python聚类算法实战详细笔记 (python3.6+(win10、Linux))

    python聚类算法实战详细笔记 (python3.6+(win10.Linux)) 一.基本概念:     1.计算TF-DIF TF-IDF是一种统计方法,用以评估一字词对于一个文件集或一个语料库 ...

随机推荐

  1. 解决vue侧边栏一级菜单问题

    最近我在学习vue,然后遇到一个问题,就是跟着视频里面的代码敲,出现了一些不好解决的问题 这是两个一级目录,我遇到的问题就是点击第一个一级目录,另外一个一级目录也会展开, 前端代码是这样的,和视频里面 ...

  2. 我把公司 10 年老系统改造 Maven,真香!!

    公司有几个老古董项目,应该是 10 年前开发的了,有一个是 JSP + Servlet,有一个还用的 SSH 框架,打包用的 Ant,是有多老啊,我想在座的各位很多都没听过吧. 为了持续集成.持续部署 ...

  3. 常用JVM 启动参数解析

    https://www.jianshu.com/p/a17dcef57559 https://www.cnblogs.com/zkyefei/p/9334562.html

  4. 提权 EXP

    windows: 漏洞列表 #Security Bulletin #KB #Description #Operating System CVE-2017-0213 [Windows COM Eleva ...

  5. C语言实现数据结构的邻接矩阵----数组生成矩阵、打印、深度优先遍历和广度优先遍历

    写在前面 图的存储结构有两种:一种是基于二维数组的邻接矩阵表示法. 另一种是基于链表的的邻接表表示法. 在邻接矩阵中,可以如下表示顶点和边连接关系: 说明: 将顶点对应为下标,根据横纵坐标将矩阵中的某 ...

  6. 花时三月 终于Spring Boot 微信点餐开源系统! 附源码

    架构 前后端分离:             Nginx与Tomcat的关系在这篇文章,几分钟可以快速了解: https://www.jianshu.com/p/22dcb7ef9172 补充: set ...

  7. 什么是垃圾搜集(GC)?为什么要有GC呢?

    GC的全称是Gabage Collection,翻译过来就是"垃圾收集"的意思.那么我们为什么用GC呢? 那么我们接下来就来聊一聊GC的创造背景.在C和C++那个年代的程序员界的长 ...

  8. 超详细的 Vagrant 上手指南

    搭建 Linux 虚拟机,别再用 VirtualBox 从 .iso 文件安装了. 概述 2020 年了,也许你已经习惯了 docker,习惯了在 XX 云上快速创建云主机,但是如果你想在个人电脑上安 ...

  9. PHP判断是否是微信浏览器访问的方法

    PHP判断是否是微信浏览器访问的方法 PHP判断是否是微信浏览器访问的方法 都是干货,微信开发可能需要用到,留着日后COPY. public function isWeichatBrowser() { ...

  10. django 的初始项目结构

    2.创建Django项目   root@dev:shiyanlou_project# workon syl (syl) root@dev:shiyanlou_project# cd /aaa/shiy ...