ElasticSearch
是一款分布式存储数据库,提供基于 RESTFUL
风格的操作方式,即通过接口请求的方式即可实现数据的增删改查,下面就让我们学习一下相应接口请求格式。
一、概念介绍
1. 索引
在 Elasticsearch
中并没有库表的概念,取而代之的为索引 (Indices)
,可以简单的理解为数据库。不过随着 7.x
版本后中对 Types
的弃用,索引此时更像是单表数据库。
2. 类型
在 Elasticsearch 6.x
中 Types
概念类似于数据库中的一张表,但随着版本更新,在最新的 7.x
版本中 Types
被逐渐废除,因此一个索引 Indices
下只允许存在一个 Types
。
3. 文档
在 Elasticsearch
中每条记录是以文档 (Documents)
的形式存在,相当于 MySQL
等数据表中的一条记录,每条文档都会自动为其分配一个唯一标识。
4. 字段
Fields
即为每条文档中的属性的类型,常用类型包含如下几类:
类型 | 描述 |
---|---|
text | 当一个字段需要用于全文搜索(会被分词)则应该使用该类型。 |
keyword | 当一个字段需要按照精确值进行过滤、排序、聚合等操作时则应该使用该类型。 |
date | 时间类型,精度不包含时分秒。 |
boolean | 布尔类型,即 true 与 false。 |
range | 区间类型,取值范围如下:integer_range, long_range, float_range, double_range, date_range, ip_range |
二、数据索引
1. 索引创建
通过 PUT
请求创建新索引,请求格式如下,其中 settings
参数非必填。
PUT http://<ip>:<port>/<index_name>
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2
}
}
settings
除了上述的示例外还包含下表参数配置:
参数 | 数据示例 | 描述 |
---|---|---|
number_of_shards | 2 | 索引的分片数量。 |
number_of_replicas | 2 | 索引的副本数量。 |
refresh_interval | 10s | 索引刷新间隔,数据写入到可搜索的过程,默认 1s。 |
index.max_result_window | 10000 | 索引查询返回条数限制,默认 1w 条。 |
index.mapping.total_fields.limit | 200 | 索引查询返回的字段数,默认 1000。 |
index.translog.durability | request/async | 事务日志写入策略,请求后写入 (request),异步写入 (async)。 |
其中较为让人混淆的即分片数 (number_of_shards)
与副本数 (number_of_replicas)
,二者具体的配置的值主要依据集群中的节点数量而定,通过 1:1
是一个相对稳定的参照,下面我们着重介绍一下二者的区别。
(1) 分区
number_of_shards
用于执行数据的分片大小,类似于关系型的中的分区操作,以提高存储查询效率。
例如上述中的 "number_of_shards": 3
表示分片数为 3
,则会将索引下创建 3
个分片,而每个分片可以位于集群中的不同节点,即减少单个数据文件的大小从而提高性能。
(2) 副本
number_of_replicas
用于指定副本大小,即集群状态多端副本保证数据的安全性以防止数据丢失或损坏。
同样以上述的示例为例,索引创建了 3
个分片且副本为 2
,即每个分片包括自身存在 2
份备份,且副本通常也分布于集群内不同节点中,当其中一个副本损坏时另一副本可以保证数据的稳定性。
2. 字段映射
在创建索引时除了 settings
配置参数外,也可通过 mapping
参数指定字段映射。
mapping
即文档记录的结构,虽然在不指定时会根据文档数据类型自动匹配,但可能存在细节差异,为了取得更优的性能建议根据实际情况手动配置,映射内容中的 type
参数上一点中的表格。
PUT http://<ip>:<port>/<index_name>
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2
},
"mapping": {
"_doc": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "text"
}
}
}
}
}
3. 索引别名
Elasticsearch
索引别名是一个指向一个或多个索引的稳定引用名称。
通过使用别名,可以将查询和索引维护从实际索引名称中分离出来,索引别名常用场景如下:
- 日志切割:对于日志类型的索引,可以使用别名轻松地进行日志切割。
- 切换版本:可以通过更改别名指向的实际索引,而不必更改应用程序中的查询代码。
- 滚动升级:在进行索引版本升级时可以创建新版本的索引,通过别名将查询指向新索引,实现滚动升级。
在 Elasticsearch
中指定索引名称也并不复杂,通过 aliases
参数进行配置,请求格式如下,其中 <alias_name>
为需要设置的别名。
PUT http://<ip>:<port>/<index_name>
{
"aliases": {
"<alias_name>": {}
}
}
如果是在索引创建完成之后添加别名,则可通过 _aliases
接口,请求格式如下:
POST http://<ip>:<port>/_aliases
{
"actions": [
{
"add": {
"index": "<index_name>",
"alias": "<alias_name>"
}
}
]
}
4. 索引删除
通过 DELETE
请求删除索引,删除索引会同步删除其对应下的数据,请求格式如下:
DELETE http://<ip>:<port>/<index_name>
三、数据增改
1. 数据插入
数据插入的请求模板示例如下,其中 <type>
的可选值为:_doc
,且 <id>
若未指定则回自动分配。
POST http://<ip>:<port>/<index>/<type>/<id>
{
"field1": "value1",
"field2": "value2",
"field3": "value3"
}
2. 数据更新
数据更新的请求模板示例如下:
PUT http://<ip>:<port>/<index>/<type>/<id>
{
"field1": "value1_1",
"field2": "value2_1",
"field3": "value3_1"
}
3. 数据删除
数据删除的请求模板示例如下:
DELETE http://<ip>:<port>/<index>/<type>/<id>
四、批量操作
1. 基本格式
Elasticsearch
批量操作的基本请求格式如下,需注意 content
中每一行代表一条记录需要以换行符结束,同时内容必须空出最后一行空白行。
{"action": {"metadata"}}
{content}
其中 action
可选值参考下表:
action | 参数 |
---|---|
create | 创建新的一条记录。 |
update | 更新已有一条记录。 |
index | 若记录存在,则进行覆盖。 |
delete | 删除一条数据记录。 |
完成 JSON
数据文件的创建后在 PostMan
中可通过下述入口选择上传。
2. 批量插入
(1) 索引不存在
批量插入数据,索引不存在时新建。
POST http://<ip>:<port>/_bulk
// 文件内容
{"create":{"_index": "<index_name>","_type": "_doc","_id": "1"}}
{"id": "1", "name":"alex"}
{"create":{"_index": "<index_name>","_type": "_doc","_id": "2"}}
{"id": "2", "name":"beth"}
(2) 索引存在
批量插入数据,索引必须已存在。
POST http://<ip>:<port>/<index>/_doc/_bulk
// 文件内容
{"create":{"_id": "1"}}
{"id": "1", "name":"alex"}
{"create":{"_id": "2"}}
{"id": "2", "name":"beth"}
(2) 增量插入
批量插入数据,若数据已存在则覆盖。
POST http://<ip>:<port>/<index>/_doc/_bulk
// 文件内容
{"index":{"_id": "1"}}
{"id": "1", "name":"alex"}
{"index":{"_id": "2"}}
{"id": "2", "name":"beth"}
3. 批量更新
批量更新的请求模板示例如下:
POST http://<ip>:<port>/<index>/_doc/_bulk
// 文件内容
{"update":{"_id": "1"}}
{"doc": {"id": "1"}}
{"update":{"_id": "2"}}
{"doc": {"id":"2"}}
4. 批量删除
批量删除的请求模板示例如下:
POST http://<ip>:<port>/<index>/_doc/_bulk
// 文件内容
{"delete":{"_id": "1"}}
{"delete":{"_id": "2"}}
五、数据查询
1. 查询得分
在 Elasticsearch
中,每个文档的匹配程度可以通过分数 (score)
表示,其反映了文档与查询条件的匹配程度,越匹配的文档得分越高。当执行查询时会计算每个文档的得分,然后按照得分排序返回结果,这个得分是由查询条件中的每个子句(例如,匹配关键词的 must
子句)的相关性计算得出的。
对于 bool
查询中的 must
子句,其中的每个条件都会影响文档的得分,若一个文档满足多个 must
子句的条件,那么它的得分会相应增加。而对于 filter
子句,其中的条件也用于筛选文档但不会影响文档的得分。filter
子句主要用于对文档进行精确匹配和过滤,由于不计算得分,filter
子句的查询更轻量,对于一些不需要计算相关性的场景可以提高性能。
因此,当使用 filter
子句时,查询的主要目的是过滤文档,而不是影响查询结果的排序和相关性。这对于一些特定的过滤条件(如范围查询、精确匹配等)非常有用,因为它们可以更高效地执行,而无需计算和排序相关性得分。
2. 结构查询
通过 _mapping
查询当前索引下的数据字段结构。
GET http://<ip>:<port>/<index>/_mapping
3. 全量查询
通过 Get
查询目标数据,效果等价于 select * from <table>
。
默认查询仅返回十条数据,若查看多数据需分页进行,同时通过 pretty=true
可格式化返回的结果数据。
GET http://<ip>:<port>/<index_name>/_search
GET http://<ip>:<port>/<index_name>/_search?pretty=true
4. 分页查询
分页查询同理,在请求体内通过 from
与 size
指定分页参数,作用类似 MySQL
中的 limit
和 offset
。
在设置分页查询时需要注意 Elasticsearch
默认一次性能够返回条数只有 1w
条,若需要查询大批量数据需调整 max_result_window
参数设置返回条数限制。
GET http://<ip>:<port>/<index_name>/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 10
}
六、条件查询
1. 精准查询
精准查询即与常见关系型中 where
条件中的 =
作用等价,通过 must
查询默认会为文档计算得分。
下述示例的作用效果即等价于 select * from <table> where <column_name> = <column_name>
。
GET http://<ip>:<port>/<index_name>/_search
{
"query": {
"bool": {
"must": [
{
"match_phrase": {
"<column_name>": {
"query": "<column_value>"
}
}
}
]
}
}
}
同理也可将上述的 must
替换为 filter
从而获取更优的性能。
{
"query": {
"bool": {
"filter": [
{
"match_phrase": {
"<column_name>": {
"query": "<column_value>"
}
}
}
]
}
}
}
同时在查询时除了上述的 match_phrase
外还可通过 term
进行过滤。
match_phrase
用于查找包含指定短语的文档,短语中的词必须按照给定的顺序出现,并且是连续的。而 term
查询用于精确匹配字段的值,输入不会被分词器处理,常用于匹配关键字字段或未分词的字段。
2. 区间查询
区间查询即与常见关系型中 where
条件中的 in
作用等价。
下述示例的作用效果即等价于 select * from <table> where <column_name> in (1, 2, 3)
。
GET http://<ip>:<port>/<index_name>/_search
{
"query": {
"bool": {
"must": [
{
"terms": {
"<column_name>": [1, 2, 3]
}
}
]
}
}
}
3. 范围查询
范围查询即与常见关系型中 where
条件中的 >, >=, <, <=
作用等价。
其中不同的符号对应参考下表:
范围符 | ES 关键字 |
---|---|
> | gt |
>= | from |
< | lt |
<= | to |
下述示例的作用效果即等价于 select * from <table> where <column_name> >= 1
。
GET http://<ip>:<port>/<index_name>/_search
{
"query": {
"bool": {
"must": [
{
"range": {
"<column_name>": {
"from": "1"
}
}
}
]
}
}
}
七、聚合查询
1. 行数统计
Elasticsearch
同样支持关系型数据库中的聚合函数等操作,如下述示例即查询索引中的记录条数。
其中 count_num_rows
为自定义名称,value_count
等价于 count(*)
,aggregations
可简写为 aggs
,其最终效果等价于 select count(*) from <table_name>
。
GET http://<ip>:<port>/<index>/_search
{
"size": 0,
"aggregations": {
"count_num_rows": {
"value_count": {
"field": "_index"
}
}
}
}
2. 分组查询
同理其支持复杂的分组查询,例如下述示例:
{
"size": 0,
"aggs": {
"group_date": {
"date_histogram": {
"field": "<time_field>",
"interval": "hour",
"time_zone": "+08:00",
"format": "yyyy-MM-dd HH:mm:ss"
},
"aggs": {
"num_filter": {
"bucket_selector": {
"buckets_path": {
"count": "_count"
},
"script": "params.count > 0"
}
}
}
}
}
}
其等价于 MySQL
中的下述查询:
select
DATE_FORMAT('<time_field>', '%Y-%m-%d %H:00:00') as time,
COUNT(*) AS num
from
<table_name>
group by
time
having
num > 0;
注意字段 <time_field>
的类型 type
必须为 date
,其中 interval
的可选值如下:
参数 | 描述 |
---|---|
year | 年 |
quarter | 季度 |
month | 月 |
week | 周 |
day | 日 |
hour | 小时 |
minute | 分钟 |
second | 秒 |