KV 存储
在大数据时代,数据量呈指数级增长,预计到2025年,全球的数据总量将达175ZB,非结构化和半结构化数据已占据主导地位。像腾讯微信、字节头条、B站、快手、小红书等众多的UGC社交平台,这些平台普遍的都存在搜索、推荐、广告和风控服务都以人为核心,服务强依赖用户的画像、行为和笔记。UGC数据的高效存储和查询是支持推广搜一体化工程的基础设施,而这种典型的属性和值的键值对结构正式我们今天要讨论的分布式KV系统。
KV存储系统对非结构化和半结构化数据进行高效存储,提供了很好的解决方案:
由于KV存储系统具有上述诸多优点,因此被广泛应用在了NewSQL和NoSQL产品中。比如目前常见的KV存储系统:LevelDB、RocksDB、Cassandra、TiKV等。
目前主流的持久化KV存储系统都采用LSM-tree(log-structured merge tree)架构作为存储引擎,其具有高效的写入效率,但同时存在严重的读写放大问题。
如图,KV数据首先缓存在内存并进行排序,然后以批量追加的方式将数据写入磁盘上,形成磁盘上的一个SSTable文件,SSTable文件中的数据是按Key有序的,这种写入方式可以将大量的随机写转换为顺序写,从而充分利用磁盘顺序写的带宽优势,获得高效的写入效率。
为兼顾读性能,磁盘上的数据被组织成多层形式,且容量逐层递增,并且除第0层以外,其他每层的数据是完全有序的。通过维护这样一个多层有序的架构,LSM-tree可以获得高效的写入和范围查询性能,并且提供高可扩展性,当需要扩展存储容量时,可以通过简单的增加LSM-tree的层数来实现高效的扩展。
然而,LSM-tree多层的数据组织结构导致查询操作需要逐层搜索,从第0层开始,直到找到查询的数据为止,并且写入期间需要执行频繁的Compaction操作,具体Compaction操作需要从LSM-tree中读取相邻两层的数据,然后执行合并排序操作,再将合并后的有效数据写回磁盘。因此,当我们将数据从第0层逐渐合并到较高层时,需要将数据频繁的读出并且写回,进而导致严重的读写放大问题,且严重消耗磁盘的IO带宽。
数据分片策略:当前流行的KV存储产品对Key的划分方法有两种:
那如何评价一个KV产品是否足够优秀呢,笔者认为可以从以下特性功能进行对比评价:
KV系统对外提供的接口比较统一,即点查、范围查询。而当今作为LSM-Tree最稳定、应用最广泛即最成熟的引擎就是Rocksdb。
RocksDB 是由 Facebook 基于 google LevelDB 开发的一款提供键值存储与读写功能的 LSM-tree 架构引擎。用户写入的键值对会先写入磁盘上的 WAL (Write Ahead Log),然后再写入内存中的跳表(SkipList,这部分结构又被称作 MemTable)。LSM-tree 引擎由于将用户的随机修改(插入)转化为了对 WAL 文件的顺序写,因此具有比 B 树类存储引擎更高的写吞吐。
内存中的数据达到一定阈值后,会刷到磁盘上生成 SST 文件 (Sorted String Table),SST 又分为多层(默认至多 6 层),每一层的数据达到一定阈值后会挑选一部分 SST 合并到下一层,每一层的数据是上一层的 10 倍(因此 90% 的数据存储在最后一层)。
RocksDB 允许用户创建多个 ColumnFamily ,这些 ColumnFamily 各自拥有独立的内存跳表以及 SST 文件,但是共享同一个 WAL 文件,这样的好处是可以根据应用特点为不同的 ColumnFamily 选择不同的配置,但是又没有增加对 WAL 的写次数。
互联网KV 产品
小米 Pegasus - 2015 ★
小米云平台长期以来一直使用开源的Apache HBase来存储结构化/半结构化数据,但是HBase并不是十全十美的,它的架构、语言、实现等决定了它具有一些难以克服的不足:
- HBase实现采用的JAVA语言,虽然开发上效率比较高,但是运行性能并不如C/C++这样的语言。
- Java GC时可能造成进程进入假死状态,导致Region Server无法提供读写服务,造降低系统可用性。
- HBase宕机恢复时间比较长(分钟级),在这段时间内服务是不可用的。其原因是:
- HBase数据存储在分布式文件hdfs上,上层的RegionServer仅仅是服务点。为了保证数据一致性,HBase要求每个Region在同一时刻只能由一个RegionServer服务。当某个RegionServer宕机,必须选一个新的RegionServer来服务该Region。恢复过程中需要做较多处理,包括日志的传输、切分、重放,这个过程比较耗时。
- HBase依赖ZooKeeper来探测宕机问题,而由于Java的GC问题存在,Zookeeper的session timeout不能设得太短,典型地设为30秒。如果设得太短,Java GC的假死机就容易造成session超时,触发RegionServer不必要的自杀。因此从RegionServer宕机到被发现,这中间可能就需要几十秒。
- HBase的分层架构使数据服务点和存储位置分离,对Data Locality不够友好,也是影响其读性能的一个原因。
以上这些原因造成了HBase的可用性和性能都存在一些不足,难以满足对服务可用性和延迟都很敏感的一些在线业务的需求,譬如广告业务。
从2015年开始,小米开始开发Pegasus系统。Pegasus系统的整体架构如下图所示,一共分为四个部分:
ReplicaServer
ReplicaServer主要负责数据存储和存取,以replica为单位进行服务:服务的replica既可能是PrimaryReplica,也可能是SecondaryReplica。底层使用RocksDB来存储数据管理commit log,并实现replication协议,提供数据一致性保证
MetaServer
MetaServer:MetaServer采用一主多备模式(one master, multiple backups),所有的状态都会持久化到Zookeeper上;同时通过Zookeeper进行选主。当master故障后,另一台backup立即抢到锁,然后从Zookeeper上恢复状态,成为新的master。
MetaServer负责的功能包括:
Zookeeper
系统元信息存储
MetaServer选主
ClientLib
ClientLib对用户提供数据存储接口
接口简洁:对用户提供最简单的接口,将寻址和容错等细节封装在内部
配置简单:用户只需通过配置文件指定MetaServer地址列表,就可以访问集群,类似于Zookeeper
尽量直接与ReplicaServer进行交互,尽量少地访问MetaServer以避免热点问题,不依赖Zookeeper
腾讯 Tendis - 2015/8 ★
Tendis 是集腾讯众多海量 KV 存储优势于一身的 redis 存储解决方案, 并 100% 兼容 Redis 协议和 Redis4.0 所有数据模型。作为一个高可用、高性能的分布式 KV 存储数据库, 从访问时延、持久化需求、整体成本等不同维度的考量, Tendis 推出了 缓存版 、 混合存储版 和 存储版 三种不同产品形态,并将存储版开源。
在版本迭代过程中,不断的业务接入,成为游戏业务和平台业务的主存储。
Tendis存储版集群架构★
Tendis存储版使用去中心化集群架构,每个数据节点都拥有全部的路由信息。
Tendis存储节点采用单进程多实例的部署形态,默认单进程使用10个rocksdb实例。
Key采用Hash打散的方式分片存储在KvStore中,每个KvStore是一个单进程多实例的Rocksdb。
Tendis存储版 vs 其他开源实现 ★
360 PiKa 2015/11 ★
Pika是一个可持久化的大容量redis存储服务,兼容string、hash、list、zset、set的绝大部分接口(兼容详情),解决redis由于存储数据量巨大而导致内存不够用的容量瓶颈,并且可以像redis一样,通过slaveof命令进行主从备份,支持全同步和部分同步,pika还可以用在twemproxy或者codis中来实现静态数据分片(pika已经可以支持codis的动态迁移slot功能,目前在合并到master分支。
PiKa 2种模式 ★
经典模式(Classic): 即1主N从同步模式,1个主实例存储所有的数据,N个从实例完全镜像同步主实例的数据,每个实例支持多个DBs。DB默认从0开始,Pika的配置项databases可以设置最大DB数量。DB在Pika上的物理存在形式是一个文件目录。
分布式模式(Sharding): Sharding模式下,将用户存储的数据集合称为Table,每个Table切分成多个分片,每个分片称为Slot,对于某一个KEY的数据由哈希算法计算决定属于哪个Slot。将所有Slots及其副本按照一定策略分散到所有的Pika实例中,每个Pika实例有一部分主Slot和一部分从Slot。在Sharding模式下,分主从的是Slot而不再是Pika实例。Slot在Pika上的物理存在形式是一个文件目录。
Pika可以通过配置文件中的instance-mode配置项,设置为classic和sharding,来选择运行经典模式(Classic)还是分布式模式(Sharding)的Pika。
美团 Cellar 2015 ★
Squirrel:基于Redis Cluster(2015 年发布),演进出全内存、高吞吐、低延迟的 KV 存储。
迭代:自研和社区并重,尽量兼容官方。
应用:数据量小,对延迟敏感
Cellar:基于 Tair,演进出持久化、大容量、数据高可靠KV 存储。
迭代:完全靠自研。和 Squirrel 在解决同样的问题时也选取了不同的设计方案。
应用:数据量大,对延迟不特别敏感
目前美团内部每天的调用量均已突破万亿,请求峰值突破每秒亿级
Cellar持久化KV架构 ★
跟阿里开源的 Tair 主要有两个架构上的不同。第一个是OB,第二个是 ZooKeeper。我们的 OB 跟 ZooKeeper 的 Observer 是类似的作用,提供 Cellar 中心节点元数据的查询服务。它可以实时与中心节点的 Master 同步最新的路由表,客户端的路由表都是从 OB 去拿。 这样做的好处主要有两点:
第一,把大量的业务客户端跟集群的大脑 Master 做了天然的隔离,防止路由表请求影响集群的管理。
第二,因为 OB 只供路由表查询,不参与集群的管理,所以它可以进行水平扩展,极大地提升了我们路由表的查询能力。
另外,我们引入了 ZooKeeper 做分布式仲裁,解决我刚才提到的 Master、Slave 在网络分割情况下的“脑裂”问题,并且通过把集群的元数据存储到 ZooKeeper,我们保证了元数据的高可靠。
Celler功能 ★
Cellar 节点容灾
如果 A 节点宕机了,会触发 Handoff 机制,这时候中心节点会通知客户端 A节点发生了故障,让客户端把分片 1 的请求也打到 B 上。B 节点正常处理完客户端的读写请求之后,还会把本应该写入 A 节点的分片 1&2 数据写入到本地的 Log 中。
如果 A 节点宕机后 3~5 分钟,或者网络抖动 30~50 秒之后恢复了,A 节点就会上报心跳到中心节点,中心节点就会通知 B 节点:“ A 节点恢复了,你去把它不在期间的数据传给它。”这时候,B 节点就会把本地存储的 Log 回写到 A 节点上。等到 A 节点拥有了故障期间的全量数据之后,中心节点就会告诉客户端,A 节点已经彻底恢复了,客户端就可以重新把分片 1 的请求打回 A 节点。
通过这样的操作,我们可以做到秒级的快速节点摘除,而且节点恢复后加回,只需补齐少量的增量数据。另外如果 A 节点要做升级,中心节点先通过主动 Handoff 把 A 节点流量切到 B 节点,A 升级后再回写增量 Log,然后切回流量加入集群。这样通过主动触发 Handoff 机制,我们就实现了静默升级的功能。
Cellar 跨地域容灾
以下图一个北京主集群、上海从集群的跨地域场景为例,比如说客户端的写操作到了北京的主集群 A 节点,A 节点会像正常集群内复制一样,把它复制到 B 和 D 节点上。同时 A 节点还会把数据复制一份到从集群的 H 节点。H 节点处理完集群间复制写入之后,它也会做从集群内的复制,把这个写操作复制到从集群的 I 、K 节点上。通过在主从集群的节点间建立这样一个复制链路,我们完成了集群间的数据复制,并且这个复制保证了最低的跨地域带宽占用。同样的,集群间的两个节点通过配置两个双向复制的链路,就可以达到双向同步异地多活的效果。
Cellar 强一致
目前业界主流的解决方案是基于 Paxos 或 Raft 协议的强一致复制。我们最终选择了 Raft 协议。主要是因为 Raft 论文是非常详实的,是一篇工程化程度很高的论文。业界也有不少比较成熟的 Raft 开源实现,可以作为我们研发的基础,进而能够缩短研发周期。
下图是现在 Cellar 集群 Raft 复制模式下的架构图,中心节点会做 Raft 组的调度,它会决定每一个 Slot 的三副本存在哪些节点上。
Cellar 智能迁移
Cellar 快慢列队
拆线程池、拆队列。我们的网络线程在收到包之后,会根据它的请求特点,是读还是写,快还是慢,分到四个队列里。读写请求比较好区分,但快慢怎么分开?我们会根据请求的 Key 个数、Value大小、数据结构元素数等对请求进行快慢区分。然后用对应的四个工作线程池处理对应队列的请求,就实现了快慢读写请求的隔离。这样如果我有一个读的慢请求,不会影响另外三种请求的正常处理。不过这样也会带来一个问题,我们的线程池从一个变成四个,那线程数是不是变成原来的四倍?其实并不是的,我们某个线程池空闲的时候会去帮助其它的线程池处理请求。所以,我们线程池变成了四个,但是线程总数并没有变。我们线上验证中这样的设计能把服务 TP999 的延迟降低 86%,可大幅降低超时率。
Cellar 热点 Key
中心节点加了一个职责,多了热点区域管理,它现在不只负责正常的数据副本分布,还要管理热点数据的分布,图示这个集群在节点 C、D 放了热点区域。我们通过读写流程看一下这个方案是怎么运转的。如果客户端有一个写操作到了 A 节点,A 节点处理完成后,会根据实时的热点统计结果判断写入的 Key 是否为热点。如果这个 Key 是一个热点,那么它会在做集群内复制的同时,还会把这个数据复制有热点区域的节点,也就是图中的 C、D 节点。同时,存储节点在返回结果给客户端时,会告诉客户端,这个 Key 是热点,这时客户端内会缓存这个热点 Key。当客户端有这个 Key 的读请求时,它就会直接去热点区域做数据的读取。
滴滴Fusion 2016★
Fusion 是滴滴自研的分布式 NoSQL 数据库,完全兼容 Redis 协议,支持超大规模数据持久化和高性能读写。在滴滴内部支撑了数百个业务,具有 PB 级别的数据存储量,是使用最广泛的主存储服务之一。在支持滴滴业务高速发展过程中,积累了很多分布式存储领域的经验,孵化了离线到在线的高速数据导入方案、NewSQL 方案、跨机房同步等,一路解决了 Redis 容量限制、 离线数据在线打通、数据库扩展性差、异地多活容灾等问题。
Fusion架构 ★
采用 hash 分片的方式来做数据 sharding。从上往下看,用户通过 Redis 协议的客户端(jedis、redigo、hiredis 等)就可以访问 Fusion,首先会经过 VIP 做负载均衡,然后转发到具体 proxy,再由 proxy 转发数据到后端 Fusion 的数据节点。proxy 到后端数据节点的转发,是根据请求的 key 计算 hash 值,然后对 slot 分片数取余,得到一个固定的 slotid,每个 slotid 会固定的映射到一个存储节点,以此解决数据路由问题。
FastLoad - 离线灌数据 ★
在FastLoad 服务器上,创建一个 DTS 任务,该任务会在 Hadoop 配置中心注册一个调度任务(周期性或一次性,由用户决定),然后 FastLoad 服务器根据用户上传的数据存储路径或 Hive 表(我们支持的数据源有:HDFS 上的 JSON 文件和 Hive 结构的数据),按照用户提交的拼 key 方式,我们启动 map/reduce 任务直接构造 Fusion 底层存储在文件系统上的文件 SST,并把它们构造好相互之间的排序,避免重复,构造好后通知 Fusion 存储节点,下载 SST 文件,然后 load 到 Fusion 数据库中。此后,用户就可以通过 Redis-Client 访问我们帮它加载的数据了。
此外,Fusion 还支持二级索引和多区数据复制
新浪LaserDB - 2019★
LaserDB 是微博设计开源的高性能分布式 KV 数据库,在满足传统 KV 存储的高性能的基础上,提供了大容量分布式存储解决方案。 并且为了满足大数据、人工智能特征模型快速加载更新,LaserDB 原生支持了快速批量、增量导入功能,LaserDB 不仅可以满足一般 的工程业务应用,并且很好的支撑了机器学习模型、特征数据存储需求。
LaserDB 整体架构★
深入了解 LaserDB 的整体架构可以更好的使用、运维 LaserDB, LaserDB 主要包括三大核心组件:Laser Server, Laser Client 和 Laser Control, 此外还有适配 Redis 协议的 Laser Proxy 以及满足数据批量导入的 Laser Transform。在具体部署时用户可以根据自己的需求选择部署 Laser Proxy 和 Laser Transform
Laser Server
LaserDB 的存储服务核心组件,负责接收 thrift 请求并且处理对应的请求。除了负责对外的服务请求处理以外,还负责具体的数据存储、数据分片、数据同步等功能
Laser Control
负责集群数据表、数据表配置以及集群分片信息的管理,提供分片可视化、动态扩容、动态缩容
Laser Client
主要是负责和 Server 进行接口交互,并且实现 LaserDB 整体请求路由机制,对 Server 端提供的 API 接口进行封装,实现 mget, mset 等批量操作的客户端层并行化, 目前提供 C++, Golang 版本的 SDK 可以直接 与 Laser server 交互获得更好的性能,其他语言的业务可以选择 Laser Proxy 代理,最终通过 redis 客户端操作
Laser Proxy
Laser Proxy 主要是负责实现 Redis 协议的大部分常用命令支持,Proxy 通过调用 Laser Client 与 Laser Server 交互,对于 Proxy 来说是一个完全无状态的服务,可以将 Proxy 当做一个存储容量特别大的 Redis server 来看 。对于原有业务是 Redis 的,获取不方便直接使用 Laser Client SDK 调用的业务场景可以选用
Laser Proxy
Laser Transform
Laser Transform 主要是负责实现数据的批量导入功能,对于有数据快速批量导入的需求,需要部署 Laser Transform 服务,并且 Laser Server 环境需要有 hdfs 客户端支持,Transform 服务主要负责定时调度提交 MapReduce 任务,将原始格式的数据转化为 Laser Server 可以识别的数据
字节ABase 2016★
自 2016 年以来,为了支撑在线推荐的存储需求而诞生的——字节跳动自研高可用 KV 存储 Abase,逐步发展成支撑包括推荐、广告、搜索、抖音、西瓜、飞书、游戏等公司内几乎所有业务线的 90% 以上的 KV 存储场景,已成为公司内使用最广泛的在线存储系统之一。ABase2 2019年替换ABase1
ABase2架构 ★
Abase2 的整体架构主要如上图所示,在用户、管控面、数据面三种视角下主要包含 5 组核心模块。
RootServer
线上一个集群的规模大约为数千台机器,为管理各个集群,我们研发了 RootServer 这个轻量级组件。顾名思义,RootServer 拥有全集群视角,它可以更好地协调各个集群之间的资源配比,支持租户在不同集群之间的数据迁移,提供容灾视图并合理控制爆炸半径。
MetaServer
Abase2 是多租户中心化架构,而 MetaServer 则是整个架构的总管理员,它主要包括以下核心功能:
物理视图
逻辑视图
DataNode
DataNode 是数据存储节点。部署时,可以每台机器或者每块盘部署一个 DataNode,为方便隔离磁盘故障,线上实际采用每块盘部署一个 DataNode 的方式。
DataNode 的最小资源单位是 CPU Core(后简称 Core),每个 Core 都拥有一个独立的 Busy Polling 协程框架,多个 Core 共享一块盘的空间与 IO 资源。
一个 Core 包含多个 Replica,每个 Replica 的请求只会在一个 Core 上 Run-to-Complete,可以有效地避免传统多线程模式中上下文切换带来的性能损耗。
Replica 核心模块如下图所示,整个 Partition 为 3 层结构:
Client/Proxy/SDK
Client 模块是用户侧视角下的核心组件,向上提供各类数据结构的接口,向下一方面通过 MetaSync 与 MetaServer 节点通信获取租户 Partition 的路由信息,另一方面通过路由信息与存储节点 DataNode 进行数据交互。此外,为了进一步提高服务质量,我们在 Client 的 IO 链路上集成了重试、Backup Request、热 Key 承载、流控、鉴权等重要 QoS 功能。
结合字节各类编程语言生态丰富的现状,团队基于 Client 封装了 Proxy 组件,对外提供 Redis 协议(RESP2)与 Thrift 协议,用户可根据自身偏好选择接入方式。此外,为了满足对延迟更敏感的重度用户,我们也提供了重型 SDK 来跳过 Proxy 层,它是 Client 的简单封装。
DTS (Data Transfer Service)
DTS 主导了 Abase 生态系统的发展,在一二代透明迁移、备份回滚、Dump、订阅等诸多业务场景中起到了非常核心的作用,由于篇幅限制,本文不做更多的详细设计叙述。
小红书RedKV - 2019★
小红书是年轻人的生活记录、分享平台,用户可以通过短视频、图文等形式记录生活点滴,分享生活方式。在当前的业务模型下,用户的画像数据和笔记数据用来做风险控制和内容推荐。存储数据具有对象-属性的特征、维度多,画像数据量已经达到数十TB, 在线业务对画像和笔记数据的访问P99 时延要求非常高。
RedKV2 架构 ★
RedKV整体架构分3层,接入层兼容Redis协议,支持各种语言的社区版SDK和公司定制的中间件版;接入代理层支持千万QPS的读写能力,无状态扩展;存储层提供高可靠读写服务。
Client接入层
RedKV集群部署完成后,通过公司内部提供的Service Mesh组件做服务发现,对Client提供服务。
Proxy
Proxy层由一个无状态CorvusPlus进程组成。它兼容老的Redis Client,扩缩容、升级对无Client和后端集群无感,支持多线程、IO多路复用和端口复用特性。对比开源版本,CorvusPlus增强了自我防护和可观测特性,实现了可在线配置的功能特性:
基于Shard管理的中心架构能更好的支持数据迁移和集群扩缩容,存储节点采用单进程多实例部署,在多活场景中可以支持副本数弹性扩展。
关键特性 ★
数据复制
与传统解决方案引入同步组件的方式不同,我们快速实现了单向数据同步以及集群扩容需求,整体架构去除了对第三方组件的依赖,通过扩展Redis复制协议实现了RedKV数据节点的直接复制,如图10。单向复制的限制是扩容需要基于做节点同步,扩容完成后后台任务根据3.3.3中定义的key的分片删除不是本节点的数据。
在多活的部署形态下,多云集群的一对多的数据复制采用单向复制对主集群性能侵入较大,因此我们实现了基于中心管控的数据复制策略。该策略支持多个集群的分片异构部署,通过Checkpoint方式定向同步数据,不再需要额外的后台任务去做数据淘汰,能很好的支持多对多的多云集群数据复制、数据破环和扩缩容。
数据批量导入
小红书大量的离线业务数据存储在S3 Hive中,每天会有部分数据需要增量更新,其他的数据会被淘汰。这类场景有几个挑战:
数据批量导出
小红书的业务模型训练数据通过Hash存储在RedKV集群中,业务下游需要对训练结果进行离线分析,希望RedKV具有和Hive数据流通的能力。RedKV本身是不支持Schema的,如果要将KV数据导入Hive表,则需要将Hash的KKV数据转化为一个Table。
RedKV的内部数据按hash打散,导入Hive表则需要提供table关键字,先按前缀扫描的方式扫描存储节点,再生成Hive识别的文件,最后通过Hive Load进行加载。为了更好的兼容其他spark任务,我们选择Hive支持的标准parquet列式存储文件
B站KV 2019★
在B站的业务场景中,存在很多种不同模型的数据,有些数据关系比较复杂像:账号、稿件信息。有些数据关系比较简单,只需要简单的kv模型即可满足。此外,又存在某些读写吞吐比较高的业务场景,该场景早期的解决方案是通过MySQL来进行数据的持久化存储,同时通过redis来提升访问的速度与吞吐。但是这种模式带来了两个问题,其一是存储与缓存一致性的问题,该问题在B站通过canal异步更新缓存的方式得以解决,其二则是开发的复杂度,对于这样一套存储系统,每个业务都需要额外维护一个任务脚本来消费canal数据进行缓存数据的更新。基于这种场景,业务需要的其实是一个介于Redis与MySQL之间的提供持久化高性能的kv存储。此外对象存储的元数据,对数据的一致性、可靠性与扩展性有着很高的要求。
基于此背景,我们对自研KV的定位从一开始就是构建一个高可靠、高可用、高性能、高拓展的系统。对于存储系统,核心是保证数据的可靠性,当数据不可靠时提供再高的可用性也是没用的。可靠性的一个核心因素就是数据的多副本容灾,通过raft一致性协议保证多副本数据的一致性。
整体架构 ★
整个系统核心分为三个组件:
Metaserver用户集群元信息的管理,包括对kv节点的健康监测、故障转移以及负载均衡。
Node为kv数据存储节点,用于实际存储kv数据,每个Node上保存数据的一个副本,不同Node之间的分片副本通过raft保证数据的一致性,并选出主节点对外提供读写,业务也可以根据对数据一致性的需求指定是否允许读从节点,在对数据一致性要求不高的场景时,通过设置允许读从节点可以提高可用性以及降低长尾。
Client模块为用户访问入口,对外提供了两种接入方式,一种是通过proxy模式的方式进行接入,另一种是通过原生的SDK直接访问,proxy本身也是封装自c++的原生SDK。SDK从Metaserver获取表的元数据分布信息,根据元数据信息决定将用户请求具体发送到哪个对应的Node节点。同时为了保证高可用,SDK还实现了重试机制以及backoff请求。
部署形态
集群的拓扑结构包含了几个概念,分别是Pool、Zone、Node、Table、Shard 与Replica。
关键特性 ★
binlog支持(多活)
类似于MySQL的binlog,我们基于raftlog日志实现了kv的binlog. 业务可以根据binlog进行实时的事件流订阅,同时为了满足事件流回溯的需求,我们还对binlog数据进行冷备。通过将binlog冷备到对象存储,满足了部分场景需要回溯较长事件记录的需求。
直接复用raftlog作为用户行为的binlog,可以减少binlog产生的额外写放大,唯一需要处理的是过滤raft本身的配置变更信息。learner通过实时监听不断拉取分片产生的binlog到本地并解析。根据learner配置信息决定将数据同步到对应的下游。同时binlog数据还会被异步备份到对象存储,当业务需要回溯较长时间的事件流的时候,可以直接指定位置从S3拉取历史binlog进行解析。
分区分裂
基于不同的业务场景,我们同时支持了range分区和hash分区。对于range场景,随着用户数据的增长,需要对分区数据进行分裂迁移。对于hash分区的场景,使用上通常会根据业务的数据量做几倍的冗余预估,然后创建合适的分片数。
bulk load
离线平台只需要根据kv的存储格式离线生成对应的SST文件,然后上传到对象存储服务。kv直接从对象存储拉取SST文件到本地,然后直接加载SST文件即可对外提供读服务。bulk load的另外一个好处是可以直接在生成SST后离线进行compaction,将compaction的负载offload到离线的同时也降低了空间的放大。
有赞ZanKV★
在有赞早期的时候,当时只有 MySQL 做存储,codis 做缓存,随着业务发展,某些业务数据用 MySQL 不太合适, 而 codis 由于当缓存用, 并不适合做存储系统, 因此, 急需一款高性能的 NoSQL 产品做补充。考虑到当时运维和开发人员都非常少, 我们需要一个能快速投入使用, 又不需要太多维护工作的开源产品。 当时对比了几个开源产品, 最终选择了 aerospike 作为我们的 KV 存储方案。
然而随着有赞的快速发展, 单纯的 aerospike 集群慢慢开始无法满足越来越多样的业务需求。 虽然性能和稳定性依然很优秀, 但是由于其索引必须加载到内存, 对于越来越多的海量数据, 存储成本会居高不下。 更多的业务需求也决定了我们将来需要更多的数据类型来支持业务的发展。 为了充分利用已有的 aerospike 集群, 并考虑到当时的开源产品并无法满足我们所有的业务需求, 因此我们需要构建一个能满足有赞未来多年的 KV 存储服务。
整体架构 ★
设计目标:
自研 ZanKV 有如下特点:
自研 ZanKV 的整体架构图如下所示:
整个集群由 placedriver + 数据节点 datanode + etcd + rsync 组成。 各个节点的角色如下:
AppleFoundationDB★
整体架构 ★
如图所示,FDB 的架构中规中矩,大的模块可以分成三部分:
Control Plane
Control Plane 负责管理集群的元数据,使用 Active Disk Paxos 来保证高可用。Control Plane 分成以下几个部分:
Data Plane
Data Plane 大体上可以划分成三大部分:
Transaction System 较为复杂,大体上可以分层三个模块:
另外FDB还支持事务特性,这个需要阅读论文区详细理解,这里不在展开
AWS DynamoDB - 2004★
Amazon DynamoDB 是一种完全托管的 NoSQL 数据库服务,提供快速而可预测的性能,能够实现无缝扩展。DynamoDB 可以从表中自动删除过期的项,从而帮助您降低存储用量,减少用于存储不相关数据的成本。
DynamoDB工作原理 ★
DynamoDB 架构
在DynamoDB中核心组件是表、项目和属性。表是项目的集合,项目是属性的集合,DynamoDB使用主键来标识表中的每个项目,还提供了二级索引来提供更大的查询灵活性,还可以使用DynamoDB流来捕获DynamoDB表中的数据修改事件。
表、项目和属性:
DynamoDB未开源,可参考的2篇论文: