[ 消息队列 | 青训营笔记 ]
标题:消息队列 - 掘金
网址:juejin.cn/course/byte… juejin.cn/course/byte… juejin.cn/course/byte…
消息队列 (MQ),指保存消息的一个容器,本质是个队列。但这个队列呢,需要支持高吞吐、高并发,并且高可用。
一、消息队列- Kafka
1.1 使用场景
Kafka 一般适用于离线的消息处理当中,比如说日志信息、Metrics()数据、用户行为等。
-
可扩展性:kafka集群支持热扩展
-
持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失
-
容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败)
-
高并发:支持数千个客户端同时读写
1.2 基本概念
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 中选出。
当生成者过来的消息,首先写入到 Leader 中去,而 Follower 的作用,它会不断的把 Leader 中的数据拉取下来,努力的跟 Leader 保持一致。
In-Sync Replicas ,如果 Follower 跟 Leader 大于了这个差距,就会被踢出这个 ISR 。
1.3 Kafka 架构图
1.4 Producer
1、批量发送
将 Message 信息进行一个 Batch,批量的从 Producer 发送到 Broker,减少 IO 次数,从而加强发送能力。
2、数据压缩
通过将1、批量发送形成的Batch进行一个压缩,减少消息的大小;目前 Kafka 内部支持 Snappy(默认)、Gzip、LZ4、ZSTD(计算机性能、压缩率上比较优秀的;推荐的) 压缩算法。
1.5 Broker
1、消息文件结构
LogSegment(日志段)、Log(日志)
在 Broker 上面,会分配 Partition 的一些副本,不管是 Leader 还是 Follower。最终这些副本会以一个日志的形式写入到磁盘上的。对于一个日志来讲的话,它是有一个有效期的,如果超过固定的时间,会消除掉这个日志,来释放我们的存储空间。
2、磁盘结构
磁盘面:磁盘是由一叠磁盘面组成,见图。
磁头(Heads):每个磁头对应一个磁盘面,负责该磁盘面上的数据的读写。。
磁道(Track):每个盘面会围绕圆心划分出多个同心圆圈,每个圆圈叫做一个磁道。
柱面(Cylinders):所有盘片上的同一位置的磁道组成的立体叫做一个柱面。
扇区(Sector):以磁道为单位管理磁盘仍然太大,所以计算机前辈们又把每个磁道划分出了多个扇区,见下右图
移动读写头找到对应的磁道,磁盘转动,找到对应的扇区,最后写入。
Linux 上面可以通过 fdisk 命令,来查看当前系统使用的磁盘的这些物理信息。
3、顺序写
不会像数据库一样,在中间进行修改或者写入,Kafka 采用的是末尾添加,采用顺序写的方式进行写入,以提高写入效率。
4、找消息
Consumer 通过发送 FetchRequest 请求消息数据,Broker 会将指定 Offset 处的消息,按照时间窗口和消息大小窗口发送给 Consumer 。
5、索引查询
偏移量索引文件
二分找到小于目标 offset 的最大索引位置
时间戳索引文件
二分找到小于目标时间戳最大的索引位置,在通过寻找 offset 的方式找到最终数据。
6、数据拷贝
传统方法
零拷贝
1.6 Consumer - 消息的接收端
Low Level
通过手动进行分配,哪一个 Consumer 消费哪一个 Partition 完全由业务来决定。
对于 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 进行自动分配
Rebalance
1.7 Kafka-数据复制
对于一个 Kafka 来讲,每个 Broker 上面都有不同的 Topic 分区,并且分区有不同的副本。对于不同的节点之间,可以通过数据复制的方式,来保持一致。
1.8 Kafka-重启操作
1.9 Kafka-替换、扩容、缩容
替换:也就是追赶数据的时候,需要从0开始追。
扩容:新分配的机器,也需要从0开始追赶数据。
缩容:比如某个节点缩掉了,那么这个副本同样要分配到我们现存的某一个 Broker 上面,这个时候也会有数据复制时间成本的问题。
1.10 Kafka 问题总结
-
运维成本高
-
对于负载不均衡的场景,解决方案复杂
-
没有自己的缓存,完全依赖 Page Cache
-
Controller 和 Coordinator 和 Broker 在同一进程中,大量 IO 会造成其性能下降
二、消息队列- BMQ
BMQ:兼容 Kafka 协议,存算分离,云原生消息队列
和 Kafka 不同的是,新引入了一个 Proxy Cluster,以及 Controller 和 Coordinator 独立出来;并且底层新增了一个分布式存储系统。
运维操作对比
HDFS 写文件流程 随机选择一定数量的 DataNode 进行写入
2.1 Broker
1、Broker-Partition 状态机
Recover 主要做两件事情,第一件事、要去争夺写入权利1;第二件事、做数据恢复(让真实的数据和原数据保持一致)。
当Append正常运行,则走下面的流程,如果出现故障,则有一个 Failover 机制来保证写入不会被中断。
2、写文件流程
3、写文件 Failover
如果 DataNode 节点挂了导致写文件失败,这时需要一个新的 DataNode 节点来进行继续存储,保持写入数据不会中断
2.2 Proxy
BMQ的读取流程
Wait 的作用是设置一个等待机制,使得请求不会那么的频繁,降低压力。
2.3 BMQ-高级特性
泳道功能、在生成者上有一个Databus功能、对集群做一个 Mirror(镜像)的功能、建立索引,建立Parquet功能
1、泳道消息
开发流程
graph LR
开发 --> BOE --> PPE --> Prod
BOE:Bytedance Offline Environment,是一套完全独立的线下机房环境
PPE:Product Preview Environment,即产品预览环境
对于每一个 Topic 来讲,都会有一个附带的 Topic ,泳道1的生产者带有一个标识传入给 Topic 后,只能由泳道2的消费者进行使用。
2.4 Databus
DataBus(数据同步组件)
github: github.com/linkedin/da…
Databus有以下特点:
数据源和消费者之间的隔离。
数据传输能保证顺序性和至少一次交付的高可用性。
从变化流的任意时间点进行消费,包括通过bootstrap获取所有数据。
分区消费
源一致性保存,消费不成功会一直消费直到消费成功
2.5 Mirror
使用 Mirror 通过最终一致的方式,解决跨 Region 读写问题。
2.6 Index
如果希望通过写入的 LogId、UserId 或者其他的业务字段进行消息的查询,可以通过直接在 BMQ 中将数据结构化,配置索引 DDL,异步构建索引后,通过 Index Query 服务读出数据。
2.7 Parquet
Apache Parquet 是 Hadoop 生态圈中一种新型列式存储格式,它可以兼容 Hadoop 生态圈中大多数计算框架(Hadoop、Spark等),被多种查询引擎支持(Hive、Impala、Drill等)。
直接在 BMQ 中将数据结构化,通过 Parquet Engine,可以使用不同的方式构建 Parquet 格式文件
三、消息队列-RocketMQ
对于这个Tag(标签)是为了在 Topic 下还能做一层区分。Producer Group是为了支持事务消息而引入的。
3.1 RocketMQ 架构
3.2 存储模型
每个Broker只有一个CommitLog,这个CommitLog会承接所有数据。
3.3 高级特性
1、事务发送
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、延迟发送
Producer将消息发送到消息队列RocketMQ服务端,但并不期望这条消息立马投递,而是延迟一定时间后才投递到Consumer进行消费,该消息即延时消息。
3、消费重试和死信队列
消费重试:以时间轴为坐标,在消息持久化存储的时间范围内(默认3天),重新设置Consumer对已订阅的Topic的消费进度,设置完成后Consumer将接收设定时间点之后由Producer发送到消息队列RocketMQ服务端的消息。
死信队列:死信队列用于处理无法被正常消费的消息。当一条消息初次消费失败,消息队列RocketMQ会自动进行消息重试;达到最大重试次数后,若消费依然失败,则表明Consumer在正常情况下无法正确地消费该消息。此时,消息队列RocketMQ不会立刻将消息丢弃,而是将这条消息发送到该Consumer对应的特殊队列中。 消息队列RocketMQ将这种正常情况下无法被消费的消息称为死信消息(Dead-Letter Message),将存储死信消息的特殊队列称为死信队列(Dead-Letter Queue)。