为什么要用 MQ

一、为什么要使用消息队列:解耦、异步、削峰

1️⃣解耦

传统模式的缺点:系统间耦合性太强。如图,系统 A 直接调用系统 B 和系统 C,系统 D 接入,系统 A 还得修改代码:

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

2️⃣异步

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

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

3️⃣削峰

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

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

二、消息队列的弊端

1️⃣系统可用性降低
系统引入的外部依赖越多,越容易挂掉。本来 A 系统调用 BCD 三个系统的接口就好了,ABCD 四个系统好好的,没啥问题。硬加个 MQ,MQ 挂了,整套系统就崩溃了,风险很大。因此,系统可用性降低。

2️⃣系统复杂性增加
要多考虑很多方面的问题,如何处理消息丢失的情况?如何保证消息传递的顺序性?如何保证消息没有重复消费?

3️⃣一致性问题
A 系统处理完了直接返回成功了,人都以为这个请求就成功了;但要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,这数据就不一致了。所以消息队列实际是一种非常复杂的架构,引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉,系统复杂度提升了一个数量级,也许是复杂了 10 倍。

三、如何保证消息队列高可用

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

多 master 多 slave 模式部署架构图:

通信过程如下:
Producer 与 NameServer 集群中的一个节点(随机选择)建立长连接,定期从 NameServer 获取 Topic 路由信息,并向提供 Topic 服务的 Broker Master 建立长连接,且定时向 Broker 发送心跳。

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

那么 kafka 呢,为了对比说明直接上 kafka 的拓补架构图:

如图,一个典型的 Kafka 集群中包含若干 Producer(可以是 web 前端产生的 Page View,或者是服务器日志,系统 CPU、Memory 等),若干 broker(Kafka 支持水平扩展,一般 broker 数量越多,集群吞吐率越高),若干 Consumer Group,以及一个 Zookeeper 集群。

Kafka 通过 Zookeeper 管理集群配置,选举 leader,以及在 Consumer Group 发生变化时进行 rebalance。

Producer 使用 push 模式将消息发布到 broker,Consumer 使用 pull 模式从 broker 订阅并消费消息。

四、如何保证消息不被重复消费?即如何保证消息队列的幂等性?

为什么会造成重复消费?

无论哪种消息队列,造成重复消费原因其实都是类似的。通常,消费者在消费消息完毕后,会发送一个确认信息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除。只是不同的消息队列发送的确认信息形式不同。

例如,RabbitMQ 是发送一个 ACK 确认消息,RocketMQ 是返回一个 CONSUME_SUCCESS 成功标志。kafka 实际上有个 offset 的概念。简单说一下,就是每一个消息都有一个 offset,kafka 消费过消息后,需要提交 offset,让消息队列知道自己已经消费过了。

##造成重复消费的原因

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

##如何解决?这个问题针对业务场景来答分以下几点:

1️⃣如果拿到这个消息做数据库的 insert 操作:给这个消息做一个唯一主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。
2️⃣如果拿到这个消息做 Redis 的 set 的操作:不用解决。因为无论 set 几次结果都是一样的,set 操作本来就算幂等操作。
3️⃣准备一个第三方介质,来做消费记录。以 Redis 为例,给消息分配一个全局 id,只要消费过该消息,将以 K-V 形式写入 Redis。那消费者开始消费前,先去 Redis 中查询有没消费记录即可。

五、如何保证消息的可靠性传输

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

1️⃣RabbitMQ

  1. 生产者丢数据
    从生产者弄丢数据这个角度来看,RabbitMQ 提供 transaction 和 confirm 模式来确保生产者不丢消息。transaction 机制就是说,发送消息前,开启事务(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事物就会回滚(channel.txRollback()),如果发送成功则提交事务(channel.txCommit())。
    缺点就是吞吐量下降了。因此,生产上用 confirm 模式的居多。一旦 channel 进入 confirm 模式,所有在该信道上面发布的消息都将会被指派一个唯一的 ID(从 1 开始)。一旦消息被投递到所有匹配的队列之后,RabbitMQ 就会发送一个 Ack 给生产者(包含消息的唯一 ID)。这就使得生产者知道消息已经正确到达目的队列了。如果 RabiitMQ 没能处理该消息,则会发送一个 Nack 消息给你,你可以进行重试操作。

处理Ack和Nack的代码如下所示:

  1. 消息队列丢数据
    处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。这个持久化配置可以和 confirm 机制配合使用,可以在消息持久化磁盘后,再给生产者发送一个 Ack 信号。这样,如果消息持久化磁盘之前,RabbitMQ 阵亡了,那么生产者收不到 Ack 信号,生产者会自动重发。那么如何持久化呢,就下面两步:
  • 将 queue 的持久化标识 durable 设置为 true,则代表是一个持久的队列。
  • 发送消息的时候将 deliveryMode=2

这样设置以后,RabbitMQ 就算挂了,重启后也能恢复数据。

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

2️⃣kafka

Producer 在发布消息到某个 Partition 时,先通过 ZooKeeper 找到该 Partition 的 Leader 然后无论该 Topic 的 Replication Factor 为多少(也即该 Partition 有多少个 Replica),Producer 只将该消息发送到该 Partition 的 Leader。Leader 会将该消息写入其本地 Log。每个 Follower 都从 Leader 中 pull 数据。

针对上述情况,得出如下分析

  1. 生产者丢数据
    在kafka生产中,基本都有一个leader和多个follwer。follwer会去同步leader的信息。因此,为了避免生产者丢数据,做如下两点配置:
  • 第一个配置要在producer端设置acks=all。这个配置保证了,follwer同步完成后,才认为消息发送成功。
  • 在producer端设置retries=MAX,一旦写入失败,这无限重试
  1. 消息队列丢数据
    针对消息队列丢数据的情况,无外乎就是,数据还没同步,leader就挂了,这时zookpeer会将其他的follwer切换为leader,那数据就丢失了。针对这种情况,应该做两个配置:
  • replication.factor参数,这个值必须大于1,即要求每个partition必须有至少2个副本
  • min.insync.replicas参数,这个值必须大于1,这个是要求一个leader至少感知到有至少一个follower还跟自己保持联系

这两个配置加上上面生产者的配置联合起来用,基本可确保kafka不丢数据

  1. 消费者丢数据
    这种情况一般是自动提交了offset,然后你处理程序过程中挂了。kafka以为你处理好了。再强调一次offset是干嘛的。offset:指的是kafka的topic中的每个消费组消费的下标。简单的来说就是一条消息对应一个offset下标,每次消费数据的时候如果提交offset,那么下次消费就会从提交的offset加一那里开始消费。比如一个topic中有100条数据,我消费了50条并且提交了,那么此时的kafka服务端记录提交的offset就是49(offset从0开始),那么下次消费的时候offset就从50开始消费。解决方案也很简单,改成手动提交即可。

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

针对这个问题,通过某种算法,将需要保持先后顺序的消息放到同一个消息队列中(kafka 中就是 partition、RabbitMQ 中就是 queue)。然后只用一个消费者去消费该队列。

如果为了吞吐量,有多个消费者去消费如何

这个问题,没有固定回答的套路。比如关于微博,发微博、写评论、删除微博,这三个异步操作。如果是这样一个业务场景,那只要重试就行。

比如某个消费者先执行了写评论的操作,然而此时微博都还没发,写评论一定是失败的。等另一个消费者,先执行发微博的操作后,再执行,就可以成功。

总之,针对这个问题,保证入队有序就行,出队以后的顺序交给消费者自己去保证,没有固定套路。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 使用MQ消息中间件的优势在于它可以让发送和接收消息的应用程序解耦,异步地进行消息传递。MQ消息中间件还可以提供可靠的消息传输,支持各种消息模式,提供灵活的消息路由,支持可靠的消息重试,可以提供高吞吐率的消息处理等优势。 ### 回答2: 使用消息中间件(MQ)的主要原因是为了解决分布式系统中的异步通信和解耦的问题,提高系统的可靠性、可扩展性和性能。以下是MQ的几个重要优势: 1. 异步通信:MQ提供了异步通信的机制,可以让发送方和接收方在时间上解耦。发送方把消息发送到MQ后,可以立即继续自己的工作,而不需要等待接收方的响应。这对于处理高并发请求和处理耗时任务非常有帮助。 2. 解耦:MQ能够将发送方和接收方解耦,发送方只需要关注把消息发送到MQ中,而不需要知道实际的接收方是谁。同样,接收方只需要从MQ中获取消息,而不需要知道消息的发送方是谁。这样可以提高系统的灵活性和可维护性。 3. 可靠性:MQ提供消息持久化的机制,确保即使在发送方和接收方的故障或者断电情况下,消息仍然能够被保存下来,并在故障恢复后重新传递。同时,MQ还提供了事务和回滚的机制,保证消息的可靠性传递。 4. 可扩展性:MQ具有高度的可扩展性,可以根据实际需求添加更多的消息生产者和消费者。同时,MQ还支持多种消息传递模式,如发布/订阅、点对点等,可以根据不同的业务场景选择合适的模式。 5. 削峰填谷:通过将消息缓存到MQ中,可以平滑处理系统的高峰请求和突发流量,避免系统的负载过高。同时,MQ还支持消息的优先级和延时发送,可以更好地满足不同类型的消息需求。 总之,使用MQ消息中间件可以提供异步通信、解耦、可靠性、可扩展性和削峰填谷等优势,帮助构建高性能、高可靠性的分布式系统。 ### 回答3: MQ消息中间件(Message Queue)是一种用于处理消息的软件架构,它被广泛应用于分布式系统和异步通信中。使用MQ消息中间件的主要原因有以下几点: 1. 解耦:使用消息中间件能够将系统中不同模块之间的通信解耦,即发送方和接收方之间不直接依赖于彼此的存在。发送方只需要将消息发送到队列中,而不需要关心具体的接收方是谁。这种解耦能够提高系统的可扩展性和可维护性。 2. 异步通信:消息中间件支持异步通信模式,即发送方发送消息后就可以继续处理其他任务,不需要等待接收方返回响应。这种方式可以提高系统的性能和吞吐量,同时也能降低系统之间的耦合度。 3. 缓冲与削峰:消息中间件能够缓冲消息,当接收方无法立即处理消息时,消息会暂时存储在队列中,等待接收方空闲时再进行处理。这种缓冲能够平衡系统中不同模块之间的处理速度差异,并且能够应对突发性的请求,避免系统过载。 4. 可靠性与持久化:消息中间件支持消息的持久化存储,保证消息不会因为系统故障或中断而丢失。即使在消息发送后出现问题,通过重新发送机制,消息仍然能够被接收方正确处理,保证消息的可靠性。 5. 可拓展性:消息中间件能够支持高并发和高可用的应用场景,通过多个消息队列实例的部署,能够实现水平扩展和负载均衡,提高系统的可拓展性。 综上所述,使用消息中间件的优势包括解耦、异步通信、缓冲与削峰、可靠性与持久化以及可拓展性。这些优势能够提高系统的性能、可用性和可维护性,使得分布式系统更加稳定和灵活。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JFS_Study

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值