Kafka 日志复制协议探索

百度水温 2017-05-04 04:52:49
日志复制模式: 大多数投票(Quorums),同步复制队列(ISRs)和状态机模式

kafka高可用的核心思想是建立在日志同步的机制上. 日志同步在分布式数据系统中是最常见同时极为重要环节之一,市面上有许多已实现的算法供参考. 其中状态机模型常被分布式系统用于实现日志复制来达到分布式系统的状态一致性

日志同步机制既要保证在分布式系统中数据的一致性,也要保证数据输入的顺序性. 当然有许多方法能实现这些功能,其中最为简单高效的处理方式是集群中选出一名leader来处理数据写入的顺序性,只要leader状态是存活的,follower副本只需按leader的写入顺序复制这些数据以达到集群内数据的一致性.

通常情况,只要leader不宕机我们是不用关心follower同步的问题,写入只发生在leader节点,副本节点只需按序复制leader日志即可. 当leader服务宕机,此时集群需要从follower节点群中选择新的leader节点, 此时这些follower节点可能存在未能同步到leader最新日志或者服务同时宕机的情形,但系统必须保证能选出具有最新日志数据的follower节点作为leader.事务日志复制算法最基本保证要做到:当客户端提交事务返回成功ack时,此时若leader宕机不可用,新选举的leader事务日志里必须包含客户端提交过的事务日志. 这里有个折中的情况:leader需要等待大多数follower返回日志复制ack信息,才能对外发布该事务消息,一定程度上增大了事务延迟,但有利的方面是当leader不可用时,更多的follower节点可以被选作leader,能加快选举的过程降低系统不可用时间.

大多数投票机制(majority vote):需要同时满足事务提交确认的数量以及日志副本数量能够进行leader选举比较的数目关系. 一个常见的解决方案是使用大多数投票(majority vote)选举来满足提交决策及leader选举. 这不是kafka内部采用的机制,但是值得一起探索和深度理解下这种权衡的艺术. 首先假设我们有2f+1个副本数, leader在发布事务消息之前必须接收到f+1个副本同步ack的信息,同时如果发生新的leader选举至少保证有f+1个副本节点完成日志同步并从同步完成的副本中选出新的leader节点,在不超过f个副本节点失败的情况下,新的leader能保证不会丢失提交的消息. 这是因为在f+1的副本中,理论上确保至少有一个副本包含全部提交的消息,这个副本的日志拥有最完全的消息日志因此会被选作新的leader对外提供服务. 详细的细节可以从每个具体算法实现上得到佐证(如如何精准定义在leader失败或者集群数目变化时怎么保证日志的完整复制,确保日志的一致性),本文不作赘述.

大多数投票方案(majority vote)具有以下优势:事务延迟取决于最快的多数(f+1)设备. 例如,如果副本数量(复制因子)设置为3,延迟时间取决于最快的那台slaver节点而不是性能差的slaver节点.

使用Quorums模型的一致性算法家族中包括zookeeper的zab协议, raft协议以及viewstamped replication协议. 在众多解决日志复制协议问题的学术论文中,发现kafka的实现更像微软的PacificA主从同步协议.

大多数选举机制(majority vote)也存在自身的弊端:当大多数副本节点不可用或宕机情况下会造成选主失败引起系统不可用. 要容忍1个节点失效或宕机必须满足数据有三个以上的副本数,若要容忍2个节点失效,需要有5个以上的副本数量. 按过往经验,只配置基本的副本数以满足单节点失效对实际系统来讲是远远不够的, 为满足实际需求至少需要5倍的副本数量,由此带来的5x的磁盘开销和1/5th的吞吐量,对大规模数据存储服务将是个严重的问题.这也是quorums常被用于共享集群配置场景(shared cluster configuration)的主要原因 比如zookeeper,但是很少使用在主流的数据存储系统中. 例如HDFS的namenode元数据日志存储的高可用采用大多数选举策略(majority vote),但是hdfs数据分片同步却没有采用如此昂贵的复制机制.
相比大多数投票机制(majority vote), Kafka采取略微不同的选举quorum集的方法. Kafka动态维护着被称为同步队列(ISR)的集合,处于ISR集合内的节点保持着和leader相同水位(hw),只有位列其中的副本节点才有资格被选为新的leader. 写请求只有等到所有ISRs中的副本(partition)节点返回ack才能被认为消息已提交, 当ISR中副本节点数量变化会及时持久化到zookeeper进行存储.

位于ISR中的任何副本节点都有资格成为leader,选举过程简单,逻辑清晰并且开销极低,这也是kafka选用此模型的重要因素;kafka拥有众多的partition数量,而主从同步复制又发生在partition这层逻辑之上,每个partition可能拥有不同副本数(不同topic的partition副本数目由创建时复制因子决定),同时kafka需保证所有partitions的leader节点在集群中的平衡性,此因素极大影响着kafka的性能指标(因为读写数据都发生在leader节点).

在采用ISR模型和f+1个副本数配置下,一个kafka的topic能够容忍最大f个节点失效但是不丢失提交的所有消息, 相比大多数选举(majority vote)方式所需的节点数大幅度减小.
为满足绝大多数使用场景,我们选择了类似PacificA的日志复制方式. 实际上,为容忍f个节点失效,大多数选举(majority vote)策略和ISR方式都需要相同数量的副本节点ack信息才能提交消息(e.g. 为容忍一个节点失败,大多数选举策略需要3个副本和一个slaver的ack信息,ISR方法则需要2个副本节点和一个slaver的ack信息),对比需要相同ack数量下,采用ISR方式需要的副本总数更少,复制带来的集群开销更低. 大多数选举方法(majority vote)优势在于可以绕开最慢设备的ack信息达到事务提交的能力,降低事务提交的延时. 然而,我们认为这种改善的能力可交由客户端用户自己去选择是否堵塞在消息提交过程还是占用更大带宽和磁盘用量(通过降低复制因子来达到目的)是更为值得采用的方案.

另外一个设计上的重要区别:kafka并不需要宕机节点必须从本地数据日志进行恢复. 不同于常见的复制算法,一般复制算法在数据恢复阶段依赖于稳定的存储系统即系统恢复时日志文件不可丢失同时不能有数据上的一致性冲突,出现以上原因主要基于以下的假设,第一,磁盘错误通常在实际操作中会发现存储系统在持久化过程中并不能完全保证数据的完整性,第二,即使不存在硬件级别故障,我们也不希望在每次写请求时执行fsync系统调用来保证数据的一致性,这样会极大降低性能吞吐.kafka同步协议上允许宕机副本重新加入到ISR队列,但进入ISR之前必须重新完整的同步leader数据(re-sync),即使在宕机时意外丢失了未刷盘的日志.

以上是kafka官方文档关于副本复制协议的相关描述. 结合对上文的理解及自己在使用kafka过程中的总结,对于Kafka选择Raft协议可能存在的一些优缺点做些论述:

kafka副本同步是建立在partition级别,如果选raft协议来取代ISR模式,意味着每个partition上需要建立一个与之对应的raft集群,所有Raft集群状态为当前partition独有;当partition数量众多时这种规模的raft集群在管理上存在很大困难. 而Kafka实现的ISR中leader选举由同一个controller来完成,所有涉及partition副本状态的监听也是由controller来处理和进行leader选举,大量partition存在的情况下换成raft协议处理同步,需要管理的raft状态众多. image

Raft选举leader发生条件是由raft协议来保证与节点Term时间有很大关系,可能时间阈值设置不当造成经常的leader切换;一段时间后leader的分布可能会出现严重倾斜情况,集群的吞吐也因此急剧下降;与kafka的ISR实现不同的是,raft要动态调整leader到指定副本节点协议上并没有很好的支持,而在kafka的实现中通过读取metadata,controller很容易调整leader节点到合适的设备上.

副本数量选择上,Raft协议需要遵从2f+1的配置方案,kafka使用上会存在一定的不便性;同时发生f+1不可用时,也不能动态调参的方式让raft集群恢复可用状态;ISR这块相对比较灵活可以通过topic级别的参数(min.insync.replicas)动态调节恢复正常; 按照官方文档描述来看,如果要达到实际生产上HA级别来使用,raft副本数会大大超过ISR的数量,额外带来的磁盘开销和网络吞吐开销很远远超过ISR的方式.

针对客户端不同的需求(acks=1,0,-1)的情况,raft原始协议不能很好解决,针对同一个topic不同业务有不同可靠级别的需求场景;kafka的设计很好的支持了此类混合使用方式;

网络分区出现时(脑裂发生),raft协议带来的partitions不可用性相比ISR方式会更明显和严重性更高.

Raft协议更适合在实例级别的事务日志同步场景,比如mysql的主从同步,其中涉及的是单实例的事务日志进行主从拷贝,简单理解是设备级别的主从关系;而kafka建立在partition之上的主从分布,颗粒度很细;从设备层面来看,存在多重raft集群重叠在设备主机之中,是多读多写的场景,也会极大的影响到性能;然而rocketmq的设计相比kafka在这个层面上更适合raft协议,其使用的是单一事务日志来记录所有消息,保证很好的顺序写的性能.

Raft协议能很好的解决读的负载平衡的问题,避免全部通过leader进行读取,kafka是通过定时扫描重建leader在集群中的平衡关系达到但这个是在集群级别上来做的平衡策略;Raft针对读操作可以在partition级别上进行处理.

Raft协议的ack机制是由集群中最快的f+1个设备确认完成即能提交事务,而kafka本身的配置很难筛选出快速设备配置到ISR队列中达到低延迟的目的,但可通过监控逐渐优化至最佳配置的方案,而Raft协议本身保证了此机制的便捷性.

Raft协议提供了选主功能(leader选举),kafka目前内部众多模块还是依赖zookeeper的leader选举机制,如果选用raft可以移除zookeeper依赖.

市面上选择Raft协议的开源项目大多应用在分布式共享配置的场景中,如etcd,Consul等一般用于注册发现/配置共享等应用类型,应用在大数据同步场景的例子比较少见
...全文
2468 1 打赏 收藏 转发到动态 举报
写回复
用AI写文章
1 条回复
切换为时间正序
请发表友善的回复…
发表回复
twtbgn 2017-05-04
  • 打赏
  • 举报
回复
楼主分析的透彻, 大神

2,408

社区成员

发帖
与我相关
我的任务
社区描述
高性能计算
社区管理员
  • 高性能计算社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧