1. 什么是消息队列
队列相信大家应该都不陌生,它是一种先进先出的数据结构,基本结构如下图。
队列
在java中已经实现了各种各样的队列了,那为什么还需要消息队列MQ(Message Queue)这种中间件呢?我们可以先尝试思考一下消息队列存在的意义,它能满足我们项目中的什么需求。
消息队列可以简单理解为,我们把想要传输的数据放到队列中(其结构与普通队列是一样的)
消息队列
我们将把数据放入到队列的那一方叫做生产者
;将从消息队列中取数据的一方叫做消费者
。
2. 使用消息队列的好处
使用消息队列可以为我们系统带来解耦
、异步
、削峰/限流
等好处,具体应用场景如下。
2.1 应用解耦
场景说明: 用户下单后,订单系统需要通知库存系统。
传统的做法为:订单系统调用库存系统的接口。如下图所示:
传统方式:调用库存接口
传统方式具有如下缺点:
-
假设库存系统访问失败,则订单减少库存失败,导致订单创建失败。
-
订单系统同库存系统过度耦合 。
如何解决上述的缺点呢?需要引入消息队列,引入消息队列后的架构如下图所示:
引入消息队列,实现应用解耦
- 订单系统:用户下单后,订单系统进行数据持久化处理,然后将消息写入消息队列,返回订单创建成功。
- 库存系统:使用拉/推的方式,获取下单信息,库存系统根据订单信息,进行库存操作。
假如在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其后续操作了。由此实现了订单系统与库存系统的应用解耦。
2.2 异步处理
场景说明:用户注册后,需要发送注册邮件和发送注册信息,传统的做法有两种:串行方式、并行方式 。
- 串行方式: 将注册信息写入数据库成功后,发送注册邮件,然后发送注册短信,而所有任务执行完成后,返回信息给客户端
串行方式
-
并行方式: 将注册信息写入数据库成功后,同时进行发送注册邮件和发送注册短信的操作。而所有任务执行完成后,返回信息给客户端。同串行方式相比,并行方式可以提高执行效率,减少执行时间。
并行方式
上面的比较可以发现,假设三个操作均需要50ms的执行时间,排除网络因素,则最终执行完成,串行方式需要150ms,而并行方式需要100ms。 因为cpu在单位时间内处理的请求数量是一致的,假设:CPU每1秒吞吐量是100此,则串行方式1秒内可执行的请求量为1000/150,不到7次;并行方式1秒内可执行的请求量为1000/100,为10次。
由上可以看出,传统串行和并行的方式会受到系统性能的局限,那么如何解决这个问题?我们需要引入消息队列,将不是必须的业务逻辑,异步进行处理,由此改造出来的流程为 :
引入消息队列,异步处理消息
根据上述的流程,用户的响应时间基本相当于将用户数据写入数据库的时间,发送注册邮件、发送注册短信的消息在写入消息队列后,即可返回执行结果,写入消息队列的时间很快,几乎可以忽略,也有此可以将系统吞吐量提升至20QPS,比串行方式提升近3倍,比并行方式提升2倍。
2.3 流量削峰
场景说明:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。
- 可以控制参与活动的人数;
- 可以缓解短时间内高流量对应用的巨大压力;
流量削峰处理方式系统图如下:
流量削峰
- 服务器在接收到用户请求后,首先写入消息队列。这时如果消息队列中消息数量超过最大数量,则直接拒绝用户请求或返回跳转到错误页面;
- 秒杀业务根据秒杀规则读取消息队列中的请求信息,进行后续处理。
3. Kafka简介
3.1 消息传递模式
常见的消息传递模式有两种、分别是:
-
点对点模式
:在点对点消息系统中,消息持久化到一个队列中。此时,可以有一个后多个消费者消费队列中的数据,但是一条消息只能被消费一次。当一个消费者消费了队列中的某条数据之后,该条数据则从消息队列中删除。该模式下,既有多个消费者同时消费数据,也能保证数据处理的顺序。如下图所示。
点对点传递模式
-
发布-订阅消息传递模式
:在发布-订阅消息系统中,消息将被持久化到一个topic中,与点对点消息系统的区别是,消费者可以订阅一个或多个topic,消费者可以消费该topic中所有的数据,同一条数据可以被多个消费者消费,且数据消费后不会立马删除,且发布者发送到topic中的消息,只有订阅了topic的订阅者才会收到。在发布-订阅消息系统中,消息的生产者成为发布者,消费者称为订阅者。其中发布-订阅模式中又可以细分为两种模式(消费者主动拉取数据
和队列主动推送数据
)如下图所示。
发布-订阅模式
3.2 概述
Kafka最初由LinkedIn公司开发,是一种高吞吐量的 基于消费者主动拉取数据的分布式发布-订阅消息系统
,常见可以用于web/nginx日志、访问日志,消息服务等,LinkedIn于2010年贡献给了Apache基金会并成为顶级开源项目。
Kafka主要设计目标如下:
- 以时间复杂度为O(1)的方式提供消息持久化能力,即使对TB级以上数据也能保证常数时间的访问性能;
- 高吞吐率。即使在非常廉价的商用机器上也能做到单机支持每秒100K条消息的传输;
- 支持Kafka Server间的消息分区,及分布式消费,同时保证每个partition内的消息顺序传输;
- 同时支持离线数据处理和实时数据处理;
- Scale out:支持在线水平扩展。
4. Kafka中常用的术语
我们通过下图的Kafka简单示例架构来熟悉Kafka中的术语。
Kafka架构示例
我们先解释图中出现的术语,最后再对整个图的内容做一个详细解释。
4.1 Topic
每天发布到Kafka集群的消息都有一个主题类别( 消息分类
),这个主题类别称为 Topic
。物理上不同的Topic的消息可能存储在同一broker中,也有可能不在同一broker中,但逻辑上只要用户指定消息的topic即可消费关于该topic 的有关数据,而不必关心数据的物理存储。
4.2 Partition
Topic中的数据分割为一个或多个分区( partition
)。partition有以下特性:
- 每个Topic至少有一个partition;
- 每个partition中的数据使用多个segment文件存储;
- partition中的数据是有序的,而不同partition间的数据丢失了数据的顺序(即我们无法知道哪个partition的数据是最先的或最后的);
- 如果topic有多个partition,消费数据时就不能保证数据的顺序。因此,在需要严格保证消息的消费顺序的场景下,需要将partition的数目设置为1。
4.3 Broker
在上图中我们可以在 Kafka Cluster
(即Kafka集群)中看到,集群里包含了一个或多个服务器,这些服务器节点称为 Broker
。Broker存储着topic的数据,常见的存储数据方式如下:
-
如果某topic有N个partition,集群有N个Broker,那么每个Broker存储该topic的一个partition;
-
如果某topic有N个partition,集群有N+M个Broker,那么其中有N个broker存储该topic的一个partition,剩下的M个Broker不存储该topic的partition数据;
-
如果某topic有N个partition,集群的Broker数目少于N个,那么一个Broker存储该topic的一或多partition
(
注意
:实际生产环境中,一般会尽量避免这种情况发生,因为这种情况容易导致Kafka集群数据不均衡)。
4.4 Producer
Producer
即数据的发布者,该角色将消息发布到Kafka的topic中。Broker接收到生产者发送的消息后,将该消息 追加
到当前用于追加数据的segment文件中。生产者发送的消息,可以存储到每一个partition中,也可以指定数据存储的partition。
4.5 Consumer
Consumer
即为消费数据的一方,消费者可以从Broker中读取数据。消费者可以消费多个topic中的数据。
4.6 Offset
Offset
即偏移量(位置),标识分区每条记录的位置,是分区当中每条记录的唯一标识。消费者通过记录当前消费到的偏移量来记录消费情况。
偏移量
4.7 Consumer Group
Consumer Group
即为消费者组,用于占用式消费。我们可以为每个Consumer指定Group name,若不指定Group name则属于默认的Group。消费者组有以下特性:
- 对于同一个topic的同一个partition,消费者消费时,会按消费者组来记录消费偏移,即一个组内只维护一个消费偏移。假设消费者1和消费者2同属一组,消费者1消费了一部分数据,那么消费者2此时再来消费数据的话,是无法消费到消费者1消费了的那部分数据的。如下图:
消费者组消费示例
- 不同的group之间记录不同的offset,这样在不同程序读取同一个topic时,才不会因为offset互相影响。
4.8 Leader
每个partition有多个副本,其中 有且仅有
一个作为 Leader
,Leader是当前负责数据读写的partition。
4.9 Follower
Follower
跟随Leader,即与Leader数据保持一致。其特性如下:
- 所有写请求都通过Leader来路由,即Leader数据的变更会广播给所有Follwer;
- Follwer与Leader保持数据同步;
- 如果Leader失效,则从Follower中选举出一个新的Leader;
- 当Follower挂掉、卡住或同步太慢,Leader会把这个Follower从"in sync replicas"(ISR)列表中删除,重新创建一个Follower。
4.10 架构示例图详细解释
Kafka架构示例
-
我们可以看到,图中主要分为四个部分。分别是
Producer
、Kafka Cluster
、Consumer Group
和Zookeeper注册中心
; -
Producer中有两个生产者A和B。Kafka Cluster中有3个Broker(即3台服务器)。Consumer Group中有3个消费者,其中1和2消费者属于同一个消费者组,消费者3属于默认消费者组。Zookeeper主要负责统一管理Kafka Cluster中的所有注册的Broker、消费者组中的消费者。
-
某个主题消息(Topic A)设置了两个分区,分别是
partition 0
与partition 1
,每个partition都有其备份分区。其中存储在Broker 1中的partition 0为partition 0的Leader(生产者生产消息与消费者消费消息时主要是与Leader交互),partition 0的备份存储在Broker 2中(图中绿色部分)。存储在Broker 2中的partition 1为partition 1的Leader,partition 1的备份存储在Broker 1中(图中浅蓝色部分)。 -
首先,最左边的是两个生产者A、B。生产者A生产了
message-A
这个消息,将消息分别发送至两个partition的Leader中。生产者B将生产的消息发送到只有一个分区的Topic B的partition 0分区中。 -
然后在消费阶段,Consumer 1消费Topic A的partition 0中的消息。Consumer 2由于和Consumer 1属于一个消费者组(对相同partition属于占用式消费,但消费不同partition时可以同时进行消费),所以此时Consumer 2可以消费Topic A的partition 1中的消息。Consumer 3 消费Topic B中的partition 0中的消息。