解决单点故障 - 有状态服务的高可用

  导语 本文介绍如何用Raft协议做有状态服务的高可用,以及提升性能。

  高可用HA(High Availability)是分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计减少系统不能提供服务的时间,解决单点故障问题。本文以Ozone举例,如何用Raft协议做有状态服务的高可用,以及提升性能。

  1. Ozone背景介绍

  1.1 为什么做Ozone

  Ozone是继HDFS的下一代对象、文件混合存储系统。做Ozone目的是,第一,解决HDFS扩展性问题,HDFS因为中心节点NameNode的性能瓶颈,导致集群达到万台时无法再扩展。业界已有的方案有两种,但成本都很高:字节跳动将Java的NameNode用C++重写,而京东有专门的JVM团队优化Java的内存垃圾回收;第二,大数据生态缺失对象存储。HDFS社区为了解决这两个问题,孵化出了Ozone项目。

  1.2 Ozone架构

  Ozone架构如图所示,Ozone有三部分构成,Datanode、SCM和OM,Datanode负责存数据,数据以128 MB Block为单位存储;SCM和OM都是中心节点,SCM管理Datanode,以及Block到Datanode的映射。OM管理Key或者文件到Block的映射。

  1.3 解决HDFS扩展性问题

  Ozone的架构可解决HDFS扩展性问题。HDFS因为中心节点NameNode的单点瓶颈,导致HDFS集群达到万台时无法再扩展。首先在内存上,NameNode的所有元数据均存在内存,内存和GC压力大,而Ozone将元数据存在RocksDB,并部分Cache在内存里,大大减轻内存压力。其次,NameNode包含所有元数据,而Ozone将NameNode拆分为两个服务OM、SCM,并将元数据分三级,OM、SCM、Datanode各存部分元数据,进一步减轻中心节点压力。最后HDFS的Datanode需要将所有128MB的Block向NameNode汇报,造成汇报风暴;而Ozone的Datanode只需要将所有5GB的Container向SCM汇报。

  

  2. OM高可用架构演化

  2.1 OM为什么做高可用

  Ozone诞生是为了替换HDFS,而最大的HDFS集群有上万个节点,同时跑的任务4K多。OM提供Key或文件到Block的映射,如果OM进程发生故障,4K多任务全部失败,如果机器故障,万台节点的元数据都会丢失。因此需要做OM的高可用。

  2.2 主从式高可用

  高可用有两种方式:多活和主从。多活采用Load Balance方式,多个节点同时提供服务,适用于无状态服务,或者服务之间状态天然隔离,无需相互可见,多用于REST服务。但对于Ozone这种存储系统,状态多、状态更新频繁、且需相互可见,只能采用主从式高可用。

  2.3 已有主从架构

  目前已有的主从方案,有HDFS的NameNode,借用ZooKeeper、JournalNode、ZKFC实现,ZooKeeper简称ZK,JournalNode简称JN。如果OM借鉴NameNode,则有两个OM,分别处于active、standby状态,active OM对外服务,standby OM作为备份。3个JN组成一致性存储系统, active OM将操作log写到JN集群,standby OM从JN集群同步操作log并执行,以便于跟上active OM状态。3个ZK和2个zkfc用于选主,zkfc检测到active OM发生故障时,借助zk选主,将standby OM切换为active OM,对外提供服务。这套架构至少10个进程,维护困难,逻辑复杂,如何简化?

  2.4 简化主从架构

  首先3个ZK可以和3个JN合并,因为JN用一致性协议paxos写操作Log,而ZK用一致性协议zab选主,可将两套一致性协议合并为一套,合并后剩下7个进程:3个JN、2个ZKFC、2个OM。3个JN里会选出一个主,2个om也会选出来一个主,可以将两个主合并成一个,这样就可将JN做到om里面,产生只有3个进程的om ha。

  2.5 选择分布式一致性协议

  3个OM的HA仍然依赖一致性协议实现写一致性和选主,目前主流分布式强一致性协议有Paxos、Zab、Raft。Paxos论文细节少,实现困难,虽然Paxos支持乱序提交log,而Zab、Raft只能顺序提交log, paxos写性能优于zab、raft,但multi-zab、multi-raft性能也可达到paxos的水平。而ZAB协议,和Raft非常相似,差异仅在于选主等细节,但论文细节少。而Raft协议有258页论文,细节充足,理论完备,且开源项目众多,如Etcd、TiKv、Ratis、SofaJraft等,从语言、开放性、活跃度和易用性考虑,选择Ratis。OM HA的架构是三个OM,接下来看下三个OM如何保证一致性和可用性。

  

  2.6 写一致性

  OM HA需要起若干个OM节点,只有一个主节点,也叫leader节点,其他都是从节点,也叫follower节点。

  首先看下3个OM的HA如何保证写一致性,假设初始三个OM的DB里变量a的值都是1,客户端发送a+2的请求给leader OM1,OM1首先将这个请求写到wal log,也就是raft log,然后将这个请求发给OM2、OM3,OM2、OM3也将这个请求写到raft log里,如果3个节点里的2个节点成功将a+2写raft log,OM1就执行a+2请求,将DB里a的值改为3,并回复客户端成功。这里OM1没有等OM2和OM3执行完a+2再返回客户端,因为OM2和OM3已将请求a+2记在raft log里,可以后续从raft log里读出请求并执行。

  

  2.7 读一致性

  可以看出,写数据是通过leader向follower同步,所以leader有最新状态,所以读数据时从leader即可读到最新数据,不需要像multi-paxos要从大多数节点读。

  3. 提升可用性

  为了提高可用性,首先分析进程的生命周期,进程的状态分为死和活,死又有宕机和升级两种情况,宕机分为主节点宕机、主从节点全宕机。针对每种case都有相应的方法提高可用性。首先只有主节点宕机时,可通过自动主从切换,选出新leader对外服务。如果主从节点全宕机,通过listener的备份数据,恢复服务。而在升级时,通过滚动升级,不中断服务。在进程活着的时候,则通过稳定主节点,避免主从频繁切换,导致切换期间服务不可用。

  

  3.1 解决主节点宕机

  首先看主节点宕机的解决办法,宕机分两种情况,如果OM进程宕机,则无法提供服务;如果机器宕机,且不可恢复,会造成数据丢失,因此HA需要保证进程或机器宕机时,服务正常,数据不丢。OM HA允许少数节点故障,假设leader OM1发生故障,OM2会当选为新leader,对外服务,保证服务正常。因为OM HA写数据时,大多数写成功才算成功,所以少数节点宕机时,剩下的节点一定有最新的状态,最新状态的节点会当选为新leader,保证不丢数据。但现在只有两个节点,不能再有节点挂掉,为了恢复集群的容灾能力,需要添加新节点OM4。但刚添加的OM4,没有最新的DB状态,提供不了容灾能力,已有的做法,leader会将raft log一条条发给OM4,OM4再执行,速度很慢,因此为了让OM4快速跟上Leader的状态,Leader会将自己DB做个snapshot发给OM4,OM4利用snapshot快速跟上leader状态,并提供容灾能力。

  

  3.2 解决主从节点全宕机

  如果3个主从OM全挂了,且数据不可恢复怎么办?第一种方案是添加节点OM4、OM5,3个OM变为5个OM,允许更多节点挂掉,但吞吐量下降9%左右,原因有两个:第一,leader负载更重,需要向四个follower同步log;第二,5个节点后,需要3个节点都提交成功才算成功。第二种做法是增量备份DB,但如果备份太频繁,备份时需要锁表,影响吞吐量,如果备份不频繁,会丢很多数据。那如何既能既能提高容灾能力,又不影响集群吞吐量?考虑到raft log本身就是DB的增量备份,因此直接利用已有的log同步机制,增量同步log即可,而增量同步raft log机制是已有的,直接利用即可。做法是在follower后添加2个listener, 增量同步follower已提交的log ,为了不影响集群性能,listener不参与提交log、选举。因为listener不参与提交log,因此可将listener部署在更远的位置,提高容灾能力。但listener也有个缺陷,因为它不参与提交log,所以它的log可能落后于leader,在leader和follower全挂的情况下,listener可能会丢几分钟的数据,对于非交易类等大部分场景是可接受的。

  

  3.3 滚动升级

  最后一个进程停止的情况就是升级,现在三个OM,如果将三个OM停机升级,那就无法做到不中断服务,因此需要滚动升级。做法是先将follower OM2停机升级,此时OM1作为leader仍然对外提供服务。然后将leader角色从OM1切换到OM2,此时OM2作为leader对外提供服务,如果OM2能稳定服务,再将OM1、OM3逐一升级。但如果OM2无法正常服务,需要回滚,此时再将leader从OM2切回OM1,然后回滚OM2即可。在此流程中,只有将leader从OM1切换到OM2过程中,无法提供服务,而该时间间隔为2秒左右,客户端自动重试,用户无感知。

  该流程难点是如何将Leader快速从OM1切换到OM2,图里OM2的log比OM1的log旧,根据之前介绍的选举机制,只有log最新才能当选为leader,因此OM2不具备当leader的条件。所以leader OM1需要将最新log a+2发给OM2。另外如果客户端一直向leader OM1发送写请求, OM1会一直有新log产生,OM2的log可能一直落后于 OM1,无法成为leader。所以在切换leader过程中,leader OM1阻塞客户端写请求。

  到现在解决的都是进程停止运行时,如何提高可用性,但进程一直运行时,也有不可用的问题,比如leader频繁切换,切换期间服务也是不可用状态,所以需要稳定leader。

  3.4 稳定leader

  首先看下leader如何选举产生的,follower在2s后收不到leader心跳,因为超时向其他节点发送选举请求,收到大多数赞成票才能当选为leader,而其他节点收到选举请求后,会依据对方RaftLog是否最新来决定是否给对方投赞成票。

  上述方案有两个问题,第一,没办法指定leader的位置,OM和SCM都采用主从方案,如果OM和SCM leader距离远,比如在不同数据中心,OM、SCM通信延时高。第二,每月因为选主,不可用时间38min,51次超时选主,每次选主耗时45秒。

  针对无法指定leader位置的问题,我采用优先级方案,高优先级的节点更容易成为leader,这样可以让OM、SCM leader分布的更近,经测试若OM、SCM leader在同一台机器,比在不同机器,吞吐量提升14%。

  针对每月38min的不可用问题,采用减少选举次数、缩短选举时间。为了减少选举次数,在我收到对方投票请求后,我检查自己近期是否收到leader心跳,如果收到,就拒绝给对方投票,这是为了防止对方网络抖动,导致整个集群频繁切换leader,加入这项优化后每月51次选举降低为2次。为了缩短选举时间,让更可能成为leader的高优先级节点率先发起选举,可将选举时间从45秒降为1.5秒。

  4. 性能优化

  接下来看HA的性能优化,对于存储系统,关注读、写性能;优化指标有两个,延时和吞吐。

  首先看下读,Raftis实现的HA,只需从leader读,所以HA对读性能没有影响。再看下写,首先在延时上,使用HA之前,只需要写一个节点,且只需要写DB。开启HA后,至少写两个节点,且要写Raft Log和DB,因此写延时增加,优化方向是利用落盘的raft log降低写延时。那是否有必要优化写吞吐?对于大数据场景,读写比例9比1,写的比例很小,因此暂时没有优化的必要,但也可以优化,可以采用分表的方式,多个raft group同时写不同的表,提高并行度。

  

  优化性能首先要定位性能瓶颈,定位性能瓶颈一般采用如下方法,如果CPU高,则采用Perf查看CPU热点。如果延时高,首先用tcpdump粗略排查,tcpdump可查看每个请求的执行时间。但tcpdump无法深入到进程内部排查。如果需要深入进程内部排查每个阶段执行时间,则有两种方式:metric和opentracing。Metric适用于统计多个请求每个阶段执行时间的分布。而opentracing则适用于分析单个请求每个阶段的执行耗时。

  

  4.1 写性能优化

  首先看下写延时的问题,启用HA后,写延时由48ms增加到107ms,通过使用上文定位性能瓶颈的方法,定位到因为刷盘导致写延时高,一次写数据有两次刷盘,raft log和DB。但只要raft log及时刷盘即可,即使DB因为未及时刷盘而丢数据,也可以从Raft log恢复数据,所以OM1写完cache立即返回客户端。

  那如果在Cache数据落盘前,OM1 Crash了如何恢复数据?可以在DB里记录已落盘数据对应的Raft log的Index。现在有两条raft log, a + 1的index是0,a + 2的index是1。DB里的值是1,对应的raft log是a + 1,所以DB里记录index 0,OM1重启后,检查DB,查index是0,知道log a + 1对应的数据落盘了,而log a + 2对应数据未落盘,所以OM1重新执行a + 2再写到cache里。优化后写延时从107ms降低到71ms。

  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值