解密腾讯云分布式块存储系统 : HCBS实现机制

作者介绍:gavinliao(廖晶贵),腾讯云研发工程师,隶属于腾讯TEG-基础架构部-CBS云存储研发团队,主要负责分布式存储系统研发与运营工作。

导语

分布式存储一直是个经久不衰的话题,在当前竞争激烈的云市场,存储系统的性能与稳定性一直是用户考量存储产品的重要指标,为适应用户需求与市场发展,腾讯云CBS团队一直在不断打磨存储产品,推出了一款新的分布式块存储系统 —— High-performance Cloud Block Storage。

1、背景

块存储是云上不可或缺的一部分,虽然很多云产品商在对外提供服务时虚拟机内还有本地硬盘的身影,但随着网络块存储的技术发展以及本地存储自身的缺陷,最终云上将只存在云盘而非本地盘。腾讯云块存储在经历了从无到有,从性能波动到性能稳定的发展,随着市场份额越来越大,原有的CBS(Cloud Block Storage)系统已经不足以适应市场需求,用户对IO性能以及价格(阿里云和腾讯云已连续两轮降价)永远是要求苛刻的,因此团队适时推出一款同时满足IO性能与价格兼容的分布式存储系统,HCBS——高性能网络块存储系统。

2、框架

HCBS是一种两层架构的分布式存储,用户IO与后端存储池以直通的方式交互,两层架构的好处是将IO路径化繁为简,既降低了因网络通信带来的IO延时,同时也减少了部署难度和运营成本。

HCBS系统架构图

类似于Ceph,HCBS分为三类节点,用于管理集群元数据的Master节点,并将集群元数据记录在zookeeper中;以客户端身份向用户呈现块设备的Driver节点;以及后端存储池Cell集群。从架构图中可以看到,用户IO直接通过Driver透传到底层存储池,不存在任何中间转发过程。

3、实现细节

3.1 客户端——Driver

Driver作为整个存储系统的客户端须部署在云母机上,通过iscsi协议提供块读写服务。Driver的主要功能是向用户呈现出块设备以及转发用户IO到后端存储池。由两部分组成:负责逻辑卷以及路由信息管理的Agent;转发用户IO请求的Client。从二者的分工可以看出Agent主要与中心控制节点Master交互,Client主要与存储池Cell交互。Client与Agent之间通过共享内存进行通信。这种设计的好处是当某个Client不服务或升级时可将IO自动切换到其它Client上而不影响用户访问,实现热升级。

Driver节点内部实现

3.1.1 局部元数据管理器——Agent

Agent作为元数据管理器,管理由Master推送过来的集群的路由信息和与其相关的逻辑卷信息,为Client提供逻辑卷鉴权和查找路由的功能,同时收集由Client上报上来的IO(带宽、IOPS、IO错误码等)信息以便上报给Master进行决策。元数据的分散管理必然会引入不一致的问题,如何保证Agent上局部元数据与中心控制器Master一致关系到整个系统的可用性。一种粗暴的方式是Master定时将与Agent相关的所有元数据全量推送过去,Agent只需做一个简单的全覆盖即可,这种方案的缺陷很明显,在分布式系统中网络通信本身就很频繁,加之每次全量导致交互数据量大,影响整个集群性能,也容易出现风暴效应;为此HCBS引入元数据版本号,Agent与Master二者之间通过心跳交互版本号,当出现版本号不一致时采用增量同步。

Agent同时类似于一个守护进程对Client进行监控管理,Client与Agent的部署模型可以是一对一,也可以是多对一。在多对一模型下Agent需保证落在每个Client上的IO负载均衡。一种简单的均衡性算法就是在不考虑每个逻辑卷的实际IO频繁度的情况下,将Agent所管理的卷均衡的映射到每个Client上。另一种合理的均衡性算法则是根据每个逻辑卷的实际IO进行动态调整。

一对一与多对一模型

Agent与Master交互涉及如挂载、卸载、扩容等操作,以挂载为例。用户通过API向Master发起挂载请求,Master收到请求后,若对应Agent是第一次挂载则推送集群全量路由信息,若不是则直接将卷信息推送到Agent上,Agent接收到卷信息将通过isici生成块设备(盘符如sdd)呈现在用户的虚拟机上(具体可参见isici协议的卷发现过程)。用户就可以在本地如使用物理硬盘一样对逻辑卷进行格式化、分区、安装系统或其他应用等操作了。

3.1.2 IO分发器——Client

Client负责响应用户IO以及如何保证IO正确路由到底层存储池中,当接收到某个卷IO请求时,通过卷ID向Agent查询卷信息,然后以diskid、lba作为输入计算出一致性hash值从而获得IO路由信息。由于存储引擎以1MB为粒度进行管理,当Client收到跨1MB的IO请求时需要对其进行拆分。Client为每个卷分配一个深度为32的IO队列,队列访问策略为FIFO,当收到IO请求时将其压入队列中,同时依次从每个队列取出固定数目的IO请求下发到存储池以保证每个逻辑卷都能获得同等优先级的待遇,不至于存在某个卷出现“饥饿现象”;另一种防止“饥饿现象”的策略是流控,流控以卷为粒度,可根据用户IO场景动态调控流控阈值,保证所有卷具有合理的带宽与IOPS。

3.2 集群控制中心——Master

Master是整个存储集群的控制中心,负责所有元数据的管理以及监控集群健康状况,同时协助存储池进行快速的故障恢复。Master主要管理两类元数据,一类是呈现给用户的逻辑卷信息;另一类是保证IO完整性的路由信息。而对集群健康状况监控主要包含如下几个方面。

  • 以盘为粒度的流量和流控信息。
  • 存储池资源消耗预警。
  • 对空闲存储控件进行回收再利用,以提高资源利用率。
  • 集群topo结构管理(包括存储池cell上下架以及三副本结对)。
  • 管理存储池故障探测与恢复。

分布式一个很常见的问题就是容易出现单点失效,为解决此问题HCBS采用主备多活部署模式,一旦主Master出现异常备Master将在秒级时间内快速接管工作(具体实现可参考zookeeper选主案例),保证系统高可用性。下面重点介绍元数据与集群自愈能力。

3.2.1逻辑卷与路由信息

卷元数据除了基本卷信息外还携带了两个特殊ID——uuid与diskid,前者向用户呈现,后者用于存储池做唯一IO区分,这样设计的好处是实现前端与后端隔离,可实现卷克隆与内部快照等特性。路由是存储系统的核心元数据,具有全局唯一且单调递增的64位版本号globalversion,每一个路由项又称为小表对,每个小表对包含三个小表——一个主副本master,两个从副本slave(分布式存储常用的三副本机制),每个小表对也同样有版本号tb_version。它不仅涉及到存储数据的完整性与一致性,同时还会影响集群的负载均衡。如何保证所有IO都能够随机均匀的落在存储池中是设计路由管理的关键。master最主要的功能之一就是如何构造与管理集群路由信息。

3.2.2路由的构造

前面已经讲到Master负责集群topo结构管理,在存储池正式对外提供IO能力时需先向Master进行注册认证,Master以结对的形式对所有cell进程进行分组管理。三个对等的Cell进程组成一组cp(cellpair也称diskpair,Cell进程对应管理一块物理盘disk)结对,以cpid标识每个cp;同时给每个Cell进程分配相同数量M的小表tb(tablet),以tbid标识每个tb;cp上对等的三个小表组成小表对tp(tabletpair),以tpid标识且全局唯一,tp上的三个tb同样也有主副本之分。如图所示相同色块tb表示属于同一个tp对。

路由组织结构

3.2.3路由的管理

HCBS采用hash环进行管理。hash环被划分为N个格子,所有的路由项被均匀打散到这N个格子上,hash环实际记录的是每个tpid。在介绍Client时讲到每次IO下发时Client都会以disk_id、lba为输入参数,通过公式indx = Hash(disk_id, lba) % N获得一个落在[0, N-1]之间的索引,通过这个indx就可从hash环上找出对应的小表对信息。最后只需要将IO发送到三副本的主副本上。

路由hash环管理

3.2.4故障探测与恢复(系统自愈)

任何分布式存储系统要想其成为永动机是不可能的,如何确保集群在故障后自动恢复同时不影响用户体验是分布式系统设计的核心。常用的三副本机制已经解决了大部分问题,即一个副本失效可继续从其他两个副本恢复数据。但是一个副本失效不能听之任之,必须让其重新恢复到正常状态,否则一旦其他两个副本也发生异常将存在数据丢失的风险。感知某个副本失效即为“探测”,而将失效副本重新启用则为“恢复”(也即系统自愈),在自愈阶段系统提供降级服务。

  • 如何探测?

故障探测主要有两种手段——心跳与IO上报。

心跳:所有的Cell进程都会定时向Master发送hb消息,若在30s后未收到下一个心跳则认为Cell处于不稳定状态,将Cell置down,同时其上的tb置为down状态且从tb将升主,每个tb所属的tp状态置为abnormal,从而发生路由变更,由Master将新路由信息推送到存储池上,保证原来落在异常Cell上的IO切换到正常Cell上;若在30s到5min内收到hb消息则原来置down的tb需进行数据恢复,若在5min后仍未收到hb消息则认为Cell永远无法进入稳定状态而需进行剔除。心跳检测一种极端情况是网络故障将导致所有Cell无法进行hb,为此在进行剔除操作时需要设置剔除上限值。

IO上报:当存储池出现坏盘时(可能仅仅只是某个扇区不可写)而Cell进程却是正常的,这时无法通过心跳监控,因此通过Driver在下发IO时Cell返回特定错误码并上报给Master,由Master进行决策踢盘。

故障鉴定约束

  • 如何自愈?

当出现故障时系统只能提供降级服务(一副本故障可读写,两副本故障只读),为提供稳定服务必须尽快进行系统自愈,自愈就需要进行数据搬迁,存在两种迁移任务——Recovery、Move,且迁移以tb为粒度,待迁移的为源小表src_tb,数据写入为目的小表des_tb。

1、丢失心跳在3s到5min中之间,tb从不稳定状态进入稳定状态时需要从其他副本进行数据Recover。

2、丢失心跳5min后或因IO上报而踢盘,需将原故障tb上的数据Move到新tb上。

两种故障恢复方式

当Master检测到故障后会生成相应类型的迁移任务下发给目的小表所在的Cell进程,由目的Cell上的迁移模块Dbtrsf负责数据搬迁,具体搬迁过程在介绍Cell时详述。

3.3 存储引擎——Cell

Cell进程为整个分布式集群提供存储能力,以1MB为粒度进行空间分配。HCBS的后端存储服务器机型为TS80,拥有12块容量为2TB的物理盘,则每块盘有2TB/1MB个数据单元。如前所述每个Cell进程负责管理一块物理盘,Cell以桶(bucket)方式将2TB按小表tb进行分段管理,实际上是将2TB容量打散到Cell上的小表tb上,则每个小表对应管理的容量平均为2TB/M(与hash函数冲突率有关)。

tb与block之间映射关系

当IO下发到Cell上时先比较与Driver的路由版本,存在如下几种可能。

1、 全局版本号global version相同tb version相同,IO接收。

2、 全局版本号global version相同tb version不同,IO拒绝,同时Driver/Cell(谁版本号小谁更新,下同)需更新路由。

3、 全局版本号global version不同tb verison相同,IO接收,同时Driver/Cell需更新路由。

4、 全局版本号global version不同tb verison不同,IO拒绝,同时Driver/Cell需更新路由。

为防止同一时间点多个Driver向Master发起更新路由请求,HCBS以“曲线救国”的方式进行路由更新——惰性同步策略。即先向主tb所在的Cell拉取,失败则继续向从tb所在Cell获取,仍失败则从Master获取直到成功为止。

Cell在经过一系列校验之后最终以扇形多副本同步策略将数据写入其他两个副本中,IO数据采用分布式经典的两阶段提交方法,先写binlog落盘,然后将数据写入内存Cache返回,进程异步将数据刷入磁盘。

存储池IO读写方式

3.1Cell的救赎

为实现存储系统自愈,每个Cell进程都配备了一个数据搬运工——Dbtrsf,数据迁移以小表tb为粒度进行。前面已经提到两类迁移任务Recovery、Move,二者的区别在于Recovery是增量搬迁(增量数据为故障期写入),Move则是全量搬迁。搬迁过程中用户IO同时在写入,这样存储池会同时存在两种IO,如何保证两者互不冲突以及最终能搬迁结束是关键(搬迁时源小表同样支持写入)。

Recovery迁移

Move迁移

从两种任务的迁移数据流可以看出,Recovery是将增量数据从主副本复制写入到故障恢复的从副本以重新达到三副本可用的过程;Move则是将原来故障的tp全量迁移到新的tp上。在迁移时不可避免的会有用户不断写入,这样会导致数据无法收敛而导致迁移不能完成。为防止此种情况出现最简单的做法就是禁止用户写入,从而可实现迁移收敛,但这严重影响了系统的可用性而用户无法接受。如何不影响用户写入而又保证迁移收敛呢?一种可行方式是进行串联写入,所谓串联写即在源接收到写IO时不仅仅写入对等副本,还需要根据串联路由将IO写入到目的,由原来的写三份变成写五份(源端故障的副本不写),而目的端收到串联IO时只写内存不落盘。串联写需要用到串联路由。串联路由同样也是路由,但生命周期只处于迁移阶段。整个迁移流程大致如下:

1、 Master检测到故障后推送路由变更与串联路由到存储池,然后发起迁移任务到目的Cell对应的Dbtrsf上。

2、 Dbtrsf从源连续迁移数据N次或某次需要迁移的数据量少于M则主动收敛(搬迁一次先dump元数据,再根据元数据迁移实际数据),主动收敛意味着需要进行串联写。

3、 Dbtrsf先通知目的端写内存,然后通知源端开始进行串联写。

4、 Dbtrsf进行最后一次数据搬迁,搬迁完通知目的端下刷内存数据。

5、 Dbtrsf向Master上报迁移完成,Master再进行一次路由变更结束串联写,故障恢复。

4、总结

HCBS对于是腾讯云分布式存储具有里程碑式意义,其无论是在性能还是成本在云市场都具有很大优势,以HCBS为基础衍生出了许多云产品如NCBS、FCBS、NAS等,HCBS同样支持快照功能。其的优势在于两层架构不仅降低了成本与网络通信,运营也同样变得灵活;同时惰性路由同步策略使得管理数据不需要强一致特性(分布式系统的一大难点在于如何保证各节点数据一致)。扇形多副本写(相比于Driver直接写三副本)不仅降低了Driver端压力,同时也起到了IO封装效果(外部IO转换为存储池内部IO),减少IO失败概率。但同样也存在不足,从之前描述可以知道HCBS存在结对概念,即副本之间并没有做到完全离散,这使得一个副本故障必须同时换掉其他两个正常副本,代价稍高,这是后续优化的重点。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Golang语言社区

Golang - 调度剖析【第一部分】

首先,Golang 调度器的设计和实现让我们的 Go 程序在多线程执行时效率更高,性能更好。这要归功于 Go 调度器与操作系统(OS)调度器的协同合作。不过在本...

902
来自专栏用户画像

3.2.6工作集

工作集(或驻留集)是指在某段时间间隔内,进程要访问的页面集合。经常被使用的页面需要在工作集中,而长时间不被使用的页面要从工作集中被丢弃。为了防止系统出现抖动现象...

591
来自专栏程序猿DD

都在说微服务,那么微服务的反模式和陷阱是什么(三)

前文导读: 《都在说微服务,那么微服务的反模式和陷阱是什么(一)》 《都在说微服务,那么微服务的反模式和陷阱是什么(二)》 九、通信协议使用的陷阱 在微服务架构...

1785
来自专栏PHP在线

缓存更新的套路

看到好些人在写更新缓存数据代码时,先删除缓存,然后再更新数据库,而后续的操作会把数据再装载的缓存中。然而,这个是逻辑是错误的。试想,两个并发操作,一个是更新操作...

31413
来自专栏java思维导图

Java中高级面试题部分答案解析(4)

这里选了几道高频面试题以及一些解答。不一定全部正确,有一些是没有固定答案的,如果发现有错误的欢迎纠正,如果有更好的回答,热烈欢迎留言探讨。

1053
来自专栏Java编程技术

深入浅出一致性Hash原理

在解决分布式系统中负载均衡的问题时候可以使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载...

571
来自专栏我是攻城师

ElasticSearch并发操作之乐观锁的使用

3603
来自专栏Golang语言社区

Golang适合高并发场景的原因分析

典型的两个现实案例: 我们先看两个用Go做消息推送的案例实际处理能力。 360消息推送的数据: 16台机器,标配:24个硬件线程,64GB内存 Linux K...

3917
来自专栏微服务生态

微服务反模式与陷阱翻译终结篇

都在说微服务,那么微服务的反模式和陷阱是什么(一) http://www.jianshu.com/p/3986239138fe

642
来自专栏后端技术探索

基于Redis实现分布式消息队列(一)

1、为什么需要消息队列? 当系统中出现“生产“和“消费“的速度或稳定性等因素不一致的时候,就需要消息队列,作为抽象层,弥合双方的差异。

1303

扫码关注云+社区