当前位置: 首页 > 科技观察

Abase2:字节跳动新一代高可用NoSQL数据库

时间:2023-03-13 06:38:11 科技观察

作者|NoSQL团队背景从2016年开始,为支持在线推荐的存储需求而诞生——字节跳动自研高可用KV存储Abase,并逐渐发展成为它支持公司几乎所有业务线90%以上的KV存储场景,包括推荐、广告、搜索、抖音、西瓜、飞书、游戏等,已成为使用最广泛的在线存储之一公司里的系统。Abase作为字节跳动完全自研的高性能、大容量、高可用的KV存储系统,支撑业务需求持续快速增长。但是随着公司的不断发展,业务数量和规模不断快速增长,我们的业务也对系统提出了更高的要求,例如:极高的可用性:相对于一致性、信息流等服务对可用性有更高的要求。希望消除因宕机选举导致的短期不可用和慢速节点问题;全局部署:无论是边缘机房还是异地机房,同一个Abase2集群的用户都可以就近访问,获得极快的响应时延;CRDT支持:保证多写架构下的数据能够自动解决冲突问题,达成最终一致;降低成本:通过资源池化,解决不同用户使用资源不均衡,导致资源利用不足,降低成本;极致高性能:在相同的资源使用情况下,要求提供尽可能高的读写吞吐量和低访问延迟。适应IO设备与CPU性能发展速度不匹配的趋势,高效利用CPU;兼容Redis协议:为了让Redis用户无障碍访问Abase,满足更大容量的存储需求,我们需要全面兼容Redis协议。在此背景下,Abase团队于2019年底开始孵化第二代Abase系统。结合业界先进的架构方案和公司内部实践中的积累和思考,团队推出了新一代NoSQL数据库——Abase2,支持多租户、多写、CRDT软硬件一体化设计。架构概述数据模型Abase支持Redis的几种主要数据结构和相应的接口:String:支持Set、Append、IncrBy,是字节线上使用最广泛的数据模型;Hash/Set:使用量仅次于String广泛应用于结构化数据访问场景,用于部分更新/查询;ZSet:广泛应用于列表拉链等线上业务场景,不同于直接使用String+Scan打包,Abase在ZSet结构上做了很多优化,从设计上避免了大量ZIncrBy导致的读性能下降;List/TTLQueue:队列接口的语义使得对应场景下的业务接入非常方便。架构视图图1:Abase2总体架构图Abase2的总体架构主要如上图所示,主要包括用户、控制平面、数据平面三个角度的5套核心模块。RootServer线上一个集群的规模大约是几千台机器。为了管理每个集群,我们开发了一个名为RootServer的轻量级组件。顾名思义,RootServer具有全集群视角,可以更好地协调集群间的资源分配,支持租户在不同集群间的数据迁移,提供容灾视图,合理控制爆炸半径。MetaServerAbase2是一个多租户集中式架构,MetaServer是整个架构的总管理员。主要包括以下核心功能:管理元信息的逻辑视图:包括Namespace、Table、Partition、Replica等状态和配置信息以及Relationship之间的关系;元信息管理的物理视图:包括IDC、Pod、Rack、DataNode、Disk、Core分布和Replica位置关系;多租户QoS主控,在异构机器场景下,根据各租户负载和机器ReplicaBalance调度;在此基础上进行故障检测、节点寿命管理、数据可靠性跟踪以及节点下线和数据修复。图2:集群物理视图图3:集群逻辑视图DataNodeDataNode是数据存储节点。部署时,可以每台机器部署一个DataNode,也可以每块磁盘部署一个DataNode。为了便于隔离磁盘故障,线上实际上采用了每块磁盘部署一个DataNode的方式。DataNode的最小资源单元是CPUCore(以下简称Core)。每个Core都有独立的BusyPolling协程框架,多个Core共享一块磁盘空间和IO资源。图4:DataNode资源视角一个Core包含多个Replica,每个Replica的请求只会在一个Core上run-to-complete,可以有效避免传统多线程模式下上下文切换带来的性能损失。Replica的核心模块如下图所示。整个Partition有三层结构:数据模型层:Redis生态中的各种数据结构接口,比如上面提到的String、Hash。一致性协议层:多主架构下,多点写入必然会造成数据不一致。Anti-Entropy一方面会及时合并冲突,另一方面协调冲突合并后的数据会被flush到引擎持久层进行协调。沃尔GC。数据引擎层:数据引擎层首先有一层轻量级的数据暂存层(或ConflictResolver),用于存储不一致的数据;下层是数据数据引擎持久层,为了满足不同用户的多样化需求,Abase2在引导设计上采用了引擎可插拔模型。有顺序需求的用户可以使用RocksDB、TerarkDB等LSM引擎,非顺序查询的用户可以使用延迟更稳定的LSH引擎。图5:副本分层架构。Client/Proxy/SDKClient模块是从用户端角度看的核心组件。向上提供各种数据结构的接口。一方面通过MetaSync与MetaServer节点通信,获取租户Partition的路由信息??。切面通过路由信息与存储节点DataNode交互。另外,为了进一步提高服务质量,我们在Client的IO链路上集成了重试、BackupRequest、热密钥承载、流量控制、鉴权等重要的QoS功能。结合Byte多种编程语言的丰富生态,团队封装了基于Client的Proxy组件,对外提供了Redis协议(RESP2)和Thrift协议。用户可以根据自己的喜好选择接入方式。另外,为了满足对延迟比较敏感的重度用户,我们还提供了重度SDK跳过Proxy层,就是对Client的简单封装。DTS(DataTransferService)DTS主导着Abase生态的发展,在一二代透明迁移、备份回滚、dump、订阅等诸多业务场景中都扮演着非常核心的角色。限于篇幅,本文不做更详细的设计说明。关键技术一致性策略我们知道,分布式系统很难同时满足强一致性、高可用性和网络故障正确处理(CAP)这三个特性,因此系统设计者不得不做出取舍,牺牲一些特性来满足主要系统的需求和目标。例如,大多数数据库系统在极端情况下通过牺牲系统可用性来满足数据更高的一致性和可靠性要求。Abase2目前支持两种同步协议来支持不同的一致性需求:Multi-Leader:相对于强数据一致性,Abase的大部分用户对系统的可用性有更高的要求。Abase2主要通过multi-master技术实现系统高可用的目标。在多主模式下,分片的任何一个副本都可以接受和处理读写请求,保证任何一个副本存活时分片都可以对外提供服务。同时,为了避免多主架构顺序同步带来的一些可用性降低问题,我们结合无主架构的优势,在网络分区等异常恢复后,同时同步最新数据和旧数据。进程重新启动。另外,对于需要立即读取写入成功的数据,不能容忍主从切换造成的秒级不可用的用户,我们提供了无更新场景下先写后读的一致性,供用户选择。实现方式是通过Client配置Quorum读写(W+R>N)。通常的配置是W=3,R=3,N=5。单主模式(Leader&Followers):Abase2支持和第一代系统一样的主从模式,半同步适用于对一致性要求高但可以容忍一定程度降低可用性的使用场景。类似于MySQL半同步。系统会选择唯一的主副本来处理用户的读写请求,并保证至少有两个副本完成同步后才通知用户写入成功。保证读写请求的强一致性,单节点故障后,新的主节点仍然有全量数据。未来会提供更一致的选项,以满足用户的不同需求。读写流程下面我们将详细介绍Abase在多主模型下的数据读写流程以及最终数据一致性的实现方案。对于一个读请求,Proxy首先根据meta信息计算出对应的shard,然后根据地理位置等信息将请求转发给该shard合适的Replica,ReplicaCoordinator查询本地或远程存储引擎最后根据一致性策略将结果按照冲突解决规则进行组合返回给Proxy,Proxy根据相应的协议组装结果返回给用户。对于写请求,Proxy将请求转发给合适的Replica,ReplicaCoordinator将写请求序列化并发给所有Replica,并根据一致性策略确定请求成功所需的最小成功响应数W。可用性与W成反比,W=1最大化写入可用性。如图6所示,假设分片副本数N=3,当用户写请求到达Proxy时,Proxy根据地理位置等信息将请求转发给分片的一个副本(ReplicaB),而ReplicaB的Coordinator负责将请求写入本地,并将请求并发转发给其他Replica。当写入成功的响应数大于等于用户配置的W(允许排除本地副本)时,可以认为请求成功。如果在一定时间内(请求超时时间)不满足上述条件,则认为请求失败。单副本时,数据先写入WAL保证数据持久化,再提交给引擎数据暂存层。引擎满足一定条件后,缓存的数据会被flush到持久化存储中,然后WAL对应的数据就可以被GC了。一个Core中的所有Replicas共享一个WAL,可以尽可能的合并不同Replicas的碎片提交,减少IO次数。引擎层由Replica独占,方便根据不同的业务场景对引擎层进行细粒度配置,也方便数据查询、GC等操作。图6:写入过程示意图。用户可以根据一致性、可用性、可靠性和性能综合考虑NWR的比例。当W(R)为1时,可以获得最大的写(读)可用性和性能;增加W/R将在数据一致性和可靠性方面取得更好的性能。Anti-Entropy从上面的写入过程可以看出,当W