Kafka学习记录

Kafka的基本组成
Kafka集群中生产者将消息发送给以Topic命名的消息队列Queue中,消费者订阅发往以某个Topic命名的消息队列Queue中的消息。其中Kafka集群由若干个Broker组成,Topic由若干个Partition组成,每个Partition里的消息通过Offset来获取。
基本组成包括:

Broker:一台Kafka服务器就是一个Broker,一个集群由多个Broker组成,一个Broker可以容纳多个Topic,Broker和Broker之间没有Master和Standby的概念,他们之间地位是平等的
Topic:每条发送到Kafka集群的消息都属于某个主题,这个主题就称为Topic。物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存在一个或多个Broker上,但是用户只需指定消息主题Topic即可生产或消费数据而不需要关心数据存放在何处。
Partition:为了实现可扩展性,一个非常大的Topic可以被分为多个Partition,从而分布到多台Broker上。Partition中的每条消息都会被分配一个自增Id(Offset)。Kafka只保证按照一个Partition中的顺序将消息发送给消费者,但是不保证单个Topic中多个Partition之间的顺序。
Offset:消息在Topic的Partition中的位置,同一个Partition中的消息随着消息的写入其对应的Offset也自增。
Replica:副本,Topic的Partition有N个副本,N为副本因子。其中一个Replica为Leader,其他都为Follower,Leader处理Partition的所有读写请求,Follower定期同步Leader上的数据。
Message:消息是通信的基本单位。每个Producer可以向一个Topic发布消息
Producer:消息生产者,将消息发布到指定的Topic中,也能够决定消息所属的Partition:比如基于Round-Robin或者Hash算法
Consumer:消息消费者,向指定的Topic获取消息,根据指定Topic的分区索引及其对应分区上的消息偏移量来获取消息
**Consumer Group:**消费者组,每个消费者都属于一个组。当消费者具有相同组时,消息会在消费者之间负载均衡。一个Partition的消息只会被相同消费者组中的某个消费者消费。不同消费者组是相互独立的。
Zookeeper:存放Kafka集群相关元数据的组件。Zookeeper集群中保存了Topic的状态信息,例如分区个数、分区组成、分区的分布情况等;保存Broker的状态信息;保存消费者的消费信息等。通过这些信息,Kafka很好地将消息生产、消息存储、消息消费的过程结合起来。

一个典型的Kafka集群中包含若干个Producer(可以是某个模块下发的Command,或者是Web前端产生的PageView,或者是服务器日志,系统CPU、Memory等),若干个Broker(Kafka集群支持水平扩展,一般Broker数量越多,整个Kafka集群的吞吐率也就越高),若干个Consumer Group,以及一个Zookeeper集群。Kafka通过Zookeeper管理集群配置。Producer使用Push模式将消息发布到Broker上,Consumer使用Pull模式从Broker上订阅并消费消息。

简单的消息发送流程如下:

  1. Producer根据指定的路由方法,将消息Push到Topic的某个Partition里
  2. Kafka集群接收到Producer发过来的消息并持久化到硬盘,并保留消息指定时长(可配置),不关注消息是否被消费。
  3. Consumer从Kafka集群Pull数据,并控制获取消息的Offset

Kafka内部的通信协议
Kafka内部各个Broker之间的角色并不是完全相等的,Broker内部负责管理分区和副本状态以及异常情况下分区的重新分片等这些功能的模块称为KafkaController。每个Kafka集群中有且只有一个Leader状态的KafkaController,当其出现异常时,其余Standby状态的KafkaController会通过Zookeeper选举出有一个Leader状态的KafkaController。

Broker的控制管理模块
每个Broker内部都会存在一个KafkaController模块,但是有且只有一个Broker内部的KafkaController模块对外提供控制管理Kafka集群的功能,例如负责Topic的创建、分区的重分配以及分区副本Leader的重新选举等。
KafkaController内部的监听器
KafkaController内部通过监听函数来维护集群的元数据。

TopicChangeListener:注册在 /broker/topics 路径,监听Topic的创建
AddPartitionListener:在Topic创建过程中会在 /broker/topics/[topic]目录下注册AddPartitionListener用于监听Topic分区的变化
PartitionReassignedListener:KafkaController转换为Leader的过程中在路径 /admin/reassign_partitions注册了PartitionReassignedListener用于监听Topic分区的重分配。在正式启动重分配之前会判断是否需要进行重分配,重分配之后的AR列表和当前的AR列表不相同并且重分配之后的AR列表所在的Broker Server都在线,满足上面两个条件才会触发分区的重分配。
ReassignedPartitionsIsrChangeListener:当Leader Replica所在的Broker Server接收到来自Follower Replica的FetchRequest请求时,KafkaApis的handleFetchRequest会统计每个Replica的状态,一旦发现改Replica同步上Leader Replica之后,此时会调用Partition的updateLeaderHWAndMaybeExpandIsr及时更新 /brokers/topics/[topic]/partitions/partitionId/state/ 目录上的Partition状态,包括Leader、ISR等信息,监听到分区状态发生变化会触发ReassignedPartitionsIsrChangeListener
PreferredReplicaElectionListener:每个Partition可以有多个Replica,即AR列表。在这个列表中的第一个Replica称为“Preferred Replica”,当创建Topic时,Kafka要确保所有的Topic的“Preferred Replica”均匀地分布在Kafka集群中。Topic的Partition需要重新均衡Leader Replica至Preferred Replica,此时会触发PreferredReplicaElectionListener
BrokerChangeListener:Broker Server的上下线影响着其中所有Replica的状态,因此ReplicaStateMachine在路径为/broker/topic的路径上注册了BrokerChangeListener,用于监听Broker Server的上下线。
Broker Server上线时步骤为:
Leader状态的KafkaController同步Kafka集群所有的Topic信息给新上线的Broker Server。
将原本位于该Broker Server上的所有Replica状态切换至OnlineReplica
如果Partition的Replica有且只有一个,并且正好位于Broker Server上,则切换Partition状态至OnlinePartition。
如果分区重分配之前由于该Broker Server下线导致推出的话,则尝试重新进行分区重分配。
如果之前由于Broker Server下线导致对应的Replica无法删除的话,则恢复删除流程。
Broker Server下线步骤:
更新ControllerContext内部正在下线的Broker Server列表
将Leader Replica位于该Broker Server上的分区状态切换为OnlinePartition,紧接着触发分区状态切换为OnlinePartition,利用OfflinePartitionLeaderSelector副本选举策略进行Leader Replica的选举。
将位于该Broker Server上的Replica状态切换为OfflineReplica
如果对应Replica的Topic处于删除队列中的话,则标记暂时无法删除。
DeleteTopicsListener:监听Topic的删除
生产者
生产者是指消息的生成者。生产者可以通过特定的分区函数决定消息路由到Topic的某个分区。消息的生产者发送消息有两种模式,分别为同步模式和异步模式。
kafka.javaapi.producer.Producer#send方法发送
指定 metadata.broker.list 属性,配置Broker地址
指定 partitioner.class 属性,配置分区函数,分区函数决定路由。分区函数必须实现 kafka.producer.Partitioner的 partition接口,参数为消息key值,分区总数,返回值为分区的索引。

Producer内部包括以下几个主要模块:

ProducerSendThread:当producer.type配置为async,则其主要用于缓存客户端的KeyedMessage,然后累积到batch.num.messages配置数量或者间隔 queue.enqueue.timeout.ms配置的时间还没有获取到新的客户端的KeyedMessage,则调用DefaultEventHandler将KeyedMessage发送出去
ProducerPool:缓存客户端和各个Broker Server的通信,DefaultEventHandler从ProducerPool中获取和某个Broker Server的通信对象SyncProducer,然后通过SyncProducer将KeyedMessage发送给指定的Broker Server。
DefaultEventHandler:将KeyedMessage集合按照分区规则计算不同Broker Server所应该接收的部分KeyedMessage,然后通过SyncProducer将KeyedMessage发送出去。在DefaultEventHandler模块内部提供来SyncProducer发送失败的重试机制和平滑扩容Broker Server的机制。
发送模式
生产者由两种发送模式:同步和异步
当producer.type配置为sync时,同步发送消息。
当producer.type配置为async时,异步发送消息。

消费者
Kafka提供了两种不同的方式来获取消息:简单消费者和高级消费者。简单消费者获取消息时,用户需要知道待消费的消息位于哪个Topic的哪个分区,并且该目的分区的Leader Replica位于哪个Broker Server上;高级消费者获取消息时,只需要指定待消费的消息属于哪个Topic即可。

简单消费者
简单消费者提供的客户端API称为低级API,本质上客户端获取消息最终时利用FetchRequest请求从目的端Broker Server拉取消息。
FetchRequest请求中可以指定Topic的名称,Topic的分区,起始偏移量、最大字节数。
客户端无论生产消息还是消费消息,最终都是通过与目的地端Broker Server建立通信链路,并且以阻塞模式允许,然后通过该条链路将不同的请求发送出去。

高级消费者
高级消费者以Consumer Group(消费组)的形式来管理消息的消费,以Stream(流)的形式来提供具体消息的读取。Stream是指来自若干个Broker Server上的若干个Partition的消息。客户端需要正确设置Stream的个数,并且应该针对每个Stream开启一个线程进行消息的消费。一个Stream代表了多个Partition消息的聚合,但是每一个Partition只能映射到一个Stream。
消息的最终获取是通过遍历KafkaStream的迭代器ConsumerIterator来逐条获取的,其数据来源于分配给该KafkaStream的阻塞消息队列BlockingQueue,而BlockingQueue的数据来源针对每个Broker Server的FetchThread线程。FetchThread线程会将Broker Server上的部分Partition数据发送给对应的阻塞消息队列BlockingQueue,而KafkaStream正是从该阻塞消息队列BlockingQueue中不断的消费消息。
ConsumerThread本质上是客户端的消费线程,消费若干个Partition上的数据,并且与BlockingQueueu相互映射,只要确定了ConsumerThread和Partition之间的关系,也就确定了BlockingQueue和Partition之间的关系。Kafka提供了两种ConsumerThread和Partition的分配算法Range(范围分区分配)和RoundRobin(循环分区分配)
高级消费者中,每个具体消费者实例启动之后会在/consumers/[group]/ids/的Zookeeper目录下注册自己的id;Kafka集群内部Topic会在/brokers/topics/[topic]/的Zookeeper目录下注册自己的Partition,因此消费者实例一旦发现以上2个路径的数据发生变化时,则会触发高级消费者的负载均衡流程,除此之外,消费者实例一旦和Zookeeper的链接重新建立时也会触发高级消费者的负载均衡流程。

kafka在高并发的情况下,如何避免消息丢失和消息重复?
消息丢失解决方案:
首先对kafka进行限速, 其次启用重试机制,重试间隔时间设置长一些,最后Kafka设置acks=all,即需要相应的所有处于ISR的分区都确认收到该消息后,才算发送成功
消息重复解决方案:
消息可以使用唯一id标识
生产者(ack=all 代表至少成功发送一次)
消费者 (offset手动提交,业务逻辑成功处理后,提交offset)
落表(主键或者唯一索引的方式,避免重复数据)
业务逻辑处理(选择唯一主键存储到Redis或者mongdb中,先查询是否存在,若存在则不处理;若不存在,先插入Redis或Mongdb,再进行业务逻辑处理)

kafka怎么保证数据消费一次且仅消费一次
幂等producer:保证发送单个分区的消息只会发送一次,不会出现重复消息
事务(transaction):保证原子性地写入到多个分区,即写入到多个分区的消息要么全部成功,要么全部回滚流处理EOS:流处理本质上可看成是“读取-处理-写入”的管道。此EOS保证整个过程的操作是原子性。注意,这只适用于Kafka Streams

kafka保证数据一致性和可靠性
数据一致性保证
一致性定义:若某条消息对client可见,那么即使Leader挂了,在新Leader上数据依然可以被读到
HW-HighWaterMark: client可以从Leader读到的最大msg offset,即对外可见的最大offset, HW=max(replica.offset)
对于Leader新收到的msg,client不能立刻消费,Leader会等待该消息被所有ISR中的replica同步后,更新HW,此时该消息才能被client消费,这样就保证了如果Leader fail,该消息仍然可以从新选举的Leader中获取。
对于来自内部Broker的读取请求,没有HW的限制。同时,Follower也会维护一份自己的HW,Folloer.HW = min(Leader.HW, Follower.offset)

数据可靠性保证
当Producer向Leader发送数据时,可以通过acks参数设置数据可靠性的级别
0: 不论写入是否成功,server不需要给Producer发送Response,如果发生异常,server会终止连接,触发Producer更新meta数据;
1: Leader写入成功后即发送Response,此种情况如果Leader fail,会丢失数据
-1: 等待所有ISR接收到消息后再给Producer发送Response,这是最强保证

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值