Kafka学习之路(三)Kafka的高可用

一丶高可用的由来
1.1为何需要Replication

在kafka在0.8以前的版本中,是没有Replication的,一旦某个Broker宕机,则其上有的Partition数据都不可被消费,这与kafka数据持久性及Delicery
Guaeantee的设计目标相悖。同时Producer都不能再将数据存在于这些Partition中.
如果Producer使用同步模式则Producer会在尝试重新发送message,send.max.retries(默认值为3)次后抛出Exception,用户可以选停止发送后续数据也可选择继续选择发送.而前者会照成数据的阻塞,后者会照成很应该发往该Broker的数据的丢失
如果Producer使用异步模式,则Producer会尝试重新发送message.send.max.retries(默认值为3)次记录异常并继续发送后续数据,这回照成数据丢失并且用户只能通过诶之发现该问题,同时,Kafka的Producer并未对异步模式提供callback接口
由此可见,在没有Replication的情况下,一旦某机器宕机或者某个Broker停止工作会造成整个系统的可用性降低,随着集群规模的增加整个集群中出现的该类异常的几率大大增加,因此对于生产系统而言Replication机制的引入非常重要

1.2Leader Election

引入Replication之后,同一个Partition可能会有多个Replica,而这时需要这些Replication之间选出一个Leader.Producer和Consumer只与这个Leader交互,其他Raplica作为Follower从Leader中复制数据.
因为需要保证同一个Partition的多个Replica之间的数据一致性(其中一个宕机后其他Replica必须要能继续服务并且既不能照成数据重复也不能照成数据丢失)如果没有一个Leader,所有Replica都可同时读写/写数据,那就需要保证多个Replica之间相互(NXN跳通路)同步数据,数据的一致性和有序性非常难保证,大大增加了Replication实现的复杂性,同时也增加了出现异常的几率,而引入Leader后,只有Leader负责数据读写,Follower只向Leader顺序Fetch数据(N跳通路),系统更加简单且高效

二丶Kafka HA设计解析
2.1 如何将所有Replica均匀分布到整个集群

为了更好的做负载均衡,kafka尽量将所有的Partition均匀分配到整个集群上,一个典型的部署方式是一个Topic的Partition数量大于Broker的数量,同时为了提高Kafka的容错能力,也需要将同一个Partition的所有Replica都无法工作,也就达不到HA的效果.同时,如果某个Broker宕机了,需要保证它上面的负载可以分配到其他幸存的所有Broker上
Kafka分配Replica的算法如下:
1.将所有Broker(假设共n个Broker)和待分配的Partition
2.将第i个Partion分配到第(i mod n )个Broker上
3.将第i个Partition的第j个Replica分配到第?((i+j)mode n )个Broker上

2.2Data Replication(副本策略)

Kafka的搞可靠性的保障来源与其健壮的副本(replication)策略.

2.2.1消息传递同步策略

Producer在发布消息到某个Partition时,先通过Zookeeper找到该Partition的Leader,然后无论该Topic的Replication
Factor为多少,Producer只将该消息发送到该Partition的leader,Leader会将该消息写入其本地Log
每个Follower都从Leader
pull数据,这种方式上,Follower存储的数据顺序与Leader保持一致,Follower在收到该消息并写入其Log后,向Leader发送ACK,一旦Leader收到了ISR中的所有Replica的ACK,该消息就被认为已经commit了,Leader将增加HW并且向Producer发送ACK
为了提高性能,每个Follower在接收导数据后就立马向Leader发送ACK,而非等到数据写入Log中,因此,对于已经commit的消息,Kafka只能保证它被存于多个Replica的内存中,而不能保证他们被持久化到磁盘中,也就不能完全保证异常发生后该条消息一定能被Consumer消费
Consumer读消息也是从leader读取,只有被commit过得消息才会暴露给Consumer Kafka
Replication的数据流如下图所示:
在这里插入图片描述

2.2.2ACK前需要保证有多少个备份

对于Kafka而言 ,定义一个Broker是否"活着"包含两个条件:
一丶是他必须维护与Zookeeper的session(这个通过Zookeeper的Heartbeat机制来实现)
二丶是Follower必须能够及时将Leader的消息复制过来,不能"落后太多"…
Leader会跟踪与其保持同步的Replica列表,该列表称为ISR(即in-sync
Replica).如果一个Follower宕机,或者落后太多,Leader将把它从ISR中移除,这里所描述的"落后太多"指Follower复制的消息落后于Leader后的条数超过预定值?(该值可在 K A F K A H O M E / c o n f i g / s e r v e r . p r o p e r t i e s 中 通 过 r e p l i c a . l a g . m a x . m e s s a g e s 配 置 , 其 默 认 值 是 4000 ) 或 者 F o l l o w e r 超 过 一 定 时 间 ( 该 值 可 在 KAFKA_HOME/config/server.properties中通过replica.lag.max.messages配置,其默认值是4000)或者Follower超过一定时间(该值可在 KAFKAHOME/config/server.propertiesreplica.lag.max.messages4000FollowerKAFKA_HOME/config/server.properties中通过replica.lag.time.max.ms来配置,其默认值是10000)未向Leader发送fetch请求。
Kafka的复制机制既不是完全的同步复制,也不是单纯的异步复制,事实上,完全同步复制要求所有能工作得Follower都复制完,这条消息才会被认为commir,这种复制方式极大的影响了吞吐率(高吞吐率是Kafka非常重要的一个特性)而异步复制方式下,Follower可以批量的从Leader复制数据,这样极大的提高复制性能(批量写磁盘),极大减少了Follower与Leader的差距
需要说明的是,kafka只解决fail/recover.不处理"Byzantine"(“拜占庭”)问题.一条消息只有被ISR里的所有Follower都从Leader复制过去才会被认为已提交,这样就避免了部分数据被写进了Leader,还没来得及被任何Follower复制就宕机了,
而造成数据库丢失(Consumer无法消费这些数据),而对于Produce而言,它可以选择是否等待消息commit.这可以通过request.required.acks来设置.这种机制确保只要ISR有一个或以上的Folloewer,一条被commit的消息就不会丢失,

2.2.3 Leader Election算法

Leader选举本质上是一个分布式锁,有两种方式实现基于Zookeeper的分布式锁:
节点名称唯一性:多个客户端创建一个节点.只有成功创建节点的客户端才能获得锁
临时顺序节点:所有客户端在某一个目录下创建自己的临时顺序节点,只要序号最小的才获得锁
一种非常常用的选举leader的方式是"Majority
Vote"(“少数服从多数”),但Kafka并未采用这种方式,这种模式下,如果我们有2f+1个Replica(包含Leader和Follower),那在commit之前必须保证有f+1Replica复制完消息,为了保证正确选出新的Leader,fail的Replica不能超过f个,因为在剩下的任意f+1Replica里,至少有一个Replica包含有最新的所有消息,这种方式有个很大的优势.系统的latency只取决于最快的几个Broker,而非最慢那个,Majority
Vote也有一些劣势,为了保证Leader
Election的正常进行,它所能容忍的fail的follower个数比较小,如果要容忍2个Follower挂掉,必须要有5个以上的Replica,也就是说,在生产环境下为了保证较高的容错程度,必须要有大量的Replica,而大量的Replica又会在大数据量下导致性能的急剧下降,这是这种算法如果要容忍1个follower挂掉,必须要有也就是说,再生产环境下为保证较高的容错性程度,必要是有大量的Replica,而大量的Replica又会在大数据量下导致新歌能的急剧下降,这就是这种算法更多用在Zookeeper这种共享集群配置的系统中而很少在需要存储大量数据的系统中使用的原因,列如:HDFS的HA
Featyre是基于Majority-vote-base journal,但是它的的数据存储并没使用这种方式
Kafka在Zookeeper中动态维护了一个ISR(in-sync
replicas),这个ISR里的所有Replica都跟上了Leader,只有ISR里的成员才有被选为Leader的可能,在这种模式下,对于f+1个Replica,一个Partition能在保证不丢失已经commmit的消息的前提下容忍f个Replica的失败,在大多数使用场景中,这种模式是非常有利的,事实上,为了容忍f个Replica的失败,Majority
Vote和ISR在commit钱需要等待的Replica数量一样的,但是ISR需要的总的Replica的个数几乎是Majority
Vote的一般 虽然Majority
Vote与ISR相比有不需等待最慢的Broker这一优势,但是Kafka作者认为Kafka可以通过Producer选择是否被Commit阻塞来改善这一问题,并且节省下来的Replica和磁盘使得ISR模式任然值得.

2.2.4如何处理所有Replica都不工作

在ISR至少有一个follower是,Kafka可以确保已经commmit的数据不丢失,但如果某个Partition的所有Replica都宕机了.就无法保证数据就不丢失了,这种情况下游两种可行的方案:
1,在等待ISR中的任一个Replica"活"过来了,并且选它作为Leader
2,选择一个"活"过来的Replica(不一定是ISR中的)作为Leader
这就需要在可用性和一致性当中做出一个简单地折衷,如果一定要等待ISR中的Replica"活"过来,那不可用的时间就可能会相对较长,而且如果ISR中的所有Replica都无法"活"过来,或者数据都丢失了,这个Paetition将永远不可用,选择第一个"活"过来的Reolica作为Leader,而这个Replica不是ISR中的Replica,那即使它并不保证已经包含了所有已commit的消息,他会成为Leader而作为consume的数据源(前文说明,所有读写都由Leader完成),kafka0.8*使用了第二种方式,根据kafka的文档,在以后的版本中,Kafka支持用户通过配置选择这两种方式中的一种,从而根据不同的使用场景选择高可用性还是强一致性.

2.2.5选举Leader

最简单最直观的方案是,所有Follower都在Zookeeper上设置一个Watch,一旦Leader宕机,其对应的ephemeralznode会自动删除,此时所有Follower都尝试创建该节点,而创建成功者(Zookeeper保证只有一个能创建成功)即是新的Learder,其它Replica即为Follower.
但是该方法会有3个问题:
1.split-brain这是由Zookeeper的特性引起的,虽然Zookeeper能保证所有Watch按顺序触发,但并不能保证同一时刻所有Relica"看"到的状态是一样的,这就可能照成
不同Rplica的相应不一致
2.herd effect 如果宕机的那个Broker上的Patition不比较多,会造成多个Watch,造成集群内大量的调整
3.Zookeeper负载过重每个Replica都要为此在Zeeper上注册一个watch,当集群规模增加到几千个Partition时zookeeper负载会过重
Kafka0.8*的Leader
Election方案解决了上诉问题,它在所有broker中选择出一个controller,所有Partition的Leader选举都由controller决定,controller会将Leader选举都由controller决定,controller会将Leader的改变直接通过RPC的方式(比Zookeerper
Queue的方式更高效)通知需为 维持作为响应的Broker,同时controller也负责增删Topic以及Replica的重新分配

三丶 HA相关Zookeeper结构
在这里插入图片描述
3.1 admin
该目录下znode只有在相关操作时才会存在,操作结束时会将其删除

/admin/reassign_partition用于将一些Partition分配到不同的broker集合上,对于每个待重新分配的Partition,Kafka会在该znode上存储其所有的Replica和相应的Broker
id ,该znode由管理进程创建并且一旦重新分配成功它将会被自动移除

3.2broker

即/brokers/ids.[brokerId]存储"活着"的broker信息
topic注册信息(/broker/topics/[topic]),存储该topic的所有partition的所有replica所在的broker
id ,第一个replica即为preferred
replica,对一个给定的partition,它在同一个broker上最多只有一个replica,因此broker id
可作为replica id

3.3 controller

/controller ->int(broker id of the controller)存储当前controller的信息
/controller_epoch->int(epoch)直接以整数形式存储controller
epoch,而非像其它znode一样以JSON字符串形式存储.

四丶producer发布消息
4.1 写入方式

producer 采用
push模式将消息发布到broker,每条消息都被append到patition中,属于顺序写磁盘(顺序写磁盘效率随机写没存要高,保障kafka吞吐率)

4.2消息路由
producer发送消息到broker时,会根据分区算法选择将其存储到哪一个partition,其路由机制为:

1丶指定了patition,则直接使用; 2丶未指定patition但指定key,通过对key的value进行hash
选出一个patition 3丶 patition和key都未指定,使用轮询选出一个patition

4.3写入流程
producer写入消息序列图如下所示:
在这里插入图片描述
流程说明:

1,producer 先从zookeeper的"/brokers/…/state"节点找到该partition的leader
2丶producer将消息发送给leader 3丶leader将消息写入本地log 4丶 followers 从leader pull
消息,写如本地log否leader发送ACK 5丶leader收到所有ISR中的replica的ACK后,增加HW(high
watermark ,最后commit 的offset)并向 producer发送ACK

五丶broker保存消息
5.1 存储方式
物理上把topic分成一个或多个patition(对应server.properties中的num.partition=3配置),每个patition物理上对应一个文件夹(改文件存储该patition的所有消息和索引文件),如下:
在这里插入图片描述
5.2 存储策略
无论消息是否被消费,kafka都会保留所有消息,有两种策略可以删除旧数据:
1.基于时间:log.retention.hours=168
2.基于大小:log.retention.bytes=1073741824

六丶Topic的创建和删除
6.1创建topic
创建topic的序列如下所示:
在这里插入图片描述
流程说明:

1丶controller在Zookeeper的/brokers/topics节点上注册watcher,当topic被创建,则controller会通过watch得到该topic的partition/replica分配.
2丶controller从/brokers/ids读取当前所有可用的broker列表,对于set_p中的每一个partition:
2.1丶从分配给该partition的所有replica(称为AR)中任选一个可用的broker作为新的leader,并将AR设置为新的ISR
2.2丶将新的leader和ISR和ISR写入/brokers/topics/[topic]/partitions/[partition]/state
3丶controller通过RPC向相关的broker发送LeaderAndISRRequest.

6.2 删除topic
删除topic的序列图如下所示:
在这里插入图片描述
流程说明:

1丶controller在zookeeper的/brokers/topics节点上注册watcher,当topic被删除,则controller会通过watch得到topic的partition/replica分配
2丶若delete.topic.enable=false,结束;否则controller注册在/admin/delete_topics
上的watch被fire,controller通过回调向对应的broker发送StopReplicaRequest.

七丶broker failover
kafka broker failocer 序列图如下所示:
在这里插入图片描述
流程说明:

1丶conroller在zookeeper的/brokers/ids/[brokerId]节点注册watcher,当broker宕机时zookeeper会fire
watch 2丶controller从/brokers/ids 节点读取可用broker 3丶
controller决定set_p,该集合包含宕机broker上的所有partition 4丶对 set_p中的每一个partition
4,1丶从/brokers/topics/[topic]/partitions/[partition]/state节点读取ISR
4.2丶决定新leader
4.3丶将新 leader,ISR,controller_epoch和leader_epoch等信息写入state节点 5丶通过 RPC 向相关broker发送leaderAndISRRequest命令

八丶controller failover
当controller宕机时会触发controlle failover.每个broker都会在zookeeper的"/controller"节点注册watcher,当controller宕机时zookeeper中的临时节点消失,所有存活的broker收到fire的通知,每个broker都尝试创建新的controller path,只有一个竞选成功并当选为controller
当新的controller当选时,会触发KafkaController.onControllerFailover方法,在该方法中完成如下操作:
1丶读取并增加Controller Epoch
2丶在reassignedPartitions Path(/admin/reassign_partitions)上注册watcher.
3丶在preferredReplicaElection Path(/admin/preferred_replice_election)上注册watcher
4丶通过 partitionStateMachion 在broker Topics Path(/brokers/topics)上注册watcher
5,若 delete.topic.enable=true(默认值是false),则partitionSateMachine在Delete Topic Patch(/admin/delete_topics)上注册watcher
6丶通过replicaStateMachine在Broker Ids Patch(/brokers/ids)上注册watch
7丶初始化ControllerContext对象,设置当前所有topic"活"着的broker列表,所有partition的leader及ISR等
8丶启动replicaStateMachine和partitionsStateMchine.
9丶将brokerState状态设置为RunningAsController
10丶将每个partition的Leadership信息发送送诶所有"活"着的broker
11丶若auto.leader.rebalance.enable=true(默认值是true),则启动partition-rebalance线程,
12丶若delete.topic.enable=true 且Delete Topic Patch(/admin/delete_topics)中有值,则删除相应的Topic.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值