对于一个复杂的分布式系统,若是没有丰富的经验和牛逼的架构能力,很难把系统作得简单易维护,咱们都知道,一个软件的生命周期中,后期维护占了70%,因此系统的可维护性是极其重要的, kafka 能成为大数据领域的事实标准,很大缘由是由于运维起来很方便简单,今天咱们来看下 kafka 是怎么来简化运维操做的。html
kafka 使用多副原本保证消息不丢失,多副本就涉及到kafka的复制机制,在一个超大规模的集群中,时不时地这个点磁盘坏了,那个点cpu负载高了,出现各类各样的问题,多个副本之间的复制,若是想彻底自动化容错,就要作一些考量和取舍了。咱们举个例子说明下运维中面对的复杂性,咱们都知道 kafka 有个 ISR集合,我先说明下这个概念:java
kafka不是彻底同步,也不是彻底异步,是一种ISR机制: 算法
1. leader会维护一个与其基本保持同步的Replica列表,该列表称为ISR(in-sync Replica),每一个Partition都会有一个ISR,并且是由leader动态维护 apache
2. 若是一个follower比一个leader落后太多,或者超过必定时间未发起数据复制请求,则leader将其重ISR中移除 架构
3. 当ISR中全部Replica都向Leader发送ACK时,leader才commit,这时候producer才能认为一个请求中的消息都commit了。运维
在这种机制下, 若是一个 producer 一个请求发送的消息条数太多,致使flower瞬间落后leader太多怎么办?若是 follower不停的移入移出 ISR 会不会影响性能?若是对这种状况加了报警,就有可能形成告警轰炸,若是咱们不加报警,若是是broker 挂掉或者 broker 由于IO性能或者GC问题夯住的状况致使落后leader太多,这种真正须要报警状况怎么办呢? 今天咱们来看下 kafka 是怎么在设计上让咱们彻底避免这种运维中头疼的问题的。异步
kafka 每一个分区都是由顺序追加的不可变的消息序列组成,每条消息都一个惟一的offset 来标记位置。分布式
kafka中的副本机制是以分区粒度进行复制的,咱们在kafka中建立 topic的时候,均可以设置一个复制因子,这个复制因子决定着分区副本的个数,若是leader 挂掉了,kafka 会把分区主节点failover到其余副本节点,这样就能保证这个分区的消息是可用的。leader节点负责接收producer 打过来的消息,其余副本节点(follower)从主节点上拷贝消息。源码分析
kakfa 日志复制算法提供的保证是当一条消息在 producer 端认为已经 committed的以后,若是leader 节点挂掉了,其余节点被选举成为了 leader 节点后,这条消息一样是能够被消费到的。性能
这样的话,leader 选举的时候,只能从 ISR集合中选举,集合中的每一个点都必须是和leader消息同步的,也就是没有延迟,分区的leader 维护ISR 集合列表,若是某个点落后太多,就从 ISR集合中踢出去。 producer 发送一条消息到leader节点后, 只有当ISR中全部Replica都向Leader发送ACK确认这条消息时,leader才commit,这时候producer才能认为这条消息commit了,正是由于如此,kafka客户端的写性能取决于ISR集合中的最慢的一个broker的接收消息的性能,若是一个点性能太差,就必须尽快的识别出来,而后从ISR集合中踢出去,以避免形成性能问题。kafka 复制机制详情参考 https://kafka.apache.org/documentation.html#replication
一个副本不能 “caught up” leader 节点,就有可能被从 ISR集合中踢出去,咱们举个例子来讲明,什么才是真正的 “caught up” —— 跟leader节点消息同步。
kafka 中的一个单分区的 topic — foo,复制因子为 3 ,分区分布和 leader 和 follower 以下图,如今broker 2和3 是 follower 并且都在 ISR 集合中。咱们设置 replica.lag.max.messages 为4,只要 follower 只要不落后leader 大于3条消息,就而后是跟得上leader的节点,就不会被踢出去, 设置 replica.lag.time.max.ms 为 500ms, 意味着只要 follower 在每 500ms内发送fetch请求,就不会被认为已经dead ,不会从ISR集合中踢出去。
如今 producer 发送一条消息,offset 为3, 这时候 broker 3 发生了 GC, 入下图:
由于 broker 3 如今在 ISR 集合中, 因此要么 broker 3 拉取同步上这条 offset 为3 的消息,要么 3 被从 ISR集合中踢出去,否则这条消息就不会 committed, 由于 replica.lag.max.messages=4 为4, broker 3 只落后一条消息,不会从ISR集合中踢出去, broker 3 若是这时候 GC 100ms, GC 结束,而后拉取到 offset 为3的消息,就再次跟 leader 保持彻底同步,整个过程一直在 ISR集合中,以下图:
一个副本被踢出 ISR集合的几种缘由:
一个副本在一段时间内都没有跟得上 leader 节点,也就是跟leader节点的差距大于 replica.lag.max.messages , 一般状况是 IO性能跟不上,或者CPU 负载过高,致使 broker 在磁盘上追加消息的速度低于接收leader 消息的速度。
一个 broker 在很长时间内(大于 replica.lag.time.max.ms )都没有向 leader 发送fetch 请求, 多是由于 broker 发生了 full GC, 或者由于别的缘由挂掉了。
一个新 的 broker 节点,好比同一个 broker id, 磁盘坏掉,新换了一台机器,或者一个分区 reassign 到一个新的broker 节点上,都会从分区leader 上现存的最老的消息开始同步。
因此说 kafka 0.8 版本后设置了两个参数 , replica.lag.max.messages 用来识别性能一直很慢的节点, replica.lag.time.max.ms 用来识别卡住的节点。
从上面的状况来看,两个参数看似已经足够了,若是一个副本超过 replica.lag.time.max.ms 尚未发送fetch同步请求, 能够认为这个副本节点卡住了,而后踢出去,可是还有一种比较特殊的状况没有考虑到,咱们上文中设置 replica.lag.max.messages 为4,之因此设置为 4, 是咱们已经知道 producer 每次请求打过来的消息数都在 4 如下,若是咱们的参数是做用于多个 topic 的状况,那么这个 producer 最大打过来的消息数目就很差估计了,或者说在常常出现流量抖动的状况下,就会出现一个什么状况呢,咱们仍是使用例子说明:
若是咱们的 topic — foo 的 producer 由于流量抖动打过来一个 包含 4条消息的请求,咱们设置的 replica.lag.max.messages 仍是为4, 这个时候,全部的 follower 都会由于超出落后条数被踢出 ISR集合:
而后,由于 follower 是正常的,因此下一次 fetch 请求就会又追上 leader, 这时候就会再次加入 ISR 集合,若是常常性的抖动,就会不断的移入移出ISR集合,会形成使人头疼的 告警轰炸。
这里的核心问题是,在海量的 topic 状况下,或者常常性的流量抖动状况下,咱们不能对 topic 的producer 每次打过来的消息数目作任何假设,因此就不太好定出来一个 合适的 eplica.lag.max.messages
值
其实只有两种状况是异常的,一种就是卡住,另一种是follower 性能慢,若是咱们只根据 follower 落后 leader 多少来判断是否应该把 follower 提出ISR集合,就必需要对流量进行预测估计,怎么才能避免这种不靠谱的估计呢,kafka 给出 的方案是这样的,对 replica.lag.time.max.ms 这个配置的含义作了加强,和以前同样,若是 follower 卡住超过这个时间不发送fetch请求, 会被踢出ISR集合,新的加强逻辑是,在 follower 落后 leader 超过 eplica.lag.max.messages 条消息的时候,不会立马踢出ISR 集合,而是持续落后超过 replica.lag.time.max.ms 时间,才会被踢出,这样就能避免流量抖动形成的运维问题,由于follower 在下一次fetch的时候就会跟上leader, 这样就也不用对 topic 的写入速度作任何的估计喽。
欢迎学Java和大数据的朋友们加入java架构交流: 855835163 群内提供免费的架构资料还有:Java工程化、高性能及分布式、高性能、深刻浅出。高架构。性能调优、Spring,MyBatis,Netty源码分析和大数据等多个知识点高级进阶干货的免费直播讲解 能够进来一块儿学习交流哦