专栏首页Jed的技术阶梯021.Elasticsearch索引管理高级篇

021.Elasticsearch索引管理高级篇

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

案例:

# 创建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中,并且允许该数据被搜索。

# 数据写入就强制刷新
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

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:

# max_num_segments指定了合并之后的segments个数
POST /index_name/_optimize?max_num_segments=1

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • [算法题] 使用数组实现栈和队列

    CoderJed
  • Hive案例03-最高气温

    现有hive表temp,其中只有一个字段(temp_record string),每一行代表某一天的气温,比如,2014010114代表,2014年1月1日的气...

    CoderJed
  • zookeeper-3.4.10的安装配置

    leader:能接收所有的读写请求,也可以处理所有的读写请求,而且整个集群中的所有写数据请求都是由leader进行处理 follower:能接收所有的读写请求...

    CoderJed
  • Apache2为什么会自动加载index.php

    我直接输入域名后,Apache2自动加载了对应目录下的index.php, 这是怎么做到的?

    Jerry Wang
  • Product change时关于change_log的讨论

    版权声明:署名,允许他人基于本文进行创作,且必须基于与原先许可协议相同的许可协议分发本文 (Creative Commons)

    Jerry Wang
  • 精解四大集合框架:List核心知识总结

    Java集合框架早也是个老话题了,今天主要是总结一下关于Java中的集合框架List的核心知识点。肯定有人会问,这篇写的是List那接下来就还有三篇?是的,ja...

    田维常
  • Elasticsearch Index Setting一览表

    索引的配置项按是否可以更改分为static属性与动态配置,所谓的静态配置即索引创建后不能修改。

    丁威
  • Oracle 重建索引脚本

          索引是提高数据库查询性能的有力武器。没有索引,就好比图书馆没有图书标签一样,找一本书自己想要的书比登天还难。然而索引在使用的过程中,尤其是在批量的D...

    Leshami
  • golang mutex锁的竞争关系浅析

    刚才对golang的锁关系进行 一番思索,想着协程获取golang 对象锁的,是按先按时间先后顺序获取的,其实不然。下面请看代码,顺带写了2种读写锁的应用。

    地球流浪猫
  • 如何入门小程序开发

    在上一篇教程中,我们教大家使用微信官方Demo快速搭建了一个小相册,并学会了如何安装开发者工具,如何创建小程序,如何做服务端配置。并利用腾讯云COS实现相册上传...

    伤旧

扫码关注云+社区

领取腾讯云代金券