这是我参与「第三届青训营 -后端场」笔记创作活动的的第4篇笔记。下面对之前学习的消息队列之Kafka进行知识总结。
消息队列的必要性
- 系统崩溃,导致存储服务发生故障,如果拥有消息队列,消息也会顺利发送消息队列中,不会影响整个搜索流程。
- 如果服务处理能力有限,面对庞大的请求量,我们可以通过削峰,即先将请求送入消息队列中,然后每次只获取10个请求进行处理。
- 链路耗时长尾,我们可通过异步操作进行改善。
- 日志丢失,我们可以先将日志放入消息队列中,通过专门组件将日志写入到搜索引擎中如ES,通过Kibana这种展示平台,对日志进行分析。
消息队列
消息队列(MQ),指保存消息的一个容器,本质是个队列。但需要支持高吞吐,高并发,并且高可用。
队列的发展历程:
其中比较代表性的:
- Kafka:分布式的、分区的、多副本的日志提交服务,在高吞吐场景下发挥较为出色
- RocketMQ:低延迟、强一致、高性能、高可靠、万亿级容量和灵活的可扩展性,在一些实时场景中运用较广
- Pulsar:是下一代云原生分布式消息流平台,集消息、存储、轻量化函数式计算为一体、采用存算分离的架构设计
- BMQ:和Pulsar架构类似,存算分离,初期定位是承接高吞吐的离线业务场景,逐步替换掉对应的Kafka集群
其中,我主要总结Kafka的相关知识和应用。
Kafka
使用场景:用于离线的消息处理当中
- 日志信息
- Metrics数据 程序在运行过程中对程序状态的采集 QPS 服务查询 写入的耗时等
- 用户行为 搜索点赞评论收藏
如何使用Kafka:
- 创建kafka集群
- 新增Topic 设置分区数量
- 编写生产者逻辑:引入kafka的SDK 实现上游的生产逻辑:把Hello world发送到Topic当中
- 编写消费者逻辑:把hello world通过pull从队列中拉取出来进行业务处理。
Kafka基本概念
Topic:逻辑队列,每一个不同的业务场景都可以作为一个Topic 所有数据都存储在Topic当中
- Partition:Topic的一个分区,Topic有多个分区,不同分区之间消息可以并发处理,提高单个Topic的吞吐能力。
- Offset:消息在partition内的相对位置信息,可以理解为唯一ID,在 partition内部严格递增。
Cluster:Topic的物理集群。每个集群中可以建立多个不同的Topic
Producer:生产者,负责将业务消息发送到Topic中
Consumer:消费者,负责消费Topic中的消息
ConsumerGroup:消费者组,不同组Consumer消费进度互不干涉
Replica
Partition中还有多个Replica,即副本。
这些副本会分布在集群中不同的机器上,以此来达到我们容灾的作用。
Leader:对外进行写入、读取的作用。(与外面生产者及消费者的接口)
Follower:从Leader把数据拉取下来,努力和Leader数据保持一致的状态。
ISR(In-Sync Replicas, 同步副本):将Follower与Leader进行配置
如果Follower的数据与Leader差距达到一定数值,则将Follower从ISR中踢出来。
配置:
- 通过Offset来进行判断(较早)
- 通过时间判断(如仅允许有10s的差距)
作用:如果Leader所在的副本出现了宕机,那么我们可以将ISR中选择一个副本让其成为Leader继续进行为生产者及消费者服务。
数据复制
Broker:Controller 负责对集群中的所有副本以及broker进行分配。
kafka架构
ZooKeeper:和Broker:Controller配合,负责存储集群信息,分区信息等等。
Kafka提高吞吐或者稳定性的功能
-
Producer:
- 批量发送:可以通过producer-批量发送 减少IO次数,加强发送能力。
- 数据压缩:通过压缩,减少消息大小,目前支持Snappy、Gzip、LZ4、ZSTD(计算性能、压缩率更好) 压缩算法
-
Broker:
-
顺序写:采用顺序写的方式进行写入,以提高写入效率
-
消息索引
偏移量索引文件:Consumer通过发送FetchRequest请求消息数据,Broker 会将指定Offset 处的消息,在对应Partition中通过二分找到小于目标Offset的最大文件,按照时间窗口和消息大小窗口发送给Consumer
时间戳索引文件:二分找到小于目标时间戳最大的索引位置,在通过寻找 offset的方式找到最终数据。
-
零拷贝
传统拷贝:数据需要从磁盘拷贝出来到内核空间,然后把数据发送到应用空间,然后把数据发送给Socket Buffer,然后再发送给NIC Buffer(网卡内存),然后再发送给消费者进程。
零拷贝:数据需要从磁盘拷贝出来到内核空间,然后把数据直接发送给NIC Buffer(网卡内存),然后再发送给消费者进程。省去了三次数据拷贝过程。
-
-
Consumer: Rebalance
-
手动分配(Consumer-Low Level):
优点:速度快。
- 如果一个Consumer意外故障,那么对应的Partition数据流就会直接断掉。无法自动容灾。
- 执行业务过程中发现Consumer能力不够临时增加一个Consumer,就需要将其他Consumer停下来来将一些partition分配给新Consumer,这样也会导致数据流的中断。
-
自动分配(Consumer-High Level)
在Broker运行过程中,对于不同的consumer Group,寻取一个Broker来作为Coordinator(协调者),帮助consumer Group中consumer进行自动分配partition。
Consumer Rebalance:
- 一开始Consumer需要找到Consumer group的Coordinater,于是向Broker Cluster发出FindCoordinaterRequest,然后Cluster会根据Broker当前负载会分配一个Broker。
- Consumer向coordinater发出一个请求 joinGroupRequest,需要加入到这个group当中。
- coordinater收到请求后,会从当前Consumer当中选取一个作为Leader,作用是计算分配策略(原因:Kafka有自己一个默认分配方式,但是某些业务有自己的业务特性,希望某些Partition分配到特定的Consumer上,也给业务提供一个接口实现自己的分配方式)
- Consumer发送SyncGroupRequest 让Coordinater给同步整个集群的分配方案。(其中Leader Consumer会同步携带一个分配方案),然后Coordinater将分配方案发送给所有Consumer
- Consumer给Coordinater发送heartbeatRequest,如果某个Consumer一定时间内还没有发送heartbeatRequest,那么Coordinater认为该Consumer在Group已经死掉了,将其踢出。
-
Kafka一些问题和弊端
- 重启场景下:
Leader回切:假如我们有100个Broker,如果需要对1-99个Broker都进行重启,那么如果不回切就会导致所有Leader都到了100的Broker上,导致100这个Broker需要请求所有的读写符,压力非常大。所以要保持负载均衡。
注意:不可以进行并发重启Broker,可能对于某个Partition他所有的Replica都包含在这些要重启的Broker上,导致该集群的这个Partition处于不可用的状态,影响整个Topic的可用性。
并且,在重启之后,相应follower只需要追赶一小段数据即可,但是替换、扩容、所有数据都为空,需要从0开始追赶。缩容情况类似。
最终导致时间成本巨大。
- 负载不均衡:
最左边Broker负载过高,于是将Partition3迁走,目的是降低Broker所机器的IO负载,但是迁出Partition3又会引来复制操作,导致IO负载再次升高。(死循环)
总结:
- 运维成本高
- 对于负载不均衡的场景,解决方案复杂
- 没有自己的缓存,完全依赖Page Cache
- Broker:Controller和 Coordinator 和Broker在同一进程中,大量IO会造成其性能下降