本篇文章记录Elasticsearch一些基本概念与数据操作方式。
Elasticsearch概念比较多,从开发人员角度来说有文档(Document)、索引(Index)、类型(Type)等逻辑概念,从运维角度来说有节点、集群、分片、副本等物理概念。
es逻辑上有关概念
先来聊聊开发人员比较关注的逻辑性概念,文档、索引、类型等。
什么是Elasticsearch?
Elasticsearch 是一个分布式的开源搜索和分析引擎,适用于所有类型的数据,包括文本、数字、地理空间、结构化和非结构化数据。Elasticsearch 在 Apache Lucene 的基础上开发而成,由 Elasticsearch N.V.(即现在的 Elastic)于 2010 年首次发布。Elasticsearch 以其简单的 REST 风格 API、分布式特性、速度和可扩展性而闻名,是 Elastic Stack 的核心组件;Elastic Stack 是适用于数据采集、充实、存储、分析和可视化的一组开源工具。人们通常将 Elastic Stack 称为 ELK Stack(代指 Elasticsearch、Logstash 和 Kibana),目前 Elastic Stack 包括一系列丰富的轻量型数据采集代理,这些代理统称为 Beats,可用来向 Elasticsearch 发送数据。
文档(Document)
Elasticsearch搜索是面向文档的,官方给的定义是:在大多数应用中,多数实体或对象可以被序列化为包含键值对的 JSON 对象。键可以为一个字段名,值可以为一个布尔值或者一个数字或者字符串等,类似于我们平时写接口最后返回的一串JSON数据。相较于关系型数据库中的行概念。
如:
一本书籍的基本信息,包括书籍名称、书籍作者、书籍价格、书籍简介、书籍出版社等等。
{
"bookName":"深入理解Java虚拟机",
"bookAuthor":"周志明",
"bookPrice":"20.00RMB",
"bookJian":"深入理解Java虚拟机底层实现原理",
"bookPress":[
"中华书局",
"电子工业出版社",
"吉林工业出版社"
]
}
上面的JSON字符串就可以表示为一个文档。文档是所有可搜索数据的最小单元,文档会被序列化成为JSON格式保存在Elasticsearch中,每一个文档都有一个unique ID,可以自己指定或者es自动生成。
文档元数据
一个文档不仅仅只包含数据,它还包含 元数据
也就是有关文档的信息。需要特别关注的三个:_index、_type、_id
- _index: 该文档在哪里存放,也就是说它属于哪个索引。
- _type: 文档表示的对象类别,也就是说它属于索引中的哪个类型,一个索引中可能存在很多不同的产品类别,使用_type可以有效区别您所需要查找的类型。es6.0已移除_type。
- _id: id是一个字符串,当它和_index、_type组合时可以唯一确定elasticsearch中的文档,id可以自己指定,也可以使用es自动生成的id。
- _source: 文档的原始JSON数据,也就是我们定义的业务数据。比如上述书籍基本信息。
- _all: 整合所有字段内容到该字段,7.0以后 已被移除。
- _version: 文档的版本信息。
- _score: 相关性打分,搜索时会有一个匹配打分,最高的分数会在前面。
索引(index)
简单来说索引就是文档的容器,一类相似文档的集合。
- index体现了逻辑空间的概念:每个索引都有自己的mapping定义。
- shard体现了物理空间的概念:每个索引中的数据分散在shard上
- 可以为索引设置mapping与settingsmapping:用于定义包含的文档的字段名和字段类型。
settings: 定义数据的分布情况
通过例子来直观的感受一下索引(index)中的元素信息。
如下语句PUT是向es中增加一个文档,index->mengxi,type->_doc(6.0已将移除,所以这里设置es默认的_doc),2->id。json字符串表示文档的原数据。
PUT /mengxi/_doc/2
{
"title":"elasticsearch",
"oper":"true",
"date":"2021-01-26"
}
通过GET 索引名称返回索引中所包含的信息。如下
GET /mengxi
//索引mengxi相对应的信息
{
"mengxi" : {
"aliases" : { },
"mappings" : {
"properties" : {
"date" : {
"type" : "date"
},
"oper" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"title" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
},
"settings" : {
"index" : {
"creation_date" : "1611630544025",
"number_of_shards" : "1",
"number_of_replicas" : "1",
"uuid" : "aJiuHLLcRyWSBZ3tb9FQjA",
"version" : {
"created" : "7060299"
},
"provided_name" : "mengxi"
}
}
}
}
通过上面的index信息,可以看出mappings所对应的内容就是在PUT时指定的文档内容(json),不过将文档的具体信息展示出来了,包括字段的名称、字段的类型等。settings所对应的内容就是我们上面讲到的物理空间信息,包括创建日期、shard分片号、空间号、uuid、版本号等。
类型(Type)
类型上面已经提到了,每个类型下面会包含一系列的文档,es7.0之前一个索引(index)下可以设置多个Types,6.0开始Type已经被废弃掉。7.0开始,一个索引(index)只能创建一个type,就是_doc。
关系型数据库与Elasticsearch的对比
我们对关系型数据库都比较熟悉,在这里把关系型数据库与elasticsearch中的概念做一个不那么严谨的对比,有助于我们加深对elasticsearch相关概念的理解。
关系型数据库 | Elasticsearch |
---|---|
Table | Index(Type) |
Row | Document |
Column | Filed |
Schema | Mapping |
SQL | DSL |
es物理上有关概念
说完逻辑概念,接下来我们聊聊有关运维物理上的概念,节点、集群、分片、副本等。
节点
elasticsearch是一个分布式存储引擎,可以实现高可用、可扩展等特性,必然会涉及到集群以及多节点实例存储相关内容。
- 节点是一个elasticsearch的实例,本质上就是一个Java进程。
- 每一个节点都是有名字的,通过配置文件配置或在启动的时候指定 -E node.name=node1
- 每一个节点在启动后,都会分配一个UID,保存在data目录下
Master eligible 与 Master 节点
- 每个节点启动后,默认就是一个Master eligible节点
- Master eligible节点可以参加选主节点流程,成为Master节点
- 当只有一个节点启动时,它将会把自己选为Master节点
- 每个节点上都有集群的状态,只有master节点才能修改集群状态信息
Data Node 与 Coordinating Node
Data Node 可以保存数据的节点叫做Data Node。负责保存分片数据。在数据扩展上起到至关重要的作用。
Coordinating Node
- 负责接收Client请求,将请求分发的合适的节点,最终把结果汇在一起。
- 每个节点都默认起了Coordinating Node的职责。
分片
主分片,解决数据水平扩展的问题,通过主分片,可以将数据分配到集群内的所有节点上去。
- 一个分片是一个运行lucene的实例
- 主分片数在索引创建时指定,后续不允许修改,除非reindex
副本,解决数据高可用的问题,是主分片的拷贝。
- 副本分片数,可以动态调整
- 增加副本的分片数,可以提高es的吞吐量
//查看集群健康状态
GET _cluster/health
集群健康状态信息
{
"cluster_name" : "elasticsearch",
"status" : "yellow",
"timed_out" : false,
"number_of_nodes" : 1,
"number_of_data_nodes" : 1,
"active_primary_shards" : 7,
"active_shards" : 7,
"relocating_shards" : 0,
"initializing_shards" : 0,
"unassigned_shards" : 1,
"delayed_unassigned_shards" : 0,
"number_of_pending_tasks" : 0,
"number_of_in_flight_fetch" : 0,
"task_max_waiting_in_queue_millis" : 0,
"active_shards_percent_as_number" : 87.5
}
Elasticsearch数据基本操作
新增
提供三种API
PUT /index/type/id
如果id不存在会创建新的文档,如果存在,先删除现有文档,再创建新的文档,然后追加版本号。注意:这里type统一设置为 _doc
//新增文档
PUT /mxblog/_doc/1
{
"title":"mxblog",
"context":"es study record",
"date":"2021-01-27"
}
//返回结果
{
"_index" : "mxblog",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
如上,新增mxblog索引以及一个id为1的文档,最终返回的result为 created
。
//再次执行PUT操作
PUT /mxblog/_doc/1
{
"title":"mxblog",
"context":"es study record",
"date":"2021-01-27"
}
//返回结果
{
"_index" : "mxblog",
"_type" : "_doc",
"_id" : "1",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1
}
如上,再次执行PUT操作,返回的结果为updated
,并且版本号_version
追加为2
PUT /mxblog/_create/1
使用create
新增是,如果ID存在则会出错。
//执行 PUT /mxblog/_create/1
PUT /mxblog/_create/1
{
"title":"mxblog",
"context":"es study record",
"date":"2021-01-27"
}
//返回结果
{
"error" : {
"root_cause" : [
{
"type" : "version_conflict_engine_exception",
"reason" : "[1]: version conflict, document already exists (current version [2])",
"index_uuid" : "j14hgRe5Tra7eOroQobBbw",
"shard" : "0",
"index" : "mxblog"
}
],
"type" : "version_conflict_engine_exception",
"reason" : "[1]: version conflict, document already exists (current version [2])",
"index_uuid" : "j14hgRe5Tra7eOroQobBbw",
"shard" : "0",
"index" : "mxblog"
},
"status" : 409
}
如上,使用_create
新增时,id为1的记录已经存在,返回的结果状态为409,出错原因为 文档已存在,当前版本号为2。
//执行 PUT /mxblog/_create/2
PUT /mxblog/_create/2
{
"title":"mxblog",
"context":"es study record one day",
"date":"2021-01-27"
}
//返回结果
{
"_index" : "mxblog",
"_type" : "_doc",
"_id" : "2",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 2,
"_primary_term" : 1
}
使用_create
新增文档,成功返回。
POST /mxblog/_doc/
新增不指定ID,es会自动生成ID。
//执行 POST /mxblog/_doc/
POST /mxblog/_doc/
{
"title":"mxblog",
"context":"es study record two day",
"date":"2021-01-27"
}
//返回结果
{
"_index" : "mxblog",
"_type" : "_doc",
"_id" : "mKNWQ3cBVRucdjq3I4VM",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 3,
"_primary_term" : 1
}
如上执行 POST
新增不指定ID时,es返回结果会随机指定一个ID字符串。
查询
使用 GET /index/type/id
可以查询对应的文档
//查询
GET /mxblog/_doc/1
//返回结果
{
"_index" : "mxblog",
"_type" : "_doc",
"_id" : "1",
"_version" : 2,
"_seq_no" : 1,
"_primary_term" : 1,
"found" : true,
"_source" : {
"title" : "mxblog",
"context" : "es study record",
"date" : "2021-01-27"
}
}
如上,查看found
属性值为true,表示该文档成功读取。_source
为文档原数据。
更新
使用 POST /index/_update/id { "doc":{"filed":"value"}}
对文档进行修改。
//执行更新操作
POST mxblog/_update/1/
{
"doc":{
"title":"liu meng xi"
}
}
//返回结果
{
"_index" : "mxblog",
"_type" : "_doc",
"_id" : "1",
"_version" : 3,
"_seq_no" : 4,
"_primary_term" : 1,
"found" : true,
"_source" : {
"title" : "liu meng xi",
"context" : "es study record",
"date" : "2021-01-27"
}
}
如上执行update
操作,将title修改为liu meng xi
,最终修改完毕后,执行GET操作查看修改成功。
删除
使用 DELETE /index/type/id
将当前文档删除
//执行删除
DELETE /mxblog/_doc/1
//返回结果
{
"_index" : "mxblog",
"_type" : "_doc",
"_id" : "1",
"_version" : 5,
"result" : "deleted",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 6,
"_primary_term" : 1
}
//再次查看被删除的文档
GET /mxblog/_doc/1
//返回结果
{
"_index" : "mxblog",
"_type" : "_doc",
"_id" : "1",
"found" : false
}
如上执行DELETE
,可以看到返回的result
为deleted
,表示已删除。查看ID为1
的文档,发现found
字段为false
。
Bulk API
这里再介绍一个Bulk API操作,它支持在一次API调用中,对不同的索引进行操作,支持index、create、update、delete
四种操作。操作中单条操作失败,并不影响其他操作,并且返回结果包含每一次操作。
//批量操作es api
POST _bulk
{"index":{"_index":"mxblog","_id":"1"}}
{"filed":"value"}
{"delete":{"_index":"mxblog","_id":"1"}}
{"create":{"_index":"mxblog","_id":"1"}}
{"filed1":"value1"}
{"update":{"_index":"mxblog","_id":"1"}}
{"doc":{"filed2":"value2"}}
//返回结果
{
"took" : 1282,
"errors" : false,
"items" : [
{
"index" : {
"_index" : "mxblog",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1,
"status" : 201
}
},
{
"delete" : {
"_index" : "mxblog",
"_type" : "_doc",
"_id" : "1",
"_version" : 2,
"result" : "deleted",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1,
"status" : 200
}
},
{
"create" : {
"_index" : "mxblog",
"_type" : "_doc",
"_id" : "1",
"_version" : 3,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 2,
"_primary_term" : 1,
"status" : 201
}
},
{
"update" : {
"_index" : "mxblog",
"_type" : "_doc",
"_id" : "1",
"_version" : 4,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 3,
"_primary_term" : 1,
"status" : 200
}
}
]
}
如上结果,可以看出每一个api的调用都会返回对应的结果集。
批量获取数据
使用 GET _mget
,POST /index/_msearch
进行批量查询,减少网络开销,提高性能
//执行批量操作
GET /_mget
{
"docs":[
{"_index":"mengxi","_id":"1"},
{"_index":"mengxi","_id":"2"}
]
}
//返回结果
{
"docs" : [
{
"_index" : "mengxi",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"name" : "mengxi",
"domain" : "www.mxblog.com.cn"
}
},
{
"_index" : "mengxi",
"_type" : "_doc",
"_id" : "2",
"_version" : 1,
"_seq_no" : 1,
"_primary_term" : 1,
"found" : true,
"_source" : {
"name" : "mengxi",
"domain" : "https://www.mxblog.com.cn"
}
}
]
}
//执行_msearch
POST /mengxi/_msearch
{}
{"query":{"match_all":{}},"size":2}
//返回结果
{
"took" : 20,
"responses" : [
{
"took" : 20,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "mengxi",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"name" : "mengxi",
"domain" : "www.mxblog.com.cn"
}
},
{
"_index" : "mengxi",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"name" : "mengxi",
"domain" : "https://www.mxblog.com.cn"
}
}
]
},
"status" : 200
}
]
}
elasticserach api 操作返回码说明
结果 | 原因 |
---|---|
无法连接 | 网络故障或集群挂了 |
连接无法关闭 | 网络故障或节点出错 |
429 | 集群繁忙 |
4xx | 请求格式错误 |
500 | 集群内部错误 |