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

52 阅读5分钟
  • 这是我参与「第五届青训营 」伴学笔记创作活动的第 13 天

四个场景,如何解决?

  1. 系统崩溃:解耦

  2. 服务处理能力有限:削峰

  3. 链路耗时长尾:异步

  4. 日志如何处理:日志处理

    Log ->消息队列->LogStash->ES->Kibana

1. 消息队列

什么是消息队列?

  • 消息队列(MQ),指保存消息的一个容器,本质是个队列。需要支持高吞吐、高并发、并且高可用
  • 消息队列:存储和管理消息,也被称为消息代理(Message Broker)
  • 生产者:发送消息到消息队列
  • 消费者:从消息队列获取消息并处理消息

业界消息队列对比

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

2. Kafka

使用场景:搜索服务、直播服务、订单服务、支付服务

用户行为:搜索、点赞、评论、收藏

2.1 如何使用Kafka

  1. 首先需要创建一个Kafka集群,但如果你是在字节工作,恭喜你这一步消息团队的小伙伴已经帮你完成了
  2. 需要在这个集群中创建一个Topic,并且设置好分片数量
  3. 引入对应语言的SDK,配置好集群和Topic等参数,初始化一个生产者,调用Send方法,将你的Hello World发送出去
  4. 引入对应语言的SDK,配置好集群和Topic等参数,初始化一个消费者,调用Poll方法,你将收到你刚刚发送的Hello World

2.2 基本概念

  • Topic: Kafka中的逻辑队列,可以理解成每一个不同的业务场景就是一个不同的topic,对于这个业务来说,所有的数据都存储在这个topic中
  • Cluster: Kafka的物理集群,每个集群中可以新建多个不同的topic
  • Producer:顾名思义,也就是消息的生产端,负责将业务消息发送到Topic当中
  • Consumer:消息的消费端,负责消费已经发送到topic中的消息
  • Partition:通常topic会有多个分片,不同分片直接消息是可以并发来处理的,这样提高单个Topic的吞吐
  • Offset:消息在partition内的相对位置信息。对于每一个Partition来说,每一条消息都有一个唯一的Offset,并且严格递增
  • Replica:分片的副本,分布在不同的机器上,可用来容灾。Leader对外服务,Follower异步去拉取leader的数据进行一个同步,如果leader挂掉了,可以将Follower提升成leader再堆外进行服务。每个分片有多个Replica、Leader Replica将会从ISR中选出
  • ISR:意思是同步中的副本,对于Follower来说,始终和leader有一定差距,但当这个差距比较小的时候,我们就可以将这个follower副本加入到ISR中,不在ISR中的副本是不允许提升成Leader的

数据复制:

  • Broker代表每一个Kafka的节点,所有的Broker节点最终组成了一个集群。中间有一个Broker同时也扮演了Controller的角色,Controller是整个集群的大脑,负责对副本和Broker进行分配

Kafka架构:

  • 在集群的基础上,还有一个模块ZooKeeper,这个模块存储了集群的元数据信息,如副本的分配信息等,Controller计算好的方案都会放到这里

2.3 Producer

批量发送:

  • 批量发送可以减少IO次数,从而加强发送能力

数据压缩:

  • 通过压缩,减少消息大小,目前支持Snappy、Gzip、LZ4、ZSTD压缩算法

2.4 Broker

消息文件结构:

  • Topic->Partition->Replica->Log->LogSegment->(.log(日志文件)|.index(偏移量索引文件)|.timeindex(时间戳索引文件)|其他文件)

磁盘结构:

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

顺序写:

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

如何找到消息

  • Consumer通过发送FetchRequest请求消息数据,Broker会将指定的Offset处的消息按照时间窗口和消息大小窗口发送给Consumer,寻找数据这个细节是如何实现的

  • 偏移量索引文件

    • 二分找到小于目标offset的最大文件,再遍历找到目标offset
  • 时间戳索引文件

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

零拷贝:

  • Consumer从Broker中读取数据,通过sendfile的方式,将磁盘读到os内核缓冲区后,直接转到socket buffer进行网络发送。Producer生产的数据持久化到broker,采用mmap文件映射,实现顺序的快速写入

2.5 Consumer

对于一个Consumer Group来说,多个分片可以并发的消费,这样可以大大提高消费的效率,但需要解决的问题是,Consumer和Partition的分配问题

Low Level:

  • 手动分配,好处:启动比较快。缺点:新增一台Consumer,需要停掉整个集群,重新修改配置再上线,保证新的Consumer也可以消费数据

High Level:

  • 在的Broker集群中,对于不同的Consumer Group,都会选取一台Broker当做Coordinator,而Coordinator的作用就是帮助Consumer Group进行分片的分配,也叫做分片的rebalance。使用这种方式,如果ConsumerGroup中有发生宕机,或者有新的Consumer加入,整个partition和Consumer都会重新进行分配来达到一个稳定的消费状态

2.6 问题总结

  1. 运维成本太高
  2. 对于负载不均衡的场景,解决方案复制
  3. 没有自己的缓存,完全依赖Page cache
  4. Controller和Coordinator和Broker在同一个进程中,大量IO会造成其性能下降