第五届字节跳动青训营Class13笔记 | 青训营笔记

296 阅读6分钟

这是我参与「第五届青训营 」笔记创作活动的第13天

1.消息队列技术背景

案例1系统崩溃

graph LR
搜索直播间 --> 搜索行为记录-->点击商品-->点击行为记录
搜索行为记录-->记录存储
点击行为记录-->记录存储

案例2服务能力有限

订单请求量过高,但是服务端处理能力有限。

案例3链路耗时长尾

graph LR
用户 --> 发起订单`5ms`-->库存记录-1`100ms`-->订单记录+1`100ms`-->通知订单商家`30s`-->用户

解决方案

  • 解耦
image.png
  • 削峰
image.png
  • 异步
image.png

消息队列

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

业界常见消息队列: Kafka(适合高吞吐场景)、RocketMQ(实时场景应用较广)、PULSAR(存算分离的架构设计)、BMQ(字节内部)

2.消息队列Kafka

使用场景

搜索服务、直播服务、订单服务、支付服务、日志信息、Metrics数据、用户行为等数据会存入Kafka中

使用Kafka的方式

graph LR
创建集群 --> 新增Topic --> 编写生产者逻辑 --> 编写消费者逻辑

基本概念

  • Topic:逻辑队列,不同的Topic可以建立不同的Topic(一个Topic中有多个Partition来处理消息)
  • Cluster:物理集群,每个集群可以建立不同的Topic
  • Producer:生产者,负责将业务消息发送到Topic中
  • Consumer:消费者,负责消费Topic中的信息
  • ConsumerGroup:消费者组,不同组中的Consumer消费进度互不干涉
  • Offset:消息在partition内部的相对位置
  • Replica:每个分片有多个副本,分为Leader和Follower两个角色,Leader负责读写操作,保持和Leader差距相近的副本可以留在队列里(衡量差距的标准是时间差)
  • ZooKeeper:负责存储集群元信息,包括分区分配信息等

数据复制

image.png

Broker中有一个Controller,负责分配各个partition的分布

消息的处理流程

消息发送

graph LR
Producer --> Message --> Broker
Broker --> Success --> Producer

当消息量大时,会将Message分为多个Batch来同时传输,并且为了防止网络带宽不足,会使用ZSTD等压缩算法进行压缩。

数据存储

image.png

Partition本质上是磁盘上存放数据的文件夹,是提高Kafka数据吞吐的关键,其中存储了多个不同的副本,副本中保存着日志。由于日志的时间戳特性,为了保证按照时间对超时数据清除,会使用Segment对其进行分割。

数据路径:/Topic/Partition/Segment/(log|index|timeindex|...)

使用每个LogSegment第一个Offset的名称作为其日志名称

Broker 磁盘结构

移动磁头找到对应的磁道,磁盘转动,找到对应扇区,最后写入。寻道成本比较高,因此可以顺序写来减少寻道所带来的成本。

image.png

Broker采取顺序写的方式进行写入,以提高写入效率

寻找消息

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

偏移量索引文件

目标是找到索引小于offset的最大索引位置,方法是通过二分找到小于目标offset的最大索引位置。

image.png

如图,Broker存储了各个Offset对应的position的映射,根据二分法找出的offset定位磁盘位置

时间戳索引文件

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

传统数据拷贝

image.png

零拷贝

image.png
mmap

上面的两个拷贝过程示意图是磁盘Output,Input即为该过程的逆向进行,使用mmap技术可以跳过读入用户缓冲区的过程,本质上是一种零拷贝行为(零拷贝并不是真正的不拷贝,是通过某些映射的手段减少数据在不同的缓冲区间拷贝的过程)

接收消息

关键是解决consumer组在partition中的分配问题。

手动分配

哪一个consumer消费哪一个partion完全由业务决定

问题:不能够自动容灾

自动分配

使用Rebalance机制来不断动态分配Partition给到consumer

截屏2023-02-09 下午1.04.36.png

  • 1.Consumer先发送请求给broker找到作为协调者的broker
  • 2.Coordinator在Consumer中选出Leader(通常是第一个)
  • 3.Consumer等待分配方案
  • 4.每个Consumer不断向Broker发送心跳

重启操作

关闭Broker时写入操作还在进行,因此会重新选取一个Leader,新的Leader等待Follower同步,同步完成要进行Leader的回切,回切可以避免长期的运行导致Leader集中到同一个Broker中(如需要重启100个节点的99个时,第100个节点将会不断成为Leader去同步数据)。

截屏2023-02-09 下午1.10.21.png

重启时不支持并发多台重启的,因为某一集群的两个分片可能同时位于需要重启的两台机器上,并发重启会直接导致集群不可用

问题总结

  • 运维成本高
  • 负载不均衡
  • 没有自己的缓存,完全依赖文件系统的Page Cache
  • Controller和Coordinator和Broker在同一个进程中

3.消息队列BMQ

字节跳动开发的BMQ是为了解决Kafka存在的问题而开发的存算分离且兼容Kafka协议的系统。

截屏2023-02-09 下午1.25.01.png

运维操作对比

  • 重启、替换、扩容、缩容:无需数据复制,秒级完成

HDFS写文件流程

选择一定数量的DataNode进行写入,因为Kafka需要将一个Partition写在同一个设备上,容易发生负载不均衡的情况,而BMQ可以将消息分布在不同设备的DataNode上面,从而分布式存储

Broker-Partition状态机

截屏2023-02-09 下午1.30.04.png

任意分片在同一时刻只能够在一个Broker上存活。

Broker写文件流程

graph LR
Messages-->数据校验-->Buffer-->Writer_Thread-->Storage

Writer Thread的过程

graph LR
Write_Data-->Flush-->Build_Index-->Checkpoint-->Roll_new_segment_file

Proxy读数据流程

graph LR
A(Cache)
Fetch_Request-->wait-->A
A-->Hit-->return_data
A-->Miss-->Storage

多机房部署

截屏2023-02-09 下午1.42.00.png

由于一个Partition是分布式部署的,因此需要Proxy需要和存储该Partition的全部Broker,如果其中之一出现问题,向上rebalance consumer,向下rebalance 机房存储。

泳道消息

开发流程:

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

BOE:Bytedance Offline Environment PPE:Product Preview Environment

截屏2023-02-09 下午1.50.05.png

Databus

使用原生SDK会导致客户端配置较为复杂,也不支持动态配置,更改配置需要停止服务。

截屏2023-02-09 下午1.53.37.png

使用Databus时使用了Agent作为代理,可以缓解集群压力,解耦业务与Topic

Mirror

跨Rigion的读写问题:如果采用多机房部署,Proxy需要访问部署在多个国家的机房。

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

即各个国家的Proxy写到对应国家的镜像中,再进行异步同步消息。

Index

实现增删改查秒级操作的方法是使用Index进行异步构建索引,这个过程直接在BMQ中发生,只需要对数据进行结构化,构建索引,然后通过Index Query服务读出数据。

Parquet

IDNameAge
1A12
2B15
3C18

行式存储:

1A122B153C18

列式存储

123ABC121518

消息存储是类似于行存的形式,使用Parquet组件可以改变存储形式,提高读写效率。

4.Rocket MQ

主要应用于实时秒杀场景,Rocket MQ可以支持标签等进一步分类消息。

截屏2023-02-09 下午2.14.57.png 与其他两个消息队列的区别是RocketMQ主要是在Broker层面上就分出了Master和Slave来分配读写权限和同步权限。

事务消息

类似于两阶段提交的方式保证一致性。 截屏2023-02-09 下午2.19.55.png

延迟发送

graph LR
Producer --> CommitLog --> ConsumerQueue
ConsumerQueue-->ScheduleMessage-->CommitLog

消费重试和死信队列

截屏2023-02-09 下午2.24.12.png

总结

本节课程主要讲解了消息队列的基本用途、使用方式以及三种常用的消息队列以及它们的高级特性。