消息队列缺点

  • 系统可用性降低:加入消息队列,当消息队列出问题,将会导致系统不可用,系统可用性会降低
  • 系统复杂性增加:加入了消息队列,要多考虑很多方面的问题,比如:一致性问题、如何保证消息不被重复消费、如何保证消息可靠性传输等,系统复杂性增加。
  • 一致性问题:多个消费者时,会引发数据一致性的问题。

应用场景分析

异步处理

  • 传统模式的缺点:一些非必要的业务逻辑以同步的方式运行,太耗费时间。
  • 中间件模式的优点:将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快相应速度

rabbitmq本地化持久 java jms rabbitmq持久化缺点_消息队列

 

应用解耦

  • 传统模式的缺点:系统间耦合性太强,新的订阅者加入还需要发布者修改代码
  • 中间件模式的优点:将消息写入消息队列,需要消息的系统自己从消息队列中订阅,从而发布者不需要做任何修改。

rabbitmq本地化持久 java jms rabbitmq持久化缺点_消息队列_02

 

流量削锋

  • 传统模式的缺点:并发量大的时间,所有的请求直接怼到数据库,造成数据库连接异常
  • 中间件模式的优点:系统慢慢的按照数据库能处理的并发量,从消息队列中慢慢拉取消息。在生产中,这个短暂的高峰期积压是允许的。

rabbitmq本地化持久 java jms rabbitmq持久化缺点_消息队列_03

消息队列模型

P2P/点对点模式(Queue;消息队列,发送者,接收者)

  • 每个消息只有一个消费者,消费后消息将不在队列中
  • 发送者与接收者没有时间依赖
  • 接收者接收消息后需向队列应答

发布订阅模式(Pub/Sub;主题,发布者,订阅者)

  • 每个消息可以有多个消费者
  • 发布者和订阅者有时间依赖,必须订阅后才能收到消息
  • 为了消费消息,订阅者必须保持运行状态

 4.如何确保消息正确地发送至RabbitMQ? 如何确保消息接收方消费了消息?
发送方确认模式:
将信道设置成confirm模式(发送方确认模式),则所有在信道上发布的消息都会被指派一个唯一的ID。
一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息唯一ID)。
如果RabbitMQ发生内部错误从而导致消息丢失,会发送一条nack(not acknowledged,未确认)消息。
发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息。

接收方确认机制
接收方消息确认机制:消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。
这里并没有用到超时机制,RabbitMQ仅通过Consumer的连接中断来确认是否需要重新发送消息。也就是说,只要连接不中断,RabbitMQ给了Consumer足够长的时间来处理消息。保证数据的最终一致性;
下面罗列几种特殊情况:
如果消费者接收到消息,在确认之前断开了连接或取消订阅,RabbitMQ会认为消息没有被分发,然后重新分发给下一个订阅的消费者。(可能存在消息重复消费的隐患,需要去重)
如果消费者接收到消息却没有确认消息,连接也未断开,则RabbitMQ认为该消费者繁忙,将不会给该消费者分发更多的消息。

5.如何避免消息重复投递或重复消费?
在消息生产时,MQ内部针对每条生产者发送的消息生成一个inner-msg-id,作为去重的依据(消息投递失败并重传),避免重复的消息进入队列;
在消息消费时,要求消息体中必须要有一个bizId(对于同一业务全局唯一,如支付ID、订单ID、帖子ID等)作为去重的依据,避免同一条消息被重复消费。
 

如何保证消息不被重复消费

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

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

解决方案:

(1)比如,你拿到这个消息做数据库的insert操作,那就容易了,给这个消息做一个唯一的主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。

(2)再比如,你拿到这个消息做redis的set的操作,那就容易了,不用解决,因为你无论set几次结果都是一样的,set操作本来就算幂等操作。

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

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

生产者丢数据

支持事务的队列,如RabbitMQ可以开始事务,但是会造成吞吐量降低

消息队列丢数据

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

如何持久化

  • 将queue的持久化标识durable设置为true,则代表是一个持久的队列
  • 发送消息的时候设置deliveryMode=2

消费者丢数据

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

如何保证消息的顺序性

  • rabbitmq:拆分多个queue,每个queue一个consumer,就是多一些queue而已,确实是麻烦点;或者就一个queue但是对应一个consumer,然后这个consumer内部用内存队列做排队,然后分发给底层不同的worker来处理。

如何解决消息队列的数据积压

  • 设立过期时间,直接丢弃数据
  • 恢复消费者,临时扩容,快速消费