原副标题:Redis和MySQL顶不住,B站分布式控制系统储存控制系统怎样重构?
译者如是说
林堂辉,bilibilibilibili现职合作开发技师,2016年重新加入B站,做为核心理念合作开发亲眼目睹了B站从乙烯构架到微服务项目的构架改建,先期又负责管理最新消息堆栈、服务辨认出、传输等微服务项目开发工具的合作开发。现阶段负责管理NoSQL储存,从零到一构筑了分布式控制系统KV储存控制系统,为拉沙泰格赖厄县销售业务提供更多了高效能平衡可信的储存服务项目。
销售业务高速路产业发展,B站的储存控制系统怎样重构以支撑力成分股快速增长的网络流量洪水?随著网络流量更进一步猛增,怎样结构设计两套平衡可信易开拓的控制系统,来满足用户今后更进一步快速增长的销售业务政治理念?与此同时,直面更高的易用性政治理念,KV是怎样透过跨县多活为应用领域提供更多更高的易用性保证?该文的最终,会如是说许多众所周知销售业务在KV储存的应用领域课堂教学。
概要将紧紧围绕上面4点进行:
一、储存重构
二、结构设计与此同时实现
三、情景&难题
四、归纳思索
一、储存重构
具体来说如是说一下B站晚期的储存重构。
特别针对相同的情景,晚期的KV储存主要包括Redix/Memcache,Redis+MySQL,HBASE。
但随著B站信息量的高速路快速增长,此种储存THF1会遭遇许多难题:
具体来说,MySQL是FPS储存,许多情景信息量早已少于 10 T,FPS难以卸下。彼时也考量了采用TiDB,TiDB是一种关系型数据库,对于播放历史此种没有强关系的数据并不适合。 其次,是Redis Cluster的规模瓶颈,因为redis采用的是Gossip协议来通信传递信息,集群规模越大,节点间的通信开销越大,并且节点之间状态不一致的存留时间也会越长,很难再进行横向扩展。 另外,HBase存在严重长尾和缓存内存成本高的难题。基于这些难题,我们对KV储存提出了如下要求:
易开拓:100x横向扩容;高效能:低延时,高QPS; 高可用:长尾平衡,故障自愈; 低成本:对比缓存; 高可信:数据不丢。二、结构设计与此同时实现
接下来如是说我们是怎样基于上述要求进行具体与此同时实现的。
1、总体构架
总体构架共分为三个部分Client,Node,Metaserver。Client是用户接入端,确定了用户的接入方式,用户可以采用SDK的方式进行接入。Metaserver主要是储存表的元数据信息,表分为了哪些分片,这些分片位于哪些no副本之间的同步复制,保证高可用。
2、集群拓扑
Pool:资源池。根据相同的销售业务划分,分为在线资源池和离线资源池。
Zone:可用区。主要用于故障隔离,保证每个切片的副本分布在相同的zone。
Node:储存节点,可包含多个磁盘,储存着Replica。
Shard:一张表信息量过大的时候可以拆分为多个Shard。拆分策略有Range,Hash。
3、Metaserver
资源管理:主要记录集群的资源信息,主要包括有哪些资源池,可用区,多少个节点。当创建表的时候,每个分片都会记录这样的映射关系。
元数据分布:记录分片位于哪台节点之上。
健康检查:注册所有的node信息,检查当前node是否正常,是否有磁盘损坏。基于这些信息可以做到故障自愈。
负载检测:记录磁盘采用率,CPU采用率,内存采用率。
负载均衡:设置阈值,当达到阈值时会进行数据的重新分配。
分裂管理:信息量增大时,进行横向扩展。
Raft选主:当有一个Metaserver挂掉的时候,可进行故障自愈。
Rocksdb:元数据信息持久化储存。
4、Node
做为储存模块,主要包含后台线程,RPC接入,抽象引擎层三个部分。
1)后台线程
Binlog管理,当用户进行写操作的时候,会记录一条binlog日志,当发生故障的时候可以对数据进行恢复。因为本地的储存空间有限,所以Binlog管理会将许多冷数据存放在S3,热门的数据存放在本地。数据回收功能主要是用来防止误删数据。当用户进行删除操作,并不会真正的把数据删除,通常是设置一个时间,比如一天,一天之后数据才会被回收。如果是误删数据,就可以采用数据回收模块对数据进行恢复。健康检查会检查节点的健康状态,比如磁盘信息,内存是否异常,再上报给Metaserver。Compaction模块主要是用来数据回收管理。储存引擎Rocksdb,以LSM与此同时实现,其特点在于写入时是append only的形式。
2)RPC接入
当集群达到一定规模后,如果没有自动化运维,那么人工运维的成本是很高的。所以在RPC模块重新加入了指标监控,主要包括QPS、吞吐量、延时时间等,当出现难题时,会很方便排查。相同的销售业务的吞吐量是相同的,怎样做到多用户隔离?透过Quota管理,在销售业务接入的时候会申请配额,比如一张表申请了10K的QPS,当少于这个值得时候,会对用户进行限流。相同的销售业务等级,会进行相同的Quota管理。
3)抽象引擎层
主要是为了应对相同的销售业务情景。比如大value引擎,因为LSM存在写放大的难题,如果数据的value特别大,频繁的写入会导致数据的有效写入非常低。这些相同的引擎对于上层来说是透明的,在运行时透过选择相同的参数就可以了。
5、分裂-元数据更新
在KV储存的时候,刚开始会根据销售业务规模划分相同的分片,默认情况下单个分片是24G的大小。随著销售业务信息量的快速增长,单个分片的数据放不下,就会对数据进行分裂。分裂的方式有两种,rang和hash。这里我们以hash为例进行如是说:
假设一张表最开始结构设计了3个分片,当数据4到来,根据hash取余,应该保存在分片1中。随著数据的快速增长,3个分片放不下,则需要进行分裂,3个分片会分裂成6个分片。这个时候数据4来访问,根据Hash会分配到分片4,如果分片4正处于分裂状态,Metaserver会对访问进行重定向,还是访问到原来的分片1。当分片完成,状态变为normal,就可以正常接收访问,这一过程,用户是无感知的。
6、分裂-数据均衡回收
具体来说需要先将数据分裂,可以理解为本地做一个checkpoint,Rocksdb的checkpoint相当于是做了一个硬链接,通常1ms就可以完成数据的分裂。分裂完成后,Metaserver会同步更新元数据信息,比如0-100的数据,分裂之后,分片1的50-100的数据其实是不需要的,就可以透过Compaction Filter对数据进行回收。最终将分裂后的数据分配到相同的节点上。因为整个过程都是对一批数据进行操作,而不是像redis那样主从复制的时候一条一条复制,得益于这样的与此同时实现,整个分裂过程都在毫秒级别。
7、多活容灾
前面提到的分裂和Metaserver来保证高可用,对某些情景仍不能满足用户需求。比如整个机房的集群挂掉,这在业界多是采用多活来解决。我们KV储存的多活也是基于Binlog来与此同时实现,比如在云立方的机房写入一条数据,会透过Binlog同步到嘉定的机房。假如位于嘉定的机房的储存部分挂了以后,proxy模块会自动将网络流量切到云立方的机房进行读写操作。最极端的情况,整个机房挂掉了,就会将所有的用户访问集中到里一个机房,保证易用性。
三、情景&难题
接下来如是说KV在B站应用领域的众所周知情景以及遇到的难题。
最众所周知的情景就是用户画像,比如推荐,就是透过用户画像来完成的。其他还有动态、追番、对象储存、弹幕等都是透过KV来储存。
1、定制优化
基于抽象与此同时实现,可以很方便地支持相同的销售业务情景,并对许多特定的销售业务情景进行优化。
Bulkload全量导入的情景主要是用于动态推荐以及用户画像。用户画像主要是T+1的数据,在没有采用Bulkload以前,主要是透过Hive来逐条写入,数据链路很长,每天全量导入10亿条数据大概需要6、7个小时。采用Bulkload之后,只需要在hive离线平台把数据构建成一个rocksdb引擎,hive离线平台再把数据上传到对象储存。上传完成之后通知KV来进行拉取,拉取完成后就可以进行本地的Bulkload,时间可以缩短到10分钟以内。
另一个情景就是定长list。大家可能辨认出你的播放历史只有3000条,动态也只有3000条。因为历史记录是非常大的,不能无限储存。最早是透过一个脚本,对历史数据进行删除,为了解决这个难题,我们合作开发了一个定制化引擎,保存一个定长的list,用户只需要往里面写入,当少于定长的长度时,引擎会自动删除。
2、遭遇难题——储存引擎
前面提到的compaction,在实际采用的过程中,也碰到了许多难题,主要是储存引擎和raft方面的难题。储存引擎方面主要是Rocksdb的难题。第一个就是数据淘汰,在数据写入的时候,会透过相同的Compaction往下推。我们的播放历史,会设置一个过期时间。少于了过期时间之后,假设数据现在位于L3层,在L3层没满的时候是不会触发Compaction的,数据也不会被删除。为了解决这个难题,我们就设置了一个定期的Compaction,在Compaction的时候回去检查这个Key是否过期,过期的话就会把这条数据删除。
另一个难题就是DEL导致SCAN慢查询的难题。因为LSM进行delete的时候要一条一条地扫,有很多key。比如20-40之间的key被删掉了,但LSM删除数据的时候不会真正地进行物理删除,而是做一个delete的标识。删除之后做SCAN,会读到很多的脏数据,要把这些脏数据过滤掉,当delete非常多的时候,会导致SCAN非常慢。为了解决这个难题,主要用了两个方案。第一个就是设置删除阈值,少于阈值的时候,会强制触发Compaction,把这些delete标识的数据删除掉。但这样也会产生写放大的难题,比如有L1层的数据进行了删除,删除的时候会触发一个Compaction,L1的文件会带上一整层的L2文件进行Compaction,这样会带来非常大的写放大的难题。为了解决写放大,我们重新加入了一个延时删除,在SCAN的时候,会统计一个指标,记录当前删除的数据占所有数据的比例,根据这个反馈值去触发Compaction。
第三个是大Value写入放大的难题,现阶段业内的解决办法都是透过KV储存分离来与此同时实现的。我们也是这样解决的。
3、遭遇难题——Raft
Raft层面的难题有两个:
具体来说,我们的Raft是三副本,在一个副本挂掉的情况下,另外两个副本可以提供更多服务项目。但在极端情况下,超过半数的副本挂掉,虽然概率很低,但我们还是做了许多操作,在故障发生的时候,缩短控制系统恢复的时间。我们采用的方法就是降副本,比如三个副本挂了两个,会透过后台的一个脚本将集群自动降为单副本模式,这样依然可以正常提供更多服务项目。与此同时会在后台启动一个进程对副本进行恢复,恢复完成后重新设置为多副本模式,大大缩短了故障恢复时间。
另一个是日志刷盘难题。比如点赞、动态的情景,value其实非常小,但吞吐量非常高,此种情景会带来很严重的写放大难题。我们用磁盘,默认都是4k写盘,如果每次的value都是几十个字节,这样会造成很大的磁盘浪费。基于这样的难题,我们会做一个聚合刷盘,具体来说会设置一个阈值,当写入多少条,或者写入量少于多少k,进行批量刷盘,这个批量刷盘可以使吞吐量提升2~3倍。
四、归纳思索
1、应用领域
应用领域方面,我们会做KV与缓存的融合。因为销售业务合作开发不太了解KV与缓存资源的情况,融合之后就不需要再去考量是采用KV还是缓存。
另一个应用领域方面的改进是支持Sentinel模式,更进一步降低副本成本。
2、运维
运维方面,一个难题就是慢节点检测,我们可以检测到故障节点,但慢节点怎么检测呢,现阶段在业界也是一个难题,也是我们今后要努力的方向。
另一个难题就是自动剔盘均衡,磁盘发生故障后,现阶段的方法是第二天看许多报警事项,再人工操作一下。我们希望做成一个自动化机制。
3、控制系统
控制系统层面就是SPDK、DPDK方面的性能优化,透过这些优化,更进一步提升KV进程的吞吐。
译者丨林堂辉
dbaplus社群欢迎广大技术人员投稿,投稿邮箱:[email protected]
关于我们
dbaplus社群是紧紧围绕Database、BigData、AIOps的企业级专业社群。现职大咖、技术干货,每天精品原创该文推送,每周线上技术分享,每月线下技术沙龙,每季度Gdevops&DAMS行业大会。