1,kafka
1)概念
Kafka是一种高吞吐量的分布式发布订阅消息系统,是观察者模式的一种实现。
Kafka是一个分布式消息队列。
2)特点
- 高性能
Kafka是一个高吞吐量的消息队列,可支持每秒处理数以百万计的消息,对于大数据处理和实时数据流分析等场景非常适用。 - 数据持久化
Kafka将消息持久化到磁盘上,并支持数据复制和备份,以确保数据不会丢失。 - 多副本备份
Kafka将数据分成多个分区和副本,并将每个分区副本分布在不同的节点上,以确保系统的容错性和可靠性。 - 横向扩展能力
Kafka具有良好的可扩展性,可以通过增加消息生产者、消费者和Kafka集群节点来实现高性能和高可靠性。
3)场景
1>异步消息处理、解耦
解耦生产者和消费者;消息的阻塞不影响主流程进行。
举例:
- 消息系统——缓存消息等。
- 实时数据分析:
- 日志采集;
- 用户活动跟踪
记录web用户或者app用户的各种活动,如浏览网页、搜索、点击等活动,这些活动信息被各个服务器发布到kafka的topic中,然后订阅者通过订阅这些topic来做实时的监控分析,或者装载到hadoop、数据仓库中做离线分析和挖掘。 - 运营指标
Kafka也经常用来记录运营监控数据。包括收集各种分布式应用的数据,生产各种操作的集中反馈,比如报警和报告。
2>削峰填谷(缓存)
数据生产者和消费者之间的速度差异大,高并发场景下平衡系统压力。将峰值压力分散到较长时间段内,同时也提高了低谷期资源利用率。
2,架构(发布订阅模型(Pub-Sub))
使用Topic 作为消息通信载体,类似于广播模式;发布者发布一条消息,该消息通过topic传递给所有的订阅者。在广播之后才订阅的用户不能收到历史消息。
1)Producer(生产者)
向Kafka集群(Broker)中一个或多个topic push消息;
- 顺序写磁盘:每条消息都被追加(append)到分区(patition)中;
顺序写磁盘比随机写内存效率要高,保障kafka吞吐率。
2)Consumer(消费者)
- 订阅消息
消费者可以订阅一个或多个主题(topic),并从Broker拉(pull)相同topic的数据消费。 - 提交偏移量(Offset)
将Offset提交回kafka以保证消息的顺序性和一致性。 - Consumer Group(消费者组,CG)是逻辑上的一个订阅者。
1)每个Consumer都有一个Consumer Group;
可指定group name,若不指定属于默认的group。
2)同一Consumer Group,只能有一个Consumer消费partition的消息;
广播:每个consumer有一个独立的Consumer Group;
单播:所有的consumer同一个Consumer Group。
在生产环境中,consumer数量 = 分区数(parition数量)
。
3)Broker(Kafka实例)
每个节点的kafka实例都是一个代理(Broker)、Kafka实例。
多个 Kafka Broker 组成一个 Kafka Cluster。
1> Controller
角色类似于其他分布式系统Master的角色,负责管理分区状态、broker状态、集群元数据。
2>topic(主题)
承载消息的逻辑容器,本质就是一个目录,而topic是由一些Partition Logs(分区日志)组成。
- topic包含一个或多个分区(partition)
==》
保证了高可用、高吞吐量;
对应 server.properties 中的num.partitions=3
配置;
生产者循环发送数据给不同的partion,==》
保证server负载均衡。 - 同一个topic的不同partition存储在不同的broker上;
3>Parition(分区、消息队列):高吞吐量的核心设计。
一个有序不变的消息队列,每个patition物理上对应一个文件夹(存储该patition的所有消息和索引文件)。
- 同一个topic的不同partion分布在不同broker上。
==》
高可用 - 消费顺序:
同一个partition中有序消费;
但一个topic的整体(多个partition间)消费无序。 - 副本(replication)
每个partition有若干个副本(replication):1个Leader和若干个Follower。server.properties 配置中的default.replication.factor=N
。
Leader负责一切对分区的读写;
Follower负责同步Leader数据;Leader故障时,Follower通过选举成为新的Leader。
每个broker都会成为某些分区的leader和follower,因此集群负载是均衡的。 - 分区策略&原则:指定了patition,直接使用;未指定按分区策略分区。
可以通过在生产者配置中设置partitioner.class
参数来指定使用哪种分区策略。
1)DefaultPartitioner(默认分区策略)
根据消息的 key 值进行哈希计算,然后将哈希值与当前 Topic 的 partition 数量取模得出目标分区索引。如果消息的 key 为 null,则使用轮询的方式依次将消息发送到所有分区。
2)RoundRobinPartitioner(轮询分区策略)
将消息依次分配到每个 partition。
3)StickyPartitioner(黏性分区策略)
将同一个 key 的消息发送到同一个分区,以保证同一个 key 的消息在同一个分区中。如果消息的 key 为 null,则使用类似于 DefaultPartitioner 的方式(即哈希计算)将消息发送到不同的分区中。
4)自定义分区器
过滤脏数据,将数据中包含某个特殊字符的发往指定分区。可以实现org.apache.kafka.clients.producer.Partitioner
接口,并在 partitioner.class 参数中设置自定义分区策略的类名
4>offset(偏移量)
偏移量是一个单调递增的整数,它是 Kafka 为每个partition中的消息分配的唯一标识。在每个topic对应的partition中,消息会按照写入的顺序依次排列,每个消息都会被赋予一个连续的偏移量,以此来标记该消息在分区中的位置。
1>作用
- 消息定位:
消费者可以通过指定偏移量来精确地从分区中读取特定位置的消息。
比如,消费者可以从某个特定的偏移量开始消费,也可以从最新的偏移量开始,或者从最早的偏移量开始,这为消息的消费提供了极大的灵活性。 - 消息消费进度跟踪:消费者会记录自己已经消费到的偏移量,以此来标记消费进度。
当消费者重启或者重新分配分区时,就能够从之前记录的偏移量处继续消费,避免消息的重复消费或者遗漏消费。 - 分区消息管理
Kafka 会根据偏移量来管理分区内的消息存储。例如,当达到一定的条件(如消息的保留时间、磁盘空间限制等)时,Kafka 会删除旧的消息,而偏移量可以帮助确定哪些消息是可以被删除的(复杂度为O(1))。
有两种策略可以删除旧数据:
1)基于时间:log.retention.hours=168
2)基于大小:log.retention.bytes=1073741824
2>存储
- 格式:kafka的存储文件都是按照
offset.kafka
来命名;
用offset做名字的好处是方便查找。例如查询位于2049的位置,只要找到2048.kafka的文件即可。the first offset是00000000000.kafka
。 - 位置:
旧版本(Kafka 0.9 之前):消费者的偏移量信息存储在 zk中。
新版本(Kafka 0.9 及以后):消费者的偏移量信息存储在 Kafka 内部的一个特殊主题 __consumer_offsets 中。
将偏移量存储在 Kafka 自身中,利用了 Kafka 高吞吐量的特性,大大提高了偏移量读写的性能。
5>数据清理
3,原理
1)kafka高性能原理
1>消息发送
- 批量发送;
将多个消息打包成一个批次发送,减少了网络传输和磁盘写入的次数。 - 消息压缩
提高io效率。 - 异步发送;
- 并行发送;
数据分布在不同的分区(Partitions)中,生产者可以并行发送数据。
2>消息存储
- 零拷贝读写数据
直接从网卡读写数据。在内核中读取文件并发送,不需要用户态和内核态的切换。 - 磁盘顺序写入
同partition有序,批量顺序写入磁盘,节省了磁盘寻址时间、减少写入次数。
同时partition分为多个segment存储,方便删除(直接删除segment)。
数据存储在磁盘,保证数据的可靠性、提高数据堆积能力。 - 页缓存(pageCache)
每次访问数据时,将数据加载到页存(而不是jvm的堆内存),如果生产消费速度相同,数据会直接通过pageCache交互数据,不需要经过磁盘IO。 - 稀疏索引
kafka存储消息用分段的日志文件,每个分段都有索引文件,这个文件每隔一定数量的消息才创建一个索引点。
这样的稀疏索引减小了索引体积,提高了加载效率。
数据结构:Bitmap Index(位图索引),存储偏移量是否有数据。 - 分区和副本
数据分散到多个节点进行处理,从而实现了分布式的高可用性和负载均衡。
3>消息消费
- 消费组
实现了消息的负载均衡和容错处理。 - 并行消费
不同消费者独立消费不同的分区,并行消费。 - 批量拉取
可以一次拉取多个消息进行消费。减少网络消耗。
2)如何保证消息不丢失?
消息发送过程:
- Producer发送消息给broker;
- broker同步消息并持久化数据;
- consumer从broker消费(pull)
目前kafka默认消息交付保证at least once,消息不丢但是会重复传递,要做好接口的幂等性校验。
1>producer解决方式
- 设置ack(消息回复确认数量),ack配置越高,越可靠、吞吐量越低;重试次数retries>1;
ack取值 | 说明 |
---|---|
0 | 发送出去不接受确认信息,消息丢失无感知 |
1 | 只要leader回复确认信息即可 |
all或者-1 | 代表kafka集群必须全部回复确认信息 |
unclean.leader.election.enable=false
不允许选举OSR作为leader。
由于OSR (Out-of-Sync Replied,滞后过多的副本)本身就数据不全,如果leader rash后,ISR (In Sync Replicas,与leader保持一定程度同步的副本)中没有follower,就会从OSR中选举leader。
AR (Assigned Replicas,分区中的副本)
AR = ISR + OSR:正常情况下,所有的follower副本都应该与leader 副本保持 一定程度的同步,即AR=ISR,OSR集合为空。
- 设置min.insync.replicas > 1
副本指定必须确认写操作成功的最小副本数量,值越大越可靠;相当于ISR副本的数量。
如果不能满足生产者会直接抛异常。只有ack=-1或者all时这个参数才生效。 - 失败的offset单独记录
放入db或者缓存,异常恢复后可单独处理恢复。
2>consumer解决方式
- 先处理消息再commit。
不容易丢数据,但是可能导致数据重复消费问题。==》保持接口的幂等性可以处理此类问题。 - 保存offset,成功后才更新偏移量。
3>broker的刷盘去解决问题
- 提高刷盘频率
数据发送到broker后是先保存在缓存中,后面再刷硬盘里。
3)如何避免重复消费?
比起丢数据,重复消费数据问题更小。客户端在接受kafka数据时注意建立幂等机制。
4)pull方式消费消息的优缺点
consumer采用pull(拉)模式从broker中读取数据。
1>优点
- 控制消费速率;
相比起来,push模式很难适应消费速率不同的消费者,因为消息发送速率是由broker决定的。从而导致消息堆积引起网络堵塞和消费者拒绝服务。 - 可以批量拉取,也可以单条拉取。
- 可以设置不同的提交方式,实现不同的传输语义。
三种方式:至少一次、最多一次、精准一次。
2>缺点
- 如果kafka没有数据,导致consumer空循环,消耗资源。
解决方式:
通过参数配置,consumer拉取数据为空或者没有达到一定数量时进行阻塞。==》等待数据量到了指定的字节数,以确保大的传输大小。
5)ZK作用
kafka前期版本用来管理kafka集群、数据存储。
kafka2.8开始引入KRaft协议,逐渐减少对zk的依赖;4.0版本不再支持zk模式。
有一些信息存在zookeeper中
1.记录服务器节点运行的状态
2.记录leader相关信息
1>zk模式kafka:push模式
- 每个broker都可以做controller角色,谁先在zk注册谁就是controller 节点。
controller 会在zk建立一个临时节点。 - 其它broker节点
watch
/controller znode
节点,如果掉线旧重新注册。 - controller节点会从zk中拉取当前集群所有的元数据信息,然后推送给集群里面所有的kafka 分区,分区会自动存储数据到磁盘,并异步传输到消费者。
可能会导致大量无效或重复的消息。
zk节点说明:
节点 | 功能 | 原理 |
---|---|---|
/brokers/ids | 注册broker,保存broker信息:物理地址、版本、启动时间 | 在zk中创建临时节点,broker会发心跳给zk |
/brokers/topics | 注册topic,保存topic信息。 每个topic节点下包含一个partitions节点; 每个partitions节点下由保存了一个state节点; state保存着当前leader分区和ISR的brokerID,同时维护ISR列表。 |
zk中创建的临时节点 |
/consumers/[group_id]/owners/[topic]/[broker_id-partition_id] | 维护消费者和分区的注册关系,保存消费者组消费的是哪个partition | zk中临时节点。如果消费者有挂掉,zk进行剔除并重新分区。 |
/consumers/[group_id]/offsets/[topic]/[broker_id-partition_id] | 分区消息的消费进度offset |
2>KRaft模式:pull模式
把集群中节点进行划分,有controller 和 broker 两种角色。
- controller 角色节点
内部组成一个由KRaft 协议(Kafka基于Raft 协议改造过的协议)构成的逻辑集群,统筹管理集群里面的状态信息。
一般为奇数台。 - broker角色节点
从controller 节点同步拉取集群状态信息到本地进行重放,从而完成整个集群的统一管理。
可以从1-N 扩展。 - offset
消费者维护,表示已经消费的消息序号,当消费者拉取消息时,kafka会返回未消费的消息。避免发送大量无效或重复的消息,减少资源的浪费。
新版本数据的存储:
数据 | 存储位置 |
---|---|
offset | 存放在一个专门的topic中的partition里 |
3>Kafka 不依赖zk的优点:
- 支持更多的分区、更高的性能
Kafka 集群规模不再受限制,Zookeeper 模式下单集群在百万topic 级别下会遇到性能瓶颈,主要是受限于跟ZK 交互。 - 更快速地切换controller,提高集群重启恢复速度。
以前要从Zookeeper 拉数据,如果partitions 总量很大,这个恢复过程会很长。 - 可以避免Controller缓存的元数据和zk存储的数据不一致带来的一系列问题。
- 更少的依赖,简化Kafka 部署
无需其他组件即可提供服务
6)rebalance机制(重平衡机制)
使用kafka的消息量很大的情况下,容易出现rebalance。rebalance期间partition的读写阻塞,降低rebalance次数,能提高性能。
1>概念
消费者组下的消费者与topic下的partition重新匹配的过程。
目的:实现消费者的负载均衡和高可用性。
2>场景
- 增加或删除broker
需要重新调整消费者和partition的分配。 - 增加或删除consumer
- consumer消费超时(可能挂了)
对应的partition没法消费,需要重新调整。
==》设置消费超时阈值 - group订阅的topic数目变化。
==》业务低峰期做topic\partition的变化操作。 - gourp订阅的分区数变化。
==》同3
3>触发条件
- 消费者组成员数量发生变化;
- 订阅主题数量发生变化;
- 订阅主题的分区数发生变化。
4>步骤
- 暂停消费
确保不会出现消息丢失或者重复消费。 - 计算分区分配方案
根据consumer group、consumer、topic的partition数量,计算每个consumer应该分配的partition,实现分区负载均衡。 - 通知消费者
一旦分区分配方案确定,发送每个消费者可消费的分区列表,邀请他们加入消费组。 - 重新分配分区
设置broker分区方案:重新分配topic的partition给各个消费者。 - 恢复消费
5>coordinator机制
针对场景1和2,是由coordinator(协调者)进行rebalance的。
coordinator一般是leader节点所在的broker,通过心跳监测consumer是否超时。具体步骤:
- coordinator心跳监测出有异常,心跳通知所有consumer进行rebalance;
- consumer请求coordinator加入消费组,coordinator选举产生leader consumer(不是主从的概念,而是有更多的责任)。
- leader consumer从coordinator获取所有的可用consumer,发送syncGroup(consumer和partition的分配信息)给到coordinator
- coordinator通过心跳机制分发syncGroup给所有的consumer。
针对场景3和4,由于topic和partition的变更broker无法感知,coordinator也就无法感知。由leader consumer监控topic变化,通知coordinator进行rebalance。
rebalance导致的重复消费问题:
consumer1宕掉之后,消费了一半的消息没有提交offset;rebalance之后进行了二次消费。此时consumer1恢复后进行了重复消费。
解决方案:
每次rebalance时标记Generation,rebalance成功后+1,consumer1再次提交offset时发现Generation值不同,则拒绝提交。
7)生产者原理(异步发送)
整个生产者客户端由两个线程协调工作,分别为主线程和Sender线程。
1>主线程
由KafkaProducer创建消息,然后会依次经过拦截器、序列化器、分区器,然后缓存到RecordAccumulator。
2>Sender线程
负责从RecordAccumulator中获取消息,然后发送给Kafka集群。
3>RecordAccumulator(消息累加器)
- 缓存消息
RecordAccumulator主要用来缓存消息,以便sender线程能批量发送。这个缓存通过客户端参数buffer.memory来配置大小,默认为32MB。 - 双端队列(Deque)
主线程过来的消息会被追加<分区,Deque<ProducerBatch>>
中(对应分区号)。
每一个partition在一个broker上存储,可以把海量数据切割成一块一块数据存储在多台broker上。合理控制分区的任务,可以实现负载均衡的效果。同时提高并行度:生产者可以以分区为单位发送数据,消费者可以以分区为单位消费数据。
双端队列满了之后数据怎么处理&#x