ElasticSearch03-基本操作
1、RestFul API 风格
(1)概述
ElasticSearch 的接口风格是 RESTful API。RESTful 是一种软件架构风格,它使用 HTTP 协议来实现客户端和服务器之间的通信。 ElasticSearch 的 RESTful API 提供了一种简单且标准化的方式来与 ElasticSearch 进行交互,包括索引、查询、分析和管理操作。
(2)特点
统一的接口规范:RESTful API 使用 HTTP 方法(如 GET、POST、PUT、DELETE)来表示对资源的操作。例如,使用 GET 方法可以获取资源,使用 POST 方法可以创建资源,使用 PUT 方法可以更新资源,使用 DELETE 方法可以删除资源。 资源的唯一标识:在 RESTful API 中,每个资源都有一个唯一的 URI(统一资源标识符)。例如,在 ElasticSearch 中,可以通过 /index/type/id
来唯一标识一个文档,其中 index
是索引名称,type
是文档类型,id
是文档的唯一标识符。 无状态性:RESTful API 是无状态的,这意味着每个请求都是独立的,服务器不会保存客户端的状态信息。每次请求都需要包含足够的信息来完成操作。 可缓存性:RESTful API 允许对响应进行缓存,以提高性能和减少服务器负载。例如,对于一些不经常变化的资源,可以将其响应缓存起来,以便后续请求可以直接从缓存中获取数据。 使用标准的媒体类型:RESTful API 使用标准的媒体类型(如 JSON、XML)来表示资源的数据格式。在 ElasticSearch 中,通常使用 JSON 格式来表示数据。
(3)实践
2、索引操作
(1)添加索引-PUT
注意:kibana\postman\head插件选择自己喜欢的一种使用即可。 这里使用kibana的dev tool,因为 kibana里面省略了http://localhost:9200,更加简洁。
# 请求
PUT shopping
# 返回
{
"acknowledged" : true ,
"shards_acknowledged" : true ,
"index" : "shopping"
}
返回值说明
acknowledged
:这个字段是一个布尔值,表示请求的操作是否被集群接收并且开始执行。如果值为true
,表示操作已被确认;如果为false
,则表示操作未被确认。shards_acknowledged
:这个字段也是一个布尔值,表示操作是否已经被所有相关的分片确认。对于写操作(如索引文档、更新设置等),这个字段表示操作是否已经被所有相关的分片接收并开始执行。如果值为true
,表示所有分片都确认了操作;如果为false
,则表示至少有一个分片没有确认操作。index
:这个字段表示操作被执行的索引名称。在这个例子中,操作是针对名为shopping
的索引执行的。
(2)查看所有索引-GET
这里_cat 表示查看的意思,indices 表示索引,所以整体含义就是查看当前 ES服务器中的所有索引
# 请求
GET _cat/ indices? v
# 返回
health status index uuid pri rep docs. count docs. deleted store. size pri. store. size
green open . geoip_databases NkdVmCBXTMmPpjiSohfXaw 1 0 36 0 34 . 9mb 34 . 9mb
green open . kibana_7. 15.0_001 IXufDJUzQz6mUJWCabgcsA 1 0 23 12 2 . 3mb 2 . 3mb
green open . apm- custom- link bdJnnr2IRBeGgAqimdrhQA 1 0 0 0 208b 208b
green open . apm- agent- configuration pYrpjjhHRCGCG0NJPM- aDw 1 0 0 0 208b 208b
green open . kibana- event- log- 7.15 .0 - 000001 zbb14WTOQHm7e52KlAzCrA 1 0 1 0 6kb 6kb
green open . kibana_task_manager_7. 15.0_001 YOaqFHuXScCBg3neLKdgcg 1 0 15 390 168 . 3kb 168 . 3kb
yellow open shopping P4UQj- moTbudvcG7didlJg 1 1 0 0 208b 208b
表头 含义 health 当前服务器健康状态: green(集群完整) yellow(单点正常、集群不完整) red(单点不正常) status 索引打开、关闭状态 index 索引名 uuid 索引统一编号 pri 主分片数量 rep 副本数量 docs.count 可用文档数量 docs.deleted 文档删除状态(逻辑删除) store.size 主分片和副分片整体占空间大小 pri.store.size 主分片占空间大小
(3)查看单个索引-GET
# 请求
GET shopping
# 返回
{
"shopping" : {
"aliases" : { } ,
"mappings" : { } ,
"settings" : {
"index" : {
"routing" : {
"allocation" : {
"include" : {
"_tier_preference" : "data_content"
}
}
} ,
"number_of_shards" : "1" ,
"provided_name" : "shopping" ,
"creation_date" : "1733810897901" ,
"number_of_replicas" : "1" ,
"uuid" : "P4UQj-moTbudvcG7didlJg" ,
"version" : {
"created" : "7150099"
}
}
}
}
}
返回值说明
aliases
:这个字段包含了索引的所有别名。在这个例子中,aliases
是空的,意味着shopping
索引没有设置别名。mappings
:这个字段定义了索引中文档的结构,包括字段类型和索引规则。在这个例子中,mappings
是空的,可能是因为索引是动态映射的,或者没有指定映射。settings
:这个字段包含了索引的配置设置,具体包括:
index
:这是针对索引的特定设置。
routing.allocation.include._tier_preference
:这个设置用于控制索引分片的分配。在这里,_tier_preference
被设置为data_content
,意味着索引的分片会被分配到标记为data_content
的节点上。number_of_shards
:这个设置定义了索引的主分片数量。在这里,shopping
索引被设置为有1个主分片。provided_name
:这是用户为索引提供的名称,在这里是shopping
。creation_date
:这是索引创建的时间戳(毫秒)。在这里,索引是在1733810897901
毫秒时创建的,你可以将其转换为日期时间格式来理解。number_of_replicas
:这个设置定义了索引的副本分片数量。在这里,shopping
索引被设置为有1个副本分片。uuid
:这是索引的唯一标识符。在这里,索引的UUID是P4UQj-moTbudvcG7didlJg
。version.created
:这个设置显示了创建索引时Elasticsearch集群的版本号。在这里,版本号是7150099
。
(5)删除索引-DELETE
# 请求
DELETE shopping
# 返回
{
"acknowledged" : true
}
3、文档操作
(1)创建文档-POST
这里的文档可以类比为关系型数据库中的表数据,添加的数据格式为 JSON 格式。 文档发送请求的方式必须为 POST,不能是PUT,否则会发生错误。
# 创建索引
PUT shopping
# 请求
POST shopping/ _doc
{
"title" : "小米手机" ,
"category" : "小米" ,
"images" : "https://www.mi.com/" ,
"price" : 4999.00
}
#返回
{
"_index" : "shopping" ,
"_type" : "_doc" ,
"_id" : "-2VDr5MBnMDr74vGJKfQ" ,
"_version" : 1 ,
"result" : "created" ,
"_shards" : {
"total" : 2 ,
"successful" : 1 ,
"failed" : 0
} ,
"_seq_no" : 0 ,
"_primary_term" : 1
}
返回值说明
_index
:这个字段表示文档被索引到的索引名称。在这个例子中,文档被索引到了名为shopping
的索引。_type
:在Elasticsearch 7.x之前的版本中,这个字段表示文档的类型。从7.x版本开始,类型被标记为废弃,并在8.x版本中被完全移除。_doc
是7.x版本引入的默认文档类型。_id
:这个字段表示被索引文档的唯一标识符。在这个例子中,文档的ID是-2VDr5MBnMDr74vGJKfQ
。_version
:这个字段表示文档的版本号。每次文档被更新时,版本号会增加。在这个例子中,文档的版本是1,意味着这是文档的初始版本。result
:这个字段表示操作的结果。在这个例子中,result
是created
,表示文档已经被成功创建。_shards
:这个字段包含了关于分片操作的统计信息。
total
:这个字段表示参与操作的分片总数。在这个例子中,总共有2个分片参与了操作。successful
:这个字段表示成功完成操作的分片数量。在这个例子中,有1个分片成功完成了操作。failed
:这个字段表示操作失败的分片数量。在这个例子中,没有分片操作失败。 _seq_no
:这个字段表示文档的序列号。序列号是一个递增的数字,用于标识文档操作的顺序。在这个例子中,序列号是0,表示这是第一个操作。_primary_term
:这个字段表示主分片的任期编号。在Elasticsearch的分布式环境中,每个主分片都有一个任期编号,用于确保操作的顺序和一致性。在这个例子中,主分片的任期编号是1。
(2)type弃用原因
type底层存储原理
Lucene 索引:在Lucene中,没有“类型”的概念。所有的文档都被存储在同一个Lucene索引中。 _type 字段:为了支持多类型的功能,Elasticsearch在每个文档中隐式地添加了一个名为 _type 的字段,该字段的值对应于文档所属的类型。这个字段并不显示在用户定义的映射中,但它在内部被用来区分不同类型的文档。 查询与过滤:当你执行查询或过滤操作时,Elasticsearch会根据你提供的类型信息来构建查询,以确保只返回特定类型的文档。这通常涉及到在查询中包含一个针对 _type 字段的过滤条件。 type底层存储结构
# 创建索引
{
"goods": {
"mappings": {
"electronic_goods": {
"properties": {
"name": {
"type": "string",
},
"price": {
"type": "double"
},
"service_period": {
"type": "string"
}
}
},
"fresh_goods": {
"properties": {
"name": {
"type": "string",
},
"price": {
"type": "double"
},
"eat_period": {
"type": "string"
}
}
}
}
}
}
# 底层实际结构
{
"goods": {
"mappings": {
"_type": {
"type": "string",
"index": "false"
},
"name": {
"type": "string"
}
"price": {
"type": "double"
}
"service_period": {
"type": "string"
},
"eat_period": {
"type": "string"
}
}
}
}
# 给不同 type 添加数据
PUT /goods/electronic_goods/1
{
"name": "小米空调",
"price": 1999.0,
"service_period": "one year"
}
PUT /goods/fresh_goods/1
{
"name": "澳洲龙虾",
"price": 199.0,
"eat_period": "one week"
}
# 底层实际数据
{
"_type": "electronic_goods",
"name": "小米空调",
"price": 1999.0,
"service_period": "one year",
"eat_period": ""
}
{
"_type": "fresh_goods",
"name": "澳洲龙虾",
"price": 199.0,
"service_period": "",
"eat_period": "one week"
}
type弃用原因
性能影响:由于所有类型的文档都存储在同一个Lucene索引中,并且需要通过 _type 来区分它们,这可能会导致一些性能上的问题。 映射冲突:如果不同类型的文档有相同名称但不同配置的字段(如不同的数据类型),那么在创建索引时可能会遇到映射冲突的问题,因为Lucene要求同名字段的数据类型必须一致。
(3)查看文档-GET
查看文档时,需要指明文档的唯一性标识,类似于 MySQL 中数据的主键查询
# 请求
GET shopping/ _doc/ - 2VDr5MBnMDr74vGJKfQ
# 返回
{
"_index" : "shopping" ,
"_type" : "_doc" ,
"_id" : "-2VDr5MBnMDr74vGJKfQ" ,
"_version" : 1 ,
"_seq_no" : 0 ,
"_primary_term" : 1 ,
"found" : true ,
"_source" : {
"title" : "小米手机" ,
"category" : "小米" ,
"images" : "https://www.mi.com/" ,
"price" : 4999.0
}
}
返回值说明
_index
:这个字段表示文档所在的索引名称。在这个例子中,文档位于名为shopping
的索引。_type
:在Elasticsearch 7.x之前的版本中,这个字段表示文档的类型。从7.x版本开始,类型被标记为废弃,并在8.x版本中被完全移除。_doc
是7.x版本引入的默认文档类型。_id
:这个字段表示被检索文档的唯一标识符。在这个例子中,文档的ID是-2VDr5MBnMDr74vGJKfQ
。_version
:这个字段表示文档的版本号。每次文档被更新时,版本号会增加。在这个例子中,文档的版本是1。_seq_no
:这个字段表示文档的序列号。序列号是一个递增的数字,用于标识文档操作的顺序。_primary_term
:这个字段表示主分片的任期编号。在Elasticsearch的分布式环境中,每个主分片都有一个任期编号,用于确保操作的顺序和一致性。found
:这个字段是一个布尔值,表示是否找到了匹配的文档。在这个例子中,found
是true
,表示文档被成功找到。_source
:这个字段包含了文档的原始数据,即在索引时提交的源数据。在这个例子中,_source
字段包含了以下信息:
title
:商品的标题,这里是“小米手机”。category
:商品的类别,这里是“小米”。images
:商品的图片链接,这里是一个URL,但是注意链接字符串中的"
是HTML实体字符,代表双引号"
,所以实际的URL应该是https://www.mi.com/
。price
:商品的价格,这里是4999.0。
(4)修改文档-PUT
输入相同的 URL 地址请求,会覆盖原有数据内容,每次覆盖版本号加1。
# 请求
PUT shopping/ _doc/ - 2VDr5MBnMDr74vGJKfQ
{
"title" : "华为手机" ,
"category" : "华为" ,
"images" : "https://www.huawei.com/" ,
"price" : 4999.00
}
# 返回
{
"_index" : "shopping" ,
"_type" : "_doc" ,
"_id" : "-2VDr5MBnMDr74vGJKfQ" ,
"_version" : 2 ,
"result" : "updated" ,
"_shards" : {
"total" : 2 ,
"successful" : 1 ,
"failed" : 0
} ,
"_seq_no" : 1 ,
"_primary_term" : 1
}
返回值说明
_index
:这个字段表示文档所在的索引名称。在这个例子中,文档位于名为shopping
的索引。_type
:在Elasticsearch 7.x之前的版本中,这个字段表示文档的类型。从7.x版本开始,类型被标记为废弃,并在8.x版本中被完全移除。_doc
是7.x版本引入的默认文档类型。_id
:这个字段表示被更新文档的唯一标识符。在这个例子中,文档的ID是-2VDr5MBnMDr74vGJKfQ
。_version
:这个字段表示文档的版本号。每次文档被更新时,版本号会增加。在这个例子中,文档的版本是2,意味着这是文档的第二次更新。result
:这个字段表示操作的结果。在这个例子中,result
是updated
,表示文档已经被成功更新。_shards
:这个字段包含了关于分片操作的统计信息。
total
:这个字段表示参与操作的分片总数。在这个例子中,总共有2个分片参与了操作。successful
:这个字段表示成功完成操作的分片数量。在这个例子中,有1个分片成功完成了操作。failed
:这个字段表示操作失败的分片数量。在这个例子中,没有分片操作失败。 _seq_no
:这个字段表示文档的序列号。序列号是一个递增的数字,用于标识文档操作的顺序。在这个例子中,序列号是1,表示这是针对这个文档的第二次操作。_primary_term
:这个字段表示主分片的任期编号。在Elasticsearch的分布式环境中,每个主分片都有一个任期编号,用于确保操作的顺序和一致性。在这个例子中,主分片的任期编号是1。
(5)删除文档-DELETE
删除一个文档不会立即从磁盘上移除,它只是被标记成已删除(逻辑删除)。版本号会加1。
# 请求
DELETE shopping/ _doc/ - 2VDr5MBnMDr74vGJKfQ
# 返回
{
"_index" : "shopping" ,
"_type" : "_doc" ,
"_id" : "-2VDr5MBnMDr74vGJKfQ" ,
"_version" : 3 ,
"result" : "deleted" ,
"_shards" : {
"total" : 2 ,
"successful" : 1 ,
"failed" : 0
} ,
"_seq_no" : 2 ,
"_primary_term" : 1
}
4、映射操作
(1)创建映射-PUT
映射:类似于数据库(database)中的表结构(table)。创建数据库表需要设置字段名称,类型,长度,约束等;索引库也一样,需要知道这个索引下有哪些字段,每个字段有哪些约束信息。 动态映射:索引没有创建映射,添加数据的时候会根据插入的数据自动创建映射。es自动建立mapping的时候,设置了不同的field不同的data type。不同的data type的分词、搜索等行为是不一样的。
# 创建索引
PUT studuent
# 创建映射
PUT studuent/ _mapping
{
"properties" : {
"name" : {
"type" : "text" ,
"index" : true
} ,
"sex" : {
"type" : "keyword" ,
"index" : true
} ,
"age" : {
"type" : "keyword" ,
"index" : false
}
}
}
# 返回
{
"acknowledged" : true
}
(2)映射说明
字段名:随意按需求 index:是否索引,默认为 true,也就是说你不进行任何配置,所有字段都会被索引。
true:字段会被索引,则可以用来进行搜索 false:字段不会被索引,不能用来搜索 store:是否将数据进行独立存储,默认为 false
false: 文本存储在 _source 里面 true: 独立的存储某个字段,获取独立存储的字段要比从 _source 中解析快得多,但是也会占用更多空间,所以要根据实际业务需求来设置。 analyzer:分词器,ik_max_word 即使用 ik 分词器 type:字段数据类型,Elasticsearch 中支持的数据类型非常丰富
text
:
用于全文搜索的字段类型。 会自动进行分词(tokenization),将文本分割成单词或短语。 适合用于需要全文搜索的场景,如文章内容、产品描述等。 keyword
:
用于精确匹配的字段类型,不会被分词。 适合用于需要精确值匹配的场景,如标签、状态、性别、ID等。 通常与text
类型配合使用,keyword
类型用于过滤、排序和聚合,而text
类型用于全文搜索。 integer
:
用于存储整数值。 适合用于需要进行数值计算或范围查询的场景,如年龄、评分、库存数量等。 short
:
用于存储短整数值。 与integer
类似,但范围更小,适合存储较小的整数值。 long
:
用于存储长整数值。 与integer
类似,但范围更大,适合存储较大的整数值。 byte
:
用于存储字节值。 适合存储小范围内的数值,如小的计数器。 double
:
用于存储双精度浮点数值。 适合存储需要高精度的小数,如价格、重量等。 float
:
用于存储单精度浮点数值。 与double
类似,但精度较低,适合存储不需要高精度的小数。 half_float
:
用于存储半精度浮点数值。 精度介于float
和double
之间。 scaled_float
:
用于存储缩放的浮点数值。 允许存储一个浮点数乘以一个因子,以节省空间并提高性能。 date
:
用于存储日期和时间。 支持多种日期格式,适合存储日志时间戳、出生日期等。 boolean
:
用于存储布尔值。 只有true
和false
两个值,适合存储开关状态、是否标志等。 binary
:
用于存储二进制数据。 适合存储文件、图片等二进制内容。 nested
:
用于存储嵌套对象。 允许在单个字段中存储多个文档,适合存储数组或对象中的复杂结构。 object
(在Elasticsearch 6.x及更早版本中使用object
):
用于存储复杂对象。 允许在单个字段中存储多个字段,类似于JSON对象。 ip
:
用于存储IP地址。 适合存储IP地址信息,支持IP地址的过滤和聚合。
(3)查看映射-GET
# 请求 查看单个索引
GET studuent/ _mapping
# 返回
{
"studuent" : {
"mappings" : {
"properties" : {
"age" : {
"type" : "keyword" ,
"index" : false
} ,
"name" : {
"type" : "text"
} ,
"sex" : {
"type" : "keyword"
}
}
}
}
}
(4)修改映射-PUT
索引中添加一个名为hobby
的新字段,类型为文本。
PUT / studuent/ _mapping
{
"properties" : {
"hobby" : {
"type" : "text"
}
}
}
# 返回
{
"acknowledged" : true
}
# 再次查看
GET studuent/ _mapping
# 返回
{
"studuent" : {
"mappings" : {
"properties" : {
"age" : {
"type" : "keyword" ,
"index" : false
} ,
"hobby" : {
"type" : "text"
} ,
"name" : {
"type" : "text"
} ,
"sex" : {
"type" : "keyword"
}
}
}
}
}
(5)删除映射-DELETE
在ES中,一旦创建了映射,你不能直接删除映射中的字段。 映射的删除通常意味着删除整个索引。删除索引将连带删除所有映射。
# 请求
DELETE studuent
# 返回
{
"acknowledged" : true
}
5、修改字段类型
(1)场景
一个field的设置是不能被修改的,如果要修改一个Field,那么应该重新按照新的mapping,建立一个新的 index,然后将数据导入新的index。 一开始我们创建一个索引 index1,添加数据,title 类型会根据数据自动设置成 date 类型的
# 创建索引
PUT index1
# 添加数据
PUT / index1/ _doc/ 1
{
"title" : "2019-09-10"
}
我们再次添加数据 title 是字符串的时候,无法转成 date 类型就报错了。我们实际需要的类型是字符串。
# 添加数据
PUT / index1/ _doc/ 1
{
"title" : "my title"
}
# 返回
{
"error" : {
"root_cause" : [
{
"type" : "mapper_parsing_exception" ,
"reason" : "failed to parse field [title] of type [date] in document with id '1'. Preview of field's value: 'my title'"
}
] ,
"type" : "mapper_parsing_exception" ,
"reason" : "failed to parse field [title] of type [date] in document with id '1'. Preview of field's value: 'my title'" ,
"caused_by" : {
"type" : "illegal_argument_exception" ,
"reason" : "failed to parse date field [my title] with format [strict_date_optional_time||epoch_millis]" ,
"caused_by" : {
"type" : "date_time_parse_exception" ,
"reason" : "Failed to parse with all enclosed parsers"
}
}
} ,
"status" : 400
}
# 修改字段类型
PUT / index1/ _mapping
{
"properties" : {
"title" : {
"type" : "text"
}
}
}
# 返回
{
"error" : {
"root_cause" : [
{
"type" : "illegal_argument_exception" ,
"reason" : "mapper [title] cannot be changed from type [date] to [text]"
}
] ,
"type" : "illegal_argument_exception" ,
"reason" : "mapper [title] cannot be changed from type [date] to [text]"
} ,
"status" : 400
}
(2)解决方案
首先把 index1 设置成一个别名index_prod,客户端使用index_prod 进行查询
# 设置别名
PUT / index1/ _alias/ index_prod
# 用别名查询
GET / index_prod/ _search
# 返回
{
"took" : 1 ,
"timed_out" : false ,
"_shards" : {
"total" : 1 ,
"successful" : 1 ,
"skipped" : 0 ,
"failed" : 0
} ,
"hits" : {
"total" : {
"value" : 1 ,
"relation" : "eq"
} ,
"max_score" : 1.0 ,
"hits" : [
{
"_index" : "index1" ,
"_type" : "_doc" ,
"_id" : "1" ,
"_score" : 1.0 ,
"_source" : {
"title" : "2019-09-10"
}
}
]
}
}
我们创建一个新的 index2,并把字段类型设置成我们想要的,并把数据导入新的 index2
# 创建索引映射
PUT / index2
{
"mappings" : {
"properties" : {
"title" : {
"type" : "text"
}
}
}
}
# 导入数据
PUT / index2/ _doc/ 1
{
"title" : "2019-09-10"
}
无缝切换:基于alias对client透明切换index,客户端无感查询index_prod
# 无缝切换
POST / _aliases
{
"actions" : [
{ "remove" : { "index" : "index1" , "alias" : "index_prod" } } ,
{ "add" : { "index" : "index2" , "alias" : "index_prod" } }
]
}
# 客户端无感继续正常查询
GET / index_prod/ _search
# 返回
{
"took" : 0 ,
"timed_out" : false ,
"_shards" : {
"total" : 1 ,
"successful" : 1 ,
"skipped" : 0 ,
"failed" : 0
} ,
"hits" : {
"total" : {
"value" : 1 ,
"relation" : "eq"
} ,
"max_score" : 1.0 ,
"hits" : [
{
"_index" : "index2" ,
"_type" : "_doc" ,
"_id" : "1" ,
"_score" : 1.0 ,
"_source" : {
"title" : "2019-09-10"
}
}
]
}
}