消息队列

 摘抄自网上,仅用于自己学习

 

一 、 为什么要用消息队列

  

  三个主要应用场景:解耦、异步、削峰

 

  1. 解耦

  传统模式

  

 

 

   传统模式的缺点:系统间耦合性太强。如图所示,系统 A 在代码中直接调用系统 B 和系统 C 的代码,如果将来系统 D 需要接入,系统 A 还需要修改代码,过于麻烦。

 

  中间件模式:

  

 

 

   中间件模式的优点:将消息写入消息队列,需要消息的系统自己从消息队列中订阅,从而系统 A 不需要做任何修改。

 

  2. 异步

  传统模式:

 

 

   传统模式的缺点:一些非必要的业务逻辑以同步的方式运行,太耗费时间。

 

  中间件模式:

  

 

 

   中间件模式的优点:将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度

 

  3. 削峰

  传统模式:

  

 

 

   传统模式的缺点:并发量大的时候,所有的请求直接怼到数据库,造成数据库连接异常

 

  中间件模式:

  

 

 

   中间件模式的优点:系统 A 逐步按照数据库能处理的并发量,从消息队列中拉取消息。在生产中,这个短暂的高峰期积压是允许的。

 

二 、 使用了消息队列会有什么缺点?

  

  1. 系统的可用性降低。如果消息队列挂了,系统就挂了了。

  2. 系统复杂性增加。要考虑很多方面的问题,比如一致性问题、如何保证消息不被重复消费,如何保证消息可靠传输。

  尽管有缺点,但是该用还是要用

 

三 、 消息队列如何选型?

 

  RabbitMQ 版本发布比 ActiveMQ 频繁很多。RocketMQ 和 kafka 也是活跃。

 

  性能对比表(这里没有 持久化的比较)

特性 ActiveMQ RabbitMQ RocketMQ kafka
开发语言 java erlang java scala
单机吞吐量 万级 万级 10万级 10万级
时效性 ms级 us级 ms级 ms级
可用性 高(主从) 高(主从) 非常高(分布式) 非常高(分布式)
功能特性  成熟的产品,在很多公司得到应用;有较多的文档;各种协议支持较好  基于 erlang 开发,所以并发能力强,性能极好,延时很低;管理界面比较丰富  MQ 功能比较完备,扩展性佳  只支持主要的 MQ 功能,像一些消息查询,消息回溯功能没有提供,毕竟是为大数据准备的,在大数据领域应用广

  

   综合比较:

  • ActiveMQ 的社区算是比较成熟,但是较目前来说,ActiveMQ 的性能比较差,而且版本迭代很慢,不推荐使用。
  • RabbitMQ 在吞吐量方面虽然稍逊于 Kafka 和 RocketMQ ,但是由于它基于 erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。但是也因为 RabbitMQ 基于 erlang 开发,所以国内很少有公司有实力做erlang源码级别的研究和定制。如果业务场景对并发量要求不是太高(十万级、百万级),那这四种消息队列中,RabbitMQ 一定是你的首选。如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。
  • RocketMQ 阿里出品,Java 系开源项目,源代码我们可以直接阅读,然后可以定制自己公司的MQ,并且 RocketMQ 有阿里巴巴的实际业务场景的实战考验。RocketMQ 社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准 JMS 规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用RocketMQ 挺好的
  • kafka 的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。kafka 唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。

 

 

四 、 如何保证消息队列是搞可用的?

 

  引入消息队列后,系统的可用性下降。在生产中,没人使用单机模式的消息队列。因此,作为一个合格的程序媛,应该对消息队列的高可用有很深刻的了解。

  以 rocketMQ 为例,他的集群就有 多master模式、多master多slave 异步复制模式、多master多slave同步双写模式。

  下图是 多master多slave 模式的部署架构图。

   

 

 

   这个图,和 kafka 很像,只是 NameServer 集群,在 kafka 中是用 zookeeper 代替,都是用来保存和发现 master 和 slave 用的。通信过程如下:

  Producer 与 NameServer 集群中的任一节点建立长连接,定期从 NameServer 获取 Topic 路由信息,并向提供 Topic 服务的 Broker Master 建立长连接,且定时向 Broker 发送心跳。

  Producer 只能将消息发送到 Broker Master,但是 Consumer 则不一样,它同时和提供 Topic 服务的 Master 和 Slave 建立长连接,既可以从 Broker Master 订阅消息,也可以从 Broker Slave 订阅消息。

 

  下图是 kafka 的拓扑架构图

  

 

   如上图所示,一个典型的 kafka 集群中包含若干 Producer (可以是 web 前端产生的 Page View,或者是服务器日志,系统 CPU、Memory 等),若干 broker (kafka 支持水平扩展,一般 broke 数量越多,集群吞吐量越高),若干 Consumer,以及一个 zookeeper 集群。kafka 通过 zookeeper 管理集群配置,选举 leader,以及在 Consumer Group 发生变化时进行 rebalance。Producer 使用 push 模式将消息发布到 broker,Consumer 使用 pull 模式从 broker 订阅并消费消息。

 

  RabbitMQ , 也有普通集群和镜像集群模式。

  (注意集群架构图)

  

 

五 、 如何保证消息不被重复消费?

  这个问题,也就是保证消息队列的幂等性。根据具体业务场景不同。

  

  1)为什么会造成重复消费?

  其实无论哪种消息队列,造成重复消费原因都是类似。正常情况下,消费者在消费消息的时候,消费完毕后,会发送一个确认信息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除。只是不同的消息队列发送的确认信息形式不同,例如 RabbitMQ 是发送一个 ACK 确认信息,RocketMQ 是返回一个 CONSUME_SUCCESS 成功标志,kafka 实际上是有个 offset 的概念,就是每个消息都有一个 offset,kafka 消费过消息后,需要提交 offset,让消息队列知道自己已经消费过了。

  那造成重复消费的原因,就是因为网络传输等等故障,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将该消息分发给其他的消费者。

 

  2)怎么解决重复消费呢?

  比如,当前是 insert 操作,可以给这个消息做一个 唯一主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。

  再比如,当前是 redis 的 set 操作,那就不用解决,因为 set 操作具有幂等性,无论 set 几次,结果都是一样的。

  如果上面两种都不行,可以准备一个第三方介质,来做消费记录。以 redis 为例,给消息分配一个全局 id ,只要消费过该消息,将 <id, message> 以 K - V  的形式写入 redis。那消费者开始消费前,先去 redis 中查询有没有消费记录即可。

 

六 、 如何保证消费的可靠传输?

 

  在使用消息队列的过程中,应该做到不能多消费,也不能少消费。

  这个可靠传输,每种 MQ 都要从三个角度来分析:生产者弄丢数据、消息队列弄丢数据、消费者弄丢数据。

 

RabbitMQ 

  1)生产者丢数据

  从生产者弄丢数据的角度看,RabbitMQ 提供 transaction 和 confirm 模式来确保生产者不丢消息。

  transaction 机制就是说,发送消息前,开启事务(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事务就会回滚(channel.txRollback()),如果发送成功则提交事务(channel.txCommit())。

  然而缺点就是吞吐量下降了。因此,按照一般经验,生产上用 confirm 模式的居多。一旦 channel 进入 confirm 模式,所有在该信道上面发布的消息都将会被指派一个唯一的 ID (从 1 开始),一旦消息被投递到所有匹配的队列后,rabbitMQ 就会发送一个 Ack 给生产者(包含消息的唯一 ID),这就使得生产者知道消息已经正确到达目的队列了,如果 rabbitMQ 没能处理该消息,则会发送一个 Nack 消息给生产者,可以进行重试操作。

  

   2)消息队列丢数据

  处理消息队列丢数据的情况,一般是开启磁盘持久化的配置。这个持久化配置可以和 confirm 机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个 Ack 信号。这样,如果消息磁盘持久化之前,rabbitMQ 阵亡了,那么生产者收不到 Ack 信号,生产者会自动重发。(这里如果第一次发的消息才到,生产者任务没发成功又来一条,消息队列怎么处理??

  怎么持久化呢?

  a. 将 queue 的持久化标识 durable 设置为 true,则代表是一个持久化的队列

  b. 发送消息的时候将 deliveryMode=2

  这样两步设置后,rabbitMQ 就算挂了,重启后也能恢复数据。

 

  3)消费者丢数据

  消费者丢数据一般是因为采用了自动确认消息模式。这种模式下,消费者会自动确认收到信息。这时 rabbitMQ 会立即将消息删除,这种情况下,如果消费者出现异常而没能处理该消息,就会丢失消息。至于解决方法,采用手动确认消息即可。

 

 

 

 

 

 

 

  

kafka 

  这里先引一张 kafka Replication 的数据流向图

 

 

 

 七 、 如何保证消息的顺序性?

  针对这个问题,通过某种算法,将需要保持先后顺序的消息放到同一个队列中(kafka 中就是 partition,rabbitMQ 就是 queue)。然后只用一个消费者去消费该队列。但是这样的吞吐量受到限制,具体的应该根据具体的业务来定。

  

 

 

 

 

 

https://www.cnblogs.com/williamjie/p/9481780.html

 

posted @ 2020-04-13 15:56  停不下的时光  阅读(539)  评论(0编辑  收藏  举报