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

89 阅读7分钟

1.消息队列的前世今生

消息中间件的概念其实很早之前就早诞生了,在1983年互联网应用还是一片荒芜的年代,有个在美国的印度小哥Vvek就设想了一种通用软件总线,世界上第一个现代消息队列软件The Information Bus(TIB)。

image.png 2001年sun发布了jms技术,试图在各大厂商的层面上再包装一层统一的java规范。java程序只需要针对ims api编程就可以了,不需要再关注使用了什么样的消息中间件,但是ims仅仅适用于的java互联互通。

后面使用Erlang语言研发的消息中间件RabbitMQ面世,后面百花齐放,Kfaka诞生。后面阿里为了应对双十一这种非常高并发的场景研发了自己开源中间件RabbitMQ,2012年雅虎内部诞生了自己中间件Pulsar。

下面对于上面的这几个消息队列进行一个对比。 Kafka:分布式的、分区的、多副本的日志提交服务,在高吞吐场景下发挥较为出色

RocketMQ:低延迟、强一致、高性能、高可靠、万亿级容量和灵活的可扩展性,在一些 实时场景中运用较广.

Pulsar:是下一代云原生分布式消息流平台,集消息、存储、轻量化函数式计算为一体 采用存算分离的架构设计

BMQ:和Pulsar架构类似,存算分离,初期定位是承接高吞吐的离线业务场景逐步 替换掉对应的Kafka集群

2.消息队列Kafka

kafka是一个流式数据处理平台,具有消息系统的能力,也有实时流式数据处理分析能力,实际上偏向于将其当做消息队列系统来使用。

2.1如何使用kafka

第一步:首先需要创建一个Kafka集群,现在普遍都是k8s集群统一管理。

第二步:需要在这个集群中创建一个Topic,并且设置好分片数量。这个Topic是消息的组织方式,可以理解为对于消息的一个分类。这里的分片可以理解为不同的节点,分片是为了负载均衡的来进行取用。

第三步:引入对应语言的SDK,配置好集群和Topic等参数,初始化一个生产者,调用Send方法,将你的Hello World发送出去

第四步:引入对应语言的SDK,配置好集群和Topic等参数,初始化一个消费者,调用Pol方法,你将收到你网刚刚发送的Hello World

image.png

2.2 Kafka的基本概念

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

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

Producer:顾名思义,也就是消息的生产端,负责将业务消息发送到Topic

Consumer:消息的消费端,负责消费已经发送到topic中的消息

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

offset:偏移量,分区中的每一条消息都会根据时间先后顺序有一个递增的序号,这个序号就是offset偏移量,可以理解为唯一的ID

Replica:分片的副本,分布在不同的机器上,可用来容灾。

broker:可以理解为kafka服务器

其整体结构如图所示:

image.png

2.3 一条消息的历程

实际上一条消息被制作出来,我们进行发布往往是将很多数据一起发布,而非一次只发一条相关数据。 image.png

批量发送可以降低IO压力,减少发送的次数,提升效率。我们的数据和很多条与之类似数据进行采用GZIP,Liz4之类的压缩算法一起发送到Broker。

接下来Broker将其batch持久化到本地磁盘,其中为了提高效率降低寻道所花费的时候,Broker使用顺序写的方式对其进行处理。

image.png 接下来某一天发现我们需要使用这个数据,采用二分查找的方法对于数据进行查询。首先通过二分的方法找到小于目标索引位置的最大位置,接下来进行遍历寻找目标offset。

image.png 这里Broker对于传统数据Copy模式进行了优化,提升了Copy的效率。 传统copy方法需要由磁盘读入内核空间,然后用户调用到socket 发送到 消费者进程。 image.png

而Broker使用零拷贝技术,不再经过应用空间,直接在内核空间就将数据发送出去了,因此效率大大提升。

image.png

接下来到了消费者接受方面了,对于一个Consumer Group:来说,多个分片可以并发的消费,这样可以大大提高消费的效率,但需要解决的问题是,Consumer和Partition的分配问题,也就是对于每一个Partition来讲,该由哪一个Consumer来消费的问题。对于这个问题,我们有两种解决方法,手动分配和自动分配。

手动分配是指通过手动进行分配,哪一个Consumer消费哪一个Partition完全由业务来决定。但是这样的话,我们进行优化基本上很难优化,业务挂掉了也无法自动处理。

所有kafka也提供自动分配方式,在Broker集群当中,选择一台Broker来当做Coordinator,让其帮助分片的分配,如果有新机器加入或者发生宕机,整个系统会重新进行分配来进行应对。下面是其重新分配的一个简单过程

image.png

2.4Kafka的问题

首先kafka之间的一份数据有着多份保存,因此存在着数据一致性问题。 其次kafka在进行重启,扩容,缩容,替换的情况下,会带来大量的运维成本。下面一个重启的示意图,无论替换重启缩容还是扩容,都是产生大量的IO操作,十分消耗资源。

image.png 最后kafka会带来负载不均衡问题,我们去解决这个问题进行复制,但是会导致IO提升太多的情况。

最后Kfaka的所遇到的主要问题是 1.运维成本高 2.对于负载不均衡的场景,解决方案复杂 3.没有自己的缓存,完全依赖Page Cache 4.Controller和Coordinatori和Broker在同一进程中,大量IO会造成其性能下降

3.消息队列RocketMQ

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

3.1基本概念

image.png 其架构形式如下:

image.png

3.2基础模型

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

image.png

3.3高级特性

事务场景:

image.png 事务消息:

image.png 延迟发送:

image.png

消息重试和死信队列:

image.png