KV存储 在大数据时代,数据量呈指数级增长,预计到2025年,全球的数据总量将达175ZB,非结构化和半结构化数据已占据主导地位。像腾讯微信、字节头条、B站、快手、小红书等众多的UGC社交平台,这些平台普遍的都存在搜索、推荐、广告和风控服务都以人为核心,服务强依赖用户的画像、行为和笔记。UGC数据的高效存储和查询是支持推广搜一体化工程的基础设施,而这种典型的属性和值的键值对结构正式我们今天要讨论的分布式KV系统。 KV存储系统对非结构化和半结构化数据进行高效存储,提供了很好的解决方案:KV存储系统具有灵活的数据模型,数据表示为Key,Value对形式,为任意数据类型,且长度不定;KV存储的访存接口非常简单,向外提供Put、Get、Scan等简单的接口进行数据读写;KV存储还具备高可扩展性,数据基于Key进行划分和索引,无需维护额外的元数据。 由于KV存储系统具有上述诸多优点,因此被广泛应用在了NewSQL和NoSQL产品中。比如目前常见的KV存储系统:LevelDB、RocksDB、Cassandra、TiKV等。 目前主流的持久化KV存储系统都采用LSMtree(logstructuredmergetree)架构作为存储引擎,其具有高效的写入效率,但同时存在严重的读写放大问题。 如图,KV数据首先缓存在内存并进行排序,然后以批量追加的方式将数据写入磁盘上,形成磁盘上的一个SSTable文件,SSTable文件中的数据是按Key有序的,这种写入方式可以将大量的随机写转换为顺序写,从而充分利用磁盘顺序写的带宽优势,获得高效的写入效率。 为兼顾读性能,磁盘上的数据被组织成多层形式,且容量逐层递增,并且除第0层以外,其他每层的数据是完全有序的。通过维护这样一个多层有序的架构,LSMtree可以获得高效的写入和范围查询性能,并且提供高可扩展性,当需要扩展存储容量时,可以通过简单的增加LSMtree的层数来实现高效的扩展。 然而,LSMtree多层的数据组织结构导致查询操作需要逐层搜索,从第0层开始,直到找到查询的数据为止,并且写入期间需要执行频繁的Compaction操作,具体Compaction操作需要从LSMtree中读取相邻两层的数据,然后执行合并排序操作,再将合并后的有效数据写回磁盘。因此,当我们将数据从第0层逐渐合并到较高层时,需要将数据频繁的读出并且写回,进而导致严重的读写放大问题,且严重消耗磁盘的IO带宽。 数据分片策略:当前流行的KV存储产品对Key的划分方法有两种:将Key的空间划分成多个PartitionShard。连续的Key在同一个PartitionShard内,或者跨两个Partition。创建一定数目的PartitionShard。将Keyhash打散到不同的PartitionShard上。 那如何评价一个KV产品是否足够优秀呢,笔者认为可以从以下特性功能进行对比评价:数据的可靠性SLA服务稳定性SLA读写性能大规模集群的弹性原地扩缩容能力服务平滑升级的不可用时间(毛刺)支持与大数据生态互通(在线、离线)跨云多活(多区多Region)KV引擎Rocksdb KV系统对外提供的接口比较统一,即点查、范围查询。而当今作为LSMTree最稳定、应用最广泛即最成熟的引擎就是Rocksdb。 RocksDB是由Facebook基于GoogleLevelDB开发的一款提供键值存储与读写功能的LSMtree架构引擎。用户写入的键值对会先写入磁盘上的WAL(WriteAheadLog),然后再写入内存中的跳表(SkipList,这部分结构又被称作MemTable)。LSMtree引擎由于将用户的随机修改(插入)转化为了对WAL文件的顺序写,因此具有比B树类存储引擎更高的写吞吐。 内存中的数据达到一定阈值后,会刷到磁盘上生成SST文件(SortedStringTable),SST又分为多层(默认至多6层),每一层的数据达到一定阈值后会挑选一部分SST合并到下一层,每一层的数据是上一层的10倍(因此90的数据存储在最后一层)。 RocksDB允许用户创建多个ColumnFamily,这些ColumnFamily各自拥有独立的内存跳表以及SST文件,但是共享同一个WAL文件,这样的好处是可以根据应用特点为不同的ColumnFamily选择不同的配置,但是又没有增加对WAL的写次数。互联网KV产品 小米Pegasus2015 小米云平台长期以来一直使用开源的ApacheHBase来存储结构化半结构化数据,但是HBase并不是十全十美的,它的架构、语言、实现等决定了它具有一些难以克服的不足: HBase实现采用的Java语言,虽然开发上效率比较高,但是运行性能并不如CC这样的语言。 JavaGC时可能造成进程进入假死状态,导致RegionServer无法提供读写服务,造降低系统可用性。 HBase宕机恢复时间比较长(分钟级),在这段时间内服务是不可用的。其原因是: HBase数据存储在分布式文件HDFS上,上层的RegionServer仅仅是服务点。为了保证数据一致性,HBase要求每个Region在同一时刻只能由一个RegionServer服务。当某个RegionServer宕机,必须选一个新的RegionServer来服务该Region。恢复过程中需要做较多处理,包括日志的传输、切分、重放,这个过程比较耗时。 HBase依赖Zookeeper来探测宕机问题,而由于Java的GC问题存在,Zookeeper的sessiontimeout不能设得太短,典型地设为30秒。如果设得太短,JavaGC的假死机就容易造成session超时,触发RegionServer不必要的自杀。因此从RegionServer宕机到被发现,这中间可能就需要几十秒。 HBase的分层架构使数据服务点和存储位置分离,对DataLocality不够友好,也是影响其读性能的一个原因。 以上这些原因造成了HBase的可用性和性能都存在一些不足,难以满足对服务可用性和延迟都很敏感的一些在线业务的需求,譬如广告业务。 从2015年开始,小米开始开发Pegasus系统。Pegasus系统的整体架构如下图所示,一共分为四个部分: ReplicaServer ReplicaServer主要负责数据存储和存取,以replica为单位进行服务:服务的replica既可能是PrimaryReplica,也可能是SecondaryReplica。底层使用RocksDB来存储数据管理commitlog,并实现replication协议,提供数据一致性保证 MetaServer MetaServer:MetaServer采用一主多备模式(onemaster,multiplebackups),所有的状态都会持久化到Zookeeper上;同时通过Zookeeper进行选主。当master故障后,另一台backup立即抢到锁,然后从Zookeeper上恢复状态,成为新的master。 MetaServer负责的功能包括:系统初始化ReplicaServer的管理Replica的分配、管理和负载均衡调度Table的创建与删除响应Client请求,向Client提供最新的路由表 Zookeeper系统元信息存储MetaServer选主 ClientLibClientLib对用户提供数据存储接口接口简洁:对用户提供最简单的接口,将寻址和容错等细节封装在内部配置简单:用户只需通过配置文件指定MetaServer地址列表,就可以访问集群,类似于Zookeeper尽量直接与ReplicaServer进行交互,尽量少地访问MetaServer以避免热点问题,不依赖Zookeeper 腾讯Tendis20158 Tendis是集腾讯众多海量KV存储优势于一身的Redis存储解决方案,并100兼容Redis协议和Redis4。0所有数据模型。作为一个高可用、高性能的分布式KV存储数据库,从访问时延、持久化需求、整体成本等不同维度的考量,Tendis推出了缓存版、混合存储版和存储版三种不同产品形态,并将存储版开源。 在版本迭代过程中,不断的业务接入,成为游戏业务和平台业务的主存储。 Tendis存储版集群架构 Tendis存储版使用去中心化集群架构,每个数据节点都拥有全部的路由信息。用户可以访问集群中的任意节点,并且通过redis的MOVE协议,最终路由到正确的节点。每个Tendis存储版节点维护属于各自的slot数据,任意两个master节点之间的slot不重复Tendis存储版的主备节点之间通过binlog进行复制任意两个节点之间通过gossip协议进行通讯master节点之间支持基于slot的数据搬迁 Tendis存储节点采用单进程多实例的部署形态,默认单进程使用10个rocksdb实例。 Key采用Hash打散的方式分片存储在KvStore中,每个KvStore是一个单进程多实例的Rocksdb。 Tendis存储版vs其他开源实现完全兼容rediscluster访问和管理模式的类redis存储方案完善的运维和管理指令,info,slaveof等管理指令完全兼容命令兼容度高,几乎所有命令和redis语义保持一致强大的数据搬迁能力,支持数据在节点中的随意搬迁,不影响原服务。强大的集群自治管理能力,支持自动failover,故障自动恢复等,运维成本低 360PiKa201511 Pika是一个可持久化的大容量redis存储服务,兼容string、hash、list、zset、set的绝大部分接口(兼容详情),解决redis由于存储数据量巨大而导致内存不够用的容量瓶颈,并且可以像redis一样,通过slaveof命令进行主从备份,支持全同步和部分同步,pika还可以用在twemproxy或者codis中来实现静态数据分片(pika已经可以支持codis的动态迁移slot功能,目前在合并到master分支。 PiKa2种模式 经典模式(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可以通过配置文件中的instancemode配置项,设置为classic和sharding,来选择运行经典模式(Classic)还是分布式模式(Sharding)的Pika。 美团Cellar2015 Squirrel:基于RedisCluster(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节点的分片12数据写入到本地的Log中。 如果A节点宕机后35分钟,或者网络抖动3050秒之后恢复了,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的读请求时,它就会直接去热点区域做数据的读取。 滴滴Fusion2016 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方式,我们启动mapreduce任务直接构造Fusion底层存储在文件系统上的文件SST,并把它们构造好相互之间的排序,避免重复,构造好后通知Fusion存储节点,下载SST文件,然后load到Fusion数据库中。此后,用户就可以通过RedisClient访问我们帮它加载的数据了。 此外,Fusion还支持二级索引和多区数据复制 新浪LaserDB2019 LaserDB是微博设计开源的高性能分布式KV数据库,在满足传统KV存储的高性能的基础上,提供了大容量分布式存储解决方案。并且为了满足大数据、人工智能特征模型快速加载更新,LaserDB原生支持了快速批量、增量导入功能,LaserDB不仅可以满足一般的工程业务应用,并且很好的支撑了机器学习模型、特征数据存储需求。 LaserDB整体架构 深入了解LaserDB的整体架构可以更好的使用、运维LaserDB,LaserDB主要包括三大核心组件:LaserServer,LaserClient和LaserControl,此外还有适配Redis协议的LaserProxy以及满足数据批量导入的LaserTransform。在具体部署时用户可以根据自己的需求选择部署LaserProxy和LaserTransform LaserServer LaserDB的存储服务核心组件,负责接收thrift请求并且处理对应的请求。除了负责对外的服务请求处理以外,还负责具体的数据存储、数据分片、数据同步等功能 LaserControl 负责集群数据表、数据表配置以及集群分片信息的管理,提供分片可视化、动态扩容、动态缩容 LaserClient 主要是负责和Server进行接口交互,并且实现LaserDB整体请求路由机制,对Server端提供的API接口进行封装,实现mget,mset等批量操作的客户端层并行化,目前提供C,Golang版本的SDK可以直接与Laserserver交互获得更好的性能,其他语言的业务可以选择LaserProxy代理,最终通过redis客户端操作 LaserProxy LaserProxy主要是负责实现Redis协议的大部分常用命令支持,Proxy通过调用LaserClient与LaserServer交互,对于Proxy来说是一个完全无状态的服务,可以将Proxy当做一个存储容量特别大的Redisserver来看。对于原有业务是Redis的,获取不方便直接使用LaserClientSDK调用的业务场景可以选用 LaserProxy LaserTransform LaserTransform主要是负责实现数据的批量导入功能,对于有数据快速批量导入的需求,需要部署LaserTransform服务,并且LaserServer环境需要有hdfs客户端支持,Transform服务主要负责定时调度提交MapReduce任务,将原始格式的数据转化为LaserServer可以识别的数据 字节ABase2016 自2016年以来,为了支撑在线推荐的存储需求而诞生的字节跳动自研高可用KV存储Abase,逐步发展成支撑包括推荐、广告、搜索、抖音、西瓜、飞书、游戏等公司内几乎所有业务线的90以上的KV存储场景,已成为公司内使用最广泛的在线存储系统之一。ABase22019年替换ABase1 ABase2架构 Abase2的整体架构主要如上图所示,在用户、管控面、数据面三种视角下主要包含5组核心模块。 RootServer 线上一个集群的规模大约为数千台机器,为管理各个集群,我们研发了RootServer这个轻量级组件。顾名思义,RootServer拥有全集群视角,它可以更好地协调各个集群之间的资源配比,支持租户在不同集群之间的数据迁移,提供容灾视图并合理控制爆炸半径。 MetaServer Abase2是多租户中心化架构,而MetaServer则是整个架构的总管理员,它主要包括以下核心功能:管理元信息的逻辑视图:包括Namespace,Table,Partition,Replica等状态和配置信息以及之间的关系;管理元信息的物理视图:包括IDC,Pod,Rack,DataNode,Disk,Core的分布和Replica的位置关系;多租户QoS总控,在异构机器的场景下根据各个租户与机器的负载进行副本Balance调度;故障检测,节点的生命管理,数据可靠性跟踪,在此基础上进行节点的下线和数据修复。 物理视图 逻辑视图 DataNode DataNode是数据存储节点。部署时,可以每台机器或者每块盘部署一个DataNode,为方便隔离磁盘故障,线上实际采用每块盘部署一个DataNode的方式。 DataNode的最小资源单位是CPUCore(后简称Core),每个Core都拥有一个独立的BusyPolling协程框架,多个Core共享一块盘的空间与IO资源。 一个Core包含多个Replica,每个Replica的请求只会在一个Core上RuntoComplete,可以有效地避免传统多线程模式中上下文切换带来的性能损耗。 Replica核心模块如下图所示,整个Partition为3层结构:数据模型层:如上文提到的String,Hash等Redis生态中的各类数据结构接口。一致性协议层:在多主架构下,多点写入势必会造成数据不一致,AntiEntropy一方面会及时合并冲突,另一方面将协调冲突合并后的数据下刷至引擎持久化层并协调WALGC。数据引擎层:数据引擎层首先有一层轻量级数据暂存层(或称ConflictResolver)用于存储未达成一致的数据;下层为数据数据引擎持久化层,为满足不同用户多样性需求,Abase2引设计上采用引擎可插拔模式。对于有顺序要求的用户可以采用RocksDB,TerarkDB这类LSM引擎,对于无顺序要求点查类用户采用延迟更稳定的LSH引擎。 ClientProxySDK Client模块是用户侧视角下的核心组件,向上提供各类数据结构的接口,向下一方面通过MetaSync与MetaServer节点通信获取租户Partition的路由信息,另一方面通过路由信息与存储节点DataNode进行数据交互。此外,为了进一步提高服务质量,我们在Client的IO链路上集成了重试、BackupRequest、热Key承载、流控、鉴权等重要QoS功能。 结合字节各类编程语言生态丰富的现状,团队基于Client封装了Proxy组件,对外提供Redis协议(RESP2)与Thrift协议,用户可根据自身偏好选择接入方式。此外,为了满足对延迟更敏感的重度用户,我们也提供了重型SDK来跳过Proxy层,它是Client的简单封装。 DTS(DataTransferService) DTS主导了Abase生态系统的发展,在一二代透明迁移、备份回滚、Dump、订阅等诸多业务场景中起到了非常核心的作用,由于篇幅限制,本文不做更多的详细设计叙述。 小红书RedKV2019 小红书是年轻人的生活记录、分享平台,用户可以通过短视频、图文等形式记录生活点滴,分享生活方式。在当前的业务模型下,用户的画像数据和笔记数据用来做风险控制和内容推荐。存储数据具有对象属性的特征、维度多,画像数据量已经达到数十TB,在线业务对画像和笔记数据的访问P99时延要求非常高。 RedKV2架构 RedKV整体架构分3层,接入层兼容Redis协议,支持各种语言的社区版SDK和公司定制的中间件版;接入代理层支持千万QPS的读写能力,无状态扩展;存储层提供高可靠读写服务。 Client接入层 RedKV集群部署完成后,通过公司内部提供的ServiceMesh组件做服务发现,对Client提供服务。 Proxy Proxy层由一个无状态CorvusPlus进程组成。它兼容老的RedisClient,扩缩容、升级对无Client和后端集群无感,支持多线程、IO多路复用和端口复用特性。对比开源版本,CorvusPlus增强了自我防护和可观测特性,实现了可在线配置的功能特性:Proxy限流数据在线压缩线程模型优化backupread优化长尾大key检测 基于Shard管理的中心架构能更好的支持数据迁移和集群扩缩容,存储节点采用单进程多实例部署,在多活场景中可以支持副本数弹性扩展。 关键特性 数据复制 与传统解决方案引入同步组件的方式不同,我们快速实现了单向数据同步以及集群扩容需求,整体架构去除了对第三方组件的依赖,通过扩展Redis复制协议实现了RedKV数据节点的直接复制,如图10。单向复制的限制是扩容需要基于做节点同步,扩容完成后后台任务根据3。3。3中定义的key的分片删除不是本节点的数据。 在多活的部署形态下,多云集群的一对多的数据复制采用单向复制对主集群性能侵入较大,因此我们实现了基于中心管控的数据复制策略。该策略支持多个集群的分片异构部署,通过Checkpoint方式定向同步数据,不再需要额外的后台任务去做数据淘汰,能很好的支持多对多的多云集群数据复制、数据破环和扩缩容。 数据批量导入 小红书大量的离线业务数据存储在S3Hive中,每天会有部分数据需要增量更新,其他的数据会被淘汰。这类场景有几个挑战:批量导入:如小红书的笔记数据,一般需要小时级别甚至天级别的更新,所以业务需要有快捷的批量导入功能。快速更新:特征数据的特点就是数据量特别大,以笔记为例,全量笔记在TB级别数据量。如果通过JedisSDK写入,那么存储集群需要支持百万QPS的机器资源。当下小红书数据平台支持业务把数据从hive通过工作流直接导入RedKV,一般是每天凌晨开始写数据,等到晚高峰时大量读取。这种方法实践下来,经常导致RedKV集群的集群内存OOM,影响稳定性。性能及稳定:数据在导入的过程中不能影响读的性能 数据批量导出 小红书的业务模型训练数据通过Hash存储在RedKV集群中,业务下游需要对训练结果进行离线分析,希望RedKV具有和Hive数据流通的能力。RedKV本身是不支持Schema的,如果要将KV数据导入Hive表,则需要将Hash的KKV数据转化为一个Table。 RedKV的内部数据按hash打散,导入Hive表则需要提供table关键字,先按前缀扫描的方式扫描存储节点,再生成Hive识别的文件,最后通过HiveLoad进行加载。为了更好的兼容其他spark任务,我们选择Hive支持的标准parquet列式存储文件 B站KV2019 在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。Pool为资源池连通域,包含多个可用区。也可用于业务资源隔离域。Zone为可用区,同一个pool内部的zone是网路联通并且故障隔离的。通常为一个机房或者一个交换机Node为实际的物理主机节点,负责具体的数据存储逻辑与数据持久化。Table对应到具体的业务表,类似MySQL里的表。Shard为逻辑分片,通过将table分为多个shard将数据打散分布。Replica为shard的副本,同一个shard的不同副本不能分布在同一个zone,必须保证故障隔离。每一个replica包含一个engine,engine存储全量的业务数据。engine的实现包含rocksdb和sparrowdb。其中sparrowdb是针对大value写放大的优化实现。 关键特性 binlog支持(多活) 类似于MySQL的binlog,我们基于raftlog日志实现了kv的binlog。业务可以根据binlog进行实时的事件流订阅,同时为了满足事件流回溯的需求,我们还对binlog数据进行冷备。通过将binlog冷备到对象存储,满足了部分场景需要回溯较长事件记录的需求。 直接复用raftlog作为用户行为的binlog,可以减少binlog产生的额外写放大,唯一需要处理的是过滤raft本身的配置变更信息。learner通过实时监听不断拉取分片产生的binlog到本地并解析。根据learner配置信息决定将数据同步到对应的下游。同时binlog数据还会被异步备份到对象存储,当业务需要回溯较长时间的事件流的时候,可以直接指定位置从S3拉取历史binlog进行解析。 分区分裂 基于不同的业务场景,我们同时支持了range分区和hash分区。对于range场景,随着用户数据的增长,需要对分区数据进行分裂迁移。对于hash分区的场景,使用上通常会根据业务的数据量做几倍的冗余预估,然后创建合适的分片数。 bulkload 离线平台只需要根据kv的存储格式离线生成对应的SST文件,然后上传到对象存储服务。kv直接从对象存储拉取SST文件到本地,然后直接加载SST文件即可对外提供读服务。bulkload的另外一个好处是可以直接在生成SST后离线进行compaction,将compaction的负载offload到离线的同时也降低了空间的放大。 有赞ZanKV 在有赞早期的时候,当时只有MySQL做存储,codis做缓存,随着业务发展,某些业务数据用MySQL不太合适,而codis由于当缓存用,并不适合做存储系统,因此,急需一款高性能的NoSQL产品做补充。考虑到当时运维和开发人员都非常少,我们需要一个能快速投入使用,又不需要太多维护工作的开源产品。当时对比了几个开源产品,最终选择了aerospike作为我们的KV存储方案。 然而随着有赞的快速发展,单纯的aerospike集群慢慢开始无法满足越来越多样的业务需求。虽然性能和稳定性依然很优秀,但是由于其索引必须加载到内存,对于越来越多的海量数据,存储成本会居高不下。更多的业务需求也决定了我们将来需要更多的数据类型来支持业务的发展。为了充分利用已有的aerospike集群,并考虑到当时的开源产品并无法满足我们所有的业务需求,因此我们需要构建一个能满足有赞未来多年的KV存储服务。 整体架构 设计目标:在设计这样一个能满足未来多年发展的底层KV服务,我们需要考虑以下几个方面:需要尽量使用有大厂背书并且活跃的开源产品,避免过多的工作量和太长的周期避免完全依赖和耦合一个开源产品,使得无法适应未来某个开源产品的不可控变化,以及无法享受将来的技术迭代更新和升级避免使用过于复杂的技术栈,增加后期运维成本由于业务需要,我们需要有能力做方便的扩展和定制未来的业务需求发展多样,单一产品无法满足所有的需求,可能需要整合多个开源产品来满足复杂多样的需求允许KV服务后端的技术变化的同时,对业务接口应该尽量稳定,后继升级不应该带来过多的迁移成本。 自研ZanKV有如下特点:使用Go语言开发,利用其高效的开发效率,也能减少后期维护难度,方便后期定制。使用大厂且成熟活跃的开源组件etcdraft,RocksDB等构建,减少开发工作量CP系统和现有aerospike的AP系统结合满足不同的需求提供更丰富的数据结构支持更大的容量,和aerospike结合在不损失性能需求的前提下大大减少存储成本 自研ZanKV的整体架构图如下所示: 整个集群由placedriver数据节点datanodeetcdrsync组成。各个节点的角色如下:PDnode:负责数据分布和数据均衡,协调集群里面所有的zankvnode节点,将元数据写入etcddatanode:负责存储具体的数据etcd:负责存储元数据,元数据包括数据分布映射表以及其他用于协调的元数据rsync:用于传输snapshot备份文件 AppleFoundationDB 整体架构 如图所示,FDB的架构中规中矩,大的模块可以分成三部分:客户端接口Client控制平面ControlPlane数据平面DataPlane ControlPlane ControlPlane负责管理集群的元数据,使用ActiveDiskPaxos来保证高可用。ControlPlane分成以下几个部分:Coordinator:几个coordinator进程组成一个paxosgroup,其中有一个leader,称为clustercontroller。Clustercontroller负责故障检测,管理各种进程的角色,汇集、传递整个集群的各种信息。同时,clustercontroller是整个集群访问的入口点。Client通过一个保存有coordinator的IP:Port的配置文件访问集群,并从clustercontroller获取最新的proxy列表。DataDistributor:DataDistributor负责监控StorageServer的故障情况和数据的平衡调度。Ratekeeper:Ratekeeper通过控制单调递增时间戳的分配速度来进行过载保护。 DataPlane DataPlane大体上可以划分成三大部分:TransactionSystem负责实现serializablesnapshotisolation级别的分布式事务。LogSystem负责日志的复制,保证系统的高可用。StorageSystem保存实际数据,或者说状态机,会从LogSystem异步拉取日志进行apply。目前单机存储引擎是使用一个修改过的SQLite。 TransactionSystem较为复杂,大体上可以分层三个模块:Proxy作为事务系统面向client的代理接口,事务请求都需要通过proxy获取readversion,获取keyranges对应的storageserver的信息,提交事务。Sequencer负责分配递增的readversion和commitversion。Resolver负责SSI级别的事务冲突检测。 另外FDB还支持事务特性,这个需要阅读论文区详细理解,这里不在展开 AWSDynamoDB2004 AmazonDynamoDB是一种完全托管的NoSQL数据库服务,提供快速而可预测的性能,能够实现无缝扩展。DynamoDB可以从表中自动删除过期的项,从而帮助您降低存储用量,减少用于存储不相关数据的成本。 DynamoDB工作原理 DynamoDB架构 在DynamoDB中核心组件是表、项目和属性。表是项目的集合,项目是属性的集合,DynamoDB使用主键来标识表中的每个项目,还提供了二级索引来提供更大的查询灵活性,还可以使用DynamoDB流来捕获DynamoDB表中的数据修改事件。 表、项目和属性:表DynamoDB将数据存储在表中,表是某类数据的集合,例如People表、Cars表。项目每个表包含多个项目,项目是一组属性,具有不同于所有其他项目的唯一标识,项目类似与SQL中的行、记录或元组。属性每个项目包含一个或多个属性,属性是基本的数据元素,属性类似与SQL中的字段或列。 People表示例 DynamoDB未开源,可参考的2篇论文:AmazonDynamoDBaFastandScalableNoSQLDatabaseServiceDesignedforInternetScaleApplications。https:www。allthingsdistributed。com201201amazondynamodb。htmlAmazonDynamoDB:AScalable,PredictablyPerformant,andFullyManagedNoSQLDatabaseService:https:www。usenix。orgsystemfilesatc22elhemali。pdfhttps:www。infoq。cnarticleaEUY5kcI1a3iqGUyGzUy