阿里HBase高可用8年抗战回忆录

39次阅读

共计 7855 个字符,预计需要花费 20 分钟才能阅读完成。

2017 年开始阿里 HBase 走向公有云,我们有计划的在逐步将阿里内部的高可用技术提供给外部客户,目前已经上线了同城主备,将作为我们后续高可用能力发展的一个基础平台。本文分四个部分回顾阿里 HBase 在高可用方面的发展:大集群、MTTF&MTTR、容灾、极致体验,希望能给大家带来一些共鸣和思考。

大集群

一个业务一个集群在初期很简便,但随着业务增多会加重运维负担,更重要的是无法有效利用资源。首先每一个集群都要有 Zookeeper、Master、NameNode 这三种角色,固定的消耗 3 台机器。其次有些业务重计算轻存储,有些业务重存储轻计算,分离模式无法削峰填谷。因此从 2013 年开始阿里 HBase 就走向了大集群模式,单集群节点规模达到 700+。

隔离性是大集群的关键难题。保障 A 业务异常流量不会冲击到 B 业务,是非常重要的能力,否则用户可能拒绝大集群模式。阿里 HBase 引入了分组概念“group”,其核心思想为:共享存储、隔离计算

如上图所示,一个集群内部被划分成多个分组,一个分组至少包含一台服务器,一个服务器同一时间只能属于一个分组,但是允许服务器在分组之间进行转移,也就是分组本身是可以扩容和缩容的。一张表只能部署在一个分组上,可以转移表到其它的分组。可以看到,表 T1 读写经过的 RegionServer 和表 T2 读写经过的 RegionServer 是完全隔离的,因此在 CPU、内存上都物理隔离,但是下层使用的 HDFS 文件系统是共享的,因此多个业务可以共享一个大的存储池子,充分提升存储利用率。开源社区在 HBase2.0 版本上引入了 RegionServerGroup。

坏盘对共享存储的冲击:由于 HDFS 机制上的特点,每一个 Block 的写入会随机选择 3 个节点作为 Pipeline,如果某一台机器出现了坏盘,那么这个坏盘可能出现在多个 Pipeline 中,造成单点故障全局抖动。现实场景中就是一块盘坏,同一时间影响到几十个客户给你发信息打电话!特别如果慢盘、坏盘不及时处理,最终可能导致写入阻塞。阿里 HBase 目前规模在 1 万 + 台机器,每周大概有 22 次磁盘损坏问题。我们在解决这个问题上做了两件事,第一是缩短影响时间,对慢盘、坏盘进行监控报警,提供自动化处理平台。第二是在软件上规避单点坏盘对系统的影响,在写 HDFS 的时候并发的写三个副本,只要两个副本成功就算成功,如果第三个副本超时则将其放弃。另外如果系统发现写 WAL 异常(副本数少于 3)会自动滚动产生一个新的日志文件(重新选择 pipeline,大概率规避坏点)。最后 HDFS 自身在高版本也具备识别坏盘和自动剔除的能力。

客户端连接对 Zookeeper 的冲击:客户端访问 hbase 会和 Zookeeper 建立长连接,HBase 自身的 RegionServer 也会和 Zookeeper 建立长连接。大集群意味着大量业务,大量客户端的链接,在异常情况下客户端的链接过多会影响 RegionServer 与 Zookeeper 的心跳,导致宕机。我们在这里的应对首先是对单个 IP 的链接数进行了限制,其次提供了一种分离客户端与服务端链接的方案 HBASE-20159

MTTF&MTTR

稳定性是生命线,随着阿里业务的发展,HBase 逐步扩大在线场景的支持,对稳定性的要求是一年更比一年高。衡量系统可靠性的常用指标是 MTTF(平均失效时间)和 MTTR(平均恢复时间)

MTTF(mean time to failure)

造成系统失效的来源有:
硬件失效,比如坏盘、网卡损坏、机器宕机等
自身缺陷,一般指程序自身的 bug 或者性能瓶颈
运维故障,由于不合理的操作导致的故障
服务过载,突发热点、超大的对象、过滤大量数据的请求
依赖失效,依赖的 HDFS、Zookeeper 组件出现不可用导致 HBase 进程退出

下面我介绍一下阿里云 HBase 在稳定性上遇到的几个代表性问题:(注:慢盘、坏盘的问题已经在大集群一节中涉及,这里不再重复)

  • 周期性的 FGC 导致进程退出

在支持菜鸟物流详情业务的时候,我们发现机器大概每隔两个月就会 abort 一次,因为内存碎片化问题导致 Promotion Fail,进而引发 FGC。由于我们使用的内存规格比较大,所以一次 FGC 的停顿时间超过了与 Zookeeper 的心跳,导致 ZK session expired,HBase 进程自杀。我们定位问题是由于 BlockCache 引起的,由于编码压缩的存在,内存中的 block 大小是不一致的,缓存的换入换出行为会逐步的切割内存为非常小的碎片。我们开发了 BucketCache,很好的解决了内存碎片化的问题,然后进一步发展了 SharedBucketCache,使得从 BlockCache 里面反序列化出来的对象可以被共享复用,减少运行时对象的创建,从而彻底的解决了 FGC 的问题。

  • 写入 HDFS 失败导致进程退出

HBase 依赖俩大外部组件,Zookeeper 和 HDFS。Zookeeper 从架构设计上就是高可用的,HDFS 也支持 HA 的部署模式。当我们假设一个组件是可靠的,然后基于这个假设去写代码,就会产生隐患。因为这个“可靠的”组件会失效,HBase 在处理这种异常时非常暴力,立即执行自杀(因为发生了不可能的事情),寄希望于通过 Failover 来转移恢复。有时 HDFS 可能只是暂时的不可用,比如部分 Block 没有上报而进入保护模式,短暂的网络抖动等,如果 HBase 因此大面积重启,会把本来 10 分钟的影响扩大到小时级别。我们在这个问题上的方案是优化异常处理,对于可以规避的问题直接处理掉,对于无法规避的异常进行重试 & 等待。

  • 并发大查询导致机器停摆

HBase 的大查询,通常指那些带有 Filter 的 Scan,在 RegionServer 端读取和过滤大量的数据块。如果读取的数据经常不在缓存,则很容易造成 IO 过载;如果读取的数据大多在缓存中,则很容易因为解压、序列化等操作造成 CPU 过载;总之当有几十个这样的大请求并发的在服务器端执行时,服务器 load 会迅速飙升,系统响应变慢甚至表现的像卡住了。这里我们研发了大请求的监控和限制,当一个请求消耗资源超过一定阈值就会被标记为大请求,日志会记录。一个服务器允许的并发大请求存在上限,如果超过这个上限,后来的大请求就会被限速。如果一个请求在服务器上运行了很久都没有结束,但客户端已经判断超时,那么系统会主动中断掉这个大请求。该功能的上线解决了支付宝账单系统因为热点查询而导致的性能抖动问题。

  • 大分区 Split 缓慢

在线上我们偶尔会遇到某个分区的数量在几十 GB 到几个 TB,一般都是由于分区不合理,然后又在短时间内灌入了大量的数据。这种分区不但数据量大,还经常文件数量超级多,当有读落在这个分区时,一定会是一个大请求,如果不及时分裂成更小的分区就会造成严重影响。这个分裂的过程非常慢,HBase 只能从 1 个分区分裂为 2 个分区,并且要等待执行一轮 Compaction 才能进行下一轮分裂。假设分区大小 1TB,那么分裂成小于 10GB 的 128 个分区需要分裂 7 轮,每一轮要执行一次 Compaction(读取 1TB 数据,写出 1TB 数据),而且一个分区的 Compaction 只能由一台机器执行,所以第一轮最多只有 2 台机器参与,第二轮 4 台,第三轮 8 台。。。,并且实际中需要人为干预 balance。整个过程做下来超过 10 小时,这还是假设没有新数据写入,系统负载正常。面对这个问题我们设计了“级联分裂”,可以不执行 Compaction 就进入下一次分裂,先快速的把分区拆分完成,然后一把执行 Compaction。

前面讲的都是点,关于如何解决某个顽疾。导致系统失效的情况是多种多样的,特别一次故障中可能交叉着多个问题,排查起来异常困难。现代医学指出医院应当更多投入预防而不是治疗,加强体检,鼓励早就医。早一步也许就是个感冒,晚一步也许就变成了癌症。这也适用于分布式系统,因为系统的复杂性和自愈能力,一些小的问题不会立即造成不可用,比如内存泄漏、Compaction 积压、队列积压等,但终将在某一刻引发雪崩。应对这种问题,我们提出了“健康诊断”系统,用来预警那些暂时还没有使系统失效,但明显超过正常阈值的指标。“健康诊断”系统帮助我们拦截了大量的异常 case,也在不停的演进其诊断智能。

MTTR(mean time to repair)

百密终有一疏,系统总是会失效,特别的像宕机这种 Case 是低概率但一定会发生的事件。我们要做的是去容忍,降低影响面,加速恢复时间。HBase 是一个可自愈的系统,单个节点宕机触发 Failover,由存活的其它节点来接管分区服务,在分区对外服务之前,必须首先通过回放日志来保证数据读写一致性。整个过程主要包括 Split Log、Assign Region、Replay Log 三个步骤。hbase 的计算节点是 0 冗余,所以一个节点宕机,其内存中的状态必须全部回放,这个内存一般可以认为在 10GB~20GB 左右。我们假设整个集群的数据回放能力是 R GB/s,单个节点宕机需要恢复 M GB 的数据,那么宕机 N 个节点就需要 M * N / R 秒,这里表达的一个信息是:如果 R 不足够大,那么宕机越多,恢复时间越不可控,那么影响 R 的因素就至关重要,在 Split Log、Assign Region、Replay Log 三个过程中,通常 Split Log、Assign Region 的扩展性存在问题,核心在于其依赖单点。Split Log 是把 WAL 文件按分区拆分成小的文件,这个过程中需要创建大量的新文件,这个工作只能由一台 NameNode 来完成,并且其效率也并不高。Assign Region 是由 HBase Master 来管理,同样是一个单点。阿里 HBase 在 Failover 方面的核心优化是采用了全新的 MTTR2 架构,取消了 Split Log 这一步骤,在 Assign Region 上也做了优先 Meta 分区、Bulk Assign、超时优化等多项优化措施,相比社区的 Failover 效率提升 200% 以上

从客户角度看故障,是 2 分钟的流量跌零可怕还是 10 分钟的流量下降 5% 可怕?我想可能是前者。由于客户端的线程池资源有限,HBase 的单机宕机恢复过程可能造成业务侧的流量大跌,因为线程都阻塞在访问异常机器上了,2% 的机器不可用造成业务流量下跌 90% 是很难接受的。我们在客户端开发了一种 Fast Fail 的机制,可以主动发现异常服务器,并快速拒绝发往这个服务器的请求,从而释放线程资源,不影响其它分区服务器的访问。项目名称叫做 DeadServerDetective

容灾

容灾是重大事故下的求生机制,比如地震、海啸等自然灾害造成毁灭性打击,比如软件变更等造成完全不可控的恢复时间,比如断网造成服务瘫痪、恢复时间未知。从现实经验来看,自然灾害在一个人的一生中都难遇到,断网一般是一个年级别的事件,而软件变更引发的问题可能是月级别的。软件变更是对运维能力、内核能力、测试能力等全方位的考验,变更过程的操作可能出错,变更的新版本可能存在未知 Bug。另一个方面为了不断满足业务的需求又需要加速内核迭代,产生更多的变更。

容灾的本质是基于隔离的冗余,要求在资源层面物理隔离、软件层面版本隔离、运维层面操作隔离等,冗余的服务之间保持最小的关联性,在灾难发生时至少有一个副本存活。阿里 HBase 在几年前开始推进同城主备、异地多活,目前 99% 的集群至少有一个备集群,主备集群是 HBase 可以支持在线业务的一个强保障。主备模式下的两个核心问题是数据复制和流量切换

数据复制

选择什么样的复制方式,是同步复制还是异步复制,是否要保序?主要取决于业务对系统的需求,有些要求强一致,有些要求 session 一致,有些可以接受最终一致。占在 HBase 的角度上,我们服务的大量业务在灾难场景下是可以接受最终一致性的(我们也研发了同步复制机制,但只有极少的场景),因此本文主要专注在异步复制的讨论上。很长一段时间我们采用社区的异步复制机制(HBase Replication),这是 HBase 内置的同步机制。

同步延迟的根因定位是第一个难题,因为同步链路涉及发送方、通道、接受方 3 个部分,排查起来有难度。我们增强了同步相关的监控和报警。

热点容易引发同步延迟是第二个难题。HBase Replication 采用推的方式进行复制,读取 WAL 日志然后进行转发,发送线程和 HBase 写入引擎是在同一台 RegionServer 的同一个进程里。当某台 RegionServer 写入热点时,就需要更多的发送能力,但写入热点本身就挤占了更多的系统资源,写入和同步资源争抢。阿里 HBase 做了两个方面的优化,第一提高同步性能,减少单位 MB 同步的资源消耗;第二研发了远程消耗器,使其它空闲的机器可以协助热点机器同步日志。

资源需求、迭代方式的不匹配是第三个难题。数据复制本身是不需要磁盘 IO 的,只消耗带宽和 CPU,而 HBase 对磁盘 IO 有重要依赖;数据复制的 worker 本质上是无状态的,重启不是问题,可以断点续传,而 HBase 是有状态的,必须先转移分区再重启,否则会触发 Failover。一个轻量级的同步组件和重量级的存储引擎强耦合在一起,同步组件的每一次迭代升级必须同时重启 HBase。一个重启就可以解决的同步问题,因为同时要重启 hbase 而影响线上读写。一个扩容 CPU 或者总带宽的问题被放大到要扩容 hbase 整体。

综上所述,阿里 HBase 最终将同步组件剥离了出来作为一个独立的服务来建设,解决了热点和耦合的问题,在云上这一服务叫做 BDS Replication。随着异地多活的发展,集群之间的数据同步关系开始变得复杂,为此我们开发了一个关于拓扑关系和链路同步延迟的监控,并且在类环形的拓扑关系中优化了数据的重复发送问题。

流量切换

在具备主备集群的前提下,灾难期间需要快速的把业务流量切换到备份集群。阿里 HBase 改造了 HBase 客户端,流量的切换发生在客户端内部,通过高可用的通道将切换命令发送给客户端,客户端会关闭旧的链接,打开与备集群的链接,然后重试请求。

切换瞬间对 Meta 服务的冲击:hbase 客户端首次访问一个分区前需要请求 Meta 服务来获取分区的地址,切换瞬间所有客户端并发的访问 Meta 服务,现实中并发可能在几十万甚至更多造成服务过载,请求超时后客户端又再次重试,造成服务器一直做无用功,切换一直无法成功。针对这个问题我们改造了 Meta 表的缓存机制,极大地提高了 Meta 表的吞吐能力,可以应对百万级别的请求。同时在运维上隔离了 Meta 分区与数据分区,防止相互影响。

从一键切换走向自动切换。一键切换还是要依赖报警系统和人工操作,现实中至少也要分钟级别才能响应,如果是晚上可能要 10 分钟以上。阿里 HBase 在演进自动切换过程中有两个思路,最早是通过增加一个第三方仲裁,实时的给每一个系统打健康分数,当系统健康分低于一个阈值,并且其备库是健康的情况下,自动执行切换命令。这个仲裁系统还是比价复杂的,首先其部署上要保持网络独立,其次其自身必须是高可靠的,最后健康分的正确性需要保证。仲裁系统的健康判断是从服务器视角出发的,但从客户端角度来讲,有些时候服务器虽然活着但是已经不正常工作了,可能持续的 FGC,也可能出现了持续网络抖动。所以第二个思路是在客户端进行自动切换,客户端通过失败率或其它规则来判定可用性,超过一定阈值则执行切换。

极致体验

在风控和推荐场景下,请求的 RT 越低,业务在单位时间内可以应用的规则就越多,分析就越准确。要求存储引擎高并发、低延迟、低毛刺,要高速且平稳的运行。阿里 HBase 团队在内核上研发 CCSMAP 优化写入缓存,SharedBucketCache 优化读取缓存,IndexEncoding 优化块内搜索,加上无锁队列、协程、ThreadLocal Counter 等等技术,再结合阿里 JDK 团队的 ZGC 垃圾回收算法,在线上做到了单集群 P999 延迟小于 15ms。另一个角度上,风控和推荐等场景并不要求强一致,其中有一些数据是离线导入的只读数据,所以只要延迟不大,可以接受读取多个副本。如果主备两个副本之间请求毛刺是独立事件,那么理论上同时访问主备可以把毛刺率下降一个数量级。我们基于这一点,利用现有的主备架构,研发了 DualService,支持客户端并行的访问主备集群。在一般情况下,客户端优先读取主库,如果主库一定时间没有响应则并发请求到备库,然后等待最先返回的请求。DualService 的应用获得的非常大的成功,业务接近零抖动。

主备模式下还存在一些问题。切换的粒度是集群级别的,切换过程影响大,不能做分区级别切换是因为主备分区不一致;只能提供最终一致性模型,对于一些业务来讲不好写代码逻辑;加上其它因素(索引能力,访问模型)的推动,阿里 HBase 团队基于 HBase 演进了自研的 Lindorm 引擎,提供一种内置的双 Zone 部署模式,其数据复制采用推拉组合的模式,同步效率大大提升;双 Zone 之间的分区由 GlobalMaster 协调,绝大部分时间都是一致的,因此可以实现分区级别切换;Lindorm 提供强一致、Session 一致、最终一致等多级一致性协议,方便用户实现业务逻辑。目前大部分阿里内部业务已经切换到 Lindorm 引擎。

零抖动是我们追求的最高境界,但必须认识到导致毛刺的来源可以说无处不在,解决问题的前提是定位问题,对每一个毛刺给出解释既是用户的诉求也是能力的体现。阿里 HBase 开发了全链路 Trace,从客户端、网络、服务器全链路监控请求,丰富详尽的 Profiling 将请求的路径、资源访问、耗时等轨迹进行展示,帮助研发人员快速定位问题。

总结

本文介绍了阿里 HBase 在高可用上的一些实践经验,结尾之处与大家分享一些看可用性建设上的思考,抛砖引玉希望欢迎大家讨论。

从设计原则上

  • 1 面向用户的可用性设计,在影响面、影响时间、一致性上进行权衡
    MTTF 和 MTTR 是一类衡量指标,但这些指标好不一定满足用户期望,这些指标是面向系统本身而不是用户的。
  • 2 面向失败设计,你所依赖的组件总是会失败
    千万不要假设你依赖的组件不会失败,比如你确信 HDFS 不会丢数据,然后写了一个状态机。但实际上如果多个 DN 同时宕机数据就是会丢失,此时可能你的状态机永远陷入混乱无法推进。再小概率的事件总是会发生,对中标的用户来讲这就是 100%。

从实现过程上

  • 完善的监控体系
    监控是基础保障,是最先需要投入力量的地方。100% 涵盖故障报警,先于用户发现问题是监控的第一任务。其次监控需要尽可能详细,数据展示友好,可以极大的提高问题定位能力。
  • 基于隔离的冗余
    冗余是可用性上治本的方法,遇到未知问题,单集群非常难保障 SLA。所以只要不差钱,一定至少来一套主备。
  • 精细的资源控制
    系统的异常往往是因为资源使用的失控,对 CPU、内存、IO 的精细控制是内核高速稳定运行的关键。需要投入大量的研发资源去迭代。
  • 系统自我保护能力
    在请求过载的情况下,系统应该具备类如 Quota 这样的自我保护能力,防止雪崩发生。系统应该能识别一些异常的请求,进行限制或者拒绝。
  • Trace 能力
    实时跟踪请求轨迹是排查问题的利器,需要把 Profiling 做到尽量详细

阿里云双 11 领亿元补贴,拼手气抽 iPhone 11 Pro、卫衣等好礼,点此参与:http://t.cn/Ai1hLLJT


本文作者:Roin123

阅读原文

本文为云栖社区原创内容,未经允许不得转载。

正文完
 0

阿里HBase高可用8年抗战回忆录

40次阅读

共计 7855 个字符,预计需要花费 20 分钟才能阅读完成。

2017 年开始阿里 HBase 走向公有云,我们有计划的在逐步将阿里内部的高可用技术提供给外部客户,目前已经上线了同城主备,将作为我们后续高可用能力发展的一个基础平台。本文分四个部分回顾阿里 HBase 在高可用方面的发展:大集群、MTTF&MTTR、容灾、极致体验,希望能给大家带来一些共鸣和思考。

大集群

一个业务一个集群在初期很简便,但随着业务增多会加重运维负担,更重要的是无法有效利用资源。首先每一个集群都要有 Zookeeper、Master、NameNode 这三种角色,固定的消耗 3 台机器。其次有些业务重计算轻存储,有些业务重存储轻计算,分离模式无法削峰填谷。因此从 2013 年开始阿里 HBase 就走向了大集群模式,单集群节点规模达到 700+。

隔离性是大集群的关键难题。保障 A 业务异常流量不会冲击到 B 业务,是非常重要的能力,否则用户可能拒绝大集群模式。阿里 HBase 引入了分组概念“group”,其核心思想为:共享存储、隔离计算

如上图所示,一个集群内部被划分成多个分组,一个分组至少包含一台服务器,一个服务器同一时间只能属于一个分组,但是允许服务器在分组之间进行转移,也就是分组本身是可以扩容和缩容的。一张表只能部署在一个分组上,可以转移表到其它的分组。可以看到,表 T1 读写经过的 RegionServer 和表 T2 读写经过的 RegionServer 是完全隔离的,因此在 CPU、内存上都物理隔离,但是下层使用的 HDFS 文件系统是共享的,因此多个业务可以共享一个大的存储池子,充分提升存储利用率。开源社区在 HBase2.0 版本上引入了 RegionServerGroup。

坏盘对共享存储的冲击:由于 HDFS 机制上的特点,每一个 Block 的写入会随机选择 3 个节点作为 Pipeline,如果某一台机器出现了坏盘,那么这个坏盘可能出现在多个 Pipeline 中,造成单点故障全局抖动。现实场景中就是一块盘坏,同一时间影响到几十个客户给你发信息打电话!特别如果慢盘、坏盘不及时处理,最终可能导致写入阻塞。阿里 HBase 目前规模在 1 万 + 台机器,每周大概有 22 次磁盘损坏问题。我们在解决这个问题上做了两件事,第一是缩短影响时间,对慢盘、坏盘进行监控报警,提供自动化处理平台。第二是在软件上规避单点坏盘对系统的影响,在写 HDFS 的时候并发的写三个副本,只要两个副本成功就算成功,如果第三个副本超时则将其放弃。另外如果系统发现写 WAL 异常(副本数少于 3)会自动滚动产生一个新的日志文件(重新选择 pipeline,大概率规避坏点)。最后 HDFS 自身在高版本也具备识别坏盘和自动剔除的能力。

客户端连接对 Zookeeper 的冲击:客户端访问 hbase 会和 Zookeeper 建立长连接,HBase 自身的 RegionServer 也会和 Zookeeper 建立长连接。大集群意味着大量业务,大量客户端的链接,在异常情况下客户端的链接过多会影响 RegionServer 与 Zookeeper 的心跳,导致宕机。我们在这里的应对首先是对单个 IP 的链接数进行了限制,其次提供了一种分离客户端与服务端链接的方案 HBASE-20159

MTTF&MTTR

稳定性是生命线,随着阿里业务的发展,HBase 逐步扩大在线场景的支持,对稳定性的要求是一年更比一年高。衡量系统可靠性的常用指标是 MTTF(平均失效时间)和 MTTR(平均恢复时间)

MTTF(mean time to failure)

造成系统失效的来源有:
硬件失效,比如坏盘、网卡损坏、机器宕机等
自身缺陷,一般指程序自身的 bug 或者性能瓶颈
运维故障,由于不合理的操作导致的故障
服务过载,突发热点、超大的对象、过滤大量数据的请求
依赖失效,依赖的 HDFS、Zookeeper 组件出现不可用导致 HBase 进程退出

下面我介绍一下阿里云 HBase 在稳定性上遇到的几个代表性问题:(注:慢盘、坏盘的问题已经在大集群一节中涉及,这里不再重复)

  • 周期性的 FGC 导致进程退出

在支持菜鸟物流详情业务的时候,我们发现机器大概每隔两个月就会 abort 一次,因为内存碎片化问题导致 Promotion Fail,进而引发 FGC。由于我们使用的内存规格比较大,所以一次 FGC 的停顿时间超过了与 Zookeeper 的心跳,导致 ZK session expired,HBase 进程自杀。我们定位问题是由于 BlockCache 引起的,由于编码压缩的存在,内存中的 block 大小是不一致的,缓存的换入换出行为会逐步的切割内存为非常小的碎片。我们开发了 BucketCache,很好的解决了内存碎片化的问题,然后进一步发展了 SharedBucketCache,使得从 BlockCache 里面反序列化出来的对象可以被共享复用,减少运行时对象的创建,从而彻底的解决了 FGC 的问题。

  • 写入 HDFS 失败导致进程退出

HBase 依赖俩大外部组件,Zookeeper 和 HDFS。Zookeeper 从架构设计上就是高可用的,HDFS 也支持 HA 的部署模式。当我们假设一个组件是可靠的,然后基于这个假设去写代码,就会产生隐患。因为这个“可靠的”组件会失效,HBase 在处理这种异常时非常暴力,立即执行自杀(因为发生了不可能的事情),寄希望于通过 Failover 来转移恢复。有时 HDFS 可能只是暂时的不可用,比如部分 Block 没有上报而进入保护模式,短暂的网络抖动等,如果 HBase 因此大面积重启,会把本来 10 分钟的影响扩大到小时级别。我们在这个问题上的方案是优化异常处理,对于可以规避的问题直接处理掉,对于无法规避的异常进行重试 & 等待。

  • 并发大查询导致机器停摆

HBase 的大查询,通常指那些带有 Filter 的 Scan,在 RegionServer 端读取和过滤大量的数据块。如果读取的数据经常不在缓存,则很容易造成 IO 过载;如果读取的数据大多在缓存中,则很容易因为解压、序列化等操作造成 CPU 过载;总之当有几十个这样的大请求并发的在服务器端执行时,服务器 load 会迅速飙升,系统响应变慢甚至表现的像卡住了。这里我们研发了大请求的监控和限制,当一个请求消耗资源超过一定阈值就会被标记为大请求,日志会记录。一个服务器允许的并发大请求存在上限,如果超过这个上限,后来的大请求就会被限速。如果一个请求在服务器上运行了很久都没有结束,但客户端已经判断超时,那么系统会主动中断掉这个大请求。该功能的上线解决了支付宝账单系统因为热点查询而导致的性能抖动问题。

  • 大分区 Split 缓慢

在线上我们偶尔会遇到某个分区的数量在几十 GB 到几个 TB,一般都是由于分区不合理,然后又在短时间内灌入了大量的数据。这种分区不但数据量大,还经常文件数量超级多,当有读落在这个分区时,一定会是一个大请求,如果不及时分裂成更小的分区就会造成严重影响。这个分裂的过程非常慢,HBase 只能从 1 个分区分裂为 2 个分区,并且要等待执行一轮 Compaction 才能进行下一轮分裂。假设分区大小 1TB,那么分裂成小于 10GB 的 128 个分区需要分裂 7 轮,每一轮要执行一次 Compaction(读取 1TB 数据,写出 1TB 数据),而且一个分区的 Compaction 只能由一台机器执行,所以第一轮最多只有 2 台机器参与,第二轮 4 台,第三轮 8 台。。。,并且实际中需要人为干预 balance。整个过程做下来超过 10 小时,这还是假设没有新数据写入,系统负载正常。面对这个问题我们设计了“级联分裂”,可以不执行 Compaction 就进入下一次分裂,先快速的把分区拆分完成,然后一把执行 Compaction。

前面讲的都是点,关于如何解决某个顽疾。导致系统失效的情况是多种多样的,特别一次故障中可能交叉着多个问题,排查起来异常困难。现代医学指出医院应当更多投入预防而不是治疗,加强体检,鼓励早就医。早一步也许就是个感冒,晚一步也许就变成了癌症。这也适用于分布式系统,因为系统的复杂性和自愈能力,一些小的问题不会立即造成不可用,比如内存泄漏、Compaction 积压、队列积压等,但终将在某一刻引发雪崩。应对这种问题,我们提出了“健康诊断”系统,用来预警那些暂时还没有使系统失效,但明显超过正常阈值的指标。“健康诊断”系统帮助我们拦截了大量的异常 case,也在不停的演进其诊断智能。

MTTR(mean time to repair)

百密终有一疏,系统总是会失效,特别的像宕机这种 Case 是低概率但一定会发生的事件。我们要做的是去容忍,降低影响面,加速恢复时间。HBase 是一个可自愈的系统,单个节点宕机触发 Failover,由存活的其它节点来接管分区服务,在分区对外服务之前,必须首先通过回放日志来保证数据读写一致性。整个过程主要包括 Split Log、Assign Region、Replay Log 三个步骤。hbase 的计算节点是 0 冗余,所以一个节点宕机,其内存中的状态必须全部回放,这个内存一般可以认为在 10GB~20GB 左右。我们假设整个集群的数据回放能力是 R GB/s,单个节点宕机需要恢复 M GB 的数据,那么宕机 N 个节点就需要 M * N / R 秒,这里表达的一个信息是:如果 R 不足够大,那么宕机越多,恢复时间越不可控,那么影响 R 的因素就至关重要,在 Split Log、Assign Region、Replay Log 三个过程中,通常 Split Log、Assign Region 的扩展性存在问题,核心在于其依赖单点。Split Log 是把 WAL 文件按分区拆分成小的文件,这个过程中需要创建大量的新文件,这个工作只能由一台 NameNode 来完成,并且其效率也并不高。Assign Region 是由 HBase Master 来管理,同样是一个单点。阿里 HBase 在 Failover 方面的核心优化是采用了全新的 MTTR2 架构,取消了 Split Log 这一步骤,在 Assign Region 上也做了优先 Meta 分区、Bulk Assign、超时优化等多项优化措施,相比社区的 Failover 效率提升 200% 以上

从客户角度看故障,是 2 分钟的流量跌零可怕还是 10 分钟的流量下降 5% 可怕?我想可能是前者。由于客户端的线程池资源有限,HBase 的单机宕机恢复过程可能造成业务侧的流量大跌,因为线程都阻塞在访问异常机器上了,2% 的机器不可用造成业务流量下跌 90% 是很难接受的。我们在客户端开发了一种 Fast Fail 的机制,可以主动发现异常服务器,并快速拒绝发往这个服务器的请求,从而释放线程资源,不影响其它分区服务器的访问。项目名称叫做 DeadServerDetective

容灾

容灾是重大事故下的求生机制,比如地震、海啸等自然灾害造成毁灭性打击,比如软件变更等造成完全不可控的恢复时间,比如断网造成服务瘫痪、恢复时间未知。从现实经验来看,自然灾害在一个人的一生中都难遇到,断网一般是一个年级别的事件,而软件变更引发的问题可能是月级别的。软件变更是对运维能力、内核能力、测试能力等全方位的考验,变更过程的操作可能出错,变更的新版本可能存在未知 Bug。另一个方面为了不断满足业务的需求又需要加速内核迭代,产生更多的变更。

容灾的本质是基于隔离的冗余,要求在资源层面物理隔离、软件层面版本隔离、运维层面操作隔离等,冗余的服务之间保持最小的关联性,在灾难发生时至少有一个副本存活。阿里 HBase 在几年前开始推进同城主备、异地多活,目前 99% 的集群至少有一个备集群,主备集群是 HBase 可以支持在线业务的一个强保障。主备模式下的两个核心问题是数据复制和流量切换

数据复制

选择什么样的复制方式,是同步复制还是异步复制,是否要保序?主要取决于业务对系统的需求,有些要求强一致,有些要求 session 一致,有些可以接受最终一致。占在 HBase 的角度上,我们服务的大量业务在灾难场景下是可以接受最终一致性的(我们也研发了同步复制机制,但只有极少的场景),因此本文主要专注在异步复制的讨论上。很长一段时间我们采用社区的异步复制机制(HBase Replication),这是 HBase 内置的同步机制。

同步延迟的根因定位是第一个难题,因为同步链路涉及发送方、通道、接受方 3 个部分,排查起来有难度。我们增强了同步相关的监控和报警。

热点容易引发同步延迟是第二个难题。HBase Replication 采用推的方式进行复制,读取 WAL 日志然后进行转发,发送线程和 HBase 写入引擎是在同一台 RegionServer 的同一个进程里。当某台 RegionServer 写入热点时,就需要更多的发送能力,但写入热点本身就挤占了更多的系统资源,写入和同步资源争抢。阿里 HBase 做了两个方面的优化,第一提高同步性能,减少单位 MB 同步的资源消耗;第二研发了远程消耗器,使其它空闲的机器可以协助热点机器同步日志。

资源需求、迭代方式的不匹配是第三个难题。数据复制本身是不需要磁盘 IO 的,只消耗带宽和 CPU,而 HBase 对磁盘 IO 有重要依赖;数据复制的 worker 本质上是无状态的,重启不是问题,可以断点续传,而 HBase 是有状态的,必须先转移分区再重启,否则会触发 Failover。一个轻量级的同步组件和重量级的存储引擎强耦合在一起,同步组件的每一次迭代升级必须同时重启 HBase。一个重启就可以解决的同步问题,因为同时要重启 hbase 而影响线上读写。一个扩容 CPU 或者总带宽的问题被放大到要扩容 hbase 整体。

综上所述,阿里 HBase 最终将同步组件剥离了出来作为一个独立的服务来建设,解决了热点和耦合的问题,在云上这一服务叫做 BDS Replication。随着异地多活的发展,集群之间的数据同步关系开始变得复杂,为此我们开发了一个关于拓扑关系和链路同步延迟的监控,并且在类环形的拓扑关系中优化了数据的重复发送问题。

流量切换

在具备主备集群的前提下,灾难期间需要快速的把业务流量切换到备份集群。阿里 HBase 改造了 HBase 客户端,流量的切换发生在客户端内部,通过高可用的通道将切换命令发送给客户端,客户端会关闭旧的链接,打开与备集群的链接,然后重试请求。

切换瞬间对 Meta 服务的冲击:hbase 客户端首次访问一个分区前需要请求 Meta 服务来获取分区的地址,切换瞬间所有客户端并发的访问 Meta 服务,现实中并发可能在几十万甚至更多造成服务过载,请求超时后客户端又再次重试,造成服务器一直做无用功,切换一直无法成功。针对这个问题我们改造了 Meta 表的缓存机制,极大地提高了 Meta 表的吞吐能力,可以应对百万级别的请求。同时在运维上隔离了 Meta 分区与数据分区,防止相互影响。

从一键切换走向自动切换。一键切换还是要依赖报警系统和人工操作,现实中至少也要分钟级别才能响应,如果是晚上可能要 10 分钟以上。阿里 HBase 在演进自动切换过程中有两个思路,最早是通过增加一个第三方仲裁,实时的给每一个系统打健康分数,当系统健康分低于一个阈值,并且其备库是健康的情况下,自动执行切换命令。这个仲裁系统还是比价复杂的,首先其部署上要保持网络独立,其次其自身必须是高可靠的,最后健康分的正确性需要保证。仲裁系统的健康判断是从服务器视角出发的,但从客户端角度来讲,有些时候服务器虽然活着但是已经不正常工作了,可能持续的 FGC,也可能出现了持续网络抖动。所以第二个思路是在客户端进行自动切换,客户端通过失败率或其它规则来判定可用性,超过一定阈值则执行切换。

极致体验

在风控和推荐场景下,请求的 RT 越低,业务在单位时间内可以应用的规则就越多,分析就越准确。要求存储引擎高并发、低延迟、低毛刺,要高速且平稳的运行。阿里 HBase 团队在内核上研发 CCSMAP 优化写入缓存,SharedBucketCache 优化读取缓存,IndexEncoding 优化块内搜索,加上无锁队列、协程、ThreadLocal Counter 等等技术,再结合阿里 JDK 团队的 ZGC 垃圾回收算法,在线上做到了单集群 P999 延迟小于 15ms。另一个角度上,风控和推荐等场景并不要求强一致,其中有一些数据是离线导入的只读数据,所以只要延迟不大,可以接受读取多个副本。如果主备两个副本之间请求毛刺是独立事件,那么理论上同时访问主备可以把毛刺率下降一个数量级。我们基于这一点,利用现有的主备架构,研发了 DualService,支持客户端并行的访问主备集群。在一般情况下,客户端优先读取主库,如果主库一定时间没有响应则并发请求到备库,然后等待最先返回的请求。DualService 的应用获得的非常大的成功,业务接近零抖动。

主备模式下还存在一些问题。切换的粒度是集群级别的,切换过程影响大,不能做分区级别切换是因为主备分区不一致;只能提供最终一致性模型,对于一些业务来讲不好写代码逻辑;加上其它因素(索引能力,访问模型)的推动,阿里 HBase 团队基于 HBase 演进了自研的 Lindorm 引擎,提供一种内置的双 Zone 部署模式,其数据复制采用推拉组合的模式,同步效率大大提升;双 Zone 之间的分区由 GlobalMaster 协调,绝大部分时间都是一致的,因此可以实现分区级别切换;Lindorm 提供强一致、Session 一致、最终一致等多级一致性协议,方便用户实现业务逻辑。目前大部分阿里内部业务已经切换到 Lindorm 引擎。

零抖动是我们追求的最高境界,但必须认识到导致毛刺的来源可以说无处不在,解决问题的前提是定位问题,对每一个毛刺给出解释既是用户的诉求也是能力的体现。阿里 HBase 开发了全链路 Trace,从客户端、网络、服务器全链路监控请求,丰富详尽的 Profiling 将请求的路径、资源访问、耗时等轨迹进行展示,帮助研发人员快速定位问题。

总结

本文介绍了阿里 HBase 在高可用上的一些实践经验,结尾之处与大家分享一些看可用性建设上的思考,抛砖引玉希望欢迎大家讨论。

从设计原则上

  • 1 面向用户的可用性设计,在影响面、影响时间、一致性上进行权衡
    MTTF 和 MTTR 是一类衡量指标,但这些指标好不一定满足用户期望,这些指标是面向系统本身而不是用户的。
  • 2 面向失败设计,你所依赖的组件总是会失败
    千万不要假设你依赖的组件不会失败,比如你确信 HDFS 不会丢数据,然后写了一个状态机。但实际上如果多个 DN 同时宕机数据就是会丢失,此时可能你的状态机永远陷入混乱无法推进。再小概率的事件总是会发生,对中标的用户来讲这就是 100%。

从实现过程上

  • 完善的监控体系
    监控是基础保障,是最先需要投入力量的地方。100% 涵盖故障报警,先于用户发现问题是监控的第一任务。其次监控需要尽可能详细,数据展示友好,可以极大的提高问题定位能力。
  • 基于隔离的冗余
    冗余是可用性上治本的方法,遇到未知问题,单集群非常难保障 SLA。所以只要不差钱,一定至少来一套主备。
  • 精细的资源控制
    系统的异常往往是因为资源使用的失控,对 CPU、内存、IO 的精细控制是内核高速稳定运行的关键。需要投入大量的研发资源去迭代。
  • 系统自我保护能力
    在请求过载的情况下,系统应该具备类如 Quota 这样的自我保护能力,防止雪崩发生。系统应该能识别一些异常的请求,进行限制或者拒绝。
  • Trace 能力
    实时跟踪请求轨迹是排查问题的利器,需要把 Profiling 做到尽量详细

阿里云双 11 领亿元补贴,拼手气抽 iPhone 11 Pro、卫衣等好礼,点此参与:http://t.cn/Ai1hLLJT


本文作者:Roin123

阅读原文

本文为云栖社区原创内容,未经允许不得转载。

正文完
 0