Kafka作为开源分布式消息系统,具备高吞吐低时延、高可靠和可扩展的特性,广泛应用于分布式分析处理平台。本文简要介绍Kafka的特性及主要概念、高可用架构及可靠性机制、生产者和消费者的处理流程等基本原理。
1、Kafka基本概念
1.1 Kafka特性
Kafka是一个开源的分布式消息系统,支持高吞吐、可持久化、可扩展的流式处理平台,已广泛集成于Cloudera、Spark、Flink等分布式分析处理平台,应用广泛。Kafka具备以下特性:
- 高吞吐量低延迟:吞吐量是衡量系统性能的重要指标,kafka每秒可以处理几十万条消息,同时延迟最低只有几毫秒。
- Kafka的写入操作实际上只把数据写入操作系统的page cache,再由操作系统决定什么时候将数据写入磁盘,减少了同IO的操作;同时写入操作append追加的方式,避免磁盘随机写操作;
- Kafka消费端的读操作利用了零拷贝技术提升数据传输的效率,同时先尝试从OS的page cache中读取数据,减少了和磁盘的交互
- 可扩展性:kafka集群支持热扩展
- 由分布式协调中心Zookeeper统一维护Kafka服务器的状态,扩展Kafka集群只需要启动新的Kafka服务
- 持久性和可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失
- 解耦消息发送与消息消费:消息持久化使得消息的生产者不需要直接和消费者耦合,只需要将数据持久化到服务器上即可
- 实现灵活的消息处理:消息的消费方可能会对已经处理过的数据进行重新处理,即为消息回放Replay,消息持久化很方便的实现这样的需求
- 容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败)
- 基于Zookeeper实现Kafka服务状态检测和维护,多副本的情况下,只要有一个副本正常,仍可以正常对外提供服务
- 高并发:支持数千个客户端同时读写
1.2 Kafka主要概念及设计思想
1.2.1 消息Message
Kafka中的数据单元称为消息,消息格式由很多字段组成,包括消息头部、key/value组成。其中消息头部包括消息的CRC码、消息版本号、属性、时间戳、键长度和消息体总长度。
- Key:消息键,对消息做Partition时使用,即决定消息被保存在某topic下的某个分区
- Value:消息体,保存实际的消息数据
- Timestamp:消息时间戳,用于流式处理及其他依赖时间的处理语义
1.2.2 主题Topic和分区Partition
Kafka中的Message由Topic进行分类,Topic是逻辑上的概念代表了一类消息,类似于数据库中的表。Topic可以被分为若干个分区Partition,通过Topic-Partition-Message这种方式来分散负载,message以追加的方式写入partition。
Partition上的每个消息都会分配一个唯一的序列号,称为offset位移,位移信息可以唯一定位到某个partition下的一条消息。实际上,这里提到的offset和消费者端的offset不是一个概念,每条消息在Partition中的offset是固定的,但是消费者消费该Partition的位移会随着消费进度不断前移。
1.2.3 生产者和消费者
Kafka系统中分为两种基本角色:生产者和消费者
- 生产者创建消息,默认会把消费均匀的分布到Topic的所有分区中
- 消费者读取消息,消费者可以订阅一个或多个Topic并按照消息生成的顺序读取。消费者通过消息的偏移量来区分已经读取的消息,在给定的分区里每个消息的偏移量都是唯一的。通常情况下,消费者会把每个分区最后读取的消息偏移量保存在Zookeeper中,如果消费者关闭或重启,这些保存的信息不会丢失
- 多个消费者组成一个消费群组Consumer Group,这些消费者共同读取一个Topic,群组保证每个分区只能被一个消费者使用。如果群组内的一个消费者失败,其它消费者可以接管失效消费者的工作
1.2.4 集群和Broker
一个独立的Kafka服务器称为broker,broker接收来自生产者的消息、为消息设置offset并将消息保存到磁盘;broker同时为消费者提供服务,响应分区读取的请求,返回消息。
集群是由broker组成的,每个集群都有一个broker充当集群控制器的角色,负责监控broker以及将分区分配给broker。在集群中,一个分区从属于一个broker,称为leader;此时如果分区分配给多个broker,其它broker称为follower。Leader和follower之间会发生分区复制,这种复制机制为分区提供了消息冗余,如果其中一个broker异常,其它broker可以接管。
1.2.5 Replica和ISR
上面提到Kafka集群中有不同的broker,如何来保证这些broker的可靠性,主要是依靠多副本Replica的机制,备份多份消息数据。Replica分为leader和follower
- Follower不能提供服务给客户端,只是作为leader的备份,一旦leader异常会从follower中选举出新的leader
- Kafka保证同一个partition的多个replica一定不会分配在同一个broker上
- Replica数在创建分区时指定
Kafka为每个Partition的Leader动态维护一个replica列表,记录和leader保持同步的follower集合,称为ISR(in-sync replica)列表
- 当ISR中的follower完成数据的同步之后,leader就会给producer发送ack
- 如果follower长时间未向leader同步数据,则该follower将被踢出ISR,该时间阈值由replica.lag.time.max.ms参数设定。
- Leader发生故障之后,就会从ISR中选举新的leader。
正常情况下,partition的所有replica都应该与leader保持同步,即所有的replica都在ISR列表中。当一部分replica落后leader超出一定范围后,Kafka会将这些replica踢出ISR;当这些replica重新追上leader后,又会重新加入到ISR中,这个过程是Kafka自动维护的。
1.2.6 一些设计理念
上文提到了Kafka中的一些主要概念,还有以下一些设计理念:
- Consumer Group:各个consumer可以组成一个组,每个消息只能被组中的一个consumer消费,如果一个消息可以被多个consumer消费的话,那么这些consumer必须在不同的组。
- 消息状态:在Kafka中,消息的状态被保存在consumer中,broker只记录一个offset值,这就意味着如果consumer处理不好的话,broker上的一个消息可能会被消费多次。
- 消息持久化:Kafka中会把消息持久化到本地文件系统中,并且保持极高的效率。
- 消息有效期:Kafka会长久保留其中的消息,以便consumer可以多次消费
- Push/Pull异步模式:Kafka中的Producer和consumer采用的是push-and-pull模式,即Producer只管向broker push消息,consumer只管从broker pull消息,两者对消息的生产和消费是异步的。
- Kafka集群中broker之间的关系:不是主从关系,各个broker在集群中地位一样,可以随意的增加或删除任何一个broker节点。对于Partition来说,如果有多个replica会存在一个leader,其它为follower,不同的replica在不同的broker上。
1.3 Kafka适用场景
1.3.1 消息传输
Kafka具备生产者和消费者解耦以及批处理消息功能,可以替代传统的消息代理或消息总线。同时与大多数消息传递系统相比,Kafka具有更好的吞吐量、内置分区和副本机制功能,既能保证高性能又能达到高可靠和高容错性,这使其成为大规模消息处理应用程序的理想解决方案。在这个领域,Kafka可与传统的消息传递系统(如ActiveMQ或 RabbitMQ)相媲美。
1.3.2 网站活动跟踪
Kafka最早是用于重建用户行为数据追踪系统的,将用户活动跟踪管道重建为一组实时发布/订阅源,以消息的形式发布到Kafka某个对应的Topic上。这些源可用于订购一系列用例,包括实时处理,实时监控以及加载到Hadoop或离线数据仓库系统以进行脱机处理和报告。
1.3.3 日志聚合
常见的案例是利用Kafka将分散在不同机器上的日志进行汇总收集,并存储在分布式文件系统如HDFS中。与其它主流的日志框架如Flume等相比,Kafka具备更好的性能、以及具备完备的可靠性解决方案和更低的时延。
1.3.4 流处理
Kafka用户在处理由多个阶段组成的管道时,原始输入数据从Kafka主题中消费,然后聚合,丰富或以其他方式转换为新主题以供进一步消费或后续处理。此类处理管道基于各个主题创建实时数据流的图形。Kafka从0.10.0.0开始推出全新的流式处理组件Kafka Streams,可用于执行此类数据处理。
1.3.5 Event Sourcing
Event Sourcing是一种应用程序设计风格(领域驱动设计DDD的名词),使用时间序列来表示状态变更。Kafka也是用不可变更的消息序列来抽象化表示业务消息的,因此非常适合作为这类应用的后端存储。
2、Kafka集群架构
2.1 分布式架构
Kafka集群架构由producer、consumer group和broker组成,producer是消息的生产者、consumer是消息的消费者、broker是Kafka服务器存放消息。生产者和消费者消费消息是面向Topic进行操作,每个Topic创建的时候指定分区数和副本数,分区能够提高吞吐方便扩展、副本数可以提高高可用性。
- Producer:生产者,即消息的产生者,是消息的入口。
- Broker:Broker是kafka实例,每个服务器上有一个或多个kafka的实例,每个broker对应一台服务器。每个kafka集群内的broker都有一个不重复的编号,如图中的broker-0、broker-1、broker-2等
- Topic:消息的主题,消息保存在topic中。每个broker上可以创建多个topic。
- Partition:Topic的分区,每个topic可以有多个分区,分区的作用是提高kafka的吞吐量。同一个topic在不同的分区的数据是不重复的。
- Replica:每一个分区都有多个副本分为Leader和Follower,副本的作用是保证高可用。当Leader出现故障的时候会从follower中重新选择一个新的Leader。
- 在kafka中默认副本的最大数量是10个,且副本的数量不能大于Broker的数量
- 同一个分区follower和leader不能在同一个机器上
- Message:每一条发送的消息主体。
- Consumer:消费者,即消息的消费方,是消息的出口。
- Consumer Group:将多个消费者组成一个消费者组,
- 在kafka的设计中同一个分区的数据只能被消费者组中的某一个消费者消费。
- 同一个消费者组的消费者可以消费同一个topic的不同分区的数据
- Zookeeper:kafka集群依赖zookeeper来保存集群的的元信息,来保证系统的可用性。
2.2 数据可靠性保证
在消息系统中,保证消息在生产和消费过程中的可靠性是十分重要的,在实际消息传递过程中,可能会出现如下情况:
- 一个消息发送失败
- 一个消息被发送多次
- 最理想的情况:exactly-once,一个消息发送成功且仅发送了一次
为了保证producer发送的数据能可靠的发送到指定的topic,topic的每个partition收到producer发送的数据后都需要向producer发送确认消息,即acknowledgement确认收到消息,如果producer收到ack就会进行下一轮的发送,否则重新发送数据。
2.2.1 数据可靠性保证方式
分区所在的broker什么时候返回ACK消息给到producer,有以下选择:
- 方案1:Leader落盘后直接返回ACK
- 优点:延迟低,无需等待Follower同步完成
- 缺点:如果producer收到ACK后leader故障,follower还未同步成功,造成消息丢失
- 方案2:Follower全部同步成功后返回ack
- 优点:leader选举时容忍n台节点故障,需要n+1个副本,leader故障时随便选一个follower即可
- 缺点:高延迟,需要等待follower都同步成功
- 方案3:半数以上follower同步成功后返回ack
- 优点:低延迟,不存在消息丢失的问题
- 缺点:需要更多的副本。选举新leader时,容忍n台节点故障,需要2n+1个副本。
Kafka默认选择所有follower同步完成后再返回,方案3需要2n+1的副本、方案2只需要n+1的副本,而且方案二虽然同步所有的follower但在同一个局域网内时延是可以接受的。
2.2.2 ACK参数配置
Producer根据ACKS参数设置不同配置不同的策略:
- ACKS=0:producer不等待broker的ack,这种方式提供了一个最低的延迟,broker一接收到还没写入磁盘就已经返回,当broker故障时可能存在数据丢失
- ACKS=1:producer等待broker的ack,partition的leader落盘成功后返回ack,如果follower同步成功之前leader故障,存在数据丢失
- ACKS=ALL:producer等待leader、follower全部落盘成功后返回ack,这种情况不存在数据丢失,但是,如果落盘成功后,leader故障还未来得及向producer发送ack,producer认为失败了会重新发送消息,就会造成数据重复
生产者可以设置重试参数,在遇到错误时候可以重试,但是会带来一个风险就是导致消息重复。如果要保证exactly-once,kafka需要进行重复判断保证幂等,如果已经有该条offset数据就不重新写入了。
2.3 基于Zookeeper的选主流程
Zookeeper是Kafka集群中的重要组件,topic和partition存储在Broker物理节点中,ZooKeeper负责维护这些Broker。
Kafka集群中有一个broker会被选举为controller,负责管理集群的broker上下线、所有topic的分区副本分配以及leader选举等工作。当某个partition的leader出现故障时,触发leader选举过程:
- Kafka Controller监听Zookeeper中brokers的状态,当发现异常时触发leader选举
- 从zookeeper中获取最新的ISR列表
- 从ISR列表中选举新的leader
- Leader选举完成后,更新Zookeeper中的Leader和ISR信息
3、Kafka核心原理
3.1 消息引擎两种模式
Kafka在本质上是一个消息系统引擎,用于在不同应用系统之间传输消息的系统。消息引擎系统中最为关键的部分是消息传输协议,指定了消息在不同系统之间的传输方式。其中两种最常见的消息传输模式是点对点模式和发布订阅模式。
3.1.1 点对点模式
点对点(Point-to-point)模式通常是基于拉取或者轮询的消息传送模型,这个模型的特点是发送到队列的消息被一个且只有一个消费者进行处理,发送者和消费者是一对一的关系。生产者将消息放入消息队列后,由消费者主动的去拉取消息进行消费,一旦消息被消费就会从队列中移除该消息。
点对点模型的的优点是消费者拉取消息的频率可以由自己控制。但是消息队列是否有消息需要消费,在消费者端无法感知,所以在消费者端需要额外的线程去监控。
3.1.2 发布订阅模式
发布/订阅模式是一个基于消息发布的消息传送模型,模型中可以有多种不同的订阅者。生产者将消息放入消息队列后,队列会将消息推送给订阅过该类消息的消费者。由于是消费者被动接收推送,所以无需感知消息队列是否有待消费的消息。
3.2 Kafka中的生产者
Producer生产端的工作原理如图所示,KafkaProducer接收到待发送的消息ProducerRecord后先对其进行系列化,然后结合本地的Topic缓存由Partitioner确定分区,最后追加的方式写入到内存中的消息缓冲池。之后,KafkaProducer中的Sender IO线程将缓冲池的消息分批次的发送到对应的broker。所以整个Producer生产者是一个异步处理的过程,先将消息发送到缓存区,再异步的写入到broker的磁盘中。
1)序列化和计算目标分区
Broker希望接收到的消息的键和值都是字节数组,因此生产者会使用key.serializer将键对象序列化为字节数组,同时结合缓存的Topic分区数信息传递给Partitioner进行分区操作。
2)追加写入消息缓存区
Producer创建时默认会创建一个32MB的缓存空间,用来存放待发送的消息。缓冲区其实是一个HashMap的数据结构,其中包含消息批次信息,里面分别保存了每个topic下的batch队列,发往不同分区的消息保存在对应分区下的batch队列中。
3)Sender线程预处理及消息发送
- 轮询查询缓存区寻找已经做好发送准备的分区
- 将轮询获得的各个batch按照目标分区所在的leader broker进行分组
- 将分组后的batch通过socket发送到各个broker
- 等到broker端返回response
4)Sender线程处理respons
Sender发送请求到broker,Broker处理完成后返回相应的response。
3.3 Kafka中的消费者
1)消费方式
Kafka中的consumer采用pull方式主动从broker拉取数据,此时会传入timeout参数,如果当前没有数据可消费,consumer会等待一段时间,直到timeout超期才返回。
2)消费者组
前文提到消费者组的概念,Kafka中的消费者从属于消费者组。一个群组内的消费者订阅的同一个Topic,每个消费者接收Topic中的一部分分区。Kafka为每个consumer group定义了5个状态:
- Empty:表名group下没有任何active consumer,但可能包含位移信息。每个Consumer Group在创建的时候是empty状态,但是当group消费一段时间所有的consumer离开组后也会处于这个状态
- PreparingRebalance:表明group正在准备group rebalance,此时group等待consumer的joinGroup请求,直到所有成员都成功加入
- AwaitingSync:表明所有成员已经加入group并等待leader consumer发送分区分配方案
- Stable:表明group开始正常消费
- Dead:表明group已被废弃,group内没有active成员并且group内的所有元数据已删除
3)Kafka消费流程
- 创建Kafka消费者:在读取消息前,需创建KafkaConsumer对象
- 订阅Topic:使用subscribe()订阅Topic
- 轮询查询:通过poll()方法轮询向broker请求数据,轮询包括Consumer Group协调、分区再均衡、发送心跳和获取数据
4)提交和偏移量
消费者会往_consumer_offset的特殊主题中发送消息,消息中记录了每个分区的偏移量。当消费者发生奔溃或者新的消费者加入消费组的时候,会触发分区再平衡,此时每个消费者可能被分配到新的分区。为了能够继续之前的工作,消费者需要读取每个分区最后一次提交的偏移量,然后从偏移量指定的地方继续处理。在Kafka中偏移量的提交方式有很多种:
- 自动提交:默认每隔5s将poll()方法接收到的最大偏移量提交上去,提交时间由参数auto.commit.interval.ms控制
- 提交当前偏移量:由应用程序决定何时提交偏移量,使用commitSync()提交,提交成功马上返回,提交失败则抛出异常
- 提交特定的偏移量:手动提交时带入分区和偏移量的map
3.4 Topic数据存储机制
Producer将数据写入kafka后,集群会将数据保存到磁盘中。每个Topic有多个partition,每个partition在服务器上的表现形式为一个个文件夹,每个文件夹下有多个segment组,每组segment文件中又包含.index文件、.log文件、.timeindex文件三个文件,log文件就实际是存储message的地方,而index和timeindex文件为索引文件,用于检索消息。
当查找消息时,配置segment+offset进行查找,如上图所示,查找一个offset为221118的message,过程如下:
- 先找到offset为221118的message所在的segment文件(利用二分法查找),这里在第二个segment文件
- 找到对应segment的.index文件,起始位置偏移量为221113+1,要查找的offset为221118的message在该index的偏移量为5。同样利用二分法查找相对offset小于或者等于指定的相对offset的索引条目中最大的那个相对offset,所以找到的是相对offset为4的这个索引
- 根据找到的相对offset为4的索引(offset为221117)确定message存储的物理偏移位置为256。打开数据文件,从位置为256的那个地方开始顺序扫描直到找到offset为221118的那条Message。
因此,消费者利用segment+有序offset+稀疏索引+二分查找+顺序查找等多种手段来实现高效的查找数据和消费。
4、总结
Kafka作为一个分布式开源的消息系统,具备高吞吐低时延、可扩展和高可靠性,已广泛适用于日志处理、消息队列、流式处理等系统中。本文简要介绍了Kafka的特性、主要概念以及设计理念,并结合Kafka的集群架构展开数据的可靠性保证和高可用实现机制,最后简要介绍了Kafka生产者和消费者的过程,加深对Kafka实现原理的理解。
参考资料:
- 《Apache Kafka实战》,胡夕著
- 《Kafka权威指南》,薛命灯译
- https://blog.csdn.net/Michaelwubo/article/details/126341364
- https://www.cnblogs.com/jingzh/p/16075548.html
- https://www.cnblogs.com/fhblikesky/p/13692666.html
- https://blog.csdn.net/khuangliang/article/details/107776046
转载:https://blog.csdn.net/solihawk/article/details/127841046