走进消息队列|青训营笔记

141 阅读10分钟

什么是消息队列?

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

前世今生

image.png

业界消息队列对比:

  1. Kafka: 分布式的、分区的、多副本的日志提交服务,在高吞吐场景下发挥较为出色
  2. RocketMQ: 低延迟、强一致、高性能、高可靠、万亿级容量和灵活的可扩展性,在一些实时场景中运用较广
  3. Pulsar: 是下一代云原生分布式消息流平台,集消息、存储、轻量化函数式计算为一体、采用存算分离的架构设计
  4. BMQ: 和Pulsar架构类似,存算分离,初期定位是承接高吞吐的离线业务场景,逐步替换掉对应的Kafka集群

消息队列-Kafka

使用场景

  1. 日志信息
  2. 用户行为信息

基本概念

image.png

Topic:Kakfa中的逻辑队列,可以理解成每一个不同的业务场景就是一个不同的topic,对于这个业务来说,所有的数据都存储在这个topic中

Cluster:Kafka的物理集群,每个集群中可以新建多个不同的topic

Producer:顾名思义,也就是消息的生产端,负责将业务消息发送到Topic当中Consumer:消息的消费端,负责消费已经发送到topic中的消息

Partition:通常topic会有多个分片,不同分片直接消息是可以并发来处理的,这样提高单个Topic的吞吐

  1. Offset

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

  1. Replica

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

数据复制

例如,某个kafka集群包含了4个Broker机器节点,有两个Topic,分别是Topic1和Topic2,Topic1有两个分片,Topic2有1个分片,每个分片都是三副本的状态。

这里中间有一个Broker同时也扮演了Controller的角色,Controller是整个集群的大脑,负责对副本和Broker进行分配。副本中的数据会从主副本拉取数据进行数据复制。

为什么快

  1. producer消息批量发送,减少网络io次数
  2. producer支持消息压缩,减少消息大小,目前支持Snappy、Gzip、LZ4、ZSTD压缩算法
  3. broker采用mmap文件映射的方式顺序写磁盘,提高写效率
  4. broker发送数据时通过零拷贝,减少数据复制次数
  5. Consumer通过reblance机制保证消费速率

当前缺陷

  1. Brocker重启、替换、扩容、缩容存在数据复制问题,运维成本高
  2. 当数据量分布不均时,数据复制会引起负载不均衡的问题,对于负载不均衡的场景,解决方案复杂
  3. 没有自己的缓存,完全依赖Page Cache
  4. Controller和Coordinator和Broker在同一进程中,大量IO会造成其性能下降

消息队列-BMQ

基本概念

BMQ兼容Kafka协议,存算分离,云原生消息队列,初期定位是承接高吞吐的离线业务场景,逐步替换掉对应的Kaka集群。

BMQ架构如下:

image.png

运维操作

操作KafkaBMQ
重启需要数据复制,分钟级重启重启后可直接对外服务,秒级完成
替换需要数据复制,分钟级替换,甚至天级别替换后可直接对外服务,秒级完成
扩容需要数据复制,分钟级扩容,甚至天级别扩容后可直接对外服务,秒级完成
缩容需要数据复制,分钟级缩容,甚至天级别缩容后可直接对外服务,秒级完成

读写流程

HDFS写文件流程:

同一个副本是由多个segment组成,我们来看看BMQ对于单个文件写入的机制是怎么样的,首先客户端写入前会选择一定数量的DataNode,这个数量是副本数,然后将一个文件写入到这三个节点上,切换到下一个segment之后,又会重新选择三个节点进行写入。这样一来,对于单个副本的所有segment来讲,会随机的分配到分布式文件系统的整个集群中。

Kafka与BMQ持久化存储对比:

image.png

对于Kafka分片数据的写入,是通过先在Leader上面写好文件,然后同步到Follwer上,所以对于同一个副本的所有Segment都在同一台机器上面。就会存在单分片过大导致负载不均衡的问题,但在BMQ集群中,因为对于单个副本来讲,是随机分配到不同的节点上面的,因此不会存在Kafka的负载不均问题。

image.png

对于写入的逻辑来说,还有一个状态机的机制,用来保证不会出现同一个分片在两个Broker上同时启动的情况,另外也能够保证一个分片的正常运行。首先,Controller做好分片的分配之后,如果在该Broker分配到了分片,首先会start这个分片,然后进入Recover状态,这个状态主要有两个目的:

  1. 获取分片写入权利,也就是说,对于hdfs来讲,只会允许一个分片进行写入,只有拿到这个权利的分片才能写入。
  2. 如果上次分片是异常中断的,没有进行save checkpoint,这里会重新进行一次save checkpoint。

然后就进入了正常的写流程状态,创建文件,写入数据,到一定大小之后又开始建立新的文件进行写入。

Broker写文件流程:

image.png

  1. CRC数据校验
  2. 参数是否合法校验完成后,会把数据放入Buffer中
  3. 通过一个异步的Write Thread线程将数据最终写入到底层的存储系统当中

对于业务的写入来说,可以配置数据返回的方式,可以在写完缓存之后直接返回,也可以数据真正写入存储系统后再返回,前者损失了数据的可靠性,带来了吞吐性能的优势,因为只写入内存是比较快的,但如果在下一次flush前发生宕机了,这个时候数据就有可能丢失了;后者因为数据已经写入了存储系统,这个时候也不需要担心数据丢失,但相应的来说吞吐就会小—些。

我们再来看看Thread的具体逻辑,首先会将Buffer中的数据取出来,调用底层写入逻辑,在一定的时间周期flush,flush完成后开始建立Index,也就是offset和timestamp对于消息具体位置的映射关系Index建立好以后,会save一次checkpoint,也就表示,checkpoint后的数据是可以被消费的。如果没有checkpoint的情况下会发生什么问题,如果flush完成之后宕机,index还没有建立,这个数据是不应该被消费的。最后当文件到达一定大小之后,需要建立—个新的segment文件来写入数据。

Broker写文件失败如何处理?

当建立一个新的文件,会随机挑选与副本数量相当的数据节点进行写入,那如果此时挑选的节点中有一个出现了问题,导致不能正常写入了,应该怎么处理?Broker具有Failover机制,Broker可以重新找正常的DataNode节点创建新的文件进行写入,这样也就保证了我们写入的可用性。

Proxy请求wait机制:

image.png

Consumer发送一个Fetch Request,会有一个Wait流程,那么他的作用是什么呢?想象一个Topic,如果一直没有数据写入,那么consumer就会一直发送Fetch Request。如果Consumer数量过多,BMQ的server端是扛不住这个请求的。因此,proxy设置了一个等待机制,如果没有fetch到指定大小的数据,那么proxy会等待一定的时间,再返回给用户侧,这样也就降低了fetch请求的IO次数。

经过wait流程后,首先会到Cache里面去找到是否有存在我们想要的数据,如果有直接返回。如果没有,再开始去存储系统当中寻找,首先会Open这个文件,然后通过index找到数据所在的具体位置,从这个位置开始读取数据,最后返回。

多机房部署:

对于一个高可用的服务,除了要防止单机故障所带来的的影响以外,也要防止机房级故障所带来的影响,比如机房断点,机房之间网络故障等等。

BMQ的多机房部署如下图所示:

image.png

高级特性

泳道

一般正式开发流程如下:

image.png

BOE:Bytedance Offline Environment,是一套完全独立的线下机房环境,数据与流量和线上都是隔离的。

PPE:Product Preview Environment,即产品预览环境,流量与线上是隔离的。

image.png

image.png

image.png

image.png

Databus

直接使用原生SDK会有什么问题?

  1. 客户端配置较为复杂
  2. 不支持动态配置,更改配置需要停掉服务
  3. 对于latency不是很敏感的业务,batch效果不佳

使用Databus:

image.png

  1. 简化消息队列客户端复杂度
  2. 解耦业务与Topic
  3. 缓解集群压力,提高吞吐

Mirror

image.png

image.png

Index

如果希望通过写入的 Logld、Userld或者其他的业务字段进行消息的查询,应该怎么做?

image.png

Parquest

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

image.png

image.png

消息队列-RocketMQ

使用场景

针对电商业务线,其业务涉及广泛,如注册、订单、库存、物流等;同时,也会涉及许多业务峰值时刻,如秒杀活动、周年庆、定期特惠等

基本概念

RocketMQ与Kafka对比:

名称KafkaRocketMQ
逻辑队列TopicTopic
消息体MessageMessage
标签Tag
分区PartitionConsumerQueue
生产者ProducerProducer
生成者集群Producer Group
消费者ConsumerConsumer
消费者集群Consumer GroupConsumer Group
集群控制器ControllerNameserver

RockerMQ架构图:

image.png

RockerMQ存储模型如下图:

image.png

对于一个Broker来说所有的消息的会append到一个CommitLog上面,然后按照不同的Queue,重新Dispatch到不同的Consumer中,这样Consumer就可以按照Queue进行拉取消费,但需要注意的是,这里的ConsumerQueue所存储的并不是真实的数据,真实的数据其实只存在CommitLog中,这里存的仅仅是这个Queue所有消息在CommitLog上面的位置,相当于是这个Queue的一个密集索引。

高级特性

事务处理

考虑以下场景:

image.png

正常情况下,下单的流程应该是这个样子,首先保证库存足够能够顺利-1,这个时候再让消息队列通知其他系统来处理,比如订单系统和商家系统,但这里有个比较重要的点,库存服务和消息队列必须要是在同—个事务内处理。这里库存记录和往消息队列里面发的消息这两个事情,是需要有事务保证的,这样不至于发生库存已经-1了,但我的订单没有增加,或者商家也没有收到通知要发货。因此RocketMQ提供事务消息来保证类似的场景,我们来看看其原理是怎么样的:

image.png

延迟消息

延迟消息应用场景:

image.png

延迟消息投递流程:

image.png

失败处理

RocketMQ的失败处理是通过消费重试和死信队列来完成的:

image.png

课后作业

  1. 手动搭建一个Kafka集群。
  2. 完成Hello World的发送与接收。
  3. 关闭其中一个 Broker,观察发送与接收的情况,并写出在关闭一个Broker后,Kafka集群会做哪些事情?