前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >021.Elasticsearch索引管理高级篇

021.Elasticsearch索引管理高级篇

作者头像
CoderJed
发布2020-07-16 17:15:19
7830
发布2020-07-16 17:15:19
举报
文章被收录于专栏:Jed的技术阶梯Jed的技术阶梯

1. 索引别名

在开发中,随着业务需求的迭代,较老的业务逻辑就要面临更新甚至是重构,而对于ES来说,为了适应新的业务逻辑,可能就要对原有的索引做一些修改,比如对某些字段做调整,甚至是重建索引,而做这些操作的时候,可能会对业务造成影响,甚至需要停机调整,由此,ES引入了索引别名来解决这些问题,索引别名就像一个快捷方式或是软链接,可以指向一个或多个索引,也可以给任意一个需要索引别名的API来使用,别名的应用为程序提供了极大的灵活性。

  • 查询别名 # 查询所有索引的别名 GET _alias # 查询某个索引的别名 GET /index_name/_alias
  • 新增别名 # 方法一 POST /_aliases { "actions": [ { "add": { "index": "index_name", "alias": "index_name_v1.0" } } ] } # 方法二 PUT /index_name/_alias/index_name_v1.0
  • 删除别名 # 方法一 POST /_aliases { "actions": [ { "remove": { "index": "index_name", "alias": "index_name_v1.0" } } ] } # 方法二 DELETE /index_name/_alias/index_name_v1.0
  • 重命名 POST /_aliases { "actions": [ { "remove": { "index": "index_name", "alias": "index_name_v1.0" } }, { "add": { "index": "index_name", "alias": "index_name_v2.0" } } ] }
  • 多个索引指定一个别名 POST /_aliases { "actions": [ { "add": { "index": "index_name1", "alias": "alias_name" } }, { "add": { "index": "index_name2", "alias": "alias_name" } } ] }
  • 一个索引指定多个别名 POST /_aliases { "actions": [ { "add": { "index": "index_name", "alias": "alias_name_1" } }, { "add": { "index": "index_name", "alias": "alias_name_2" } } ] }
  • 通过别名查询索引,如果多个索引都是这个别名,那么就是查询多个索引 GET /alias_name/_search { "query": {...} }
  • 通过别名写数据到索引 PUT /alias_name/type_name[/id] { data... } # 如果多个索引都是这个别名,那么可以指定具体写入哪个索引 # is_write_index设置了此索引是可以通过此别名被写入的索引 POST /_aliases { "actions": [ { "add": { "index": "index_name1", "alias": "alias_name", "is_write_index": true } }, { "add": { "index": "index_name2", "alias": "alias_name" } } ] }

2. 索引重建

Elasticsearch的索引在创建好之后其数据结构和一些固有设置是不能修改的,如果一定要修改其数据结构和某些设置例如主分片的个数,那么就必须重建索引,ES提供了一些辅助工具来支持索引重建。

索引重建步骤:

  • index_old取一个别名index_service,index_service对外提供服务
  • 新创建一个索引index_new,数据结构与index_old一样,但是根据需求修改了某些字段或者修改了某些设置
  • 将index_old的数据同步到index_new
  • 给index_new取别名index_service,删除index_old的别名
  • 删除index_old

案例:

代码语言:javascript
复制
# 创建index_old
PUT index_old
{
  "mappings": {
    "_doc": {
      "properties": {
        "id": {"type": "long"},
        "name": {"type": "text"}
      }
    }
  }
}

POST /_bulk
{"index":{"_index":"index_old","_type":"_doc","_id":"1"}}
{"id": 1001, "name": "张三丰"}
{"index":{"_index":"index_old","_type":"_doc","_id":"2"}}
{"id": 1002, "name": "张无忌"}
{"index":{"_index":"index_old","_type":"_doc","_id":"3"}}
{"id": 1003, "name": "虚竹"}
{"index":{"_index":"index_old","_type":"_doc","_id":"4"}}
{"id": 1004, "name": "云中鹤"}
{"index":{"_index":"index_old","_type":"_doc","_id":"5"}}
{"id": 1005, "name": "杨过"}

# 此时我们要把name字段的类型由text修改为keyword,就必须重建索引
# 1.index_old取一个别名index_service,index_service对外提供服务
POST /_aliases
{
  "actions": [
    {
      "add": {
        "index": "index_old",
        "alias": "index_service"
      }
    }
  ]
}

# 2.新创建一个索引index_new,数据结构与index_old一样,但是name字段的类型为keyword
PUT index_new
{
  "mappings": {
    "_doc": {
      "properties": {
        "id": {"type": "long"},
        "name": {"type": "keyword"}
      }
    }
  }
}

# 3.将index_old的数据同步到index_new
# ES6.X需要指定旧索引的type和新索引的type
POST /_reindex?wait_for_completion=false
{
  "source": {
    "index": "index_old", 
    "type": "_doc"
  },
  "dest": {
    "index": "index_new", 
    "type": "_doc"
  }
}
# ES7.X不需要指定type
POST /_reindex?wait_for_completion=false
{
  "source": {
    "index": "index_old"
  },
  "dest": {
    "index": "index_new"
  }
}
# wait_for_completion=true,同步执行,会一直等待直到同步完成或者报错,默认
# wait_for_completion=false,异步执行,返回taskId,然后后台进行同步,数据量大的情况下建议使用

# 4.将index_old的别名删除,index_new的别名设置为index_service
POST /_aliases
{
  "actions": [
    {
      "remove": {
        "index": "index_old",
        "alias": "index_service"
      }
    },
    {
      "add": {
        "index": "index_new",
        "alias": "index_service"
      }
    }
  ]
}

# 5.删除index_old
DELETE /index_old

3. refresh、flush和merge

3.1 document写入原理一

  • 一个document先被写入一个内存buffer中
  • 当Buffer被写满或者到达一定时间后,执行commit操作,将buffer中的所有数据写入到segment中,一个index在物理上就是由多个segment文件组成的,但此时将buffer中的数据写到segment中,并非是写入磁盘,而是先写到页面缓存中,也就是OS Cache中
  • 如果是删除操作,那么在commit的时候,会往.del文件中将此document标记为"delete",搜索的时候,比如搜索到id=1的文档,发现在.del文件中已经被标记为"delete"了,就不会返回这条数据
  • 如果是更新操作,那么commit的时候,那么在.del文件中将旧数据标记为"delete"(document是有版本的),然后将新数据写入到新的segment中,查询的时候返回最新版本的document
  • 然后OS cache将segment刷写到磁盘上,形成segment文件,然后对这个segment文件执行一个"open"操作,这个这个segment文件就可以用于搜索了,同时刷写到磁盘之后,还会清空内存buffer

3.2 document写入原理二:refresh

上述流程是有问题的,从写入一条document到OS Cache将segment刷写到磁盘,并且该segment被打开允许搜索这个过程是有延迟的,可能达到min级别,这就不是近实时的搜索了,上述流程的主要瓶颈在于OS Cache将segment刷写到磁盘的过程,于是,我们想,只要数据被写入OS Cache中了,就可以被搜索,而不用刷写到磁盘后才能搜索,这样就可以提高搜索效率,索引刷新(refresh)功能就是让某条数据在写入OS Cache后就可以被搜索。

refresh默认时间间隔是1s,也就是说每隔一秒,将buffer中的数据写入到OS Cache中,并且允许该数据被搜索。

代码语言:javascript
复制
# 数据写入就强制刷新
PUT /index_name/type_name/id?refresh
{
  "data": "..."
}

# 设置index的刷新时间
PUT /index_name/_settings
{
  "index": {
    "refresh_interval": "5s"
  }
}

3.3 document写入原理三:translog和flush

在原理二的优化之后,ES就可以满足近实时的查询效率,但是还有一个问题是可靠性,因为document在真正的写入磁盘之前都是在内存中的,那么ES宕机就可能会丢失数据,Translog文件可以解决这个问题:

改进后得流程如下:

(1) document写入buffer缓冲,同时写入Translog日志文件

(2) 每隔1s,buffer中的数据被写入新的segment,并进入OS Cache,此时segment被打开并可以用于搜索

(3) buffer被清空

(4) 重复(1)-(3),新的segment不断添加,buffer不断被清空,而translog中的数据不断累加

(5) 当translog的大小达到一定程度,触发flush操作:

​ (5-1) buffer中的所有数据写入一个新的segment,并写入OS Cache,segment被打开并可以用于搜索

​ (5-2) buffer被清空

​ (5-3) 一个commit ponit文件被写入磁盘,其中标明了index当前所有的segment有哪些

​ (5-4) OS Cache中的所有segment缓存数据,被强行刷写到磁盘上

​ (5-5) 现有的translog被清空,创建一个新的translog

基于Translog如何进行数据恢复?

ES宕机之后,内存中的数据全部丢失,重启之后,上一次flush之后的全部数据都在Translog中保存着,将这些数据重新加载到内存buffer中并形成一个个segment然后写入OS Cache中即可。

相关设置参数如下:

index.translog.durability:新增、删除,更新或批量操作之后是否写入Translog并触发flush操作

  • request(默认):每次请求都写入Translog并触发flush操作
  • async:按照时间间隔来写入Translog并触发flush操作

index.translog.sync_interval:写数据请求提交到Translog并触发flush操作的时间间隔,默认5s,最小不能低于100ms

index.translog.flush_threshold_size:Translog触发flush操作的文件大小阈值,默认512mb

代码语言:javascript
复制
PUT /index_name/_settings
{
    "index.translog.durability": "async",
    "index.translog.sync_interval": "5s"
}

# 手动触发flush操作
POST /index_name/_flush

3.4 document写入原理四:segment merge

在上述写入流程中,每秒生成一个segment,segment数量太多,每次search都要搜索所有的segment,性能不好,ES默认会在后台执行segment merge操作,在merge的时候,被标记为deleted的document也会被彻底物理删除,每次merge操作的执行流程:

  • 选择一些有相似大小的segment,merge成一个大的segment
  • 将新的segment flush到磁盘上去
  • 写一个新的commit point,其中记录着新的segment的元数据,删除那些旧的segment的元数据
  • 将新的segment打开用于搜索
  • 将旧的segment删除

手动执行merge的API:

代码语言:javascript
复制
# max_num_segments指定了合并之后的segments个数
POST /index_name/_optimize?max_num_segments=1
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 索引别名
  • 2. 索引重建
  • 3. refresh、flush和merge
    • 3.1 document写入原理一
      • 3.2 document写入原理二:refresh
        • 3.3 document写入原理三:translog和flush
          • 3.4 document写入原理四:segment merge
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档