Kafka

文章介绍了消息队列的主要功能,包括解耦系统、实现异步处理和削峰填谷。以Kafka为例,详细阐述了Kafka的基本概念,如Producer、Consumer、ConsumerGroup、Broker、Partition、Replication等,并讨论了Kafka的高可用性和数据可靠性策略。此外,还提到了Kafka的分区机制、存储机制以及消费者模型,最后讨论了解决消息积压和保证消息顺序性的方法。
摘要由CSDN通过智能技术生成

为什么要使用消息队列?

总结一下,主要三点原因:解耦、异步、削峰。

1、解耦。比如,用户下单后,订单系统需要通知库存系统,假如库存系统无法访问,则订单减库存将失败,从而导致订单操作失败。订单系统与库存系统耦合,这个时候如果使用消息队列,可以返回给用户成功,先把消息持久化,等库存系统恢复后,就可以正常消费减去库存了。

2、异步。将消息写入消息队列,非必要的业务逻辑以异步的方式运行,不影响主流程业务。

3、削峰。消费端慢慢的按照数据库能处理的并发量,从消息队列中慢慢拉取消息。在生产中,这个短暂的高峰期积压是允许的。比如秒杀活动,一般会因为流量过大,从而导致流量暴增,应用挂掉。这个时候加上消息队列,服务器接收到用户的请求后,首先写入消息队列,如果消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面。

使用了消息队列会有什么缺点

系统可用性降低。引入消息队列之后,如果消息队列挂了,可能会影响到业务系统的可用性。
系统复杂性增加。加入了消息队列,要多考虑很多方面的问题,比如:一致性问题、如何保证消息不被重复消费、如何保证消息可靠性传输等。

Kafca基本概念

在这里插入图片描述

名称解释
Producer消息⽣产者,向 Kafka Broker 发消息的客户端。
Consumer消息消费者,从 Kafka Broker 取消息的客户端。Kafka支持持久化,生产者退出后,未消费的消息仍可被消费。
Consumer Group消费者组(CG),消费者组内每个消费者负责消费不同分区的数据,提⾼消费能⼒。⼀个分区只能由组内⼀个消费者消费,消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的⼀个订阅者
Broker⼀台 Kafka 机器就是⼀个 Broker。⼀个集群(kafka cluster)由多个 Broker 组成。⼀个 Broker 可以容纳多个 Topic。
Controller由zookeeper选举其中一个Broker产生。它的主要作用是在 Apache ZooKeeper 的帮助下管理和协调整个 Kafka 集群。
Topic可以理解为⼀个队列,Topic 将消息分类,⽣产者和消费者⾯向的是同⼀个 Topic。
Partition为了实现扩展性,提⾼并发能⼒,⼀个⾮常⼤的 Topic 可以分布到多个 Broker上,⼀个 Topic 可以分为多个 Partition,同⼀个topic在不同的分区的数据是不重复的,每个 Partition 是⼀个有序的队列,其表现形式就是⼀个⼀个的⽂件夹。不同Partition可以部署在同一台机器上,但不建议这么做。
Replication每⼀个分区都有多个副本,副本的作⽤是做备胎。当主分区(Leader)故障的时候会选择⼀个备胎(Follower)上位,成为Leader。在kafka中默认副本的最⼤数量是10个,且副本的数量不能⼤于Broker的数量,follower和leader绝对是在不同的机器,同⼀机器对同⼀个分区也只可能存放⼀个副本(包括⾃⼰)。
Message每⼀条发送的消息主体。
Leader每个分区多个副本的“主”副本,⽣产者发送数据的对象,以及消费者消费数据的对象,都是 Leader。
Follower每个分区多个副本的“从”副本,使用发布订阅模式主动拉取Leader的数据(与redis不同),实时从 Leader 中同步数据,保持和 Leader 数据的同步。Leader 发⽣故障时,某个 Follower 还会成为新的 Leader。
Offset消费者消费的位置信息,监控数据消费到什么位置,当消费者挂掉再重新恢复的时候,可以从消费位置继续消费。
ZooKeeperKafka 集群能够正常⼯作,需要依赖于 ZooKeeper,ZooKeeper 帮助 Kafka存储和管理集群信息。
High Level API 和Low Level API高水平API,kafka本身定义的行为,屏蔽细节管理,使用方便;低水平API细节需要自己处理,较为灵活但是复杂。

生产者

( 1 )acks=0: 表示producer不需要等待任何broker确认收到消息的回复,就可以继续发送下一条消息。性能最高,但是最容易丢消息。
( 2 )acks=1: 至少要等待leader已经成功将数据写入本地log,但是不需要等待所有follower是否成功写入。就可以继续发送下一条消息。这种情况下,如果follower没有成功备份数据,而此时leader又挂掉,则消息会丢失。
( 3 )acks=-1或all: 需要等待 min.insync.replicas(默认为 1 ,推荐配置大于等于2) 这个参数配置的副本个数都成功写入日志,这种策略会保证只要有一个备份存活就不会丢失数据。这是最强的数据保证。一般除非是金融级别,或跟钱打交道的场景才会使用这种配置。

设计一个不丢数据的方案:数据不丢失的方案:
1)分区副本 >=2
2)acks = -1
3)min.insync.replicas >=2。

分区机制

分区原因:

⽅便在集群中扩展,每个 Partition 可以通过调整以适应它所在的机器,⽽⼀个 Topic ⼜可以有多个 Partition 组成,因此可以以 Partition 为单位读写了。
可以提⾼并发,避免两个分区持久化的时候争夺资源。
备份的问题。防止一台机器宕机后数据丢失的问题。

分区原则:

我们需要将 Producer 发送的数据封装成⼀个 ProducerRecord 对象。该对象需要指定⼀些参数:

topic:string 类型,NotNull。
partition:int 类型,可选。
timestamp:long 类型,可选。
key:string 类型,可选。
value:string 类型,可选。
headers:array 类型,Nullable。

指明 Partition 的情况下,直接将给定的 Value 作为 Partition 的值;没有指明 Partition 但有 Key 的情况下,将 Key 的 Hash 值与分区数取余得到 Partition 值;既没有 Partition 又没有 Key 的情况下,第⼀次调⽤时随机⽣成⼀个整数(后⾯每次调⽤都在这个整数上⾃增),将这个值与可⽤的分区数取余,得到 Partition 值,也就是常说的 Round-Robin轮询算法。

存储机制

每个分片目录中,kafka 通过分段的方式将 数据 分为多个 LogSegment,一个 LogSegment 对应磁盘上的一个日志文件(.log)和一个索引文件(.index),其中日志文件是用来记录消息的。索引文件是用来保存消息的索引。每个LogSegment 的大小可以在server.properties 中log.segment.bytes=107370 (设置分段大小,默认是1gb)选项进行设置。
在这里插入图片描述

指定offset读取

通过每个segment文件的命名为当前segment中offset起始位查找对应segment,然后在index中二分查找索引,最后到log中顺序查找。稀疏索引比稠密索引节省了更多的存储空间,但查找起来需要消耗更多的时间。

LEO&HW更新原理

LEO(last end offset):该副本底层日志文件上的数据的最大偏移量的下一个值
HW(highwater mark):水位,它一定会小于LEO的值。这个值规定了消费者仅能消费HW之前的数据。follower在和leader同步数据的时候,同步过来的数据会带上LEO的值。leader partition就会记录这些follower同步过来的LEO,然后取最小的LEO值作为HW值。follower获取数据时也带上leader partition的HW的值,然后和自身的LEO值取一个较小的值作为自身的HW值

ISR

follower如果超过10秒没有到leader这里同步数据,就会被踢出ISR。它的作用就是帮助我们在leader宕机时快速再选出一个leader,因为在ISR列表中的follower都是和leader同步率高的,就算丢失数据也不会丢失太多。

当follower的LEO值>=leader的HW值,就可以回到ISR。

消费者

Consumer 采⽤ Pull(拉取)模式从 Broker 中读取数据。Pull 模式则可以根据 Consumer 的消费能⼒以适当的速率消费消息。Pull 模式不⾜之处是,如果Kafka 没有数据,消费者可能会陷⼊循环中,⼀直返回空数据。因为消费者从 Broker 主动拉取数据,需要维护⼀个⻓轮询,针对这⼀点, Kafka 的消费者在消费数据时会传⼊⼀个时⻓参数 timeout。如果当前没有数据可供消费,Consumer 会等待⼀段时间之后再返回,这段时⻓即为 timeout。

分区分配策略

⼀个 Consumer Group 中有多个 Consumer,⼀个 Topic 有多个 Partition。消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费。不同组间的消费者是相互独立的,相同组内的消费者才会协作,这就必然会涉及到Partition 的分配问题,即确定哪个 Partition 由哪个 Consumer 来消费。
Kafka 有三种分配策略:

  1. Range,默认为Range:Range是对每个Topic而言的,尽量顺序的均分,前面的会多分几个,如4个partition 2个consumer则为0-1,2-3
  2. RoundRobin:依次每个订阅了的消费者一个一个分,如4个partition2个consumer则为02,13
  3. Sticky:与RoundRobin相似,但是再分配会尽可能的与上次的分配结果保持一致

当消费者组内消费者发⽣变化时,会触发分区分配策略(⽅法重新分配),在分配完成前,kafka会暂停对外服务。注意为了尽量确保消息的有序执行,一个分区只能对应一个消费者,这也说明消费者的数量不能超过分区的数量。

为啥kafca快

  1. Kafka 利用了一种分段式的、只追加 (Append-Only) 的日志,基本上把自身的读写操作限制为顺序 I/O
  2. 消息先被写入页缓存,由操作系统负责刷盘任务。
  3. 零拷贝降低数据拷贝次数和内核切换次数
  4. Kafka 的 message 是按 topic分 类存储的,topic 中的数据又是按照一个一个的 partition ,每个 partition 对应了操作系统上的一个文件夹,partition 实际上又是按照segment分段存储的提高并行写性能

如何做到可靠

  1. 发送确认机制,副本写入才成功
  2. 多partion副本存在多brocker
  3. 消费端offset确认

如何保证消息的顺序性?

  1. 全局有序:只创建1个Partition(分区),这样生产者的所有数据都发送到了一个Partition(分区),保证了消息的消费顺序。或者消费端通过顺序编排,或者依赖检测保证
  2. 局部消费顺序:比如3个直播间同时发消息,局部顺序就是直播间1先发,直播间2后发,但是可能直播间2的消息先到,这个顺序是不保证的。但是直播间1先发了“消息1”,再发了“消息2”,这个顺序是能保证的,也就是在直播间内是有序的,但是直播间之间的消息顺序不能保证。实现方式:生产者在发送消息的时候指定要发送到哪个Partition(分区)

大量消息在 MQ 里长时间积压,该如何解决?

一般这个时候,只能临时紧急扩容了,具体操作步骤和思路如下:

先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉;
新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量;
然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue;
接着临时用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据;
等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的 consumer 机器来消费消息。

如何做到高可用?

Kafka 的基础集群架构,由多个broker组成,每个broker都是一个节点。当你创建一个topic时,它可以划分为多个partition,而每个partition放一部分数据,分别存在于不同的 broker 上。也就是说,一个 topic 的数据,是分散放在多个机器上的,每个机器就放一部分数据。

每个partition放一部分数据,如果对应的broker挂了,那这部分数据是不是就丢失了?那不是保证不了高可用吗?

Kafka 0.8 之后,提供了复制多副本机制来保证高可用,即每个 partition 的数据都会同步到其它机器上,形成多个副本。然后所有的副本会选举一个 leader 出来,让leader去跟生产和消费者打交道,其他副本都是follower。写数据时,leader 负责把数据同步给所有的follower,读消息时,直接读 leader 上的数据即可。如何保证高可用的?就是假设某个 broker 宕机,这个broker上的partition 在其他机器上都有副本的。如果挂的是leader的broker呢?其他follower会重新选一个leader出来。

如何处理消息积压

事前可以建立批处理能力,如汇总账单下发。

能导致积压突然增加,最粗粒度的原因,只有两种:要么是发送变快了,要么是消费变慢了。
大部分消息队列都内置了监控的功能,只要通过监控数据,很容易确定是哪种原因。如果是单位时间发送的 消息增多,比如说是赶上大促或者抢购,短时间内不太可能优化消费端的代码来提升消费性能,唯一的方法 是通过扩容消费端的实例数来提升总体的消费能力。

如果短时间内没有足够的服务器资源进行扩容,没办法的办法是,将系统降级,通过关闭一些不重要的业 务,减少发送方发送的数据量,最低限度让系统还能正常运转,服务一些重要业务。

如果是Kafka消费能力不足,则可以考虑增加Topic的分区数(一般一个Topic分区数为3-10个),并且同时提升消费组的消费者数量,消费者数==分区数。两者缺一不可

如果监控到消费变慢了,你需要检查你的消费实例,分析一下是什么原因导致消费变慢。优先检查一下日志 是否有大量的消费错误,如果没有错误的话,可以通过打印堆栈信息,看一下你的消费线程是不是卡在什么 地方不动了,比如触发了死锁或者卡在等待某些资源上了。

大部分消息队列都内置了监控的功能,只要通过监控数据,很容易确定是哪种原因。如果是单位时间发送的 消息增多,比如说是赶上大促或者抢购,短时间内不太可能优化消费端的代码来提升消费性能,唯一的方法 是通过扩容消费端的实例数来提升总体的消费能力。

消息回溯

消费过程中因为消费失败、上线bug或其他原因,会存在重新消费旧数据的需求。

kafka支持两种方式回溯。一种是基于消息偏移量回溯,一种是基于时间点的消息回溯。

基于消息偏移量回溯

在kafka的每个分区中,每条消息都有一个唯一的offset值 ,即消息偏移量,用来表示消息在partition分区中的位置。消费者每次消费了消息,都会把消费的此条消息的offset提交到broker(消息节点),用于记录消费到分区中的位置,下条消息从这个位置之后开始消费。所以基于消息偏移量回溯很简单,只需要重置offset,然后消费者会从该offset之后开始消费

基于时间点的消息回溯

时间索引文件找到时间对应的offset偏移量,在通过偏移量索引文件找到消息位置。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值