阿里云InfluxDB®是阿里云基于开源版InfluxDB打造的一款时序数据库产品,提供更稳定的持续运行状态、更丰富强大的时序数据计算能力。在现有的单节点版本以外,阿里云InfluxDB®团队还将推出多节点的高可用版本。数据库
咱们知道现有的开源版InfluxDB只提供单节点的能力,早期开源的集群版本功能不完善、且社区再也不提供更新与支持。通过对官网商业版InfluxDB现有文档的研究,咱们猜想在商业版InfluxDB集群方案中,meta信息集群是基于一致性协议Raft作同步的,而数据是异步复制的。这种分离的方式虽然有优势,但也引发了一系列的一致性问题,在一些公开的文档中,官方也认可这种数据复制方案并不使人满意。缓存
所以,团队在参考多项技术选型后,决定采用最为普遍使用并有较长历史积累的ETCD/Raft做为核心组件实现阿里云InfluxDB®的Raft内核,对用户全部的写入或一致性读请求直接进行Raft同步(不作meta信息同步与数据写入在一致性过程当中的拆分),保证多节点高可用版本拥有知足强一致性要求的能力。数据结构
有幸笔者参与到多节点的高可用版本的开发中,期间遇到很是多的挑战与困难。其中一项挑战是ETCD的Raft框架移植过程当中,在移除了ETCD自身较为复杂、对时序数据库没有太多做用的Raft日志模块后,所带来的一系列问题。本文就业界Raft日志的几种不一样实现方案作讨论,并提出一种自研的Raft HybridStorage方案。架构
因为咱们采用了ETCD/Raft的方案,绕不开讨论一下ETCD本家的Raft日志实现方式。并发
官网对Raft的基本处理流程总结参考下图所示,协议细节本文不作扩展:app
对于ETCD的Raft日志,主要包含两个主要部分:文件部分(WAL)、内存存储部分(MemoryStorage)。框架
文件部分(WAL),是ETCD Raft过程所用的日志文件。Raft过程当中收到的日志条目,都会记录在WAL日志文件中。该文件只会追加,不会重写和覆盖。异步
内存存储部分(MemoryStorage),主要用于存储Raft过程用到的日志条目一段较新的日志,可能包含一部分已共识的日志和一些还没有共识的日志条目。因为是内存维护,能够灵活的重写替换。MemoryStorage有两种方式清理释放内存:第一种是compact操做,对appliedId以前的日志进行清理,释放内存;第二种是周期snapshot操做,该操做会建立snapshot那一时刻的ETCD全局数据状态并持久化,同时清理内存中的日志。分布式
在最新的ETCD 3.3代码仓库中,ETCD已经将Raft日志文件部分(WAL)和Raft日志内存存储部分(MemoryStorage)都抽象提高到了与Raft节点(Node)、Raft节点id以及Raft集群其余节点信息(*membership.RaftCluster)平级的Server层级,这与老版本的ETCD代码架构有较大区别,在老版本中Raft WAL与MemoryStorage都仅仅只是Raft节点(Node)的成员变量。高并发
通常状况下,一条Raft日志的文件部分与内存存储部分配合产生做用,写入时先写进WAL,保证持久化;随之立刻追加到MemoryStorage中,保证热数据的高效读取。
不管是文件部分仍是内存存储部分,其存储的主要数据结构一致,都是raftpb.Entry。一条log Entry主要包含如下几个信息:
参数 | 描述 |
---|---|
Term | leader的任期号 |
Index | 当前日志索引 |
Type | 日志类型 |
Data | 日志内容 |
此外,ETCD Raft日志的文件部分(WAL)还会存储针对ETCD设计的一些额外信息,好比日志类型、checksum等等。
CockroachDB是一款开源的分布式数据库,具备NoSQL对海量数据的存储管理能力,又保持了传统数据库支持的ACID和SQL等,还支持跨地域、去中 心、高并发、多副本强一致和高可用等特性。
CockroachDB的一致性机制也是基于Raft协议:单个Range的多个副本经过Raft协议进行数据同步。Raft协议将全部的请求以Raft Log的形式串行化并由Leader同步给Follower,当绝大多数副本写Raft Log成功后,该Raft Log会标记为Committed状态,并Apply到状态机。
咱们来分析一下CockroachDB Raft机制的关键代码,能够很明显的观察到也是从鼻祖ETCD的Raft框架移植而来。可是CockroachDB删除了ETCD Raft日志的文件存储部分,将Raft日志所有写入RocksDB,同时自研一套热数据缓存(raftentry.Cache),利用raftentry.Cache与RocksDB自身的读写能力(包括RocksDB的读缓存)来保证对日志的读写性能。
此外,Raft流程中的建立snapshot操做也是直接保存到RocksDB。这样实现的缘由,我的推测是可能因为CockroachDB底层数据存储使用的就是RocksDB,直接使用RocksDB的能力读写WAL或者存取snapshot相对简单,不须要再额外开发适用于CockroachDB特性的Raft日志模块了。
在阿里云InfluxDB多节点高可用方案实现过程当中,咱们采用了ETCD/Raft做为核心组件,根据移植过程当中的探索与InfluxDB实际须要,移除了原生的snapshot过程。同时放弃原生的日志文件部分WAL,而改用自研方案。
为何移除snapshot呢?原来在Raft的流程中,为了防止Raft日志的无限增长,会每隔一段时间作snapshot,早于snapshot index的Raft日志请求,将直接用snapshot回应。然而咱们的单Raft环架构若是要作snapshot,就是对整个InfluxDB作,将很是消耗资源和影响性能,并且过程当中要锁死整个InfluxDB,这都是不能让人接受的。因此咱们暂时不启用snapshot功能,而是存储固定数量的、较多的Raft日志文件备用。
自研的Raft日志文件模块会周期清理最先的日志防止磁盘开销过大,当某个节点下线的时间并不过长时,其余正常节点上存储的日志文件若是充足,则足够知足它追取落后的数据。但若是真的发生单节点宕机太长,正常节点的日志文件已出现被清理而不足故障节点追取数据时,咱们将利用InfluxDB的backup和restore工具,将落后节点还原至被Raft日志涵盖的较新的状态,而后再作追取。
在咱们的场景下,ETCD自身的WAL模块并不适用于InfluxDB。ETCD的WAL是纯追加模式的,当故障恢复时,正常节点要相应落后节点的日志请求时,就有必要分析并提取出相同index且不一样term中那条最新的日志,同时InfluxDB的一条entry可能包含超过20M的时序数据,这对于非kv模式的时序数据库而言是很是大的磁盘开销。
咱们自研的Raft日志模块命名为HybridStorage,即意为内存与文件混合存取,内存保留最新热数据,文件保证所有日志落盘,内存、文件追加操做高度一致。
HybridStorage的设计思路是这样的:
(1)保留MemoryStorage:为了保持热数据的读取效率,内存中的MemoryStorage会保留做为热数据cache提高性能,可是周期清理其中最先的数据,防止内存消耗过大。
(2)从新设计WAL:WAL再也不是像ETCD那样的纯追加模式、也不须要引入相似RocksDB这样重的读写引擎。新增的日志在MemoryStorage与WAL都会保存,WAL文件中最新内容始终与MemoryStorage保持彻底一致。
通常状况下,HybridStorage新增不一样index的日志条目时,须要在写内存日志时同时操做文件执行相似的增减。正常写入流程以下图所示:
当出现了同index不一样term的日志条目的状况,此时执行truncate操做,截断对应文件位置以后一直到文件尾部的所有日志,而后从新用append方式写入最新term编号的日志,操做逻辑上十分清晰,不存在Update文件中间的某个位置的操做。
例如在一组Raft日志执行append操做时,出现了以下图所示的同index(3七、3八、39)不一样term的日志条目的状况。在MemoryStorage的处理方式是:找到对应index位置的内存位置(内存位置37),并抛弃从位置A之后的所有旧日志占用的内存数据(由于在Raft机制中,这种状况下内存位置37之后的那些旧日志都是无效的,无需保留),而后拼接上本次append操做的所有新日志。在自研WAL也须要执行相似的操做,找到WAL文件中对应index的位置(文件位置37),删除从文件位置37以后的全部文件内容,并写入最新的日志。以下图分析:
ETCD的方案,Raft日志有2个部分,文件与内存,文件部分由于只有追加模式,所以并非每一条日志都是有效的,当出现同index不一样term的日志条目时,只有最新的term以后的日志是生效的。配合snapshot机制,很是适合ETCD这样的kv存储系统。但对于InfluxDB高可用版本而言,snapshot将很是消耗资源和影响性能,并且过程当中要锁死整个InfluxDB。同时,一次Raft流程的一条entry可能包含超过20M的时序数据。因此这种方案不适合。
CockroachDB的方案,看似偷懒使用了RocksDB的能力,但因其底层存储引擎也是RocksDB,因此无何厚非。但对于咱们这样须要Raft一致性协议的时序数据库而言,引入RocksDB未免太重了。
自研的Raft HybridStorage是比较符合阿里云InfluxDB®的场景的,自己模块设计轻便简介,内存保留了热数据缓存,文件使用接近ETCD append only的方式,遇到同index不一样term的日志条目时执行truncate操做,删除冗余与无效数据,下降了磁盘压力。
本文对比了业内常见的两种Raft日志的实现方案,也展现了阿里云InfluxDB®团队自研的HybridStorage方案。在后续开发过程当中,团队内还会对自研Raft HybridStorage进行多项优化,例如异步写、日志文件索引、读取逻辑优化等等。也欢迎读者提出本身的解决方案。相信阿里云InfluxDB®团队在技术积累与沉淀方面会越作越好,成为时序数据库技术领导者。
原文连接 本文为云栖社区原创内容,未经容许不得转载。