学习·Apache Doris元数据管理

学习·Apache Doris元数据管理

    • 一、 关键词
    • 二、 整体架构
    • 三、 元数据内容
    • 四、 高可用元数据
    • 五、元数据如何在集群间保证一致性与高可用?
    • 六、 元数据操作
    • 七、宕机恢复
    • 八、 参考

一、 关键词

FE、BE、bdbje

  1. FE
    Frontend,即 Doris 的前端节点。主要负责接收和返回客户端请求、元数据以及集群管理、查询计划生成等工作。
  2. BE
    Backend,即 Doris 的后端节点。主要负责数据存储与管理、查询计划执行等工作。
  3. bdbje
    全称Oracle Berkeley DB Java Edition,在Doris 中,使用 bdbje 存放Doris元数据,保证元数据持久性和一致性、FE 高可用等功能。
    Berkeley DB是一款分布式支持事务嵌入式KV数据库。

二、 整体架构

学习·Apache Doris元数据管理_第1张图片Doris 的整体架构分为两层。多个 FE 组成第一层,提供 FE 的横向扩展和高可用。多个 BE 组成第二层,负责数据存储与管理。元数据的设计与实现方式,主要与第一层的设计架构相关。

  1. FE 节点分为 follower 和 observer 两类。各个 FE 之间,通过 bdbje(BerkeleyDB Java Edition (opens new window))进行 leader 选举数据同步等工作。
  2. follower 节点通过选举,其中一个 follower 成为 leader 节点,负责元数据的写入操作。当 leader 节点宕机后,其他 follower 节点会重新选举出一个 leader,保证服务的高可用。
  3. observer 节点仅从 leader 节点进行元数据同步,不参与选举。可以横向扩展以提供元数据的读服务的扩展性。

三、 元数据内容

Doris 的元数据是全内存的。每个 FE 内存中,都维护一个完整的元数据镜像。
元数据在内存中整体采用树状的层级结构存储,并且通过添加辅助结构,能够快速访问各个层级的元数据信息。
下图是 Doris 元信息所存储的内容。
学习·Apache Doris元数据管理_第2张图片
如上图,Doris 的元数据主要存储4类数据:

  1. 用户数据信息。包括数据库、表的 Schema、分片信息等。
  2. 各类作业信息。如导入作业,Clone 作业、SchemaChange 作业等。
  3. 用户及权限信息。
  4. 集群及节点信息。

四、 高可用元数据

学习·Apache Doris元数据管理_第3张图片
元数据的数据流周期具体过程如下:

  1. 只有 leader FE 可以对元数据进行写操作。 写操作在修改 leader 的内存后,会序列化为一条log,按照 key-value 的形式写入 bdbje。其中 key 为连续的整型数字,全局唯一、递增,作为 log id;value 即为序列化后的操作日志,由2部分组成。OperationType为操作类型,如建库操作、建表操作等。Writable Entity为序列化后的操作具体内容,通过反序列化该值,可以从镜像上回放元数据操作。
    在这里插入图片描述

学习·Apache Doris元数据管理_第4张图片

  1. 集群节点同步元数据
    主节点按照元数据操作顺序,将元数据日志log,写入bdbje。日志写入 bdbje 后,bdbje 会根据策略(写多数/全写),将日志复制到其他 non-leader 的 FE 节点。non-leader FE 节点通过对bdbje中元数据日志的回放,修改自身的元数据内存镜像,完成与 leader 节点的元数据同步。
  2. 元数据checkpoint持久化
    leader 节点的日志条数达到阈值后(默认 10w 条),调用rollEditLog()方法,会在bdbje中生成1个新的DB(下面会提到),每形成1个新的DB,就会启动 checkpoint检查,生成新的image。checkpoint 会读取已有的 image 文件,和其之后的元数据日志,重新在内存中回放出一份新的元数据镜像,然后将新的镜像image写入到磁盘,在磁盘上形成一个新的 image。
    之所以是重新生成一份镜像副本,而不是将已有镜像写成 image,主要是考虑 image在写出期间,会加锁防止写入,阻塞写操作。
    因此,每次 checkpoint,会占用双倍镜像的内存空间。
    学习·Apache Doris元数据管理_第5张图片

图中代码,logEdit()接收元数据日志作为参数,op代表元数据操作类型,writable就是序列化后的元数据操作内容。
在方法体中,首先journal.write()将元数据写入bdbje。当写入bdbje失败,则系统会执行退出。这样可以保证Doris内存和持久化的元数据的一致性,方便元数据发生异常后回滚处理。

学习·Apache Doris元数据管理_第6张图片
日志写到bdbje后,记录当前日志的条数,判断超过阈值后,就滚动日志,形成checkpoint分割点,生成最新的镜像。这里的checkpoint分割点,可以这么理解:元数据日志一条一条被写入bdbje,加入配置阈值为10000,则达到10000条元数据日志之后,就会在bdbje中形成一个DB,然后新生成的10000条元数据日志和之前的image回放合并,形成新的镜像image。
学习·Apache Doris元数据管理_第7张图片

  • 从BDBJE回放元数据
    从bdbje中回放元数据时,首先会调用open方法,初始化bdbje环境,找到bdbje中当前最新的DB,就是前面说的checkpoint分割点形成的DB。
    学习·Apache Doris元数据管理_第8张图片

回放元数据日志时,找到当前新写入bdbje中,最大的日志id。然后从已经回放的日志id的下一条到最新的日志,遍历调用回放的逻辑。
学习·Apache Doris元数据管理_第9张图片

具体执行回放元数据日志逻辑时,从JournalEntity也就是元数据日志的value中,先获取到操作类型,根据操作类型匹配执行不同的具体回放操作。
学习·Apache Doris元数据管理_第10张图片

  • 问题

1)元数据持久化以及元数据同步,为什么采用image+日志回放的方式?
单纯采用回放的方式,当FE重启时,需要从镜像中回放日志到内存中,这样的话,日志越多,回放越慢,重启就会越慢。
采用image+镜像的方式,定期将日志合并成一个image。镜像image是内存元数据副本,直接加载不需要回放。重启加载镜像后,再从bdbje中回放镜像之后的元数据日志,进而减少回放日志,加快重启速度。
2)单节点故障如何保障元数据高可用

  • 生成镜像
    生成镜像时,会将磁盘中之前的旧镜像加载到内存,然后从bdbje中,将新的元数据操作日志在旧的镜像中回放,得到要checkpoint的新镜像。在代码中调用loadImage()从磁盘加载旧的镜像,调用replayJournal
    ()在旧的镜像基础上回放新的元数据日志。调用saveImage()将新的镜像保存到磁盘中。这期间,始终是可以提供正常的读写服务。之所以从旧的镜像回放日志,而不是直接将内存中提供读服务的元数据写出到磁盘,是为了避免元数据从内存写出磁盘过程中,加锁导致元数据服务不可写的情况(假设当需要生成镜像的数据很大时,写出过程几分钟甚至几十分钟,则元数据服务被阻塞,不可写入)
    学习·Apache Doris元数据管理_第11张图片
  1. image 文件生成后,leader 节点会通知其他 non-leader 节点新的 image 已生成。non-leader 主动通过 http 拉取最新的 image 文件,来更换本地的旧文件。

  2. bdbje 中的日志,在 image 做完后,会定期删除旧的日志。
    学习·Apache Doris元数据管理_第12张图片

  3. 删除旧的磁盘元数据镜像。
    学习·Apache Doris元数据管理_第13张图片
    总结一下:

Leader节点修改内存元数据后,元数据日志首先写入bdbje,达到阈值后,bdbje形成一个新的DB,然后将新的DB内的日志,在旧的image上回放,生成一个新的image,然后删除bdbje中已经被集群节点全部同步的旧的元数据日志。这样周而复始。保证bdbje中的数据不会无限制得增长,也保证了image中始终保存着较新的元数据。同时,其他非leader节点,也访问bdbje,将bdbje中的新写入的元数据日志,在自己内存中的元数据上回放。

学习·Apache Doris元数据管理_第14张图片

五、元数据如何在集群间保证一致性与高可用?

在前面提到,集群中的FE节点,被分为Follower和Observer两种角色,Follower节点被分为Leader和非Leader节点。只有Leader节点可以执行元数据,非Leader节点和Observer节点只进行元数据同步操作。那么Doris是如何保证Leader节点和非Leader节点与Observer节点之间的元数据高可用与一致性呢?

  1. 元数据一致性
  • 元数据同步
    前面提到,Leader节点在操作修改自身内存中的元数据后,会生成一条元数据操作日志。这条元数据操作日志,会被写入到bdbje中。leader节点的bdbje,会向非leader节点的bdbje同步数据(类似副本的概念)。其他非Leader节点和Observer节点,通过读取本机bdbje中新写入的操作日志,在自身节点内存中的元数据镜像上回放日志,完成元数据同步。
    也就是说,Doris中,Leader节点与非Leader节点和Observer节点之间的元数据高可用和一致性,是通过bdbje的一致性和高可用实现的。
    由于bdbje没有回调机制,写入数据后不能通知到非Leader节点和Observer节点,所以非Leader节点和Observer节点通过轮询的机制,去bdbje中查看是否有新的日志被写入。轮询间隔默认1ms。
    学习·Apache Doris元数据管理_第15张图片

当非leader节点与leader节点之间,通信发生异常,导致非leader节点无法同步最新的元数据时,如何处理呢?
答:对于这种情况,leader节点会定期向bdbje中写入一条内容为本地时间戳的bdbje日志(默认5s)。当非leader节点收到这条时间戳日志后回放,如果与本地时间超过阈值,则该非leader节点将处于元数据服务不可读状态。
下面会再次提到这个机制。

  1. 元数据高可用
  • Leader节点宕机
    当Leader节点发生异常宕机后,会在非Leader节点中选举产生一个新的Leader节点,继续对外提供元数据服务。
    Master节点的选举,依赖bdbje的master的选举,bdbje的master节点即为follower的leader。
    follower启动一个监听器,监听bdbje的事件。当一个非leader节点,收到MASTER事件后,被告知提升为leader,则该follower就开始提供元数据写服务。

学习·Apache Doris元数据管理_第16张图片

六、 元数据操作

  1. 元数据目录
  • 元数据目录通过 FE 的配置项 meta_dir 指定。
  • bdb/ 目录下为 bdbje 的数据存放目录。
  • image/ 目录下为 image 文件的存放目录。
    • image.[logid] 是最新的 image 文件。后缀 logid 表明 image 所包含的最后一条日志的 id。
    • image.ckpt 是正在写入的 image 文件,如果写入成功,会重命名为 image.[logid],并替换掉旧的 image 文件。
    • VERSION 文件中记录着 cluster_id。cluster_id 唯一标识一个 Doris 集群。是在 leader 第一次启动时随机生成的一个 32 位整型。也可以通过 fe 配置项 cluster_id 来指定一个 cluster id。
    • ROLE 文件中记录的 FE 自身的角色。只有 FOLLOWER 和 OBSERVER 两种。其中 FOLLOWER 表示 FE 为一个可选举的节点。(注意:即使是 leader 节点,其角色也为 FOLLOWER)
      在这里插入图片描述
  1. FE启动流程
  • FE 第一次启动,如果启动脚本不加任何参数,则会尝试以 leader 的身份启动。在 FE 启动日志中会最终看到 transfer from UNKNOWN to MASTER。
  • FE 第一次启动,如果启动脚本中指定了 -helper 参数,并且指向了正确的 leader FE 节点,那么该 FE 首先会通过 http 向 leader 节点询问自身的角色(即 ROLE)和 cluster_id。然后拉取最新的 image 文件。读取 image 文件,生成元数据镜像后,启动 bdbje,开始进行 bdbje 日志同步。同步完成后,开始回放 bdbje 中,image 文件之后的日志,完成最终的元数据镜像生成。

注1:使用 -helper 参数启动时,需要首先通过 mysql 命令,通过 leader 来添加该 FE,否则,启动时会报错。
注2:-helper 可以指向任何一个 follower 节点,即使它不是 leader。
注2:bdbje 在同步日志过程中,fe 日志会显示 xxx detached, 此时正在进行日志拉取,属于正常现象。

  • FE 非第一次启动,如果启动脚本不加任何参数,则会根据本地存储的 ROLE 信息,来确定自己的身份。同时根据本地 bdbje 中存储的集群信息,获取 leader 的信息。然后读取本地的 image 文件,以及 bdbje 中的日志,完成元数据镜像生成。(如果本地 ROLE 中记录的角色和 bdbje 中记录的不一致,则会报错。)
  • FE 非第一次启动,且启动脚本中指定了 -helper 参数。则和第一次启动的流程一样,也会先去询问 leader 角色。但是会和自身存储的 ROLE 进行比较。如果不一致,则会报错。
  1. 元数据读写与同步
  • 用户可以使用 mysql 连接任意一个 FE 节点进行元数据的读写访问。如果连接的是 non-leader 节点,则该节点会将写操作转发给 leader 节点。leader 写成功后,会返回一个 leader 当前最新的 log id。之后,non-leader 节点会等待自身回放的 log id 大于回传的 log id 后,才将命令成功的消息返回给客户端。这种方式保证了任意 FE 节点的 Read-Your-Write 语义。

注:一些非写操作,也会转发给 leader 执行。比如 SHOW LOAD 操作。因为这些命令通常需要读取一些作业的中间状态,而这些中间状态是不写 bdbje 的,因此 non-leader 节点的内存中,是没有这些中间状态的。(FE 之间的元数据同步完全依赖 bdbje 的日志回放,如果一个元数据修改操作不写 bdbje 日志,则在其他 non-leader 节点中是看不到该操作修改后的结果的。)

  • leader 节点会启动一个 TimePrinter 线程。该线程会定期向 bdbje 中写入一个当前时间的 key-value 条目。其余 non-leader 节点通过回放这条日志,读取日志中记录的时间,和本地时间进行比较,如果发现和本地时间的落后大于指定的阈值(配置项:meta_delay_toleration_second。写入间隔为该配置项的一半),则该节点会处于不可读的状态。此机制解决了 non-leader 节点在长时间和 leader 失联后,仍然提供过期的元数据服务的问题。
  • 各个 FE 的元数据只保证最终一致性。正常情况下,不一致的窗口期仅为毫秒级。我们保证同一 session 中,元数据访问的单调一致性。但是如果同一 client 连接不同 FE,则可能出现元数据回退的现象。(但对于批量更新系统,该问题影响很小。)

七、宕机恢复

  1. leader 节点宕机后,其余 follower 会立即选举出一个新的 leader 节点提供服务。
  2. 当多数 follower 节点宕机时,元数据不可写入。当元数据处于不可写入状态下,如果这时发生写操作请求,目前的处理流程是 FE 进程直接退出。后续会优化这个逻辑,在不可写状态下,依然提供读服务。
  3. observer 节点宕机,不会影响任何其他节点的状态。也不会影响元数据在其他节点的读写。

八、 参考

https://www.slidestalk.com/doris.apache/88689
https://doris.apache.org/zh-CN/internal/metadata-design.html
https://docs.oracle.com/cd/E17277_02/html/ReplicationGuide/BerkeleyDB-JE-Replication.pdf

你可能感兴趣的:(数据仓库,内功修炼,数据库,大数据,apache)