Elasticsearch 入门

1. 安装与准备

安装 es 就是按部就班:

  • 查看插件:./elasticsearch-plugin list
  • 安装插件:./elasticsearch-plugin install xxx
  • 部署:./elasticsearch
  • 多节点部署:
1
2
3
./elasticsearch -E node.name=node1 -E cluster.name=geektime -E path.data=node1_data -d
./elasticsearch -E node.name=node2 -E cluster.name=geektime -E path.data=node2_data -d
./elasticsearch -E node.name=node3 -E cluster.name=geektime -E path.data=node3_data -d
  • 删除部署的节点:
1
ps -ef | grep elasticsearch | awk '{print $2}' | xargs kill -9

安装一下 Kibana ,端口是 5601 :

  • 查看插件 ./kibana-plugin list
  • 安装插件 ./kibana-plugin install xxx

当然也能用 docker 装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
version: '2.2'
services:
cerebro:
image: lmenezes/cerebro:0.9.1
container_name: cerebro
ports:
- "9000:9000"
command:
- -Dhosts.0.host=http://elasticsearch:9200
networks:
- es7net
kibana:
image: docker.elastic.co/kibana/kibana:7.7.0
container_name: kibana7
environment:
- I18N_LOCALE=zh-CN
- XPACK_GRAPH_ENABLED=true
- TIMELION_ENABLED=true
- XPACK_MONITORING_COLLECTION_ENABLED="true"
ports:
- "5601:5601"
networks:
- es7net
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.7.0
container_name: es7_01
environment:
- cluster.name=geektime
- node.name=es7_01
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- discovery.seed_hosts=es7_01,es7_02
- cluster.initial_master_nodes=es7_01,es7_02
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- es7data1:/usr/share/elasticsearch/data
ports:
- 9200:9200
networks:
- es7net
elasticsearch2:
image: docker.elastic.co/elasticsearch/elasticsearch:7.7.0
container_name: es7_02
environment:
- cluster.name=geektime
- node.name=es7_02
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- discovery.seed_hosts=es7_01,es7_02
- cluster.initial_master_nodes=es7_01,es7_02
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- es7data2:/usr/share/elasticsearch/data
networks:
- es7net

volumes:
es7data1:
driver: local
es7data2:
driver: local

networks:
es7net:
driver: bridge

一个 cerebro 0.9.1、一个 kibana 7.7.0、两个 es 7.7.0。

接着是 Logstash ,尝试将 movielens 数据集写入 es ,使用指令:./logstash -f logstash.conf 执行。

需要提前准备数据集。不再继续打印输信息了,就算结束了。logstash 会监听文件,如有变化,会继续更新。

2. 基本概念

ES 是面向文档的,文档被保存成 JSON 格式,每一个文档都有一个 Unique ID。

每一个文档都有『元数据』,_index 文档索引名,_type 文档所属的类型名,_id 唯一 ID,_source 原始 JSON,_all 整合所有内容(已经废除),_version 文档版本信息,_score 相关性打分。

索引 Index 是文档的容器,表示一类文档的集合。

  • Index 提现了逻辑空间的概念。每个索引都有自己的 Mapping ,用于定义包含文档的字段与类型。
  • Shard 是物理空间的概念。索引中的数据分散在 Shard 中。
  • Mapping 定义字段类型,Setting 定义不同的数据分布。

索引在 es 中也可以给表示动词:索引(动)文档到 es 的索引(名)中。

动词『索引』指保存的意思。

在 7.0 前,一个 Index 可以设置多个 Type ,但是 7.0 开始一个索引只能设置一个 Type _doc

与 RDBMS 相比,es 中与之对应的概念:

RDBMS Elasticsearch
Table Index(Type)
Row Document
Column Filed
Schema Mapping
SQL DSL
  • es 偏向于相关性的全文检索;
  • RDBMS 事务性更强。

节点与分片是重要的概念:

  • es 不同的集群通过名字来划分,默认为『elasticsearch』。通过 -E cluster.name=xxx 来设置集群名字,一个集群有一个或多个节点。
  • 节点是一个 es 的实例,本质上是一个进程,生产环境推荐一台机器一个实例(节点),每个节点都有名字通过 -E node.name=xxx 来设置,每个节点启动后会有一个 UID,保存在 data 目录下。
  • 每个节点启动默认是一个『Master-eligible』节点,可以通过设置 node.master: false 禁止,只有 Master 节点可以修改集群的状态信息。
    • Data Node 是可以保存数据的节点,负责保存分片数据;
    • Coordinating Node 负责接收 Client 的请求,将请求分发到合适的节点上,最终将结果聚集返回(每个节点默认都有 Coordinating Node 的职责);
    • Hot Node 是配置比较高的节点(一般而言),而 Warm Node 则保存比较旧的数据(一般配置较低);
    • Machine Learning Node 负责机器学习的 Job ,用来异常检测;
    • Tribe Node 逐渐淘汰,改用 Cross Cluster Search 用于连接不同的集群,将这些集群当做一个集群。

开发环境一个节点可以承担多个角色,生产环境则应该设置单一角色。

1
2
3
4
5
6
7
8
9
10
# master eligible
node.master = true
# data
node.data = true
# ingest
node.ingest = true
# ml
node.ml = true
# coordinating
#
  • Primary Shard,主分片,解决水平扩展的问题,将数据分布到集群内所有节点上。
    • 一个分片是一个 Lucene 实例。
    • 主分片数在索引创建时指定,后续不能更改,除非 Reindex。
  • Replica Shard,副本,用于解决数据高可用的问题,分片是主分片的拷贝。
    • 副本分片可以动态调整。
    • 增加副本数,可以在一定程度上提高服务的可用性。
  • 主分片如果过小,导致无法增加节点实现水平扩展,同时单个节点数据量过大,数据查询耗时。
  • 主分片如果过大,默认是 1 ,将影响搜索结果的相关性打分,同时单个节点上分片过多,会资源浪费,同时影响性能。

集群有健康状况 GET _cluster/health ,Green 都正常,Yellow 主正常部分副本不正常,Red 主不正常。

3. CRUD 与批量操作

Http Method + Index Name + Type + Doc Id。

  • Type 约定用 _doc
  • Create 如果 Id 存在,会失败;
  • Index 如果 Id 不存在,创建新的文档,否则删除现有文档,再创建新的文档,版本会增加;
  • Update 如果文档存在,更新只会对相应字段做增量操作;

3.1 Create

  • 通过 post /users/_doc 系统自动生成 Doc Id;
  • 使用 put user/_create/1put users/_doc/1?op_type=create 显式指定 Id ,如果 Id 存在,则失败;
    • 先执行了自动生成,再执行手动指定的时候,会成功,但是第二次执行会失败(因为自动生成的 Id 是随机的,不会在这里发生冲突)

3.2 Get

  • 找到则返回 200 get users/_doc/1
    • _version 表示文档经过多少次改动;
  • 找不到则 404

3.3 Index

如果文档不存在,就创建新的文档;否则现有文档被删除,再创建新的文档,版本增加。

  • 使用 put user/_doc/1 进行操作;

3.4 Update

不会删除原有的文档,而是实现真正的数据更新。

  • 使用 post users/_update/1 进行操作;
  • 在执行时必须指明 doc

3.5 Bulk API

  • 在一次 API 调用中,对不同的索引进行操作,支持:Index Create Update Delete。
  • 单条操作失败不会影响其他结果;最终结果包含每一条操作。
  • post _bulk 开始,其中指定各种 Index 。

3.6 批量读取 mget

  • 批量操作,用 Id 获得详情,减少网络连接所产生的开销,提高性能。
  • get _mget 开始,可以在 URI 中指定 Index,也可以在请求体中说明 Index。

3.7 批量查询 msearch

  • post _msearch 开始,API 更加丰富,根据条件进行查询。

429 集群繁忙。

4. 倒排索引

目录是正排索引 Id -> 内容,书的结尾的索引页是倒排索引 内容 -> Id。

倒排索引包括两个部分:

  • 单词词典 Term Dictionary,记录所有文档的单词,记录单词到倒排索引的关系,可以用 B+ 树或哈希拉链,满足高性能的插入与查询。
  • 倒排列表 Posting List,记录单词对应的文档结合,由倒排索引项组成。倒排索引项包括:
    • 文档 Id
    • 词频 TF
    • 位置,用于语句搜索
    • 偏移,单词开始到结束的位置,高亮显示

5. Analyzer 分词

Analysis 文本分析可以把全文文本转化成一系列单词,也就是分词。Analysis 是通过 Analyzer 实现的。

Analyzer 分词器由三个部分组成:Character Filters 针对原始文本进行处理、Tokenizer 根据一定规则进行字符串切分、Token Filters 将切分结果进行二次加工。

  • Standard Analyzer - 是默认分词器,按词进行切分,小写处理。
  • Simple Analyzer – 按照非字母切分(符号、数字被过滤),小写处理
  • Stop Analyzer – 小写处理,停用词过滤(the,a,is)
  • Whitespace Analyzer – 按照空格切分,不转小写
  • Keyword Analyzer – 不分词,直接将输入当作输出
  • Patter Analyzer – 正则表达式,默认 \W+ (非字符分隔)
  • Language – 提供了30多种常见语言的分词器
  • ICU Analyzer - 中文分词,提供 Unicode 支持,Character Filters 是 Normalization,Tokenizer 是 ICU Tokenizer,Token Filters 是 Normalization + Folding + Collation + Transform。

get /_analyze 指定 Analyzer 进行分词,post xxx/_analyze 指定索引字段进行分词,post /_analyzer 自定义分词器进行分词。

6. Search API 概览

  • URI Search
    • 在 URL 中使用查询参数。
    • 使用 get 方法,在 URL 中使用 q 指定查询字符串,字符串是 k:v 形式的键值对。
  • Request Body Search
    • 使用 es 提供的,基于 JSON 格式的更加完备的 DSL 。
    • 使用 get 或 post 方法,在请求体中使用 DSL 。
    • /_search 查询集群上所有的索引。
    • /index1/_search 查询 index1 索引上的内容。
    • /index1,index2/_search 查询 index1 和 index2 索引上的内容。
    • /index*/_search 匹配 index 开头的索引上的内容。

响应结果中:

  • took 花费时间。
  • total 符合条件的文档总数。
  • hits 结果集,默认前 10 个文档。

另外一个重点在于 score :

  • 搜索结果会根据 score 进行排名。
  • Precision 查准率 = True Positive / 全部返回的结果
  • Recall 查全率 = True Positive / 所有应该返回的结果
  • 使用 es 的查询和相关参数可以改善搜索的 Precision 和 Recall 。

7. URI Search 详解

  • q 查询语句,使用 Query String Syntax。
  • df 默认字段,不指定时,返回所有字段的查询。
  • sort 排序。
  • fromsize 用于分页。
  • 请求体里的 Profile 查看查询是如何被执行的。
  • 指定字段查询 q=title:2012,范查询则是 q=2012
  • Term vs Phrase
    • Beautiful Mind 等效于 Beautiful OR Mind title:(Beautiful AND Mind)
    • “Beautiful Mind” 等效于 Beautiful AND Mind,同时要求顺序一致 title="Beautiful Mind"
  • 布尔操作
    • AND $$
    • OR ||
    • NOT !
  • 分组操作
    • +%2B 必须要。
    • - 一定不要。
    • title:(Beautiful %2BMind) 这种情况下 Mind 不需要有,Beautiful 可以没有。
  • 范围查询
    • [] 闭区间,{} 开区间。
    • year:{2018 TO 2019]
    • year:[* TO 2019]
  • 数学符号
    • year:>2010
    • year:(>2010 %% <=2018)
    • year:(+>2010 +<=2018)
  • 当然也能有通配符、正则.
  • 模糊与近似查询
    • title:beautifl~1 可以找到拼写完整的 befautiful。
    • title:"lord rings"~2 可以找到 lord of the rings。

8. Request Body 与 Query DSL

  • 推荐用 Request Body Search,有些操作这能在这里用。
  • 请求体的 query"match_all":{} 查询所有文档。
  • 请求体的 from 从 0 开始,默认返回 10 个结果。
  • 请求体的 sort 可以进行排序,比如 "sort":[{"order_date":"desc"}]。最好是数字和日期。
  • 请求体的 _source 可以进行过滤,比如 "_source":["order_date","category.keyword"]
  • URI 中的参数 ignore_unavailable=true 可以忽略访问不存在的索引导致的报错。
  • 请求体中使用 script_fields 表示脚本字段,比如如下例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET kibana_sample_data_ecommerce/_search
{
"script_fields": {
"test_field": { # 自定义的名称
"script": {
"lang": "painless",
"source": "doc['order_date'].value+'hello'"
}
}
},
"_source":["order_date"],
"query": {
"match_all": {}
}
}

比如计算汇率的时候可以用到。

  • Match 请求对应了 Term 操作,在请求体的 query 中使用 match 开始,默认的 operator 是 OR,比如如下例子:
1
2
3
4
5
6
7
8
9
10
11
POST movies/_search
{
"query": {
"match": {
"title": {
"query": "last christmas",
"operator": "AND"
}
}
}
}
  • 响应的也有 Match Phrase 查询对应了 Phrase 操作,在请求体的 query 中使用 match_phrase 开始,如下:
1
2
3
4
5
6
7
8
9
10
11
POST movies/_search
{
"query": {
"match_phrase": {
"title":{
"query": "one love",
"slop": 1 # 表示允许中间可以有 1 个其他的字符
}
}
}
}

可以输出 "title" : "One I Love, The"

9. Query String 和 Simple Query String 查询

  • 类似与 URI Query。
  • 在请求体的 query 中使用 query_string 进行填充,df = default_field,在其中也能用到 query
  • simple_query_stringquery_string 类似,但是会忽略一些错误的语法,同时只支持部分查询。
    • 不支持 AND OR NOT,都当做字符串处理。
    • Term 之间默认关系是 OR,但是可以指定 Operator。
    • 支持逻辑,+ 代替 AND| 代替 OR- 代替 NOT

10. Dynamic Mapping 和常见字段类型

  • Mapping 和 Dynamic Mapping
    • Mapping 类似于 DB 中的 schema,可以定义索引中的字段,定义字段类型,进行倒排索引配置。
    • Mapping 会将 JSON 映射成 Lucene 所需要的扁平格式。
  • 字段类型自动识别
    • 简单类型有 Text/Keyword,Date,Integer/Floating,Boolean,IPv4/IPv6。
    • 复杂类型有对象和嵌套类型。
    • 特殊类型有 geo_point/geo_shape/percolator。
  • 控制 Mapping 的 Dynamic 属性
    • 一个 Mapping 属于一个索引的 Type,现在一个索引只能有一个 Type。
  • Dynamic Mapping 是在写入文档的时候,如果索引不存在就会自动创建索引,但是类型推断有时候会发生错误。
    • 字符串且匹配日期格式,自动为 Date。
    • 字符串且匹配数字,设置 float 或 long,该转换默认关闭。
    • 其他字符串则为 Text。
    • 布尔对应 boolean。
    • 浮点对应 float。
    • 整数对应 long。
    • 对象对应 Object。
    • 数组由第一个非空数值类型决定。
    • 控制直接忽略。
  • 如果 Dynamic 为 true,一旦有新字段加入,Mapping 会更新;如果 Dynamic 为 false,新字段无法被索引,但是信息会在 _source 中;如果 Dynamic 是 strict,文档写入失败。
  • 对于已经有了的字段,一旦已经有数据写入,则不再支持字段定义的更改。
  • 更改字段类型需要 Reindex API,重建索引。

11. 显式 Mapping 设置与常见参数

  • 可以参考 API 手册,纯手写。
    • 创建一个临时 Index,写入一些样本数据。
    • 通过访问 Mapping API 获得该临时文件的动态 Mapping 定义。
    • 修改该定义为你的索引。
    • 删除临时索引。
  • index 控制是否字段被索引,false 时,倒排索引就不会被生成了。
  • index_options 倒排索引的四个级别。
    • docs 记录 doc id。(除 Text 默认)
    • freqs 记录 doc id 和 term frequencies。
    • positions 记录 doc id 和 term frequencies 和 term position。(Text 默认)
    • offsets 记录 doc id 和 term frequencies 和 term position 和 character offects。
  • 如果需要对 Null 值进行搜索,在 Keyword 类型下支持设定 null_value,在搜索时是真正的 Null 值。
  • _allcopy_to 替代,将字段值拷贝到目标字段,搜索可以用目标字段搜索,copy_to 字段不会出现在 _source 中。
  • es 没有专门的数组类型,但是任何字段都可以包含多个相同类型的数值。

12. 多字段特性与 Mapping 中自定义 Analyzer

  • 对某一个字段增加一个子字段,也可以指定不同的 Analyzer 进行分词。
  • es 中的 Keyword 是一种精确值,包括数字、日期、具体的字符串,没有必要对其进一步分词;而全文本是非结构化的文本数据,就是 es 中的 Text。
  • 当自带分词器无法满足需求,可以自定义分词器。
    • Character Filter 分此前对文本进行特殊处理,会影响 position 和 offset 的信息,HTML Strip 去除 html 标签,Mapping 字符串替换,Pattern Replace 正则替换。
    • Tokenizer 分词时把文本按照一定规则分成词,比如空格、正则、不处理、文件路径等。
    • Token Filter 分词后进行二次加工,将输出的单词进行增加、修改、删除。

13. Index Template 和 Dynamic Template

  • Index Templates 帮助设置 Mappings 和 Settings,按照一定的规则自动匹配到新创建的索引之上。
    • 模板仅在一个索引被创建时才会有用,模板不会影响已经创建的索引。
    • 可以设置多个索引模板,这些设置会被 merge 在一起。
    • 可以指定 order 的数值,用来控制 merge 的过程。
  • 当一个索引被创建:
    • 应用 es 默认的 Settings 和 Mappings。
    • 应用 order 值低的 Index Template 中的设定。
    • 用高 order 设置覆盖低 order 设定。
    • 用户指定的 Settings 和 Mappings 覆盖之前的设定。
  • Dynamic Template 是应用在一个具体的索引上的,结合字段名称,数据类型,来动态设置字段类型。
    • 比如所有字符串都设置为 keyword,所有 is 开头都为 boolean,所有 long_ 开头都设置为 long 等。

14. es 聚合分析

  • 所谓聚合就是数据统计分析,过滤结果。
    • Bucket Aggregation 满足一些特定条件的文档的集合。
    • Metric Aggregation 数学运算,可以对文档字段进行统计分析。
    • Pipeline Aggregation 对其他的聚合结果进行二次聚合。
    • Matrix Aggregation 支持对多个字段的操作提供一个结果矩阵。
1
2
3
SELECT COUNT(brand) # Metrix
FROM cars
GROUP BY brand # Bucket
  • 聚合功能 aggs 可以叠加也可以嵌套。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs":{
"flight_dest":{
"terms":{ # Bucket
"field":"DestCountry"
},
"aggs":{
"stats_price":{
"stats":{ # Metrix
"field":"AvgTicketPrice"
}
},
"wather":{
"terms": { # Bucket
"field": "DestWeather",
"size": 5
}
}
}
}
}
}

15. 总结

  • 判断题:ES 支持使用 Http Put 写入新文档,并通过 Elasticsearch 生成文档 id
    • 错,需要用 POST 命令创建
  • 判断题:Update 一个文档,需要使用 Http Put
    • 错,Update 文档,使用 POST,PUT 只能用来做 Index 或者 Create
  • 判断题:Index 一个已存在的文档,旧的文档会先被删除,新的文档再被写入,同时版本号加 1
  • 尝试描述创建一个新的文档到一个不存在的索引中,背后会发生一些什么
    • 默认情况下,会创建相应的索引,并且自己设置 Mapping,当然,实际情况还是要看是否有合适的 Index Template
  • ES7 中的合法的 type 是什么
    • _doc
  • 精确值和全文的本质区别是什么
    • 精确值不会被 Analyzer 分词,全文本会
  • Analyzer 由哪几个部分组成
    • 三部分,Character Filter + Tokenizer + Token filter
  • 尝试描述 match 和 match_phrase 的区别
    • Match 中的 terms 之间是 or 的关系,Match phrase 的 terms 之间是 and 的关系,并且 term 之间位置关系也影响搜索的结果
  • 如果你希望 match_phrase 匹配到更多结果,你应该配置查询中什么参数
    • slop
  • 如果 Mapping 的 dynamic 设置成 strict,索引一个包含新增字段的文档时会发生什么
    • 直接报错
  • 如果 Mapping 的 dynamic 设置成 false,索引一个包含新增字段的文档时会发生什么
    • 文档被索引,新的字段在 _source 中可见。但是该字段无法被搜索
  • 判断题:可以把一个字段的类型从 integer 改成 long,因为这两个类型是兼容的
    • 错。字段类型修改,需要重新 reindex
  • 判断题:你可以在 Mapping 文件中为 indexing 和 searching 指定不同的 analyzer
    • 对。可以在 Mapping 中为 index 和 search 指定不同的 analyzer
  • 判断题:字段类型为 Text 的字段,一定可以被全文搜索
    • 错。可以通过为 Text 类型的字段指定 Not Indexed,使其无法被搜索