这是我参与「第三届青训营 -后端场」笔记创作活动的第3篇笔记。
一、消息队列的作用
系统解耦
通过MQ发布订阅消息模型,可以成功将上游消息和下游系统进行解耦。例如:如果A系统需要向多个系统发送消息,但是一段时间后其中某个系统不需要该消息了,那么就得从A系统中删除给该系统发送消息的内容,或者一段时间后新增一个系统需要获取该消息,此时就又得从A系统中进行修改了。利用MQ可以让A系统将内容发送到MQ中存放,哪个系统需要消息只需要增加对MQ的消费即可,对上下游代码进行有效解耦。
流量削峰
这其实是MQ一个很重要的应用。假设系统A在某一段时间请求数暴增,有5000个请求发送过来,系统A这时就会发送5000条SQL进入MySQL进行执行,MySQL对于如此庞大的请求当然处理不过来,MySQL就会崩溃,导致系统瘫痪。如果使用MQ,系统A不再是直接发送SQL到数据库,而是把数据发送到MQ,MQ短时间积压数据是可以接受的,然后由消费者每次拉取2000条进行处理,防止在请求峰值时期大量的请求直接发送到MySQL导致系统崩溃。
异步调用
异步主要用来降低操作等待时间过长的问题,例如有四个ABCD系统,A系统收到一个请求,需要在本地写库并同时写入BCD中,但是A本地写库需要3ms,BCD写库耗费时间较长,加起来该功能耗费时间太长,影响用户体验,假如MQ中只需要A系统发送三条消息到MQ中即可,让BCD异步处理去。
二、消息队列的缺点有哪些
- 系统可用性降低:系统引入的外部依赖越多,系统要面对的风险越高,例如当A发送消息到MQ中,B、C、D订阅了这个MQ,当MQ出错时,可能会导致整个系统出错。
- 系统复杂度提高:当producer发送多个消息到MQ中、或者msg丢失、或者本来有序的msg变成无序的了以及consumer端出错,将会导致大量的消息堆积。
- 一致性问题:假设有ABCD四个系统依赖MQ,要求四个系统全部执行成功,结果执行过程中其中有的失败了,但是返回给用户的状态是成功,则会导致整个链路的数据不一致。
三、MQ介绍
- Kafka:分布式的、分区的、多副本的日志提交服务,在高吞吐量场景下发挥较为出色、
- RocketMQ:低延迟、强一致、高性能、高可靠、万亿级容量和实时的可扩展性,在一些实时场景下发挥较为出色。
- PULSAR:下一代云原生分布式消息流平台,集消息、存储、轻量化函数计算为一体、采用存算分离的架构设计。
Kafka
使用场景
一般会将搜索服务、直播服务、订单服务、支付服务等产生的离线消息例如日志信息、Metrics数据(程序状态采集数据例如:QPS)、用户行为(评论、点赞)。
基本流程
- 创建Kakfa集群。
- 新增Topic(逻辑队列,相当于每个业务都有有个对应的Topic)。
- 编写生产者逻辑。
- 编写消费者逻辑。
1. 基本概念
- Producer :生产者,负责将消息发送到 Broker
- Consumer :消费者,从 Broker 接收消息
- Consumer Group :消费者组,由多个 Consumer 组成。消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费;消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。
- Broker :可以看做一个独立的 Kafka 服务节点或 Kafka 服务实例。如果一台服务器上只部署了一个 Kafka 实例,那么我们也可以将 Broker 看做一台 Kafka 服务器。
- Topic :一个逻辑上的概念,包含很多 Partition,同一个 Topic 下的 Partiton 的消息内容是不相同的,多个Partition之间可以并发处理,提高它的吞吐量。
- Partition :为了实现扩展性,一个非常大的 topic 可以分布到多个 broker 上,一个 topic 可以分为多个 partition,每个 partition 是一个有序的队列。
- Replica :副本,同一分区的不同副本保存的是相同的消息,为保证集群中的某个节点发生故障时,该节点上的 partition 数据不丢失,且 kafka 仍然能够继续工作,kafka 提供了副本机制,一个 topic 的每个分区都有若干个副本,一个 leader 和若干个 follower。
- Leader :每个分区的多个副本中的"主副本",生产者以及消费者只与 Leader 交互。
- Follower :每个分区的多个副本中的"从副本",负责实时从 Leader 中同步数据,保持和 Leader 数据的同步。Leader 发生故障时,从 Follower 副本中重新选举新的 Leader 副本对外提供服务。
Offset
Replica
- Partition中不同的副本会分布在不同的集群上,以此达到容灾的作用。
- 生产或者消费消息会从leader中进行操作,Follower主要用来保证和leader的一致,如果flower和leader的差距太大(早期通过Offset,现在通过时间判断),会被踢出ISR中(例如第三个Follower),当leader所在的机器发生宕机无法继续服务的时候,会从ISR中找到一个Follower继续进行服务。
- 与Leader保持一定同步的Replica组成ISR,与Leader同步差距太多的组成OSR,因此默认情况下只有ISR中的Replica会晋升为Leader。
Producer
Producer生产消息,并通过Batch打包并压缩的方式进行发送(默认采用Snappy压缩算法):
为了减少因拷贝造成的时间浪费,Kafka采用了顺序写入来减少磁盘寻道时间、零拷贝技术直接将内核空间的内容拷贝到网卡中,跳过了拷贝用户态的过程。
Consumer
consumer消费消息,由于每个consumer group需要消费所有Partition的消息,因此采用了Coordinator的角色来自动控制每个consumer拉取哪些Partition中的消息(Rebalance机制):
2. Kafka重启操作
Kafka中的重启操作实质上是Brocker的重启,某个Brocker下线重启后会选择相近的一个Brocker作为新的Leader,然后将原先的Brocker进行重启,重启完成后追赶新Leader的消息,达到最新同步后进行Leader切换。(实质上就是一个一个Brocker的升级,而不是一次性全部升级)
3. Kafka的缺点
- 运维成本高
- 对于负载不均衡的场景,解决方案复杂
- 没有自己的缓存,完全依赖Page Cache
- Controller、Coordinator和Brocker在同一进程中,大量IO会造成其性能下降,任一角色出错可能会导致其他角色挂掉。
4. Kafka生产者发送消息有哪些模式
- 发后即忘:它只管往 Kafka 里面发送消息,但是不关心消息是否正确到达,这种方式的效率最高,但是可靠性也最差,比如当发生某些不可充实异常的时候会造成消息的丢失。
- 同步(sync):producer.send()返回一个Future对象并进行同步等待,就知道消息是否发送成功,发送一条消息需要等上个消息发送成功后才可以继续发送。
- 异步(async):Kafka支持 producer.send() 传入一个回调函数,消息不管成功或者失败都会调用这个回调函数,这样就算是异步发送,我们也知道消息的发送情况,然后再回调函数中选择记录日志还是重试都取决于调用方。
5. 发送消息的分区策略有哪些
- 轮询:依次发送消息到所有的Partition中。
- key指定分区:通过哈希函数指定每个key发送到哪个partition中,缺点是当partition增加的时候,就很难保证整个关系了。
- 自定义策略
- 指定Partition发送
6. Kafka如何实现负载均衡?
Kafka 的负责均衡主要是通过分区来实现的,Kafka 采用的是主写主读的架构,每个 broker 都有消费者拉取消息,每个 broker 也都有生产者发送消息,每个 broker 上的读写负载都是一样的,broker中每个Partition有自己对应的副本,因此通过主读主写的方式实现负载均衡。
Kafka负载均衡问题:
7. Kafka的可靠性保证
- acks:这个参数用来指定分片中有多少个副本收到消息后,生产者才认为此消息写成功了:
- ack=1,默认为1,生产者发送消息,只要 leader 副本成功写入消息,就代表成功。这就表示当leader写入还没来得及同步就宕机,会造成数据丢失。
- ack=0,生产者发送消息后直接算写入成功,不需要等待响应。这个方案的问题很明显,只要服务端写消息时出现任何问题,都会导致消息丢失。
- acks = -1 或 acks = all。生产者发送消息后,需要等待 ISR 中的所有副本都成功写入消息后才能收到服务端的响应。这种情况的安全性是最高的。
- 消息发送的方式:
- 可以通过同步或者异步来获取响应结果,采用失败或重做保证消息的可靠性。
- 手动提交位移:
- 默认情况下,当消费者消费到消息后,就会自动提交位移。但是如果消费者消费出错,没有进入真正的业务处理,那么就可能会导致这条消息消费失败,从而丢失。我们可以开启手动提交位移,等待业务正常处理完成后,再提交offset。
8. Kafka 的消息消费方式有哪些
- 点对点形式:当所有消费者属于同一个消费组,那么所有的消息都会被均匀的投递给每一个消费者,每条消息只会被其中一个消费者消费。
- 发布订阅:如果所有消费者属于不同的消费组,那么所有的消息都会被投递给每一个消费者,每个消费者都会收到该消息。
9. Kafka重复消费情况
- 消费者宕机、重启或者被强行kill进程,导致消费者消费的offset没有提交。
- 设置自动提交后,如果在关闭消费者进程之前,取消了消费者的订阅,则有可能部分offset没提交,下次重启会重复消费。
- 消费后的数据,当offset还没有提交时,Partition就断开连接。比如,通常会遇到消费的数据,处理很耗时,导致超过了Kafka的
session timeout.ms时间,那么就会触发reblance重平衡,此时可能存在消费者offset没提交,会导致重平衡后重复消费。
10. kafka 控制器是什么?有什么作用
在 Kafka 集群中会有一个或多个 broker,其中有一个 broker 会被选举为控制器,它负责管理整个集群中所有分区和副本的状态,kafka 集群中只能有一个控制器。
- 当某个分区的 leader 副本出现故障时,由控制器负责为该分区选举新的 leader 副本。
- 当检测到某个分区的ISR集合发生变化时,由控制器负责通知所有 broker 更新其元数据信息。
- 当为某个 topic 增加分区数量时,由控制器负责分区的重新分配。
11. Kafka控制器的选举方式
kafka 中的控制器选举工作依赖于 Zookeeper,成功竞选成为控制器的 broker 会在Zookeeper中创建/controller临时节点。
每个 broker 启动的时候会去尝试读取/controller 节点的 brokerid的值。
- 如果读取到的 brokerid 的值不为-1,表示已经有其他broker 节点成功竞选为控制器,所以当前 broker 就会放弃竞选;
如果Zookeeper中不存在/controller 节点,或者这个节点的数据异常,那么就会尝试去创建/controller 节点,创建成功的那个 broker 就会成为控制器。
每个 broker 都会在内存中保存当前控制器的 brokerid 值,这个值可以标识为 activeControllerId。
Zookeeper 中还有一个与控制器有关的/controller_epoch 节点,这个节点是持久节点,节点中存放的是一个整型的 controller_epoch 值。controller_epoch 值用于记录控制器发生变更的次数。
controller_epoch 的初始值为1,即集群中的第一个控制器的纪元为1,当控制器发生变更时,每选出一个新的控制器就将该字段值加1。
每个和控制器交互的请求都会携带 controller_epoch 这个字段,
- 如果请求的 controller_epoch 值小于内存中的 controller_epoch值,则认为这个请求是向已经过期的控制器发送的请求,那么这个请求会被认定为无效的请求。
- 如果请求的 controller_epoch 值大于内存中的 controller_epoch值,那么说明已经有新的控制器当选了。
12. kafka 为什么这么快
- 顺序读写磁盘分为顺序读写与随机读写,基于磁盘的随机读写确实很慢,但磁盘的顺序读写性能却很高,kafka 这里采用的就是顺序读写。
- Page Cache为了优化读写性能,Kafka 利用了操作系统本身的 Page Cache,就是利用操作系统自身的内存而不是JVM空间内存。
- 零拷贝Kafka使用了零拷贝技术,也就是直接将数据从内核空间的读缓冲区直接拷贝到内核空间的 socket 缓冲区,然后再写入到 NIC 缓冲区,避免了在内核空间和用户空间之间穿梭。
- 分区分段+索引Kafka 的 message 是按 topic分 类存储的,topic 中的数据又是按照一个一个的 partition 即分区存储到不同 broker 节点。每个 partition 对应了操作系统上的一个文件夹,partition 实际上又是按照segment分段存储的。通过这种分区分段的设计,Kafka 的 message 消息实际上是分布式存储在一个一个小的 segment 中的,每次文件操作也是直接操作的 segment。为了进一步的查询优化,Kafka 又默认为分段后的数据文件建立了索引文件,就是文件系统上的.index文件。这种分区分段+索引的设计,不仅提升了数据读取的效率,同时也提高了数据操作的并行度。
- 批量读写Kafka 数据读写也是批量的而不是单条的,这样可以避免在网络上频繁传输单个消息带来的延迟和带宽开销。假设网络带宽为10MB/S,一次性传输10MB的消息比传输1KB的消息10000万次显然要快得多。
- 批量压缩Kafka 把所有的消息都变成一个批量的文件,并且进行合理的批量压缩,减少网络 IO 损耗,通过 mmap 提高 I/O 速度,写入数据的时候由于单个Partion是末尾添加所以速度最优;读取数据的时候配合 sendfile 进行直接读取。
13. 什么情况下 kafka 会丢失消息
Kafka 有三次消息传递的过程:生产者发消息给 Broker,Broker 同步消息和持久化消息,Broker 将消息传递给消费者。
这其中每一步都有可能丢失消息。