[ 消息队列 | 青训营笔记 ]

137 阅读9分钟

[ 消息队列 | 青训营笔记 ]

标题:消息队列 - 掘金

网址:juejin.cn/course/byte… juejin.cn/course/byte… juejin.cn/course/byte…

消息队列 (MQ),指保存消息的一个容器,本质是个队列。但这个队列呢,需要支持高吞吐、高并发,并且高可用。

一、消息队列- Kafka

1.1 使用场景

Kafka 一般适用于离线的消息处理当中,比如说日志信息、Metrics()数据、用户行为等。

  • 可扩展性:kafka集群支持热扩展

  • 持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失

  • 容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败)

  • 高并发:支持数千个客户端同时读写

1.2 基本概念

1.png Topic: 逻辑队列,不同 Topic 可以建立不同的 Topic(每一个业务场景就是一个 Topic ,对于业务来说,所有的数据都存在 Topic 中。)

Cluster:物理集群,每个集群中可以建立多个不同的 Topic

Producer:生产者,负责将业务消息发送到 Topic 中

Consumer:消费者,负责消费 Topic 中的消息

ConsumerGroup:消费者组,不同组 Consumer 消费进度互不干扰。(对于不同的消费者组来说,相同的消费者是互不干涉的。)

Partition 对于 Topic 来说是一个分区的概念。对于 Topic 来说,它有多个分区,不同分区的消息是可以并发处理的。

Offset

Offset:消息在 partition 内的相对位置信息,可以理解为唯一ID,在 partition 内部严格递增。

Replice

每个分片有多个 Replice(),Leader Replice 将会从 ISR 中选出。

2.png

当生成者过来的消息,首先写入到 Leader 中去,而 Follower 的作用,它会不断的把 Leader 中的数据拉取下来,努力的跟 Leader 保持一致。

In-Sync Replicas ,如果 Follower 跟 Leader 大于了这个差距,就会被踢出这个 ISR 。

1.3 Kafka 架构图

3.png

1.4 Producer

1、批量发送

将 Message 信息进行一个 Batch,批量的从 Producer 发送到 Broker,减少 IO 次数,从而加强发送能力。

2、数据压缩

通过将1、批量发送形成的Batch进行一个压缩,减少消息的大小;目前 Kafka 内部支持 Snappy(默认)、Gzip、LZ4、ZSTD(计算机性能、压缩率上比较优秀的;推荐的) 压缩算法。

1.5 Broker

1、消息文件结构

4.png

LogSegment(日志段)、Log(日志)

在 Broker 上面,会分配 Partition 的一些副本,不管是 Leader 还是 Follower。最终这些副本会以一个日志的形式写入到磁盘上的。对于一个日志来讲的话,它是有一个有效期的,如果超过固定的时间,会消除掉这个日志,来释放我们的存储空间。

2、磁盘结构

磁盘面:磁盘是由一叠磁盘面组成,见图。

磁头(Heads):每个磁头对应一个磁盘面,负责该磁盘面上的数据的读写。。

磁道(Track):每个盘面会围绕圆心划分出多个同心圆圈,每个圆圈叫做一个磁道。

柱面(Cylinders):所有盘片上的同一位置的磁道组成的立体叫做一个柱面。

扇区(Sector):以磁道为单位管理磁盘仍然太大,所以计算机前辈们又把每个磁道划分出了多个扇区,见下右图

5.png

移动读写头找到对应的磁道,磁盘转动,找到对应的扇区,最后写入。

Linux 上面可以通过 fdisk 命令,来查看当前系统使用的磁盘的这些物理信息。

6.png

3、顺序写

不会像数据库一样,在中间进行修改或者写入,Kafka 采用的是末尾添加,采用顺序写的方式进行写入,以提高写入效率。

4、找消息

Consumer 通过发送 FetchRequest 请求消息数据,Broker 会将指定 Offset 处的消息,按照时间窗口和消息大小窗口发送给 Consumer 。

5、索引查询

偏移量索引文件

二分找到小于目标 offset 的最大索引位置

时间戳索引文件

二分找到小于目标时间戳最大的索引位置,在通过寻找 offset 的方式找到最终数据。

6、数据拷贝

传统方法

7.png

零拷贝

8.png

1.6 Consumer - 消息的接收端

Low Level

通过手动进行分配,哪一个 Consumer 消费哪一个 Partition 完全由业务来决定。

9.png

对于 Consumer1 来说,拉取 Partition1、Partition2、Partition3;对于 Consumer2 来说,拉取 Partition4、Partition5、Partition6;对于 Consumer3 来说,拉取 Partition7、Partition8。

但是这样容灾能力不够,并且会有数据中断的问题(如果新引进一个 Consumer,那么就需要停止 Consumer1和2,来将Partition3和6分配给新的 Consumer)

High Level

High Level(自动分配),对于 Consumer Group 来说,会选取一个 Broker 来当做 Coordinator(协调者),可以帮助我们每一个 Consumer Group内部的 各个 Consumer 进行自动分配

10.png

Rebalance

11.png

12.png

13.png

14.png

15.png

1.7 Kafka-数据复制

16.png

对于一个 Kafka 来讲,每个 Broker 上面都有不同的 Topic 分区,并且分区有不同的副本。对于不同的节点之间,可以通过数据复制的方式,来保持一致。

1.8 Kafka-重启操作

17.png

1.9 Kafka-替换、扩容、缩容

替换:也就是追赶数据的时候,需要从0开始追。

扩容:新分配的机器,也需要从0开始追赶数据。

缩容:比如某个节点缩掉了,那么这个副本同样要分配到我们现存的某一个 Broker 上面,这个时候也会有数据复制时间成本的问题。

1.10 Kafka 问题总结

  • 运维成本高

  • 对于负载不均衡的场景,解决方案复杂

  • 没有自己的缓存,完全依赖 Page Cache

  • Controller 和 Coordinator 和 Broker 在同一进程中,大量 IO 会造成其性能下降

二、消息队列- BMQ

BMQ:兼容 Kafka 协议,存算分离,云原生消息队列

18.png

和 Kafka 不同的是,新引入了一个 Proxy Cluster,以及 Controller 和 Coordinator 独立出来;并且底层新增了一个分布式存储系统。

运维操作对比

19.png

HDFS 写文件流程 随机选择一定数量的 DataNode 进行写入

2.1 Broker

1、Broker-Partition 状态机

20.png

Recover 主要做两件事情,第一件事、要去争夺写入权利1;第二件事、做数据恢复(让真实的数据和原数据保持一致)。

当Append正常运行,则走下面的流程,如果出现故障,则有一个 Failover 机制来保证写入不会被中断。

2、写文件流程

21.png

3、写文件 Failover

如果 DataNode 节点挂了导致写文件失败,这时需要一个新的 DataNode 节点来进行继续存储,保持写入数据不会中断

2.2 Proxy

BMQ的读取流程

22.png

Wait 的作用是设置一个等待机制,使得请求不会那么的频繁,降低压力。

2.3 BMQ-高级特性

泳道功能、在生成者上有一个Databus功能、对集群做一个 Mirror(镜像)的功能、建立索引,建立Parquet功能

1、泳道消息

开发流程

graph LR
开发 --> BOE --> PPE --> Prod

BOE:Bytedance Offline Environment,是一套完全独立的线下机房环境

PPE:Product Preview Environment,即产品预览环境

23.png

对于每一个 Topic 来讲,都会有一个附带的 Topic ,泳道1的生产者带有一个标识传入给 Topic 后,只能由泳道2的消费者进行使用。

2.4 Databus

DataBus(数据同步组件)

github: github.com/linkedin/da…

Databus有以下特点:
数据源和消费者之间的隔离。
数据传输能保证顺序性和至少一次交付的高可用性。
从变化流的任意时间点进行消费,包括通过bootstrap获取所有数据。
分区消费
源一致性保存,消费不成功会一直消费直到消费成功

24.png

2.5 Mirror

25.png

使用 Mirror 通过最终一致的方式,解决跨 Region 读写问题。

2.6 Index

如果希望通过写入的 LogId、UserId 或者其他的业务字段进行消息的查询,可以通过直接在 BMQ 中将数据结构化,配置索引 DDL,异步构建索引后,通过 Index Query 服务读出数据。

26.png

2.7 Parquet

Apache Parquet 是 Hadoop 生态圈中一种新型列式存储格式,它可以兼容 Hadoop 生态圈中大多数计算框架(Hadoop、Spark等),被多种查询引擎支持(Hive、Impala、Drill等)。

27.png

直接在 BMQ 中将数据结构化,通过 Parquet Engine,可以使用不同的方式构建 Parquet 格式文件

三、消息队列-RocketMQ

28.png

对于这个Tag(标签)是为了在 Topic 下还能做一层区分。Producer Group是为了支持事务消息而引入的。

3.1 RocketMQ 架构

29.png

3.2 存储模型

30.png

每个Broker只有一个CommitLog,这个CommitLog会承接所有数据。

3.3 高级特性

1、事务发送

31.png

Send half message: 将消息发送给服务端

Success: 服务端返回给 Producer一个成功的请求

Execute local transaction: 执行本地的事务逻辑

Commit or rollback: 执行完成后,将事务进行一个提交

Miss 4 confirmation,check back: 没有收到4的消息,会进行一个回查

Check local transaction status: 检查本地事务的当前一个执行状态

Base 6 Result,send commit or rollback: 根据这个状态发送第二轮次的一个提交,告诉Server我这个事务应该是提交还是回滚

Commit: 正常消费

Rollback: 不会发送给消费者

2、延迟发送

32.png

Producer将消息发送到消息队列RocketMQ服务端,但并不期望这条消息立马投递,而是延迟一定时间后才投递到Consumer进行消费,该消息即延时消息。

3、消费重试和死信队列

33.png

消费重试:以时间轴为坐标,在消息持久化存储的时间范围内(默认3天),重新设置Consumer对已订阅的Topic的消费进度,设置完成后Consumer将接收设定时间点之后由Producer发送到消息队列RocketMQ服务端的消息。

死信队列:死信队列用于处理无法被正常消费的消息。当一条消息初次消费失败,消息队列RocketMQ会自动进行消息重试;达到最大重试次数后,若消费依然失败,则表明Consumer在正常情况下无法正确地消费该消息。此时,消息队列RocketMQ不会立刻将消息丢弃,而是将这条消息发送到该Consumer对应的特殊队列中。 消息队列RocketMQ将这种正常情况下无法被消费的消息称为死信消息(Dead-Letter Message),将存储死信消息的特殊队列称为死信队列(Dead-Letter Queue)。