Elasticsearch基础使用介绍(数据类型,索引操作)
环境准备
- Elasticsearch 服务(单机或集群)
- Kibana 服务
如果对ES不了解或没有上述环境,可以看下我之前的博客。
Elasticsearch查看集群信息,设置ES密码,Kibana部署
进入 Kibana 开发工具
数据类型
官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.10/mapping-types.html
基础数据类型
keyword
keyword类型是不进行切分的字符串类型。
这里的“不进行切分”指的是:在索引时,对keyword类型的数据不进行切分,直接构建倒排索引;在搜索时,对该类型的查询字符串不进行切分后的部分匹配。
keyword类型数据一般用于对文档的过滤、排序和聚合在现实场景中。
keyword经常用于描述姓名、产品类型、用户ID、URL和状态码等。keyword类型数据一般用于比较字符串是否相等,不对数据进行部分匹配,因此一般查询这种类型的数据时使用term查询。
text
text类型是可进行切分的字符串类型。
这里的“可切分”指的是: 在索引时,可按照相应的切词算法对文本内容进行切分,然后构建倒排索引;
在搜索时,对该类型的查询字符串按照用户的切词算法进行切分,然后对切分后的部分匹配打分。一般查询这种类型的数据时使用match查询
数值类型
官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.10/number.html
ES支持的数值类型有long、integer、short、byte、double、float、half float.scaled float和unsigned long等。
为节约存储空间并提升搜索和索引的效率,在实际应用中,在满足需求的情况下应尽可能选择范围小的数据类型。
数值类型的数据也可用于对文档进行过滤、排序和聚合。
boolean
用来表示 true 和 false
日期类型(date)
在ES中,日期类型的名称为date。ES中存储的日期是标准的UTC格式。
UTC(Universal Time Coordinated)叫做世界统一时间,中国大陆和 UTC 的时差是+8,也就是 UTC+8
在elasticsearch中的date类型可以是下面的形式:
- 格式化好的字符串,比如"2015-01-01",“2015/01/01 12:10:30”
- 一个long类型整数,代表从纪元以来的毫秒数
- 一个integer类型整数,表示从纪元开始的秒数
实际上不管日期以何种格式写入,在 ES 内部都会先换成 UTC 时间并存储为 long 类型
注意:
- long和integer必须是非负数
- 使用带格式的日期表示1970年之前的日期
- 日期将始终对外呈现为字符串
可以自定义日期格式(format),多个日期格式 用 || 分割
PUT my-index-000001
{
"mappings": {
"properties": {
"date": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
}
}
如果不指定使用默认格式strict_date_optional_time||epoch_millis
其中strict _date_optional time的含义是严格的时间类型,支持
yyyy-MM-dd、
yyyyMMdd.yyyyMMddHHmmss、
yyyy-MM-ddTHH:mm:ss、
yyyy-MM-ddTHH:mm:ss.SSS
yyyyMM-ddTHH:mm:ss.SSSZ等格式
epoch milis的含义是从1970年1月1日0点到现在的毫秒
注意strict_date_optional_time
并不支持 yyyy-MM-dd HH:mm:ss
格式
如果需要,需要指定format
为 yyyy-MM-dd HH:mm:ss
复杂数据类型
数组类型
官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.10/array.html
ES数组没有定义方式,其使用方式是开箱即用的,即无须事先声明。
在写入时把数据用中括号 [] 括起来,由ES对该字段完成定义。
PUT my-index-000001/_doc/1
{
"message": "some arrays in this document...",
"tags": [ "elasticsearch", "wow" ],
"lists": [
{
"name": "prog_list",
"description": "programming list"
},
{
"name": "cool_list",
"description": "cool stuff list"
}
]
}
当然,如果事先已经定义了字段类型,在写数据时以数组形式写入,ES也会将该类型转为数组。
数组应该不算一个纯粹的数据类型,而是一种逻辑存储方式
数组中的所有值都必须是 相同的数据类型。
数组类型的字段适用于元素类型的搜索方式,也就是说,数组元素适用于什么搜索,数组字段就适用于什么搜索。
例如,数组元素类型是keyword,该类型可以适用于term搜索。
对象类型
官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.10/object.html
对象类型 也不用事先定义,在写入文档时 ES 会自动识别转化为对象类型。
在写入数据时 使用花括号 {} 括起来,ES将自动识别为 对象类型
PUT my-index-000001/_doc/1
{
"region": "US",
"manager": {
"age": 30,
"name": {
"first": "John",
"last": "Smith"
}
}
}
在内部,此文档被索引为键值的对应关系如下:
{
“region”: “US”,
“manager.age”: 30,
“manager.name.first”: “John”,
“manager.name.last”: “Smith”
}
上述文档索引的主动创建方式可以为:
PUT /my-index-000001
{
"mappings": {
"properties": {
"region": {
"type": "keyword"
},
"manager": {
"properties": {
"age": { "type": "integer" },
"name": {
"properties": {
"first": { "type": "text" },
"last": { "type": "text" }
}
}
}
}
}
}
}
地理位置类型
官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.10/geo-point.html#geo-point
geo_point类型,用来存储经纬度数据
PUT my-index-000001
{
"mappings": {
"properties": {
"location": {
"type": "geo_point"
}
}
}
}
# 五种方式指定经纬度
# 用 和 键表示为对象的地理点。latlon
PUT my-index-000001/_doc/1
{
"text": "Geo-point as an object",
"location": {
"lat": 41.12,
"lon": -71.34
}
}
# 地理点表示为格式为:的字符串。"lat,lon"
PUT my-index-000001/_doc/2
{
"text": "Geo-point as a string",
"location": "41.12,-71.34"
}
# 以地理哈希表示的地理点。
PUT my-index-000001/_doc/3
{
"text": "Geo-point as a geohash",
"location": "drm3btev3e86"
}
# 以数组形式表示的地理点,格式为:[ ,lonlat]
PUT my-index-000001/_doc/4
{
"text": "Geo-point as an array",
"location": [ -71.34, 41.12 ]
}
# 地理点表示为已知文本点,格式为:"POINT(lon lat)"
PUT my-index-000001/_doc/5
{
"text": "Geo-point as a WKT POINT primitive",
"location" : "POINT (-71.34 41.12)"
}
# 一个地理边界框查询,用于查找框内的所有地理点。
GET my-index-000001/_search
{
"query": {
"geo_bounding_box": {
"location": {
"top_left": {
"lat": 42,
"lon": -72
},
"bottom_right": {
"lat": 40,
"lon": -74
}
}
}
}
}
多字段(子字段)
针对同一个字段,有时需要不同的数据类型,这通常表现在为了不同的目的以不同的方式索引相同的字段。
例如,按照用户姓名进行搜索,又希望按照姓氏进行排列,可以在mapping定义中将姓名字段先后定义为text类型和keyword类型。
其中,keyword类型的字段叫作子字段,这样ES在建立索引时会将姓名字段建立两份索引,即text类型的索引和keyword类型的索引。
索引的定义如下:
PUT /user-0001
{
"mappings": {
"properties": {
"user_name": {
"type": "text",
"fields": {
"user_name_keyword": {
"type": "keyword"
}
}
}
}
}
}
添加数据
POST /_bulk
{"index":{"_index":"user-0001","_id":"001"}}
{"user_name":"Mic John"}
{"index":{"_index":"user-0001","_id":"002"}}
{"user_name":"Tim John"}
{"index":{"_index":"user-0001","_id":"003"}}
{"user_name":"Bobe John"}
查询数据
GET /user-0001/_search
{
"query": {
"match": {
"user_name": "John"
}
}
, "sort": [
{
"user_name.user_name_keyword": {
"order": "desc"
}
}
]
}
数据类型动态映射
S支持根据写入的数据自动创建mapping中对应的字段类型(生产不建议使用)
提前定义好数据类型并将索引创建语句纳入SVN或Git管理范围是良好的编程习惯,同时还能增强项目代码的连贯性和可读性。
索引
增删查
# 创建索引user
PUT /user
{
// 指定别名user_alias ,可以多个
"aliases": {
"user_alias": {
// 过滤哪些文档 设置别名
"filter": {},
// 指定读写路由值,用来限定操作的分片,
// 指定了routing 就无需指定index_routing 和 search_routing ,否则会无效
"routing": "1",
// 指定写操作路由值,用来限定操作的分片
"index_routing": "1",
// 指定读操作路由值,用来限定操作的分片
"search_routing": "1,2"
}
},
"settings": {
"number_of_shards": 4, // 主分片数
"number_of_replicas": 2 // 副分片数
},
//映射
"mappings": {
// 指定属性和类型
"properties": {
// name 属性为 text类型
"name":{
"type": "text"
},
// age 属性为 integer类型
"age":{
"type": "integer"
}
}
}
}
# 查看索引
GET /user
GET /user/_settings
GET /user/_mappings
GET /user/_alias
# 删除
DELETE /user
修改索引
索引的修改 分为 静态部分 和 动态部分
静态部分 即 索引建立后 不能修改
动态部分 即 索引建立后 可以修改
动态可修改部分
# 关闭索引,一般用于冷备份数据归档,索引关闭后无法读写数据
POST /user/_close
# 打开索引
POST /user/_open
# 修改映射(不能修改已有的字段类型,因为会破坏倒排索引)
PUT /user/_mappings
{
//新增id字段
"properties": {
"id": {
"type": "long"
}
}
}
# 修改设置(不能修改主分片数,因为会导致找到文档的存储位置)
PUT /user/_settings
{
//修改副分片数
"settings": {
"number_of_replicas": 3 // 副分片数
}
}
静态不可修改部分
- 主分片数不能修改
ES 的路由规则是 根据 主分片数取余来确定 需要操作的分片,一旦修改了主分片数,会导致找不到对应的文档位置。所以索引一旦创建,主分片数就不能修改
路由规则 在之前文章中有介绍: Elasticsearch入门基础和集群部署
- 已经确定了的字段类型不能修改
ES 采用倒排索引来增加 搜索的性能,倒排索引是根据分词器 ,字段类型等规则创建的。所以索引创建后,也不能直接修改分词器,字段类型。
但是 随着业务系统的发展,不可避免的需要修改索引 静态内容
例如:
随着数据的增加,需要增加索引的主分片数
数据类型建立不合适,程序运行一段时间后,发现需要修改数据类型
ES 对于 静态索引内容的修改 提供了 索引别名 和 索引重建 两个功能来实现静态内容的 修改
索引重建
索引重建 可以将 旧索引的数据 拷贝到新索引
具体步骤如下:
- 创建新索引: 创建一个新的索引(修改索引静态内容)
- 重新索引(Reindex): 使用Elasticsearch的Reindex API将旧索引中的数据重新索引到新创建的索引中。这一步会消耗一些计算和存储资源,因此在生产环境中可能需要谨慎操作。
- 切换到新索引: 在完成重新索引后,将应用中的写操作切换到新的索引,确保新数据写入新的索引。
- 删除旧索引(可选): 如果确认新索引运行正常且数据完整,可以选择删除旧索引释放资源。
# 创建新索引(示例中主分片数量为6)
PUT new_test_index
{
"settings": {
"index": {
"number_of_shards": "6",
"number_of_replicas": "2"
}
}
}
# 使用Reindex API将旧索引数据重新索引到新索引
POST /_reindex
{
"source": {
"index": "test_index"
},
"dest": {
"index": "new_test_index"
}
}
# 切换应用中的写操作到新索引
# 可选:删除旧索引
DELETE /old_index
索引别名
类似 关系数据库中的视图
es可以对一个或者多个索引指定别名,通过别名可以查到一个或者多个索引的内容
# 给已经创建的索引指定别名
POST /_aliases
{
"actions": [
//增加索引别名
{
"add": {
"index": "user",
"alias": "alias1"
}
},
//删除索引别名
{
"remove": {
"index": "user",
"alias": "alias1"
}
}
]
}
特别注意:当一个别名指向多个索引时,针对别名的写入操作会失败(除非指定哪个索引用来写入)
针对这一特性,可以平滑的完成新旧索引的替换
如图:
初始状态索引别名hotel 指向 索引hotel_1
如果随着业务的增长,需要修改 hotel_1的分片数或字段类型,这时必须要重建一个索引
这里重建一个新的索引hotel_2
然后将hotel_1 的数据 reindex 到 hotel_2,同时将新数据都写入到hotel_2
最后指定 hotel 别名指向 hotel_2, 删除hotel_1 的别名
这样 就完成了搜索端 平滑的过度