Elasticsearch要点简记
1、ES概述
ES(elasticsearch)是一个分布式搜索和分析引擎,它用于快速、实时地搜索和分析大规模数据。ES是一个面向文档的分布式搜索引擎。它的数据模型基于文档,每个文档是一个包含了JSON格式数据的独立单元。文档可以属于不同的索引,每个文档可以具有不同的结构。
小记:
- Elasticsearch提供了一个易于使用的RESTful
API。可以使用HTTP请求来执行各种操作,例如索引文档、执行搜索查询、管理索引和节点等。 - 专注于全文搜索和复杂查询,支持分词、模糊搜索、短语匹配等高级搜索功能。
2、基础概念
(1)索引、文档、字段
索引(Index):是Elasticsearch中的一个逻辑容器,用于组织和存储相关的文档数据。类似于关系数据库中的表。
文档(Document):文档是存储在索引中的基本数据单元,通常以JSON格式表示。每个文档代表了一个独立的数据记录。类似于关系数据库中的一行记录。
字段(Field):是文档中的数据项,它们包含了文档的具体信息。每个字段都有一个名称和一个对应的值。例如,一个产品文档可以包含字段如"productName"、"productDescription"、"price"等,每个字段存储相关信息。
一个索引可以包含多个文档,这些文档代表了不同类型的数据。每个文档包含多个字段,每个字段存储文档的具体数据。
(2)映射
映射(Mapping)用于定义索引中文档的结构和字段的属性。映射指定了每个字段的数据类型、如何分析文本、是否存储原始数据等信息。映射确保了索引中的文档被正确地存储和检索,并允许执行高级的搜索和分析操作。通常,映射可以自动创建,但在需要更精细的控制时,也可以手动定义映射。以下是关于映射的详细介绍:
字段的数据类型: 映射确定了每个字段的数据类型,例如文本、数值、日期、布尔值等。指定正确的数据类型有助于Elasticsearch正确地索引和搜索数据。
分析器(Analyzer): 对于文本字段,映射可以指定使用哪种分析器来处理文本数据。分析器决定了如何将文本拆分成词条,以及如何处理这些词条,例如小写化、删除停用词等。
日期格式: 对于日期字段,映射可以定义日期的格式,以确保正确的日期解析和排序。
(3)DSL
DSL(Domain-Specific Language 领域特定语言),它是一种用于构建和执行复杂查询的结构化查询语言。DSL是Elasticsearch查询的核心组成部分,用于与Elasticsearch进行交互,从而检索和操作数据。DSL查询通常以JSON(JavaScript Object Notation)格式编写,因此它是一种使用JSON语法的查询语言。
3、架构原理
主节点(Master Node): 负责集群的管理和协调,包括索引和分片的创建、删除、重新分配等。它还负责维护集群状态信息。
数据节点(Data Node): 负责存储索引数据和执行搜索操作。
协调节点(Coordinator Node): 不存储数据,但负责接收来自客户端的搜索和查询请求,然后将请求分发到适当的数据节点。
候选主节点(Candidate Master Node): 是潜在的主节点候选者,当主节点失败时可以自动接管主节点的角色。
4、索引字段的数据类型
(1)keyword族
-
keyword: 用于结构化内容,如id、电子邮件地址、主机名、状态码、邮政编码或标签。
-
constant_keyword: 表示始终包含相同值(常量)的keyword字段。
-
wildcard: 用于机器生成的非结构化的内容。wildcard字段类型针对大值或高基数的字段进行了优化。
Keyword 字段经常用于结构化内容的查询,通常用于过滤、排序、聚合和 term-level queries。例如,id、电子邮件地址、主机名、状态码、邮政编码或标签等。
(2)text
最适合于非结构化的内容(全文索引),text字段不用于排序,也很少用于聚合,text字段会被分析(分词),也就是说,在建立索引之前,它们会通过分析器(分词器)将字符串转换为单个词的列表,例如,电子邮件的正文或产品的描述。
(3)数值类型
byte、short、integer、long、float、double、unsigned_long、half_float、scaled_float(一个使用long表示的浮点数,精度由一个换算系数决定,比如俩位小数,3.14 缩放因子是100的话,在文档中就存储314,取的时候除以100,表示金额时可以使用))
(4)日期类型
代表UNIX诞生的UTC时间1970年1月1日0时0分0秒。JSON没有日期数据类型,所以Elasticsearch中的日期可以是:格式化日期的字符串, 例如:"2015-01-01" or "2015/01/01 12:10:30".
(5)布尔类型
布尔字段接受JSON的true和false值,但也可以接受被解释为true或false的字符串:
5、ES的三种分页方式
(1)深度分页(from+size)
默认采用的分页方式是 from+ size 的形式,即查询10-20条数据时,就在相应的各节点上直接查询前20条数据,然后截断前10条,只返回10-20的数据。
其中,from定义了目标数据的偏移值,size定义了返回的数据量(默认from为0,size为10),即所有的查询默认仅仅返回前10条数据。
json
GET /student/student/_search
{
"query":{
"match_all": {}
},
"from":100,
"size":10
}
上面的查询意味着 es 需要在各个分片上匹配排序并得到110条数据,而随着页数增加,深度分页的效率将会非常低,因为我们只需要查询size条数据,而es则处理了from+size条数据。
(2)快照查询(scroll)
如果我们分页请求大数据集,scroll是一个好的解决方案。使用scroll滚动搜索,可以先搜索一批数据,然后下次再搜索一批数据,以此类推,直到搜索出全部的数据来。scroll搜索会在第一次搜索的时候,保存一个当时的视图快照,之后只会基于旧的视图快照提供数据搜索(如果这个期间数据变更,是不会让用户看到的)。每次发送scroll请求,我们需要指定一个scroll参数,指定一个时间窗口,每次搜索请求只要在这个时间窗口内能就可以了。
一个滚屏搜索允许我们做一个初始阶段搜索并且持续批量从Elasticsearch里拉取结果直到没有结果剩下。这有点像传统数据库里的cursors(游标)。
scroll的使用很简单。执行如下curl,每次请求两条。可以定制 scroll = 5m意味着该窗口过期时间为5分钟。
请求:
json
GET /student/student/_search?scroll=5m
{
"query": {
"match_all": {}
},
"size": 2
}
返回:
{
"_scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAC0YFmllUjV1QTIyU25XMHBTck1XNHpFWUEAAAAAAAAtGRZpZVI1dUEyMlNuVzBwU3JNVzR6RVlBAAAAAAAALRsWaWVSNXVBMjJTblcwcFNyTVc0ekVZQQAAAAAAAC0aFmllUjV1QTIyU25XMHBTck1XNHpFWUEAAAAAAAAtHBZpZVI1dUEyMlNuVzBwU3JNVzR6RVlB",
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 6,
"max_score" : 1.0,
"hits" : [
{
"_index" : "student",
"_type" : "student",
"_id" : "5",
"_score" : 1.0,
"_source" : {
"name" : "fucheng",
"age" : 23,
"class" : "2-3"
}
},
{
"_index" : "student",
"_type" : "student",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"name" : "xiaoming",
"age" : 25,
"class" : "2-1"
}
}
]
}
}
返回信息里有一个重要的参数scroll_id(base64编码),在后面的请求参数中我们都要带着 scroll_id 。 student这个索引中共有6条数据,当我们使用 scroll 查询第4次的时候,返回结果为空。这时结果集就已经匹配完了。
(3)search_after
from + size的分页方式虽然是最灵活的分页方式,但是存在深度分页的问题。scroll能够解决深度分页的问题,但是其无法实现实时查询,即当scroll_id生成后无法查询到数据的变更,因为其底层原理是生成数据的快照。
search_after (es-5.x之后)是一种假分页方式,根据上一页的最后一条数据来确定下一页的位置,同时在分页请求的过程中,如果有索引数据的增删改查,这些变更也会实时的反映到游标上。为了找到每一页最后一条数据,每个文档必须有一个全局唯一值,官方推荐使用 _uid 作为全局唯一值,但是只要能表示其唯一性就可以。
在索引中增加一个uid字段表示其唯一性,请求如下:
json
GET /student/student/_search
{
"query":{
"match_all": {}
},
"size":2,
"sort":[
{
"uid": "desc"
}
]
}
结果:
json
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 6,
"max_score" : null,
"hits" : [
{
"_index" : "student",
"_type" : "student",
"_id" : "6",
"_score" : null,
"_source" : {
"uid" : 1006,
"name" : "dehua",
"age" : 27
},
"sort" : [
1006
]
},
{
"_index" : "student",
"_type" : "student",
"_id" : "5",
"_score" : null,
"_source" : {
"uid" : 1005,
"name" : "fucheng",
"age" : 23
},
"sort" : [
1005
]
}
]
}
}
下一次分页,需要将上述分页结果集的最后一条数据的值带上。
json
GET /student/student/_search
{
"query":{
"match_all": {}
},
"size":2,
"search_after":[1005],
"sort":[
{
"uid": "desc"
}
]
}