这是我参与「第五届青训营 」笔记创作活动的第15天
一、背景
1. 背景内容
对于消息队列来说,在互联网的各种业务中发挥着至关重要的作用,但是在大学课程中却少有提及,通过本门课程了解工业界比较成熟消息队列的底层原理、架构设计、以及它们的一些高级特性,在以后学习工作中可以更加熟练的掌握使用消息队列或者进行技术选型,今天由浅入深的介绍
2. 案例
2.1 案例一 —— 系统崩溃
想象一下这个场景,有一天晚上上完课回到宿舍,想着新出的游戏机,摸了摸钱包,太贵了买不起,这时候突然想到今天抖音直播搞活动,瞬间拿出手机进抖音搜索,找到直播间后,点开心心念念的游戏机详细界面,看到价格只要500,这时候,通过这几步操作,程序背后做了什么事情?
首先,请求会先到搜索商品这个服务上,并记录下你的搜索行为,然后点击商品的时候,又记录了我们的点击商品,这些数据最终都会通过计算分析,目的是为了下一次给你准确的消息,这个时候问题来了,如果这个时候,负责存储数据的数据库被一个小哥删库跑路了,我们所有的操作都动不了,这个时候我们应该怎么办?
2.2 案例二 —— 服务能力有限
看到这个价格你非常心动,定睛一看,商品即将在3分钟后开抢,这个价格必须要抢到啊!但是此时无数台手机的后面,藏着无数和你一样饥渴的同学,时间快到了,心里想着赶紧抢,我们再来看看,后面的程序又做了哪些事情呢?
可以看到,一堆人都发起了订单请求,可是公司给的预算不够,服务器的配置太弱,订单服务只能同时处理10个订单请求。这个时候我们又该怎么办呢?
2.3 案例三 —— 链路耗时长尾
在我们点击提交订单之后,这个怎么一直在转圈圈,卡在这个界面啊,等了半分钟后,终于抢到了,不过这个app也太慢了,下次不用了,我们进一步看看这次问题出在哪里了?
一通分析,发现,库存服务和订单都挺快的,但是最后通知商家这一步咋这么慢,是不是还可以进行优化?
2.4 案例四 —— 日志存储
在大家都抢到了自己心仪商品准备去睡觉的时候,在字节跳动的会议室里传出了悲伤的声音,因为刚刚有服务器坏掉了,我们的本地日志都丢了,没有了日志,我们还怎么去修复那些刚刚出现的那些问题,周围一片寂静,突然小张站出来说了一句话,众人才露出微笑准备下班离开,大家能猜到小张说了什么吗?
3. 思考时间
四个场景,如何解决?
- 系统崩溃
- 服务处理能力有限
- 链路耗时长尾
- 日志如何处理
4. 解决方案
4.1 案例一
解决方案:解耦
4.2 案例二
解决方案:削峰
4.3 案例三
解决方案:异步
4.4 案例四
解决方案:日志处理
5. 什么是消息队列?
消息队列(MQ),指保存消息的一个容器,本质是一个队列,但是这个队列需要支持高吞吐、高并发、并且高可用
二、本堂课重点知识点
- 前世今生
- 消息队列 - Kafka
三、详细知识点介绍
1. 前世今生
1.1 消息队列发展历程
2. 业界消息队列对比
到目前为止,比较流行的MQ是以下几个:
- Kafka:分布式的、分区的
- Rocket MQ:
- Pulsar:
- BMQ:
2. 消息队列 - Kafka
2.1 使用场景
- 搜索服务、直播服务、订单服务、支付服务
- 日志信息、Metrics数据、用户行为(搜索、点赞、评论、收藏)
- Kafka
2.2 如何使用 Kafka
- 创建集群
- 首先需要创建一个Kafka集群(字节内部消息团队已经完成)
- 新增 Topic
- 需要在这个集群中创建一个Topic,并且设置好分片数量
- 编写生产者逻辑
- 引入对应语言的SDK,配置好集群和Topic等参数,初始化一个生产者,调用Send方法,将你的Hello World发送出去
- 编写消费者逻辑
- 引入对应语言的SDK,配置好集群和Topic等参数,初始化一个消费者,调用Poll方法,你将收到你刚刚发送的Hello World
2.3 基本概念
- Topic:逻辑队列,不同 Topic 可以建立不同的 Topic
- Kafka 中的逻辑队列,可以理解成每一个不同的业务场景就是一个不同的 topic,对于这个业务来说,所有的数据都存储在这个 topic 中
- Cluster:物理集群,每个集群中可以建立多个不同的 Topic
- Producer:生产者,负责将业务消息发送到 Topic 中的消息
- 消息的生产端,负责将业务消息发送到 Topic 当中
- Consumer:消费者,负责消费 Topic 中的消息
- 消息的消费端,负责消息已经发送到 topic 中的消息
- Consumer Group:消费者组,不同组 Consumer 消费进度互不干涉
- 通常 topic 会有多个分片,不同分片直接消息是可以并发来处理的,这样提高单个 Topic 的吞吐
2.3.1 Offset
Offset:消息在 partition 内的相对位置信息,可以理解为唯一ID,在 partition 内部严格递增
对于每一个 Partition 来说,每一条消息都有一个唯一的 offset,消息在 partition 内的相对位置信息,并且严格递增。
2.3.2 Replica
每个分片有多个 Replica,Leader Replica 将会从 ISR 中选出。
- Replica:分片的副本,分布在不同的机器上,可用来容灾,Leader对外服务,Follower异步去拉取leader的数据进行一个同步,如果leader挂掉了,可以将Follower提升成leader再堆外进行服务
- ISR:意思是同步中的副本,对于Follower来说,始终和leader是有一定差距的,但当这个差距比较小的时候,我们就可以将这个follower副本加入到ISR中,不在ISR中的副本是不允许提升成Leader的
2.3.3 数据复制
这张图代表着Kafaka的副本分布图。途中Broker代表每一个Kafaka的节点,所有的Broker节点最终组成了一个集群。整个图标识,图中整个集群,包含了4个Broker机器节点,集群有两个Topic,分别是Topic1和Topic2,Topic1有两个分片,Topic2有1个分片,每个分片都是三副本的状态。这里中间有一个Broker同时也扮演了Controller的角色,Controller是整个集群的大脑,负责对副本和Broker进行分配。
2.4 Kafaka架构
ZooKeeper:负责存储集群元信息,包括分配信息等。
而在这个集群的基础上,还有一个模块是Zookeeper,这个模块其实是存储了集群的元数据信息,比如副本分配信息等等,Controller计算好的方案都会放到这个地方。
2.5 一条消息的自述
从一条消息的视角,看看为什么 Kafaka 能支撑这么高的吞吐?
了解完整个Kafaka的基本概念和架构之后,我们从一条消息视角来看看完整的处理流程,了解一下Kafaka为什么可以支撑如此高的吞吐。
2.6 思考
首先思考一下:
如果发送一条消息,等到其成功后再发送一条会有什么问题?
2.7 Producer
2.7.1 Producer-批量发送
思考:如果消息量很大,网络带宽不够用,如何解决?
2.7.2 Producer-数据压缩
2.8 Broker
2.8.1 Broker-数据的存储
如何存储到磁盘?我们先看一下Kafaka最终存储的文件结构是什么样子的
2.8.2 Broker-消息文件结构
数据路径:/Topic/Partition/Segment/(log | index | timeindex l ...)
在每一个Broker,都分布着不同Topic的不同分片
2.8.3 Broker-磁盘结构
移动磁头找到对应磁道,磁盘转动,找到对应扇区,最后写入。寻道成本比较高,因此顺序写可以减少寻道所带来的时间成本。
只看一个盘面:磁头->磁道->扇区 寻道
2.8.4 Broker-顺序写
采用顺序写的方式进行写入,以提高写入效率
2.8.5 Broker-如何找到消息
Consumer 通过发送 FetchRequest 请求消息数据,Broker 会将指定 Offset 处的消息,按照时间窗口和消息大小窗口发送给 Consumer,寻找数据这个细节是如何做到的呢?
此时我们的消息写入到 Broker 磁盘上了,那么这些数据又该怎么被找到然后用来消费呢?
2.8.6 Broker-偏移量索引文件
目标:寻找 offset = 28
二分找到小于目标 offset 的最大文件。
介绍文件:文件名是文件中第一条消息的 offset
第一步通过二分找到目标文件的最大文件
二分找到小于目标 offset 的最大索引位置
通过二分找到小宇目标 offset 最大索引位置,再遍历找到目标 offset
2.8.7 Broker-传统数据拷贝
2.8.8 Broker-零拷贝
Consumer 从 Broker 中读取数据,通过 sendfile 的方式,将磁盘读到 o s内核缓冲区后,直接转到 socket buffer 进行网络发送 Producer 生产的数据持久化到 broker,采用 mmap 文件映射,实现顺序的快速写入
2.9 Consumer
2.9.1 Consumer-消息的接收端
如何解决 Partition 在 Consumer Group 中的分配问题?
对于一个 COnsumer Group 来说,多个分片可以并发的消费,这样可以大大提高效率,但需要解决的问题量,Consumer 和Partition 的分配问题,也就是对于每一个 Partition 来讲,该有哪一个 Consumer 来消费的问题,对于这个问题,我们一般有两种解决方法,手动分配和自动分配。
2.9.2 Consumer-Low Level
通过手动进行分配,哪一个 Consumer 消费哪一个Partition 完全由业务来决定
思考一下,这种方式的缺点是什么?
第一,