《Apache Kafka 实战》读书笔记-认识Apache Kafka

                《Apache Kafka 实战》读书笔记-认识Apache Kafka

                                           作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

 

 

 

 

一.kafka概要设计
  kafka在设计初衷就是为了解决互联网公司的超级大量级数据的实时传输。为了实现这个目标,kafka在设计之初就需要考虑以下四个方面:
  第一:吞吐量/延迟
  第二:消息持久化 
  第三:负载均衡和故障转移
  第四:伸缩性
1>.吞吐量/延时介绍
  我们先打个比方:若kafka处理一条消息需要花费2ms,那么计算得到的吞吐量不会超过500条消息每秒(1000ms/2ms=500条/s)。但是若我们采用批处理(batching)的思想,假设在发送前我们首先会等待一段时间(假设是8ms),那么此时消息发送的延迟变成了10ms(2ms+8ms),即延迟增加了4倍,但假设在这8ms中我们总共积累了1000条消息,那么整个系统的吞吐量就变成了100000 条/s。此时你会发现吞吐量提升了200倍!看到micor-batch的威力了吧?这就是目前诸如Storm Trident 和 Spark Streaming等消息处理平台所采用的批处理思想。 
2>.Kafka如何做到高吞吐量,低延迟的呢? 
  首先,kafka的写入操作是很快的,这主要得益于它对磁盘的使用方法的不同。虽然kafka会持久化所有数据到磁盘,但本质上每次写入操作其实都只是把数据写入到操作系统的页缓存(page cache)中,然后由操作系统自行决定什么时候把页缓存中的数据写入磁盘上。这样的设计由三个主要的优势:
  第一:操作系统页缓存是内存中分配的,所以消息写入的速度非常快;
  第二:kafka不必直接与底层的文件系统打交道。所以烦琐的I/O操作都交由操作系统来处理;
  第三:kafka写入操作采用追加写入(append)方式,避免了磁盘随机写操作(据资料统计,顺序磁盘I/O速度是毫不逊色于随机读写内存I/O速度。感兴趣的小伙伴可以使用相关工具测试一下。); 
3>.Kafka的高吞吐量,低延迟的设计目标
  第一:大量使用操作系统页缓存,内存操作速度快且命中率高; 
  第二:Kafka不直接参与物理I/O操作,而是交由最擅长此时的操作系统来完成; 
  第三:采用追加写入方式,摒弃了缓慢的磁盘随机读/写操作;
  第四:使用sendfile为代表的零拷贝技术加强网络间的数据传输效率; 
4>.消息持久化的优点
  第一:解耦消息发送和消息消费
    本质上来说,kakfa最核心的功能就是提供了生产者-消费者模式的完整解决方案。通过将消息持久化使得生产者方不再需要直接和消费者方耦合,它只是简单的把消息生产出来并交由kafka服务器保存即可,因此提升了整体的吞吐量。 
  第二:实现灵活的消息处理
    很多kafka的下游子系统(接受kafka消息的系统)都有这样的需求:对于已经处理过的消息可能在未来的某个时间点重新处理一次,即所谓的消息消息重演(message replay)。消息持久化便可以很方便地实现这样的需求。 
  第三:负载均衡和故障转移 
    作为一个功能完备的分布式系统,kafka如果只提供了最基本的消息引擎功能肯定不足以帮助它脱颖而出。一套完整的消息引擎解决方案中韩必然要提供负载均衡(load balancing)和故障转移(fail-over)功能。
    何为负载均衡?顾名思义就是让系统的负载根据一定的规则均衡地分配在所有参数工作的服务器上,从而最大限度的提升整体的运行效率。kafka实现负载均衡实际上是通过智能化的分区领导者选举(partition leader election)来实现的。 
    除了负载均衡,完备的分布式系统还支持故障转移,所谓故障转移,是指当服务器意外终止时,整个集群可以快速的检测到该失效(failure),并立即将该服务器上应用或服务自动转移到其他服务器上。故障转移通常是“心跳”和“会话“的机制来实现的。kafka服务器支持故障转移的方式就是使用绘画机制。每台kafka服务器启动后会以会话的形式把自己注册到zookeeper服务器上。一旦该服务运转出现问题,与zookeeper的会话变不能维持从而超时失效,此时kafka集群会选举出另外一台服务器来完全代替这台服务器继续提供服务。
  第四:伸缩性
    所谓伸缩性,英文名是scalability。伸缩性表示想分布式系统中增加额外的计算资源(比如CPU,内存,存储或带宽)时吞吐量提升的能力。阻碍线性扩容的一个很常见的因素就是状态的保存。我们知道,不论是哪类分布式系统,集群的每台服务器一定会维护很多内部状态。如果由服务器自己来保存这些状态信息,则必须处理一致性的问题。相反,如果服务器是无状态的,状态的保存和管理交与专门的协调服务来做(比如zookeeper)。那么整个集群的服务武器之间就无需繁重的状态共享,者极大的降低了维护复杂度。倘若要扩容集群节点,只需要简单的启动新的节点集群和进行自动负载均衡就可以了。 
  Kafka正式采用了这样的思想:每台kafka服务器上的状态统一交友zookeeper保管。扩展kafka集群也只需要一步:启动新的kafka服务器即可。当然这里需要言明的是,在kafka服务器上并不是所有的状态信息都不保存,它只保存了很轻量级的内部状态(比如从kakka 0.10.x版本之后,它将每个topic的消费者的偏移量自己维护了,把这些偏移量存放到了一个叫做“__consumer_offsets”的的topic进行维护)。
 
 
二.Kafka基本概念与术语
1>.Kafka的消息格式
  既然kafka的核心功能就是消息引擎,那么对于消息的设计自然是首当其冲的时期。kafka没有令人失望,其对消息格式的设计与保存的确有很多创新之处。首先,kakfa中的消息有很多字段组成,其中有的很多字段都是用于管理消息的原数据字段,对用户来说是完全透明的。kakfa的消息格式经历过3次变迁(我们此次暂不考虑新出的kafka 2.0.1版本,因为我并没有对这个版本做深入的调研。)他们分别称为V0,V1和V2版本。目前大部分用户使用的应该还是V1版本的消息格式。V1版本的消息格式如下图所示:
  如上图所示(上图摘自互联网),消息由消息头部,key和value组成。消息头部包括消息的CRC码,消息版本号,属性,时间戳,键长度,和消息长度等信息。其实对于普通用户来说,掌握一下3个字段的含义一般就够用了:
    key :
      消息键,对消息做partition时使用,即决定消息被保存在某topic下的哪个partition。
    value:
      消息体,保存实际的消息数据。
    timestamp:
      消息发送时间戳, 用于流式处理及其他依赖时间的处理语义。如宝不指定,则取当前时间。
  另外这里单独提一下消息的属性字段,kafka为该字段分配了一个字节,目前只使用了最低的3我为用于保存消息的压缩类型,其余5为尚未使用。当前支持4中压缩类型:0(无压缩),1(GZIP),2(Snappy)和3(LZ4)。关于kafka消息格式V0和V2版本,大家自行百度,推荐一片不错的文章:更多资料请参考:https://www.cnblogs.com/qwangxiao/p/9043491.html。
  其次,kafka使用紧凑的二进制字节数组来保存消息格式的字段,也就是说没有任何多余的比特位浪费。kafka在消息设计时特意避开了繁重的Java堆内存分配,直接使用紧凑二进制字节数组ByteBuffer而不是独立的对象,因此我们至少能够访问多一倍的可用内存。按照Kafka官网的说法,在一台32GB内存的机器上,Kafka几乎能够用到28~30GB的物理内存,同时还不必担心GC的糟糕性能。如果使用ByteBuffer来保存同样的消息,只需要24字节,比起纯Java堆的实现减少了40%的空间占用,好处不言而喻。这种设计的好处还包括加入了扩展的可能性。
  同时,大量使用也缓存而非对内存还有一个好处:当出现Kafka broker进程崩溃时,堆内存的数据也一并小时,但页缓存的数据易燃存在。下载Kafka broker重启后可以继续提供服务,不需要再单独“热”缓存了。
2>.topic和partition
  在概念上来说,topic只是一个逻辑概念,代表了一类消息,也可以认为是消息被发送到的地方。通常我们可以使用topic来区分实际业务,比如业务A使用一个topic,业务B使用另一个topic。从本质上说,每个Kafka topic都由若干个partition组成,而Kafka的partition是不可修改的有序消息序列,也就是说是有序的消息日志。每个partition有自己专属的partition号,通常是从0开始的。用户堆partition我唯一能做的操作就是在消息序列的尾部追加写入消息。
  partition上的每条消息都会被分配一个唯一的序列号,按照Kafka的术语来讲,该序列号被称为位移(offset)。该位移值是从0开始顺序递增的证书。位移信息可以唯一定义到某partition下的一条消息。值得一提的是,Kafka的partition实际上并没有太多的业务含义,它的引入就是单纯的为了提升系统的吞吐量,因此在创建Kafka topic的时候可以根据集群实际配置设置具体的partition数,实现整体性能的最大化。
3>.offset
  上面说过,topic partition下的每条消息都被分配了一个位移值。实际上,Kafka消费者端也有位移(offset)的概念,但一定要注意这两个offset属于不同的概念。
  显然,每条消息在某个partition的位移是固定的,但消费该partition的消费者的位移是会随着消费进度不断迁移,但终究不可能超过该分区最新一条消息的位移。综合之前说的topic,partition和offset,我们可以断言Kafka中的一条消息其实就是一个<topic,partition,offset>三元组(tuple),通过该元组值我们可以在Kafka集群中找到位移对应的那条消息。
4>.replia
  既然我们已知partition是有序的消息日志,那么一定不能只保存者一份日志,否则一旦保存在partition的Kafka服务器挂掉了,其上保存的消息也就都丢失了。分布式系统必然要实现高可靠性,而目前实现的主要途径还是依靠冗余机制。换句话说,就是备份多份日志。这些分贝日志在Kafka中被称为副本(replica),它们存在的唯一目的就是防止数据丢失,这一点一定要记住!
5>.leader和follower
  副本(replia)分为两类:领导者副本(leader replia)和追随者副本(follower replia)。follower replica是不能提供服务给客户端的,也就是说不负责响应客户端发来的消息写入和消息消费请求。它只是被动地向领导者副本(leader replia)获取数据,而一旦leader replica 所在的broker宕机,Kafka会从剩余的replica中选举出新的leader继续提供服务。
  Kafka保证同一个partition的多个replica一定不会分配在同一台broker上。毕竟如果同一个broker上有同一个partition的多个replica,那么将无法实现备份冗余的效果。
6>.ISR
  ISR的全称是in-sync replica,翻译过来就是与leader replica保持同步的replica集合。这是一个特别重要的概念。前面讲了很多关于Kafka的副本机制,比如一个partition可以配置N个replica,那么这是否就意味着该partition可以容忍N-1个replica实现而不丢失数据呢?答案是:“否”!

  副本数对Kafka的吞吐率是有一定的影响,但极大的增强了可用性。默认情况下Kafka的replica数量为1,即每个partition都有一个唯一的leader,为了确保消息的可靠性,通常应用中将其值(由broker的参数offsets.topic.replication.factor指定)大小设置为大于1,比如3。 所有的副本(replicas)统称为Assigned Replicas,即AR。ISR是AR中的一个子集,由leader维护ISR列表,follower从leader同步数据有一些延迟(包括延迟时间replica.lag.time.max.ms和延迟条数replica.lag.max.messages两个维度, 当前最新的版本0.10.x中只支持replica.lag.time.max.ms这个维度),任意一个超过阈值都会把follower剔除出ISR, 存入OSR(Outof-Sync Replicas)列表,新加入的follower也会先存放在OSR中。AR=ISR+OSR。相反的,当这些replicas重新“追上”了leader的进度时,那么Kafka会将他们加回到ISR中。这一切都是自动维护的,不需要用户进行人为干预,因而在保证了消息交付语义的同时,还简化了用户的操作成本。

  更多学习笔记请参考:https://www.cnblogs.com/yinzhengjie/p/9652392.html。

 
三.Kafka的使用场景
  Kafka以消息引擎闻名,因此它特别适合处理生产环境中的那些流式数据。以下就是Kafka在实际应用中一些典型的使用场景。
1>.消息传输
  Kafka非常适合替代传统的消息总线(message bus)或消息代理(message broker)。传统的这类系统擅长于解耦生产者和消费者以及批量处理消息,而这些特点Kafka都具备。除此之外,Kafka还具有更好的吞吐量特性,其内置的分区机制和副本机制既实现了高性能的消息传输,同时还达到了高性能的高容错性。一次Kafka特别适合用于实现一个超大量级消息处理应用。
2>.网站行为日志追踪
   Kafka最早就是用于重建用户行为数据追踪系统的。很多网站上的用户操作都会以消息的形式发送到Kafka的某个对应的topic上。这些点击流蕴含了巨大的商业价值,事实上,目前就有很多创业公司使用机器学习或其他实时处理框架来帮助收集并分析用户的点击流数据。鉴于这种点击流数据量是很大的,Kafka超强的吞吐量特性此时就有了用武之地。
3>.审计数据收集
   很多企业和组织都需要对关键的操作和运维进行监控和审计。这就需要从各个方面运维应用程序处实时汇总操作步骤信息进行集中式管理。在这种使用场景下,你会发现Kafka是非常适合的解决方案,它可以便捷的对多路消息进行实时收集,同时由于其持久化的特性,是的后续离线审计称为可能。
4>.日志收集
   这可能是Kafka最常见的使用方式了(日志收集汇总解决方案),每个企业都会产生大量的服务日志,这些日志分散在不同的机器上。我们可以使用Kafka对他们进行全量收集,并集中往下游的分布式存储中(比如HDFS等)。比起其他主流的日志抽取框架(比如Apache Flume),Kafka有更好的性能,而且提供了完备的可靠性解决方案,同时还保持 了低延迟的特点。
5>.Event Sourcing
   Event Sourcing实际上是领域驱动设计(Domain-Driven Design,简称DDD)的名次,它使用事件序列来表示状态变更,这种思想和Kafka的设计特性不谋而合。还记得吧,Kafka也是用不可变更的消息序列来抽象化表示业务信息的,因此Kafka特别适合作为这种应用的后端存储。
6>.流式处理
   很多用户接触到Kafka都是因为它的消息存储引擎。自0.10.0.0版本开始,Kafka社区推出了一个全新的流式组件 Kafka Streams。这标志着Kafka正式进入流式处理框架俱乐部。相比老牌流式处理框架Apache Storm,Apache Samza,或是最近风头正劲的Spark Streaming,抑或是Apache Flink,Kafka Streams的竞争力如何?让我们拭目以待吧!
 

 

四.集群环境规划 
1>.操作系统的选型
  我们知道Kafka依赖于Java环境,因此我们只要能在操作系统上安装jdk理论上就可以部署kafka环境了。没错,事实上kafka的确可以运行在主流的操作系统上,比如windows,Linux,mac OS等等。但是这么多操作系统我们究竟应该选择哪个操作系统去安装呢?为什么大家部署kafka集群都选择的是Linux环境呢?其实咱们是可以分析原因的:
    第一:Kafka新版本clients在设计底层网络库时采用了Java的Selecor机制(NIO),而后者在Linux实现机制就是epoll;但是在window平台上,Java NIO的Selector底层是使用select模型而非IOCP实现的,只有Java NIO2擦拭使用IOCP实现的。因此这一点上,在Linux部署Kafka要在比Windows上部署能够得到高效的I/O处理能力;
    第二:对于数据网络传输效率而言,Linux也更具有优势。具体来说,Kafka这种应用必然需要大量的通过网络于磁盘进行数据传输,而大部分这样的操作都是通过Java的FileChannel.transferTo方法实现的,在Linux平台上该方法底层会调用sendfile系统调用,即采用了Linux提供的零拷贝(Zero Copy)技术。
2>.磁盘规划
  事实上,根据公开的资料显示,LinkedIn公司的Kafka集群就是使用RAID 10作为底层存储的。除了默认提供的数据冗余之外,RAID 10 还可以将数据自动的负载分布到多个磁盘上。由此可见,RAID作为Kafka的底层存储其实主要的优势有两个:
    第一:提供冗余的数据存储空间;
    第二:天然提供负载均衡;
  以上两个优势对于任何系统而言都是很好的特性。不过对于Kafka而言,Kafka在框架层面其实已经提供了这两个特性:通过副本机制提供冗余和高可靠性,以及通过分散到各个节点的领导者选举机制来实现负载均衡,所以从这方面来看,RAID的优势就显得不是那么明显了。事实上,LinkedIn公司目前正在计划将整个Kafka集群从RAID 10 迁移到JBOD上,只不过在整个过程中JBOD方案需要解决当前的Kafka一些固有缺陷,比如:
    第一:任意磁盘损坏都会导致broker宕机,普通磁盘损坏的概率是很大的,因此这个缺陷从某种程度上来说是致命的。不过社区正在改进这个问题,未来版本中只要为broker配置的多块磁盘中还有良好的磁盘,broker就不会挂掉。
    第二:JBOD的管理需要更加细粒度化,目前Kafka没有提供脚本或其他工具用于在不同磁盘间进行手动分配,但这是使用JBOD方案中必要的功能。
    第三:JBOD也应该提供类似于负载均衡的功能,目前只是间的依赖轮训的方式为副本数据选择磁盘,后续需要提供更加丰富的策略。
   结合JBOD和RAID之间的优劣对比以及LinkIn公司的实际案例,咱们可以给硬盘规划的结论性总结如下:
    第一:追求性价比的公司可以考虑使用JBOD;
    第二:使用机械硬盘完全可以满足Kafka集群的使用,SSD更好(尽量不要使用NAS(Network Attached Storage)这样的网络存储设备。);
3>. 磁盘容量规划
   对于磁盘容量的规划和以下结果因素有关:
    第一:新增消息数;
    第二:消息留存时间;
    第四:平均消息大小;
    第五:副本数;
    第六:是否启用压缩;
4>.内存规划
  Kafka对于内存对使用可称作其设计亮点之一。虽然在前面我们强调了Kafka大量依靠和磁盘来保存消息,但其实它还会对消息进行缓存,而这个消息换粗你得地方就是内存,具体来说是操作系统对页缓存(page cache)。Kafka虽然会持久化每条消息,但其实这个工作都是底层对文件系统来完成。Kafka仅仅将消息写入page cache而已,之后将消息“flush”到磁盘对任务完全交由操作系统来完成。
  一般情况下,broker所需的堆内存都不会超过6GB。所以对于一台16GB内存的机器而言,文件系统page cache的大小甚至可以达到10~14GB!总之对于内存规划的建议如下:
    第一:尽量分配更多的内存给操作系统的page cache;
    第二:不要为broker设置过大的堆内存,最好不超过6GB;
    第三:page大小至少要大于一个日志段的大小;
5>.CPU规划
   比起磁盘和内存,CPU于kafka而言并没有那么重要,严格来说,kafka不属于计算密集型(CPU-bound)的系统,因此杜宇CPU需要记住一点就可以了:追求多核而非高时钟频率。咱们对CPU资源规划如下:
    第一:使用多核系统,CPU核数最好大于8;
    第二:如果使用Kafka 0.10.0.0之前的版本或clients端消息版本不一致(若无显式配置,这种情况多半由clients和broker版本不一致造成),则考虑多配置一些资源以防止消息解压操作消耗过多CPU)。
6>.带宽规划
  第一:尽量使用高速网络;
  第二:根据自身网络条件和带宽来评估Kafka集群机器数量;
  第三:避免使用跨机房网络;
7>.典型线上环境配置
  下面给出一份典型的线上环境配置,用户可以参考这份配置以及结合自己的是实际情况进行二次调整:
    CPU 24核心;
    内存 32GB;
    磁盘 1TB 7200转SAS盘2快;
    带宽:1Gb/s;
    ulimit -n 1000000;
    Socket Buffer 至少64KB,适合于跨机房网络传输;
 
五.reblance扫盲

1>.rebalance简介

   consumer group的rebalance本质上是一组协议,它规定了一个consumer group 是如何达成一致来分配订阅topic的所有分区的。假设某个组下有20个consumer实例,该组订阅一个有着100个分区的topic。正常情况下,Kafka会为每个consumer平均分配5个分区。这个分配过程就被称为rebalance。

  当consumer成功执行rebalance后,组订阅topic的每个分区只会分配给组内一个consumer实例。换句话说,同一个消费者组的消费者不能同时对同一个topic的同一个分区进行消费。

  和旧版本consumer依托于zookeeper进行rebalance不同,新版本consumer使用了Kafka内置的一个全新的协议(group coordination protocol)。对于每个组而言,Kafka的某个broker会被选举为组协调者(group coordinator)。coordinator负责对组对状态进行管理,他的主要责任就是当新成员到达时促成组内所有成员达成新对分区分配方案,即coordinator负责对组执行rebalance操作。

2>.rebalance触发条件

  组rebalance触发对条件有以下3个:

    第一:组成员发生变更,比如新consumer加入组,或已有consumer主动离开组,再或是已有consumer崩溃时则触发rebalance;

    第二:组订阅topic数发生变更,比如使用基于正则表达式对订阅,当匹配正则表达式对新topic被创建时则会触发rebalance;

    第三:组订阅topic时分区发生变更,比如使用命令行脚本增加了订阅topic的分区数;

  真实应用场景引发rebalance最常见的原因就是违背了第一个条件(比如flume的kafka source相对于broker集群来说就是consumer对象),特别是consumer崩溃的情况。这里的崩溃不一定就是指consumer进程“挂掉”或consumer进程所在的机器宕机。当consumer无法在指定的时间内完成消息处理,那么coordinator就认为该consumer已经崩溃,从而引发新一轮rebalance。

  我在生产环境中也使用flume消费kafka的数据到hdfs集群上,也遇到过这种rebalance的情况,最终分析原因是:该group下的consumer处理消息的逻辑过重,而且事件处理时间波动很大,非常不稳定,从而导致coordinator会经常行的认为某个consumer已经挂掉,引发rebalance。鉴于目前一次rebalance操作的开销很大,生产环境中用户一定要结合自身业务特点仔细调优consumer参数:“request.timeout.ms”,“max.poll.records”和“max.poll.interval.ms”这几个参数,以避免不必要的rebalance出现。

3>.rebalance协议

  前面我们提到过rebalance本质上是一组协议。group于coordinator共同使用这组协议完成group的rebalance。最新版本的Kafka中提供了下面5个协议来处理rebalance相关事宜。

    第一:JoinGroup请求:consumer请求加入组;

    第二:SyncGroup请求:group leader 把分配方案同步更新到组内所有成员中;

    第三:Heartbeat请求:consumer定期向coordinator汇报心跳表明自己依然存活;

    第四:LeaveGroup请求:consumer主动通知coordinator该consumer即将离组;

    第五:DescribeGroup请求:查看组的所有信息,包括成员信息,协议信息,分配方案以及订阅信息等。该请求类型主要供管理员使用。coordinator不使用该请求执行rebalance。

  在rebalance过程中,coordinator主要处理consumer发过来的joinGroup和SyncGroup请求。当consumer主动离组时会发送LeaveGroup请求给coordinator。

  在成功rebalance过程中,组内所有consumer都需要定期向coordinator发送Hearbeat请求。而每个consumer也是根据Heartbeat请求的响应中是否包含REBALANCE_IN_PROGRESS来判断当前group是否开启来新一轮rebalance。

  好啦~关于rebalance咱们了解到这里基本上就够用来,感兴趣的小伙伴可以查看rebalance genneration,rebalance流程,rebalance监听器等技术,我们这里就不用深入探讨啦~

 

posted @ 2018-11-17 12:23  尹正杰  阅读(798)  评论(0编辑  收藏  举报