基本概念
Kafka是一个成熟的消息队列,是一个天然分布式、支持分区(partition)、多副本(replica)。是基于Zookeeper协调的分布式消息系统。它最大特性就是可以实时的处理大量数据以满足各种需求场景:比如:基于Hadoop的批处理系统,低延迟的实时系统、storm/Spark流式处理引擎,web/nginx日志、访问日志,消息服务等等。Kafka使用Scala语言编写的。
Zookeeper用于维护Kafka集群的状态和元数据信息,例如主题和分区的分配信息、消费者组和消费者偏移量等。
关于Zookeeper可以参考我之前的文章了解:
Kafka的特性
- 高吞吐量、低延迟:Kafka每秒可以处理几十万条消息,它的延迟最低只有几毫秒
- 可扩展性:Kafka集群支持热扩展
- 持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失
- 容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败)
- 高并发:支持数千个客户端同时读写
Kafka的场景应用
- 日志收集:一个公司可以使用Kafka收集各种服务的log,通过Kafka以通义接口服务的方式开放给各种consumer,例如Hadoop、Hbase、Solr等。
- 消息系统:解耦、异步、削峰、分布式一致性等。 关于为什么使用MQ(为什么使用消息队列)可参考文章:
- 流式处理:比如:storm/Spark流式处理引擎
Kafka的架构是怎么样的
Kafka的架构是整体设计比较简单,是显示的分布式架构,主要由Producer(生产者)、broker(Kafka集群)、和consumer(消费者)组成。
如图所示:
Producer(生产者):生产者负责将消息发布到Kafka集群中的一个或多个Topic(主题)中,每个Topic包含一个或多个Partition(分区)
Topic:主题,是承载消息的逻辑容器,在实际使用中多用来区分具体的业务
Partition:分区,一个有序不变的消息序列。每个主题可以有多个分区
Consumer(消费者):消费者负责从Kafka集群中的一个或多个主题消费消息,并将消息的offset(偏移量)提交回Kafka以保证消息的顺序性和一致性。
偏移量:offset,表示分区中每条消息的位置信息,是一个单调递增且不变的值
Kafka集群:Kafka集群是由多个Kafka节点(Broker)组成的分布式系统。每个节点都可以存储一个或多个Topic(主题)的Partiton(分区副本),以提高可用性和容错能力
Leader Broker:Leader Broker是分区的副本,它是负责处理消息读写请求的节点。生产者将消息发送到LeaderBroker,消费者从Leader Broker中拉取消息
Follower Broker:Follower Broker是Leader Broker的备份节点,它负责与LeaderBroker进行数据同步,以保持自己的数据与Leader Broker一致
在集群中,每个分区都有一个Leader Broker和多个Follower Broker,只有Leader Broker才能处理生产者和消费者的请求,而Follower Broker只是Leader Broker的备份,用于提供数据的冗余备份和容错能力。如果Leader Broker发生故障,Kafka集群将会自动将Follower Broker提升为新的Leader Broker,从而实现高可用性和容错能力
架构图:
Kafka中比较重要的思想
- ConsumerGroup(消费者群组):各个消费者consumer可以组成一个组,每个消息中只能被族中的一个consumer消费,如果一个消息可以被多个consumer消费的话,那么这些consumer必须在不同的组
- 消息状态:在Kafka中,消息的状态被保存在consumer中,broker不会关心哪个消息被消费了或被谁消费了,只记录一个offset值(指向partition中下一个要被消费的消息位置),这就意味着如果consumer处理不好的话,broker上的一个消息可能会被消费多次
- 消息持久化:Kafka会把消息持久化到本地文件系统中,并且保持极高的效率
- 消息有效期:Kafka会长久保留其中的消息,以便consumer可以多次消费,当然其中很多细节是可配置的
- 批量发送:Kafka支持以消息集合为单位进行批量发送,以提高push效率
- push-and-pull:Kafka中的producer和consumer采用的是push-and-pull模式,即producer只管向broker push消息,consumer只管从broker中pull消息。两者对消息的生产和消费是异步的
- Kafka集群中broker之间的关系:不是主从关系,各个broker在集群中的地位是一样的,我们可以随意的增加或删除任何一个broker节点。
- 负载均衡方面:Kafka提供了一个metadata API来管理broker之间的负载(仅对Kafka 0.8.x 而言,对于0.7.x主要是靠zookeeper来实现负载均衡)
- 同步异步:producer采用异步push方式,极大提高了Kafka系统的吞吐量(并且可以通过参数控制是采用同步还是异步方式)
- 分区机制partition:Kafka的broker端支持消息分区,producer可以决定把消息发送到哪个分区,在一个分区中消息的顺序就是producer发送消息的顺序,一个主题中可以有多个分区(partition),具体分区数量也是可配置的。分区的意义很大。
- 离线数据装载:Kafka由于对可扩展的数据持久化的支持,它也非常适合向Hadoop或者数据仓库中进行数据装载
- 插件支持:现在已经有不少活跃社区开发出很多插件来支持扩展Kafka的功能,如用来配合Storm、Hadoop、Flume等相关的插件。
当我们需要自己设计一个MQ的时候也可以从上述比较好的思想中提炼出我们所需要的:
关于如何写一个消息队列,该如何进行架构设计,可参考文章:
场景题-如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路。
Kafka为什么这么快
消息发送方面:
- 批量发送:Kafka通过将多个消息大巴拼成一个批次,减少了网络传输和磁盘写入的次数,从而提高了消息的吞吐量和传输效率
- 异步发送:生产者可以异步发送消息,不必等待每个消息的确认,这大大提高了消息发送的效率
- 消息压缩:支持对消息进行压缩,减少网络传输的数据量
- 并行发送:通过将数据分别不在不同的分区(Partitions),生产者可以并行发送这些消息,从而提高了吞吐量
消息存储方面:
- 零拷贝技术:Kafka使用零拷贝技术来避免了数据的拷贝操作性能问题,降低了内存和CPU的使用率,提高了系统的性能
- 磁盘顺序写入:Kafka把消息存储在磁盘上,且以顺序的方式写入数据。顺序写入比随机写入速度快很多,因为它减少了磁头寻道时间。避免了随机读写带来的性能损耗,提高了磁盘的使用效率
- 页缓存:Kafka将其数据存储在磁盘中,但在访问数据时,它会先将数据加载到操作系统中的页缓存中,并在页缓存中保留一份副本,从而实现快速的数据访问。
- 稀疏索引:Kafka存储消息是通过分段的日志文件,每个分段都有自己的索引。这些索引文件中的条目不是对分段中的每条消息都建立索引,而是每隔一定数量的消息建立一个索引点,这就构成了稀疏索引。稀疏索引减少了索引大小,使得加载内存中的索引更小,提高了查找特定消息的效率
- 分区和副本:Kafka采用分区和副本的机制,可以将数据分散到多个节点上进行处理,从而实现了分布式的高可用性和负载均衡
消息消费方面:
- 消费者群组:通过消费者群组可以实现消息的负载均衡和容错处理
- 并行消费:不同的消费者可以独立地消费不同的分区,实现消费的并行处理
- 批量拉取:Kafka支持批量拉取消息,可以一次性拉取多个消息进行消费。减少网络消耗,从而提升性能
Kafka如何保证消息不丢失
正常情况下,消息丢失大概分为三种情况:
- 生产者消息丢失(Producer端发送消息到Kafka Broker时丢失)
- Kafka(MQ)本身将消息弄丢了(Kafka 处理消息进行同步持久化时失败)
- 消费者消费的时候消息丢失(Consumer从Kafka Broker端拉取数据进行消费出现异常)
注意:**Kafka只对已提交的消息做最大限度地持久化保证不丢失,但是办法保证100%。**后面会讲
Producer(生产者)角度
消息的生产者,消息发送给Kafka集群的过程中有可能会出现异常失败。所以需要有机制来确保消息能够成功发送。(但是还是存在网络波动的问题无法保证一次消息一定能发送成功)。如果没有从成功需要重新发送知道成功。
我们在使用Kafka发送消息的时候,通常使用的时producer.send(msg)
来发送消息,这是一种异步发送
,发送消息的时候方法会立即返回,但不一定代表消息发送成功了。当时方法prodcuer.send(msg).get()
是同步等待返回的。
所以我们通常为了保证消息在发送不丢失,会建议使用producer.send(msg, callback)
方法,这个方法支持传入一个callback,我们可以在消息发送的时候进行重试。同时Producer还提供了一些配置参数来提升发送成功率:
bash
acks=-1 # 或者 acks=all 该参数表示Leader 和 Follower都接受成功时确认,可以最大限度保证消息不丢失,但是吞吐量低。
retries=3 # 重试次数,也可以设置为max ,一旦失败就会无限重试,卡在这里。
retry.backoff.ms = 300 # 消息发送超时或失败后,间隔的重试时间
acks = 0:表示Producer请求立即返回,不需要等待Leader的任何确认。这种方案有最高的吞吐率,但是不保证消息是否真的发送成功
acks = -1:等价于(acks = all),表示分区Leader必须要等待消息被成功写入到所有的ISR副本(同步副本)中才认为Producer请求成功。这种方案提供最高的消息持久性保证,但是理论上吞吐率也是最差的。
acks = 1:表示Leader副本必须应答此Producer请求并写入消息到本地日志,之后Producer请求被认为成功。如果此时Leader副本应该请求之后挂掉了,消息会丢失。这个方案,提供了不错的持久性保证和吞吐
Kafka本身丢失消息(broker集群)
在实际业务场景会存在Kafka的Leader接收到了消息,但是还没有来得及同步给Follower就挂掉了,此时Follower变成了Leader,导致数据丢失。
在Kafka集群中有一些机制来保证消息的不丢失,比如:复制机制、持久化存储机制以及ISR机制。
- 持久化存储:Kafka使用持久化存储来存储消息。这意味着消息在写入Kafka时将被写入磁盘,这种方式可以防止消息因为节点宕机而丢失。
- ISR复制机制:Kafka使用ISR机制来确保消息不会丢失,Kafka使用复制机制来保证数据的可靠性。每个分区都有多个副本,副本可以分布在不同的节点上。当一个节点宕机时,其它节点上的副本仍然可以提供服务,保证消息不丢失。
当然在Kafka中还提供了一些配置参数来避免消息丢失的问题:
bash
replication.factor # 表示分区副本数量,replication.factor >1 当Leader副本挂了,Follower副本会被选举为Leader继续提供服务
min.insync.replicas # 表示 ISR 最少副本的数量,通常设置min.insync.replias > 1 这样才能有可用的follower副本执行替换,保证消息不丢失
unclean.leader.election.enable = false # 是否可以把非 ISR 集合中的副本选举为 Leader副本
消费者角度消息丢失(Consumer)
消费者消费消息的时候,消息还没有处理完成,便自动提交了offset。导致消息没有消费丢失掉。
所以就需要保证不要乱提交offset就行了。在这方面Kafka消费者会跟踪每个分区的offset(偏移量),消费者每次消费消息时,都会将offset向后移动。当消费者宕机或者不可用时,Kafka会将该消费者所消费的分区的offset保存下来,下次该消费者重新启动时,可以从上一次offset重新开始消费
另外,Kafka消费者还可以组成消费者组,每个消费者组可以同时消费多个分区。当一个消费者组中的消费者宕机或者不可用时,其他消费者仍然可以消费该组的分区,保证消息不丢失。
**同时也可以关闭自动提交offset,去手动提交offset,**避免拉取了消息以后,业务逻辑没处理完,提交偏移量后但是消费者挂了的问题:
bash
enable.auto.commit=false
如有问题,欢迎加微信交流:w714771310,备注- 技术交流 。或关注微信公众号【码上遇见你】。
好了,本章节到此告一段落。希望对你有所帮助,祝学习顺利。