1 kafka工作流程,存储机制

kafka以topic为单位进行工作,一个topic是一个整体,一个topic是一个消息队列,生产者不停地向队列中生产数据,消费者从topic中不停的消费数据。

topic是一个逻辑上的整体,物体上一个topic分为许多个partition,目的:增加扩展性,提高吞吐量,partition内部数据有序。

partition才是kafka存储数据的基本单位,一个partition就是一个不停追加的文件,通过位置索引文件和时间索引文件标识每条数据,综上,三个文件构成一组文件。

当文件越来越大时,使得查找信息十分不方便,当数据增长到一定程度时,进行切分,形成新的文件(Segment,默认1GB),新产生的数据存储到新文件当中。默认一星期之后,一个Segment会被删除,默认5分钟检查一次Segment的生命周期。

在查找消息的时候,先定位消息在哪个Segment,然后根据Segment的索引定位此消息在Segment中的位置,最后将数据取出来。

2 kafka生产者

2.1 分区策略

分区原因:方便在集群中扩展,每个Partition可以通过调整以适应它所在的机器,而一个topic又可以由多个Partition组成,因此整个集群就可以适应任意大小的数据了;提高并发,因为可以以Partition为单位读写了。

分区规则:指明 partition 的情况下,直接将指明的值直接作为 partition 值;没有指明 partition 值但有 key 的情况下,将 key 的 hash 值与 topic 的 partition 数进行取余得到 partition 值;既没有 partition 值又没有 key 值的情况下,第一次调用时随机生成一个整数(后面每次调用在这个整数上自增),将这个值与 topic 可用的 partition 总数取余得到 partition 值,也就是常说的 round-robin 算法。

2.2 数据可靠性保证

producer向server发送数据,最重要的是发送的数据是否安全。

安全性分为两个部分:①producer向server发送的路上信息是否会丢失;②server在保存信息时是否会丢失。

①:如果server接收到producer发送的信息,会返回一个ACK,能够返回ACK的时机:1)server收到信息以后立即回复ACK,这种方式会产生安全隐患,在返回ACK之后,server将消息丢失了;2)server将信息写入到topic中再发送ACK,partition中有很多副本,一个leader,很多follower,a)写入到leader中就发送ACK,当leader写完信息后宕机了,就会造成数据丢失;b)数据写入leader和全部follower再发送ACK,此时如果发送的ACK producer没有收到(可能是leader宕机引起),可能会重发,造成数据的重复

综上分为三种发送ACK时机:消息收到就发送;只有leader写完就发送;集群全部写完再发送。三种方式可以手动设置,为了安全性,一般采取第三种。

②:数据在server端的保存机制,如何验证集群的所有机器全部保存?第一种策略是集群中的所有节点全部保存成功;第二种是类似于Zookeeper集群的半数以上原则。前者安全性高,后者更快。kafka使用第一种策略。为了防止某一个follower迟迟不响应leader(follower写入数据成功之后需要向leader报告),leader使用ISR(与leader完成同步的follower),当出现上述过程,leader会将这个迟迟不响应的节点从ISR中去除,只有在ISR中的follower才有写数据,当选leader的权利。

**被去除的切点如何回到ISR:**ISR中每个分区的副本的最后一个日志的序号叫做这个分区的LEO(Log End Offset),每个分区都有一个LEO。ISR中所有副本最小的LEO叫做HW(High Watermark)。consumer能够消费的数据是HW之前的数据,被从ISO中除名的节点经过若干时间,恢复过来,会寻找被去除之前的HW和LEO,此时HW之前的数据完全可靠,其会将HW之后的数据全部清除,然后去找leader同步旧HW与新HW(此节点被去除后,ISR中的节点仍然正常工作)之间的数据,当旧HW与新HW相同时,其能够回到ISR中。

2.3 Exactly Once语义

数据重复的问题是否严重要取决于具体应用的场景,leader写入数据重复了之后,consumer就会消费到重复的数据,如果不允许数据重复,consumer消费到重复数据后就会在下游(consumer端)进行全局去重,然后处理数据,这样每一个consumer都会消费一部分算力进行上述工作,引起资源浪费。

所以在kafka 0.11版本后,引入幂等性的概念来解决数据重复问题(一件事情不管干了多少次,都和第一次一样,比如e的求导)。在Producer的参数中enable.idompotence设置为true启动幂等性功能。使用全局主键来唯一标识一份数据,如果没有主键,kafka会为其安排一个主键。当kafka 的producer向server发送消息以后,server不仅存储这份数据,还会存储这份数据的全局主键,主键包括PID,Partition,SeqNumber,根据主键判断数据是否重复。但是PID重启就会变化,同时不同的Partition也具有不同主键,所以幂等性无法保证跨分区跨会话的Exactly Once。

3 kafka消费者

3.1 消费方式

一种是topic的server端向consumer推送消息,另一种是consumer向server端拉取数据。前者,消息的实时性比较好,问题就是consumer无法适配server端的推送速率,在一对一通信中,consumer可以增加一些反馈机制,但是kafka是一对多的,不同consumer的消费能力不同,想要增加一个统一的反馈机制不是很容易。所以kafka采用第二种消费方式,好处在于各个consumer之间根据自己的消费能力自行消费,缺点就是数据的及时性问题,如果kafka没有数据,消费者可能会陷入循环中,一直返回空数据。针对这一点,Kafka的消费者在消费数据时会传入一个时长参数timeout,如果当前没有数据可供消费,consumer会等待一段时间之后再返回,这段时长即为timeout。

3.2 分区分配策略

一个consumer group中有多个consumer,一个 topic有多个partition,所以必然会涉及到partition的分配问题,即确定那个partition由哪个consumer来消费。

一个消费者消费一个分区是最优情况,但现实情况中很难做到这一点,当消费者数量和分区数量不一样时,Kafka有两种分配策略,一是roundrobin,一是range,仅适用于consumer数量小于partition数量时。

roundrobin:每个consumer都会被按照顺序分配一个分区,循环,到无分区可分配时。

range:提前算好每个consumer分配几个分区(尽可能平均)。

3.3 offset的维护

消费者消费完消息,这个消息不会被立刻删除,所以每个消费者将哪些消息消费到哪里,需要做一个记录(offset)。同时由于consumer在消费过程中可能会出现断电宕机等故障,consumer恢复后,需要从故障前的位置的继续消费,所以consumer需要实时记录自己消费到了哪个offset,以便故障恢复后继续消费。

Kafka 0.9版本之前,consumer默认将offset保存在Zookeeper中,从0.9版本开始,consumer默认将offset保存在Kafka一个内置的topic中,该topic为**__consumer_offsets**。

3.4 kafka如何实现高效读写数据

(1)顺序写磁盘

Kafka的producer生产数据,要写入到log文件中,写的过程是一直追加到文件末端,为顺序写。官网有数据表明,同样的磁盘,顺序写能到到600M/s,而随机写只有100k/s(最极端情况)。这与磁盘的机械机构有关,顺序写之所以快,是因为其省去了大量磁头寻址的时间,使用顺序读写增加消息的吞吐量。

(2)大量使用PageCache

PageCache是一个操作系统的优化技术,操作系统的运行很大程度上依赖于硬盘的随机读写能力。

Kafka数据持久化是直接持久化到Pagecache中,这样会产生以下几个好处:

  • I/O Scheduler 会将连续的小块写组装成大块的物理写从而提高性能;
  • I/O Scheduler 会尝试将一些写操作重新按顺序排好,从而减少磁盘头的移动时间;
  • 充分利用所有空闲内存(非 JVM 内存)。如果使用应用层 Cache(即 JVM 堆内存),会增加 GC 负担;
  • 读操作可直接在 Page Cache 内进行。如果消费和生产速度相当,甚至不需要通过物理磁盘(直接通过 Page Cache)交换数据;
  • 如果进程重启,JVM 内的 Cache 会失效,但 Page Cache 仍然可用。

尽管持久化到Pagecache上可能会造成宕机丢失数据的情况,但这可以被Kafka的Replication机制解决。如果为了保证这种情况下数据不丢失而强制将 Page Cache 中的数据 Flush 到磁盘,反而会降低性能。1000

(3)零复制技术

当用户进行复制的时候,需要经过User space(所有应用程序运行在这一层) 和 kernel space(运行操作系统的底层,负责一些内核驱动的程序,如读写磁盘)层,复制的操作发生在user space层,由上层调用kernel space层,内核层调用IO读写功能,读取文件,再由应用程序层将流写出去,写入到迷你盘上面(NIC)。
在这里插入图片描述

复制操作不需要应用层将文件查看一遍,直接从Page Cache中写出去即可,最佳路径如下所示,称为零拷贝技术,应用层执行一个管理者的角色,仅发出复制命令即可。
在这里插入图片描述

零拷贝也是操作系统提供的一项优化措施,kafka调用接口实现此功能,如果所在的操作系统不允许此操作,kafka的此项技术将会消失,一般的linux操作系统都支持此项功能。

3.5 Zookeeper在Kafka中的作用

Kafka集群中有一个broker会被选举为Controller(谁快谁就是Controller),负责监视集群broker的上下线,所有topic的分区副本分配和leader选举等工作。

leader选举工作:
在这里插入图片描述

当一个leader(Broker)失效之后:
在这里插入图片描述

3.6kafka事务

Kafka从0.11版本开始引入了事务支持。事务可以保证Kafka在Exactly Once语义的基础上,生产和消费可以跨分区和会话,要么全部成功,要么全部失败。

一般来说事务就是在Producer到Server那一端。

(1)Producer事务

为了实现跨分区跨会话的事务,kafka引入一个全局唯一的Transaction ID,并将Producer获得的PID和Transaction ID绑定,写入Transation state中。这样当Producer重启后就可以通过正在进行的Transaction ID获得原来的PID。

为了管理Transaction,Kafka引入了一个新的组件Transaction Coordinator。Producer就是通过和Transaction Coordinator交互获得Transaction ID对应的任务状态。Transaction Coordinator还负责将事务所有写入Kafka的一个内部Topic,这样即使整个服务重启,由于事务状态得到保存,进行中的事务状态可以得到恢复,从而继续进行。

kafka不支持数据的删除,执行回滚操作时,kafka会标记一些数据对consumer不可见。

(2)Consumer事务

上述事务机制主要是从Producer方面考虑,对于Consumer而言,事务的保证就会相对较弱,尤其是无法保证Commit的信息被精确消费。这是由于Consumer可以通过offset访问任意信息,而且不同的Segment File生命周期不同,同一事务的消息可能会出现重启后被删除的情况。

如果想完成Consumer端的精准一次性消费,那么需要kafka消费端将消费过程和提交offset过程做原子绑定。此时我们需要将kafka的offset保存到支持事务的自定义介质中(比如mysql)。

因为kafka采用Customer主动拉取数据,所以事务在此处作用不是很大,想要启动事务,需要在Customer端进行控制,与kafka的关系不大。

Logo

Kafka开源项目指南提供详尽教程,助开发者掌握其架构、配置和使用,实现高效数据流管理和实时处理。它高性能、可扩展,适合日志收集和实时数据处理,通过持久化保障数据安全,是企业大数据生态系统的核心。

更多推荐