AeroSpike踩坑手记1:Architecture of a Real Time Operational DBMS论文导读

又开了一个新的坑,笔者工做以后维护着一个 NoSQL 数据库。而笔者维护的数据库正是基于社区版本的 Aerospike打造而来。因此这个踩坑系列的文章属于工做总结型的内容,会将使用开发 Aerospike 的各类问题进行总结梳理,但愿可以给予你们启发和帮助。第一篇开山之文,就先从Aerospike 公司在16年数据库顶会 VLDB的一篇论文 《Aerospike: Architecture of a Real Time Operational DBMS》展开,来高屋建瓴的审视一下 Aeropike 的设计思路,来看看如何Aerospike这款分布式数据库有什么亮点值得咱们学习借鉴的,因为论文发布在2016年,笔者完成这篇文章时Aerospike的版本已经发布到4.5了,不少最新的实现与老论文已经有些不一样了,这点但愿你们理解。准备好,老司机发车了~~算法

1.AeroSpike 的定位与场景

从论文的题目出发,这篇文章的核心在于实时操做数据库的架构,在论文引言之中对Aerospike的定位是一个高性能分布式数据库,用于处理实时的交互式在线服务。因此说,大多数使用Aerospike的场景是实时决策系统,它们有海量的数据规模,而且有严格的SLA要求,同时是百万级别的 QPS,具备ms的查询时延。显然,这样的场景使用传统的 RDMS 是不现实的,在论文之中,提到 Aerospike 的一个典型的应用场景,广告推荐系统,咱们来一块儿看看它们是如何契合的:数据库

众所周知,广告推荐系统这样的应用场景须要极高的吞吐量、低延迟和稳定的可用性。同时,广告推荐系统具备随时间增长其数据使用量以提升其推荐的质量的趋势,即,在固定时间量中可访问的数据越多,推荐就越精确。下图展现了一个广告推荐系统是如何结合 Aerospike来提供推荐服务的:
利用 Aerospike 实现的广告推荐系统,一个典型的 Lambda 架构的场景缓存

显然,这就是笔者以前的文章之中聊到的典型的Lambda架构,笔者当时正是以广告推荐系统进行举例的。因此在这里笔者就不展开再聊Aerospike在其中充当的实时流存储的角色了,感兴趣的朋友能够看这里服务器

2.Aerospike的整体架构

除了广告推荐系统以外,论文的原文还介绍了许多关于Aerospike的适用场景,有兴趣的能够经过原文深刻了解。接下来咱们直奔主题,来看看Aerospike的整体架构网络

Aerospike的整体架构

由上图所示,Aerospike核心分为三个层次:架构

  • 客户端层
  • 分布式层
  • 数据层

因此接下来咱们来一一解构,Aerospike的各个层次。分布式

2.1 分布式层

与Cassandra相似的是,Aerospike也采用了P2P的架构,也就是说,集群之中不存在的中心节点,每一个节点都是对等的结构。而分布式层聚焦在两点之上:函数

  • 节点分布
  • 数据分布

2.1.1 节点分布

节点须要处理节点成员关系,并对Aerospike集群当前成员达成共识。好比:网络故障和节点加入或离开。post

节点分布所关心的点在于:性能

  • 集群中的全部节点到达当前集群成员的单一一致视图。

  • 自动检测新节点的加入与离开。

  • 检测网络故障而且可以容忍网络的不稳定性。

  • 尽可能缩短集群成员变化的时间。

2.1.1.1 集群视图

每一个Aerospike节点都会自动分配一个惟一的节点标识符,它是其MAC地址和监听端口惟一肯定的。集群总体视图由一个元组定义:<cluster_key,succession_list>

  • cluster_key是随机生成的8字节值标识一个惟一的集群视图
  • succession_list 是一个集合,标识了全部属于集群的Aerospike节点

cluster_key标识当前集群成员身份状态,并在每次集群视图更改时更改。 它使得Aerospike节点用于区分两个不一样的集群视图。对集群视图的更改都对集群的性能有着有着显著影响,这意味着须要快速检测节点加入/离开,而且随后须要存在有效的一致性机制来处理对集群视图的更改。

2.1.1.2 节点检测

节点的加入或离开是经过不一样节点之间按期交换的心跳消息来检测的。集群中的每一个节点都维护一个邻接列表,该列表是最近向节点发送心跳消息的节点列表。若是在配置的超时间隔内,因为没有收到对应的心跳消息,从邻近列表中删除对应的节点。

而节点检测机制须要保证:

  • 避免因为零星和短暂的网络故障而将节点误删除出集群。
  • 防止不稳定节点频繁加入和离开集群。
辅助心跳

在阻塞的网络中,有可能任意丢失某些数据包。所以,除了常规的心跳消息以外,节点还使用了按期交换的其余消息做为备选的辅助心跳机制。例如,副本写能够用做心跳消息的辅助。这确保了,只要节点之间的主要或次要心跳通讯是完整的,仅主心跳信息的丢失不会引发集群视图的变动。

健康检测

集群中的每一个节点能够经过计算平均消息丢失来评估其每一个节点的健康评分,健康评分是经过:每一个节点接收的预期消息数量与每一个节点接收的实际消息数量的加权平均值计算而成的。

设t为心跳消息的发送间隔,w为心跳信息的发送频率,r为在这个窗口时间中丢失的心跳消息的数量,α是一个比例因子,la(prev)以前的健康因子。la(new)为更新以后的健康因子,因此它的计算方式以下图所示:
健康因子的计算

健康因子在全部节点标准差两倍的节点是异常值,而且被认为是不健康的。若是不健康的节点是集群的成员,则将其从集群中删除。若是不是成员,则直到其平均消息丢失在可容忍的限度内才能加入集群。在实践中,α被设置为0.95,节点的历史表现比赋予了更多的权重。窗口时间通常设置为1秒。

2.1.1.3 视图更改

对邻近列表的更改就会产生新集群视图,这须要一次Paxos一致性算法。邻接链表之中节点标识符最高的节点充当Paxos提议者,若是建议被接受,节点就开始从新分配数据。

Aerospike实现了最小化集群因为单一故障事件而更改视图的次数。例如,有故障的网络交换机可能使集群成员的子集不可到达。一旦恢复了网络,就须要将这些节点添加到集群中。若是每一个丢失或加入的节点都须要触发建立新的集群视图,这种代价是很高的。因此Aerospike仅在固定的集群更改间隔(间隔自己的时间是可配置的)开始时作出集群视图的调整。这里的想法是避免如心跳子系统检测到的那样对节点到达和离开事件反应太快,而是用一个集群视图更改来处理一批节点加入或删除的事件。这避免了由重复的集群视图更改和数据分布致使的大量潜在开销。集群更改间隔等于节点超时值的两倍,确保在单个间隔中明确检测到因为单个网络故障而失败的全部节点。

2.2 数据分布

Aerospike使用RipeMD160算法将record的key散列为160bit的digest,digest被划分为4096个分区。分区是Aerospike中最小的数据分布单元,根据key 的digest为记录分配分区。即便key的分布是倾斜的,在digest空间中分布也是均匀的,它有助于避免在数据访问期间建立热点,这有助于系统的容错。
一个好的数据分布须要知足下列条件:

  • 存储负载均匀地分布在集群中,
  • 具备较好的扩展性
  • 节点出现变化时,数据的从新平衡是非破坏性的

Aerospike 的数据分布

数据分配算法为每一个分区生成一个副本列表。副本列表中的第一个节点是该分区的主节点,其他的节点是副本。在默认状况下,全部读/写都经过副本的主节点。Aerospike支持任意数量的副本,(一般设置为两副本,笔者在实际使用中也是两副本)。 Aerospike 采起的是一致性哈希的分片分配的方式,当节点出现失效或宕机的状况时。这个节点能够从副本列表中删除,然后续节点的左移。以下图所示,若是该节点须要承载了数据的副本,则须要将此分区中的记录复制到新节点。一旦原始节点返回并再次成为集群的一部分,它将简单地从新得到其在分区复制列表中的位置。向集群中添加一个全新的节点将具备将此节点插入各个分区副本列表中的某个位置的效果。所以,将致使每一个分区的后续节点的右移,而新节点左侧的分配不受影响。

主副本的分布与副本的移动

上面的讨论给出了算法就能确保副本的最低迁移成本。可是当一个节点被删除并从新加入集群时,它须要和其余副本进行同步。当一个全新的节点加入一个拥有大量现有数据的集群,因此新的节点须要得到对应分区中全部记录的全新副本,而且还可以处理新的读写操做。接下来咱们来看看副本同步的机制:

2.2.1 数据迁移

将record从一个节点移动到另外一个节点的过程称为迁移。在每次集群视图改变以后,就须要进行数据迁移。每一个分区的主副本为对应的分区分配惟一的分区版本,这个版本号会被复制到各个副本中。在集群视图更改以后,节点之间交换分区的分区版本和数据。

2.2.1.1 增量迁移

Aerospike使用增量迁移的方式优化迁移的速度。若是在可以在分区版本上创建总顺序,那么数据迁移的过程将更加有效。例如,若是节点1上的分区版本的值小于节点2上的相同分区版本的值,则节点1上的分区版本可能被丢弃。可是,经过分区版本号的排序是有问题的,由于网络分区引发的集群分裂会引发分区版本的冲突。
因此当两个版本冲突时,节点须要协商实际记录中的差别,并经过只对应于两个分区版本之间的差别的数据发送。在某些状况下,能够根据分区版本顺序彻底避免迁移。在其余状况下,如滚动升级,能够传递增量的数据,而不是迁移整个分区。

  • 迁移流程中的读写
    若是分区正在进行迁移时,若是此时对应的分区有读写,主副本会读取全部的分区版本,协调出一个最终胜出的版本用于读或写事务。(按照笔者对文章的理解,这个流程会涉及多个副本,是一个耗时的操做
  • 没有数据的主副本
    新添加到正在运行的集群的空节点成为了主副本,而且没有对应分区的数据,没有任何数据的分区的副本被标记为处于DESYNC状态。Aerospike会指定一个最多记录的分区版本做为这个分区的代理主副本。全部的读操做都会指向代理主副本。(此时写仍是在主副本上若是客户端能够容忍读取旧版本的记录,则能够减小协调胜出版本的损耗。此代理主副本的工做会持续到对应分区的迁移完成。
  • 迁移顺序
    • 小分区优先
      让分区版本中记录最少的分区开始迁移。这种策略能够快速减小特定分区的不一样副本的数量。随着迁移的完成,延迟会改善,须要进行协调副本版本会减小对应的节点进行的通讯。
    • 热分区优先
      根据分区的 qps 的大小确认分区迁移的顺序。这种策略的目标与小分区优先的逻辑是一致的。

2.2.2 快速重启

节点从新启动是很常见的场景,好比:服务升级,宕机重启等。Aerospike的索引是内存中的而没有存储在持久设备上。在节点从新启动时,须要经过扫描持久设备上的记录来从新构建索引。(这个过程巨慢无比,笔者目前维护的大集群,单机存储数据量达1T,单次启动须要30分钟之久

为了不在每次从新启动时从新构建索引,Aerospike的利用了共享内存来实现快速重启。(目前开源的版本是不支持这个功能的,笔者所在的团队经过二次开发实现了对应的功能。可是机器一旦重启以后,也必须重建索引,因此有机器频繁重启的,能够考虑一些对应索引进行落盘

2.3 客户端层

2.3.1 服务发现

在Aerospike中,每一个节点维护着一个邻接列表标识着全局的节点分布状况。客户端从一个种子节点,发现整个集群的节点。
每一个客户端进程都将集群分区映射的信息存储在共享内存之中。为了保持信息最新,客户端进程按期经过AeroSpike节点,来检查集群是否有任何变更。它经过根据服务器的最新版本检查本地存储的版原本实现这一点。对于单机的多个客户端,AeroSpike将数据存储在共享内存之中,而且用跨进程的互斥代码来实现集群信息的共享。
####2.3.2 链接管理
对于每一个集群节点,在初始化时,客户端需为节点建立一个内存结构,并存储其分区映射,而且为节点维护链接。一旦出现节点和客户端的网络问题,这种频繁的内存调整容易产生性能问题。因此Aerospike客户端实现如下策略:
####2.3.2.1 健康计数
为了不因为偶尔的网络故障致使上文的问题。当客户端链接集群节点操做发生问题时,会对集群节点进行故障计数。当故障计数超过特定阈值时,客户端才会删除集群节点。对集群节点的成功操做能够将故障计数重置为0。
####2.3.2.2 节点咨询
网络的故障一般很难复杂。在某些极端状况下,集群节点能够彼此感知,可是客户端不能直接感知到集群节点X。在这些状况下,客户端链接集群之中全部可见节点,并咨询集群之中的全部节点在其邻接列表中是否包含X。若是没有包含,则客户端将等待一个阈值时间,永久移除X节点。

3 跨数据中心同步

3.1.1 失效接管

在正常状态下(即,当没有故障时),每一个节点只将节点上主副本的数据传送到远程集群。只在节点出现故障时才使用从副本。若是一个节点出现失效,全部其余节点可以检测到,并表明失效的节点接管工做。

3.1.2 数据传输优化

当发生写操做时,主副本在日志之中记录。进行数据传输时,首先读取一批日志,若是同一个记录有多个更新,选取一批之中最近的更新记录。一旦选取了记录,将其与实际记录比较。若是日志文件上的记录小于实际的记录,则跳过该记录。对于可是跳过记录的次数有一个上限,由于若是记录不断更新,那么可能永远不会推送记录。当系统中存在频繁更新记录的热键时,这些优化提供了巨大的好处。

4 存储落地

4.1 存储管理

Aerospike的存储层是一个混合模型,其中索引存储在内存中(不持久),数据能够选择存储在持久存储(SSD)或内存之中。而随机的读写SSD容易产生写放大。(笔者以前的文章也一样聊过这个问题,能够参考这里)为了不在SSD的单个块上产生不均匀的磨损,Aerospike采起了批量写的方式。当更新记录时,从SSD读取旧记录,并将更新后的副本写入缓冲区。当缓冲区在充满时刷新到SSD上。
Aerospike存储层
读取单元RBLOCKS的大小是128字节。而WBLOCK的大小,可配置,一般为1MB。这样的写入优化了磁盘寿命。Aerospike经过Hash函数在多个设备上切分数据来操做多个设备。这容许并行访问多个设备,同时避免任何热点。

4.2 Defragmentation垃圾清理

Aerospike经过运行后台碎片整理进程来回收空间。每一个设备对应的块都存在填充因子。块的填充因子写入在块中。系统启动时,存储系统载入块中的填充因子,并在每次写入时保持更新。当块的填充因子低于阈值时,块成为碎片整理的候选者,而后排队等待碎片整理。
块进行碎片整理时,将读取有效记录并将其移动到新的写入缓冲区,当写入缓冲区已满时,将其刷新到磁盘。为了不混合新写和旧写,Aerospike维护两个不一样的写缓冲队列,一个用于普通客户端写,另外一个用于碎片整理。
设置一个较高的阈值(一般为50%)会致使设备不断的刷写。而较低的设置会下降磁盘的利用率。因此基于可当即被写入可用磁盘空间,调整碎片整理速率以确保有效的空间利用。

4.3 性能与调优

4.3.1 Post Write Queue

Aerospike没有维护LRU缓存,而是维护的post write queue。这是最近写入的数据缓存,这个缓存不须要额外的内存空间。post write queue提升了缓存命中率,并减小了存储设备上的I/O负载。

5.小结

关于论文之中对Aerospike的设计笔者已经夹带私货的阐述清晰了。而关于单机优化和Aerospike性能测试,笔者就再也不赘述了,感兴趣的能够回到论文之中继续一探究竟。对于论文之中的细节想要进一步的了解,能够继续关注笔者后续关于Aerospike的拆坑手记~~~

相关文章
相关标签/搜索