Elasticsearch

Elasticsearch & AWS S3 备份

2017-10-30  本文已影响0人  RomainXie

我们的系统中大部分都是时序数据,一些数据被清洗后,过期的数据意义已经不大,但是保不齐哪天需要重新清洗或者查阅历史,所以准备将 Indices 的内容备份在 AWS S3 当中。

虽然我们采用 Elastic Cloud 的服务,他每 30 分钟会自动全盘用 Elastic 自己的 S3 系统备份一次,但是他只保留最近 48 小时左右的数据,并且他做的是整个 ES 系统的全量备份。这个备份的目标更像一个灾备的状态,而不是我们需要的历史数据备份。所以我们准备从 Elastic Cloud 每天备份过期数据到自己的 AWS S3 当中。

基本概念

使用 Elasticsearch Snapshot 时需要有一些基本概念澄清,他不是拿指定的 Indices 文件做个压缩包丢在 S3 完事,他是有控制的。

snapshot 结构

Elasticsearch 的 snapshot 是由其自身控制的,整个系统保持了一个如下的从下到上的控制结构,他们具备包含关系:

snapshot --> repository --> single snapshot --> indices

GET _snapshot/_all
{
  "found-snapshots": {
    "type": "s3",
    "settings": {
      "bucket": "PRIVATE-UUID",
      "base_path": "snapshots/PRIVATE-UUID",
      "server_side_encryption": "true",
      "region": "us-west-2",
      "compress": "true"
    }
  }
}

S3 插件

如果要想让 Elasticsearch 备份到 S3 当中需要单独安装一个插件 S3 Repository Plugin,在我们使用的 Elastic Cloud 当中这个插件已经默认安装了。我们并不知道安装中是否会遇到什么阻碍。

Snapshot 配置

在 Elasticsearch 中进行 snapshot 操作官方文档已经说得非常清楚了,整体结构和控制并不复杂。

Repository

在正常执行 _snapshot 以前,需要先建立好自己的 Repository。具体操作可以参考 S3 Repository Plugin 完成 S3 的配置操作。

其中 AWS 的账户口令控制不必非要写在系统的 YAML 配置文件中,直接在创建 Repository 的设定用起来会更加灵活。获取 Repository 的时候,系统会自动屏蔽账户信息部分。

AWS IAM

这个稍微复杂一点,也可能是我们对 AWS IAM 并不熟悉。按照 Elastic 官方给出的 Recommanded S3 Permissions 直接配置即可。

这里他需要获取到 AWS S3 Bucket 的列表权限,因为他会放置自己的一些控制文件进入,并且还需要进行比对操作。

如果你需要备份不同的 ES 系统到一个 AWS S3 Bucket 一定要分配到不同的目录当中,因为 Elasticsearch 的那些控制文件会导致他们之间冲突。这里可以参考这个 S3 Permissions 的说明中后面一个 IAM 配置说明即可。

snapshot 操作

当 Repository 做好后,就可以直接执行 _snapshot 操作了。具体的操作方法可以参考 Snapshot and Restore 部分的说明了。指令比 _search 不知道简单多少倍 🙄

如果你执行的时候,其他的 snapshot 正在执行,你会得到一个 503: Service Unavailable 的错误信息。这就是上面说的 snapshot 执行的独占性,哪怕不同的 repository 之间也不能并行。

S3 中文件的作用

Elasticsearch 在 S3 中创建 snapshot 的时候,会形成一些辅助文件,帮他管理 snapshot 内容。

最坑爹是 Elasticsearch 并不在自己的 Indices 当中创建备份信息,而是将所有这些信息都放在了 S3 当中。当然熟悉后也明白,这么做的好处是,可以让另一个 ES 系统通过同样的 Repository 配置读取这些 S3 中的内容。

文件目录结构

下面是我们专门保存 Elasticsearch Snapshot 的 S3 Bucket 中的文件结构,"-" 表示这是一个文件,"+" 表示这是一个目录。

S3 Bucket
+ Directory for each ES
  - incompatible-snapshots
  - index-A
  - index-B
  - index.latest
  - meta-SNAPUUID.dat
  - snap-SNAPUUID.dat
  + NO NAMED DIR
    + indices
      + INDEXUUID
        - meta-SNAPUUID.dat
        + 0
          - __*
          - index-0
          - snap-SNAPUUID.dat

其中目录的情况如下,对于那些文件后面专门说。

UUID 的使用

在 snapshot 执行的时候,他会给每个 snapshot 生成一个 UUID,我们叫他 SNAPUUID。

在 S3 文件系统说明中会在每个 meta-* & snap-* 的部分使用这个 SNAPUUID 生成相关文件。这些文件都是二进制文件,应该是内部的一些 mapping 内容。

并且他会给每个被 snapshot 的 Index 文件生成一个 UUID,我们叫他 INDEXUUID。虽然在 ES 系统中,每个 Index 都有自己的 UUID,但是在这里保存的时候,snapshot 并没有复制这个 UUID,而是给每个 Index 起了新的 UUID。这样的方式应该可以有效的防止冲突问题。

这些 INDEXUUID 在 <indices> 目录中标记了每个独立的 Index,其中保存这个 Index 所有数据内容。这些 UUID 都在 <index-A> & <index-B> 中有重要作用,稍后再说。

数据文件

在 <indices.INDEXUUID.0> 目录当中,所有这个 Index 的数据文件都在这里了。数据文件采用 "__" 开头,后面使用 [0-9, a-z] 进行顺序编号。在我们的系统中单个文件好像被限制在 1GB 大小,如果一个 "__" 数据文件超过这个限制,会形成类似 "__1q.part0" & "__1q.part1" 这样的分片文件。

这些数据文件的数目多少应该跟 Index 在系统中的 Segment 数量有关系,但是并不是 Segment 有多少这里就有多少。我们并没有通过实验查出来其中的关联关系。

这里单独谈数据文件是因为我们鸡贼了一下,后面再说。

控制文件

在根目录中有几个控制文件:

根目录 index* 文件

这些文件比较重要,着重做说明。

index-A & index-B

这里的 A & B 只是个标记,您的系统中可能叫做 index-18 & index-19。这个 index- 开头的文件在系统中会一直保持有两个。

他保存了一次 snapshot 操作开始执行时的 S3 当中 snapshot 的情况,和执行后的 snapshot 情况。这些文件是 JSON 文件,有兴趣完全可以打开看看。

{
  "snapshots": [
    {
      "name": "snap-2017-10-14",
      "uuid": "ilVKxRUxRxyJRmfnps6HDA",
      "state": 1
    },
    {
      "name": "snap-2017-10-18",
      "uuid": "T4rGaActS6-dJW6Pz6KGHA",
      "state": 1
    }
  ],
  "indices": {
    "metric-2017-10-24": {
      "id": "1smmvcuATkuy-hokdARDPw",
      "snapshots": [
        "nhFg0A3QT4yHOAhzDMY_3w"
      ]
    },
    "metric-2017-10-25": {
      "id": "pjFu2jmKSNGpp1L-W55iHw",
      "snapshots": [
        "sFtZ2JGRTqmWzK3wH9vVlg"
      ]
    }
  }
}

我们的这个文件比较长,我摘取了其中一段,上下并不能对应,不好意思。

这个文件的结构很清晰,两个部分:snapshots & indices,其中包含了所有的 S3 中刚才提到的 UUID 内容。

后面谈到数据出现问题恢复的时候,就靠这里的 UUID 来解决问题了。

index.latest

这是用文本文件存储的 64bits 数字,他说明当前上面的 index-* 文件后面的最新编号应该是什么。

通过修改这个文件中的编号,然后自己仿造一个 index-XX 文件后,完全可以让 ES 系统只读取你希望他看到的 snapshot 或者 indices 记录内容。后面文件损坏后的恢复中会用到这个文件。

AWS S3 存储格式选择

AWS S3 提供了 4 中存储文件的类型:

类型 Remark
Standard 标准的文件存储
Standard_IA 不频繁调用的文件存储,价格比 Standard 便宜 50% OFF
Glacier 存档文件,必须手动恢复才能调用,价格比 Standard 便宜 75% OFF
Reduced_Redundancy 可靠性降低的存储,读取速度也会慢一点,但是价格比 Standard 贵一点点,我都怀疑是不是我眼花了,实在不明白他存在的意义 😓

他们具体的价格可以参考 AWS S3 存储类别 而对于 Reduced Redundancy Price 是单列的。有懂得朋友给解释解释 Reduced Redundancy 的类型工作目标到底是啥?存在即合理吗,我实在想不明白。

S3 操作

在 AWS S3 的控制台操作文件转换的时候,可以设定 Standard / Standard_IA (SIA) / Reduced_Redundancy (RRS) 为文件转换目标格式。而针对 Glacier 格式,只能通过 “生存周期” 控制中指定创建天数后进行转换。

不明白 AWS 这样的设定意义是什么,不过系统控制台操作有这么个限制。大家别说在控制台找不到转换为 Glacier 的位置。

服务端压缩与加密

在设定 Repository 的时候,建议设定 "compress": "true" 选项。我们的数据内容大概压缩的比例是 43% OFF 的状态。

AWS S3 存储和执行命令收钱 CPU 又不收钱,让他服务端去压缩去吧。这个存储容量大概省一半银子。

而对于加密选项我们没有测试,看过说明应该会使用到一些证书的配置。我们采用独立的 S3 Bucket 进行访问权限限制来进行一定的安全限制,就没有添加这个加密选项,省的其他 ES 从 S3 恢复数据时给自己找麻烦。

多省点银子 😄

我们的系统选择了 Standard_IA 模式存储。 "storage_class": "standard_ia"

Standard_IA 模式有两个限制:

我们的每个 Index 文件大概在 10GB - 20GB 左右。统计了一下一个 Index 一共有 119 个文件,小于 128KB 的只有 46 个,容量有 9.1MB。这个状态怎么算都是用 SIA 模式更便宜。并且在数据备份后,应该从 S3 恢复到 ES 系统的频率会比较低,所以我们选择了 Standard_IA 模式,来降低存储的成本。50% OFF 哦 :-)

并且针对所有存储的文件我们设定创建 390 天后,自动转换为 Glacier 模式,再省 50% 银子。因为 SIA 模式 30 天的限制,365 天肯定是亏的 😀

但是在转换 Glacier 模式的时候,请注意要添加一个 "__" 前缀的模式匹配。

前前后后在 AWS S3 的存储中,这一系列的操作可以节省大概 85% OFF 的成本。下面是我们的 Repository 的设定,供参考:

{
  "my-repository": {
    "type": "s3",
    "settings": {
      "bucket": "MY-BUCKET",
      "endpoint": "s3-us-west-2.amazonaws.com",
      "protocol": "http",
      "server_side_encryption": "false",
      "storage_class": "standard_ia",
      "max_retries": "3",
      "readonly": "false",
      "compress": "true",
      "base_path": "ES-DIRECTORY/"
    }
  }
}

传输速率

在我们测试的过程中,同一个 AWS Region 传送的速率在 90Mbps - 40Mbps 之间,不知道是否因为虽然同一个 Region 而不同的账户会导致速率下降。因为 Elastic Cloud 自己做的全量备份,明显速度比这个快很多。大家可以通过这个速度预估自己的 snapshot 执行的时间长度。

并且,在备份的过程中,我们发现有时候一个 snapshot 中的不同 Index 是同时并行发送的,而有时候是串行同步的。感觉上应该是如果 Index 有 Replicas 那么就可以并行,否则串行,这个没有特别认真的核对过。我们对现在的速度已经很满意了。毕竟一个 snapshot 是 10 秒执行完还是 10分钟执行完,没太多人关心 😄

传输异常情况

操作过程中我们发生过一次卡死在指定位置不动,一个十来分钟的 snapshot,我们等了超过一个小时。并不知道这个问题出现在哪里。

这时候,我们通过 DELETE 这个正在操作的 snapshot 内容,中断了这个操作。但是,在后一次的操作过程中有点奇怪,不过我们再也模拟不出这种卡死情况,就只剩一个奇怪的感觉了。后面 snapshot & restore 的所有过程中,并没有发现有什么不对的地方。

Snapshot 操作监控

我们通过 AWS Lambda 启动 Elasticsearch Snapshot 后,2 个小时添加了一个 Elasticsearch Watcher 程序用于检查备份是否完成。

监控通过 GET _snapshot/my-repository/_all 获取所有 snapshot 的列表,然后根据 <snapshots.snapshot> 这个位置的内容判断是否存在指定的 snapshot。若有,则判断 <snapshots.state> 位置的内容,如果正在备份中会标记 "IN_PROGRESS",如果已经完成会标记 "SUCCESS"。

我们整体备份过程中没有发现出现其他的错误状态,哪位大虾碰到过给个范例,万分感谢。

个别文件类型不对

有些 snapshot 生成的文件,应该是 Elasticsearch 自己控制的,他们并不受你指定的存储类型限制。默认这些文件都是 standard 格式的。

上面这些文件不管你怎么搞,都会是 standard 格式。

文件损坏和修复

AWS S3 从来没有保证自己 100% 的文件可靠性。当一些文件出现问题的时候,需要进行一些修复才能正常使用 _snap 的相关指令内容。

当然我们关心这个是希望降低存储成本,定期会将文件转换为 Glacier 格式,这种格式将导致系统会认为这个文件不存在了。

当一些文件出现问题时,我们将遇到如下的一些问题。

对状态指令的影响

在执行相关指令的时候大概经历了如下的过程:

如果这些文件出现问题,就会得到一个 500: no_such_file_exception 的错误信息。这时候除非恢复这些关联的文件,则 DELETE 指令和 _restore 指令都无法执行。

对 restore 的影响

若其中个别文件出现问题,则会给出一个 503: concurrent_snapshot_execution_exception 的错误信息。请注意,在进行 _restore 操作的时候,建议设置 "ignore_unavailable": false 参数,避免出现无法恢复而得到一个正确结果的假象。

这时执行 DELETE _snapshot/my-repository/snap-YYYY-MM-DD 指令一样会得到 503: concurrent_snapshot_execution_exception 的错误信息。

必须对 AWS S3 上的控制文件进行修复后,才能继续执行这些实际的操作指令。

修复

当这些文件有问题的时候,只能屏蔽指定的 Index 文件或者指定的 snapshot 了。

就跟前面说的一样,这时需要打开根目录中的 <index-B> 文件,编辑其中的内容。将出现问题的内容删除即可,当然如果是跳过 snapshot,需要改 snapshot & indices 两个部分的内容。

在修改的时候,就需要通过 UUID 对应目录结构中的内容和 <index-B> 中的内容处理了。当然,你也可以创建一个新的 <index-XX> 文件,然后修改 <index.latest> 文件指向他,来进行这个恢复。这样如果不行,起码还有个回退的机会。😀

当然还有一个更加保险一些的方案,就是当接收到一个 snapshot 后,可以将根目录的 <meta-SNAPUUID> <snap-SNAPUUID> <indices.INDEXUUID> 这些文件弄个 ZIP 包,在备份到另外一个 AWS S3 区域当中。不过这个花费就不仅仅是存储了,还有传输等各种费用问题。也可以利用 AWS Glacier 的独立控制台生成这些内容,他跟 AWS S3 上的限定日期转换为 Glacier 是不同的。这个部分我们没有这么高的要求,就没有详细看了。

如果有损坏文件的备份,只要将其恢复到指定位置中,受影响的 snapshot 指令立刻就可以执行了。在 Elasticsearch 内部没有保存对应信息的好处就在这里了,如果他保存了一个什么状态机,那就等着哭吧。

Snapshot 指令的一些说明

官方在 Snapshot and Restore 已经介绍的非常详细了。一般就这些指令:

相关参考

上一篇 下一篇

猜你喜欢

热点阅读