...
IoTDB中以Raft数据组(Data Group)进行数据复制的一致性维护,数据组内的所有节点维护同样的数据副本。每个数据组内的数据副本由该组所管理的哈希槽中的所有数据分区组成,每次数据写入都会在Raft组利用一条Raft log来进行同步,当超过Quorum数的组内成员收到log后,即可commit,提高了数据写入的效率。
数据写入
IoTDB集群遵循以下的写入流程:
用户选择集群中的任何一个节点作为协调者节点,建立连接并发送数据写入请求。
协调者节点先解析写入操作中的所有时间序列所属存储组,例如写入时间序列 "root.sg1.d1.s1" 和 "root.sg2.d1.s1",若存储组不存在时,会自动创建时间序列,并计算时间戳对应的时间片,然后根据一致性哈希算法计算所有被写入的时间序列所在的数据分区即 < 存储组,时间戳 > 对应的哈希槽,最后确定管理所有哈希槽的数据组,例如数据组1和数据组2。
协调者节点将写入请求转化的物理计划发送给数据组1和数据组2的任意节点。
在数据组1和数据组2的任意节点收到物理计划后,会转发给该数据组的leader,管理该数据的数据组leader分发日志并等到超过半数数据组成员收到后,数据组 leader 提交日志,若该存储组内不存在被写入的时间序列,则会自动创建时间序列,之后再重新执行写入操作。
最后将结果返回给用户。
数据查询
对于数据查询请求,从整体而言,收到请求的协调者节点会首先解析该请求(可能需要从其他节点拉取时间序列元数据),接着会通过LocalSeriesReader / RemoteSeriesReader(是对Apache IoTDB 单机版SeriesReader的一个封装)的方式以batchData为单位从本地或远程来获取查询结果。
Apache IoTDB 分布式查询的具体流程:解析SQL -> 逻辑计划 QueryOperator -> 物理计划 QueryPlan -> 物理计划执行Executor 生成结果集 -> 返回结果集。
“解析SQL -> 逻辑计划 QueryOperator”:该步骤会在协调者节点上完成antlr解析和内部结构赋值等计算工作。
“逻辑计划 QuryOperator -> 物理计划 QueryPlan”:该步骤涉及到与元数据的交互,包括类型获取/检查,通配符消除等等,往往需要协调者节点完成计算工作,也可能涉及到远程节点完成相应的IO操作。
“物理计划 QueryPlan -> 执行物理计划”:
为每个待查序列创建SeriesReader:
对于本地序列,会建立LocalSeriesReader,并且保存在本地的DataSet中
对于远端序列,会创建RemoteSeriesReader,并且在远程注册一个queryId对应的LocalSeriesReader。
将所有待查序列看做本地序列,执行单机查询流程。
“执行物理计划 -> 结果集返回”:
该步骤会不断的调用next函数来获取到数据:
对于本地的情况,会利用刚刚的LocalSeriesReader从本地的tsfile里面来获取BatchData。
对于远程的情况,会利用刚刚的RemoteSeriesReader通过RPC请求,利用远程节点的LocalSeriesReader从远程的TsFile里面获取BatchData。
在结果集返回后需要释放所有的资源:
对于本地的情况,直接释放对应的LocalSeriesReader的相关资源即可。
对于远程的情况,不仅仅需要释放本地RemoteSeriesReader相关的资源,还需要发送携带queryId的RPC请求释放对应远程节点的LocalSeriesReader的相关节点。
对于聚合,groupby 等可以将计算下推的查询,协调者节点会将该部分计算也下推到数据节点去执行,对应的数据节点只需返回计算结果个协调者节点即可。这样的设计可以更好地利用集群的计算资源。
查询一致性:提供强、中、弱三种级别的一致性,允许用户通过配置参数consistency_level来进行调整,主要针对的是数据组Follower read的场景,保证每个集群节点优先在本地进行查询,支持用户根据自己的具体业务场景来进行选择。
强一致性(strong consistency):如果协调者节点注册的RemoteSeriesReader路由到follower节点,则follower节点需要先与leader节点进行通信并等待本地数据为最新数据后返回结果,如果和leader节点通信超时或失败则报错。
中一致性(mid consistency):如果协调者节点注册的RemoteSeriesReader路由到follower节点,则follower节点需要先与leader节点进行通信并等待本地数据为最新数据后返回结果,不同的是如果和leader节点通信超时或失败则执行本地查询,并不会报错。
弱一致性(weak consistency):如果协调者节点注册的RemoteSeriesReader路由到follower节点,则follower节点直接使用本地数据进行查询并返回节点,而不和leader节点进行同步。
中一致性和弱一致性满足最终一致性。
...
集群的成员变更必然会带来数据迁移,数据迁移的本质就是数据分区的重分配,哈希环上的哈希槽会在成员变更后,均匀分布到变更后的所有Raft数据组里。在IoTDB中,使用了哈希槽的概念,该方法将哈希环等分为 Q 个区间,每个区间称为槽,集群中的各个节点以槽为单位管理数据,为了减少数据的倾斜性槽数 Q 远大于集群中的节点数。假设集群节点数为 N,则各个节点管理的槽数为 Q/N,当集群删除节点时将负责的槽均匀地分配给集群中的其他节点;同理当新增节点时将均匀地从其他节点获得一定数量的槽。IoTDB中通过计算数据分区的哈希值找到对应的哈希槽,确定存储位置。
对于加入节点的成员变更,如下图所示,在 5 节点 3 副本的集群中加入节点 6 后除了增加一个数据组 6(数据组成员 {6,3,4})外,数据组 1 和数据组 2 发生了成员替换。数据组 1 中新加入的节点 6 替换了节点 3,数据组成员由 {1,2,3} 变成了 {1,2,6};数据组 2 中新加入的节点 6 替换了节点 4,数据组成员由 {2,3,4} 变成了 {2,6,3}。
对于删除节点的成员变更,在 5 节点 3 副本的集群中移除节点 3 后除了删除了一个数据组 3(数 据组成员 {3,4,5})外,数据组 1 和数据组 2 发生了成员替换。数据组 1 中节点 4 替换了节点 3,数据组成员由 {1,2,3} 变成了 {1,2,4};数据组 2 中节点 5 替换了节点 3,数据组成员由 {2,3,4} 变成了 {2,4,5}。
增加节点涉及原有 Raft 组的成员替换、并增加一个 Raft 数据组(直接新增 Raft 组并进行选举)。删除节点涉及原有Raft组的成员替换、并删除一个 Raft 数据组(等数据迁移完后进行删除)。Raft 组的成员替换不可以直接执行,否则可能会产生脑裂现象。为了解决这一问题,需要在原有 Raft组内先增加新成员,再删除旧成员。
...