MQ学习继续进行中

学习极客时间——李玥老师的消息队列高手课的总结,夯实自己的基础,开干!

一、为什么需要消息队列

1、异步处理
2、流量控制-削峰填谷
3、服务解耦
4、做个发布/订阅系统
5、做消息广播
6、做流计算
7、做日志统计


二、该如何选择消息队列

RabbitMQ
1、优点:少数支持AMQP协议的消息队列之一。
2、优点:轻量级,便于上手,开箱即用易于维护。
3、缺点:对消息堆积的支持不是很好,当大量堆积的时候,性能急剧下降。
4、缺点:它的性能是最差的,根据硬件的配置不同,大概每秒钟几万到十几万,如果要求特别高,建议不要选择它。
5、缺点:基于Erlang开发,不好做一些拓展和二次开发。

RocketMQ
1、优点:很容易进行拓展或者二次开发。
2、优点:对响应延时做了很多优化,支持毫秒级的响应。
3、优点:性能比RabbitMQ高一个数量级,每秒钟大概能处理几十万条消息。
4、缺点:在国外不那么兼容,与周边生态系统的集成和兼容度要略逊一筹。

Kafka
1、优点:与周边生态系统的兼容性是最好的,没有之一。
2、优点:基于Scala和Java开发,设计上使用了批量和异步的思想,所以具有超高性能,在异步收发的性能上,比前两者中是最好的。但是和RocketMQ没有量级的差异,每秒钟大约能处理几十万条消息。-(大神测极限 ,每秒2000万条消息)
3、缺点:因为异步,并且是攒一波再发送,所以延时会比较高。所以不太适合在线业务场景。
4、缺点:不保证消息的可靠性,可能会丢失,也不支持集群。当初设计的目的就是为了处理海量的日志。

ActiveMQ
1、说明:老牌开源的消息队列,社区不活跃。

ZeroMQ
1、说明:是一个基于消息队列的多线程网络库,如果你的需求是将消息队列的功能集成到你的系统进程中,开源考虑它。

Pulsar
1、说明:最早由Yahoo开发,采用存储和计算分离的设计,目前处于成长期。


三、消息模型:主题和队列有什么区别?

  • 3.1、概念

    1、队列(Queue)是一种数据结构,是先进先出的线性表,只允许后端(rear)插入操作,前端(front)进行删除操作。
    什么是队列
    2、队列的“发布-订阅模型”,普通队列只能解决一对一,如果是一对多就不满足,所以就这样演变出来这个概念。
    发布-订阅模型

  • 3.2、RabbitMQ的消息模型

    1、它是少数坚持使用队列模型的产品之一。那它如何解决多个消费者的问题?使用的Exchange模块,再配置对应的策略。
    RabbitMQ的消息模型

  • 3.3、RocketMQ的消息模型

    1、因为设计有一个Broker机制,做生产者发送确认的响应,以及消费者 消费成功的确认。这样一个一个消费,继而引发消息空洞,违背了有序性这个原则。所以也有主题队列的概念,通过多个队列来并行生产和消费。

    2、订阅者的概念是通过消费组(Consumer Group)来体现的。每个消费组都消费主题中一份完整的消息,不同消费组之间消费进度彼此不受影响,也就是说,一条消息被 Consumer Group1 消费过,也会再给 Consumer Group2 消费。

    3、消费组中包含多个消费者,同一个组内的消费者是竞争消费的关系,每个消费者负责消费组内的一部分消息。如果一条消息被消费者 Consumer1 消费了,那同组的其他消费者就不会再收到这条消息。

    4、在 Topic 的消费过程中,由于消息需要被不同的组进行多次消费,所以消费完的消息并不会立即被删除,这就需要 RocketMQ 为每个消费组在每个队列上维护一个消费位置(Consumer Offset),这个位置之前的消息都被消费过,之后的消息都没有被消费过,每成功消费一条消息,消费位置就加一。这个消费位置是非常重要的概念,我们在使用消息队列的时候,丢消息的原因大多是由于消费位置处理不当导致的。
    RocketMQ的消息模型

  • 3.4、Kafka的消息模型

    1、Kafka的消息模型和RocketMQ是完全一样的,唯一的区别就是在 Kafka 中,队列这个概念的名称不一样,Kafka 中对应的名称是“分区(Partition)”,含义和功能是没有任何区别的。

  • 3.5、总结

     1、区别:这两个概念的背后实际上对应着两种不同的消息模型:队列模型和发布 - 订阅模型。然后你需要理解,这两种消息模型其实并没有本质上的区别,都可以通过一些扩展或者变化来互相替代。
    
     2、常用的消息队列中,RabbitMQ 采用的是队列模型,但是它一样可以实现发布 - 订阅的功能。RocketMQ 和 Kafka 采用的是发布 - 订阅模型,并且二者的消息模型是基本一致的。
    
     3、队列模型:消息数据不可重复消费;发布-订阅模型:消息数据可重复消费。
    

四、如何利用事务消息实现分布式事务?

消息队列中的“事务”,主要解决的是消息生产者和消息消费者的数据一致性问题。比如做个订单+购物车系统,如图:
消息队列事务的使用
在上述任意步骤都有可能失败的情况下,还要保证订单库和购物车库这两个库的数据一致性。这时候就需要事务来解决问题了。

  • 4.1、什么是分布式事务?
    分布式事务就是要在分布式系统中的实现事务。那什么是事务呢?也就是我们所谓的ACID:原子性、一致性、隔离性、持久性。但是,对于分布式系统来说,严格的实现 ACID 这四个特性几乎是不可能的!

     	原子性:是指一个事务操作不可分割,要么成功,要么失败,不能有一半成功一半失败的情况。
     	一致性:是指这些数据在事务执行完成这个时间点之前,读到的一定是更新前的数据,之后读到的一定是更新后的数据,不应该存在一个时刻,让用户读到更新过程中的数据。
     	隔离性:是指一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对正在进行的其他事务是隔离的,并发执行的各个事务之间不能互相干扰,这个有点儿像我们打网游中的副本,我们在副本中打的怪和掉的装备,与其他副本没有任何关联也不会互相影响。
     	持久性:是指一个事务一旦完成提交,后续的其他操作和故障都不会对事务的结果产生任何影响。
    

    在实际应用中,比较常见的分布式事务实现有 2PC(Two-phase Commit,也叫二阶段提交)、TCC(Try-Confirm-Cancel) 和事务消息。事务消息适用的场景主要是那些需要异步更新数据,并且对数据实时性要求不太高的场景。

  • 4.2、消息队列是如何实现分布式事务的?
    使用消息队列提供的功能实现事务消息,Kafka 和 RocketMQ 都提供了事务相关功能。拿订单+购物车系统来说:
    消息队列实现事务消息

     1、首先,订单系统在消息队列上开启一个事务。然后订单系统给消息服务器发送一个“半消息”,这个半消息不是说消息内容不完整,它包含的内容就是完整的消息内容,半消息和普通消息的唯一区别是,在事务提交之前,对于消费者来说,这个消息是不可见的。半消息发送成功后,订单系统就可以执行本地事务了,在订单库中创建一条订单记录,并提交订单库的数据库事务。
     
     2、然后根据本地事务的执行结果决定提交或者回滚事务消息。如果订单创建成功,那就提交事务消息,购物车系统就可以消费到这条消息继续后续的流程。如果订单创建失败,那就回滚事务消息,购物车系统就不会收到这条消息。这样就基本实现了“要么都成功,要么都失败”的一致性要求。
     
     3、如果你足够细心,可能已经发现了,这个实现过程中,有一个问题是没有解决的。如果在第四步提交事务消息时失败了怎么办?
     
     4、对于这个问题,Kafka 和 RocketMQ 给出了 2 种不同的解决方案。Kafka 的解决方案比较简单粗暴,直接抛出异常,让用户自行处理。我们可以在业务代码中反复重试提交,直到提交成功,或者删除之前创建的订单进行补偿。RocketMQ 则给出了另外一种解决方案。
    
  • 4.3、RocketMQ 中的分布式事务实现
    在 RocketMQ 中的事务实现中,增加了 “事务反查的机制” 来解决事务消息提交失败的问题。

     1、如果 Producer 也就是订单系统,在提交或者回滚事务消息时发生网络异常,RocketMQ的 Broker 没有收到提交或者回滚的请求,Broker 会定期去 Producer 上反查这个事务对应的本地事务的状态,然后根据反查结果决定提交或者回滚这个事务。
     
     2、为了支撑这个事务反查机制,我们的业务代码需要实现一个反查本地事务状态的接口,告知RocketMQ 本地事务是成功还是失败。
     
     3、在我们这个例子中,反查本地事务的逻辑也很简单,我们只要根据消息中的订单 ID,在订单库中查询这个订单是否存在即可,如果订单存在则返回成功,否则返回失败。RocketMQ 会自动根据事务反查的结果提交或者回滚事务消息。
     
     4、这个反查本地事务的实现,并不依赖消息的发送方,也就是订单服务的某个实例节点上的任何数据。这种情况下,即使是发送事务消息的那个订单服务节点宕机了,RocketMQ 依然可以通过其他订单服务的节点来执行反查,确保事务的完整性。
    

    综合上面讲的通用事务消息的实现和 RocketMQ 的事务反查机制,使用 RocketMQ 事务消息功能实现分布式事务的流程如下图:
    Rocket MQ中的分布式事务实现


五、如何确保消息不会丢失?

5.1、检测消息丢失的方法
我们可以利用消息队列的有序性来验证是否有消息丢失。原理非常简单,在 Producer 端,我们给每个发出的消息附加一个连续递增的序号,然后在 Consumer 端来检查这个序号的连续性。

1、如果没有消息丢失,Consumer 收到消息的序号必然是连续递增的,或者说收到的消息,其中的序号必然是上一条消息的序号 +1。如果检测到序号不连续,那就是丢消息了。还可以通过缺失的序号来确定丢失的是哪条消息,方便进一步排查原因。

2、大多数消息队列的客户端都支持拦截器机制,你可以利用这个拦截器机制,在 Producer 发送消息之前的拦截器中将序号注入到消息中,在 Consumer 收到消息的拦截器中检测序号的连续性,这样实现的好处是消息检测的代码不会侵入到你的业务代码中,待你的系统稳定后,也方便将这部分检测的逻辑关闭或者删除。

如果是在一个分布式系统中实现这个检测方法,有几个问题需要你注意。

1、首先,像 Kafka 和 RocketMQ 这样的消息队列,它是不保证在 Topic 上的严格顺序的,只能保证分区上的消息是有序的,所以我们在发消息的时候必须要指定分区,并且,在每个分区单独检测消息序号的连续性。

2、如果你的系统中 Producer 是多实例的,由于并不好协调多个 Producer 之间的发送顺序,所以也需要每个 Producer 分别生成各自的消息序号,并且需要附加上 Producer 的标识,在 Consumer 端按照每个 Producer 分别来检测序号的连续性。Consumer 实例的数量最好和分区数量一致,做到 Consumer 和分区一一对应,这样会比较方便地在 Consumer 内检测消息序号的连续性。

5.2、确保消息可靠传递
你可以看下这个图,一条消息从生产到消费完成这个过程,可以划分三个阶段,为了方便描述,我给每个阶段分别起了个名字。
在这里插入图片描述
生产阶段: 在这个阶段,从消息在 Producer 创建出来,经过网络传输发送到 Broker端。

存储阶段: 在这个阶段,消息在 Broker 端存储,如果是集群,消息会在这个阶段被复制到其他的副本上。

消费阶段: 在这个阶段,Consumer 从 Broker 上拉取消息,经过网络传输发送到Consumer 上。

  1. 生产阶段

  2. 存储阶段

  3. 消费阶段


六、如何处理消费过程中的重复消息?


七、消息积压了该如何处理?

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值