消息队列的优势与缺点

文章目录

  • 消息队列是什么?
  • 消息队列能力的对比
  • 消息队列的优势
  • 消息队列的缺点
  • 消息队列的应用场景
    • 异步
    • 消息分发
    • 削峰
  • 消息队列常见问题
    • 1、重复消费
      • 发生的原因
      • 解决
    • 2、消息丢失
      • 发生的原因
      • 解决
    • 3、消息的顺序性
      • 原理
      • 解决
        • RabbitMQ
        • RockerMQ
    • 消息延迟
      • 发生的原因
      • 预防
      • 解决
    • 消息队列高可用
  • 参考

消息队列是什么?

直译而来就是传递消息的队列,有着队列的特性 先进先出,同时具有可靠性和高性能等特点。
消息队列有很多,ActiveMQ、RabbitMQ、RockerMQ等,根据自己的取舍使用。

消息队列能力的对比

特性 ActiveMQ RabbitMQ RocketMQ
单机吞吐性 万级 万级 10万级
时效性 毫秒级 微秒级 毫秒级
可用性 高(主从) 高(主从) 非常高(分布式)
消息重复 至少一次 至少一次 至少一次 最多一次
消息顺序性 有序 有序 有序
支持主题数 千级 百万级 千级
消息回溯 不支持 不支持 支持(按时间回溯)

根据这个表,可以取决我们该如何去使用消息队列,首先就是要看我们的业务要去做什么?
吞吐量大么?吞吐量达就需要采用 RockerMQ、Kafka这种消息队列
比如技术中台,支撑其他服务的运行,对外提供认证服务等,可以考虑采用支持主题数多的RabbitMQ,RocketMQ
总结来说,就是根据项目的需求去选取,对于个人开发来说,我更喜欢去使用RabbitMQ,更容易去上手,图形化界面也比较友好,基于 erlang,天生高并发,也使得他比较快。

消息队列的优势

  • 解耦
    • 将两个服务脱离,避免因为另一个服务宕机了,该服务不能够使用的情况发生
    • 效果就体现在,A系统发布了消息给B系统,B系统挂了,那也不会影响A系统,消息已经发送到消息队列中了,A系统不需要再去考虑是否完成的问题
  • 异步
    • 简而言之,就是类似于新开一个线程,去操作想要干的事情
    • 由消息队列堆积耗时长的事务,不仅限于IO、业务流程复杂繁琐的业务
  • 削峰
    • 对某个服务访问流量过大,需要放入队列中减缓访问的次数
    • 与异步架构类似,但不相同,专注于一个服务或者说一个请求,它本身可能耗时很短

消息队列的缺点

有优点必然会有缺点出现:

  1. 系统可用性降低
    引入消息队列后,就需要保证MQ的高可用性,本来可能 A 系统直接调用 B系统,因为引入了消息队列,使得 有一个MQ的中介,但是 MQ 挂掉了,这时候就完了。
  2. 系统复杂度提高
    引入消息队列后,处理的逻辑变多了,需要去考虑,消息有没有被重复消费,消息有没有丢失,消息的顺序性…等问题
  3. 一致性问题
    比如秒杀系统,订单系统处理完了,返回成功,但库存系统减少失败了,这时候就数据不一致了。

消息队列的应用场景

异步

这种场景出现在耗时长,等待时间长的情况下。
消息队列的优势与缺点_第1张图片

比如秒杀系统下订单后,可能会涉及物流、会员、库存、订单等多个子系统,所以需要异步去执行,从而降低响应时间,不用使用户一直同步等待在下订单的页面。
消息队列的优势与缺点_第2张图片

消息分发

当有一个上游服务需要向下游的服务发送消息,比如通知公告。当下游的服务数量一直不变的话,问题不大
消息队列的优势与缺点_第3张图片

但是需要 新增下游服务 的时候,就需要去修改上游服务的代码,每多一个下游服务,上游就需要修改一次,这样肯定不行
所以,可以尝试去引入消息队列,让下游服务去订阅该队列,获取信息
消息队列的优势与缺点_第4张图片

削峰

业务中有一个概念叫做流量,流量有高有低,当有一段时间高于平常的时候,就会形成一个山峰,这个就叫做峰值流量。峰值流量过高就可能会导致系统压力过大,无法处理请求,导致服务崩溃,乃至系统崩溃。
消息队列的优势与缺点_第5张图片
当然,也可以通过加服务器的方案去解决,既然一台不行,那就多几台嘛。但很明显如果只是某一时间段这么大的话,这就是很浪费的行为,比如淘宝的双十一,就只有一天流量会这么大,整年的其他天都没有,就显得很浪费了。当然成本和利益是相辅相成的,利益够大,成本提升也没啥。这服务器也可以在空闲的时候给别的地方用。
但是我们在这里的方案是采取消息队列的方式去解决,当请求过多的时候,首先将请求放到消息队列中,然后再让后续服务慢慢处理,和上面所说的异步场景架构相似,但削峰针对的是单个请求速度不慢,但是无奈次数太多,只能一条一条处理的场景。

消息队列常见问题

1、重复消费

发生的原因

  • 生产者:或者说是客户端,重复推送一条数据到MQ中,有可能是超时后重传,有可能是网络比较慢客户端重复推送到MQ中
  • MQ:在消费者消费完成后,返回MQ给ACK的时候,MQ宕机重启了,这时候,根据持久化恢复的消息,以为消费者还没有消费完这条数据,再次推送这条消息,导致重复消费
  • 消费者:MQ会挂掉,消费者也会挂掉,消费者在准备发送ACK时候,宕机重启,然后MQ认为消费者还没有消费这条数据,重新推送数据到消费者

解决

解决的方法很简单,每一条消息都携带一个唯一ID
消费端保证数据不重复消费:

  1. 代码层面:先查询在保存
    比如秒杀系统中的异步秒杀,携带用户ID和商品ID,确定该用户有没有成功下单过该商品,避免重复下单的情况,直接查库可能会很慢,可以引入Redis,减少访问的速度
  2. 数据库层面:数据库唯一约束
    利用数据库的索引唯一性,避免重复插入
  3. 储存层面:增加一个消息表
    对于已经消费的消息,将消息ID插入到消息表中,同样为了避免直接查库,可以用Redis来储存,其实和方法1差不多

2、消息丢失

发生的原因

生产者:生产者推送消息到MQ,网络波动,导致消息丢失;
MQ:MQ接收到了消息,由于内部错误,导致消息丢失
消费者:这种属于代码层面错误,消息收到了,但还没有完全处理完,发送了ACK给MQ,结果消费者宕机了,没有成功消费,导致的消息丢失

解决

需要去保证消息的可靠性,也就是发送接收处理一定是要完成的,类似于TCP的机制,如果没有完成需要尝试重试

  1. 生产者-生产阶段
    通过请求确认机制,来保证可靠性传输,参考TCP中的数据传输,客户端发送消息到消息队列,MQ返回一个ACK响应,否则客户端重试,收到响应,则说明消息完成。
  2. MQ-储存阶段
    当 MQ 确定储存好当前消息后,才给客户端发送 ACK 确定消息
  3. 消费者-消费阶段
    跟生产阶段相同,消费完了,给消息队列发送确认消息

3、消息的顺序性

原理

一种是以时间为轴,A事件必须发生在B事件的前面
一种是因果关系,A事件必须发生在B事件的前面,比如你想吃冰箱里的东西,那么打开冰箱必须是第一步

解决

消息队列内部解决,不同的消息队列有不同的解决方案

RabbitMQ

产生这个问题的原因是属于同一个事件的消息被不同的消费者解决,比如一个事件分为增加、修改、删除三个步骤,但是由于给到了不同的消费者去处理这三个步骤,导致顺序被打乱
消息队列的优势与缺点_第6张图片
由上图可看出,如果C的速度比较快,就会打乱原有的执行顺序
所以说,如果要保证顺序性,就需要将一个事件的消息,丢到同一个队列中
消息队列的优势与缺点_第7张图片

RockerMQ

产生的原因类似,都是因为一个事件的不同步骤进入了不同的队列中,导致会被乱序处理
所以我们需要保证同一个事件的步骤进入同一个队列。这时候就一定是有序的了
消息队列的优势与缺点_第8张图片

消息延迟

发生的原因

这里的消息延迟并不是指消息队列自带的延迟消息功能,这个情况发生的原因是生产者的生产速度和消费者的消费速度不匹配导致的消息堆积问题
这里解决取决于业务的不同,主要看各自速度的匹配是否相同,由于消息队列有两种消费模式

  • push
    push 模式相当于广播模式,生产者发布消息后,通知每个消费者有新的消息到来,需要去处理
    • 优点:尽可能实时的将消息发送给消费者消费
    • 缺点:消费能力弱的消费者,跟不上生产者的速度,可能会产生消息丢失,缓存溢出等问题
  • pull
    pull 模式相当于订阅模式,生产者发送消息后,就不需要再管消息,让消费者各自去拉取消息
    • 优点:消费者可自主拉取消息
    • 缺点:拉取消息的频率不好控制

如果每次pull的时间间隔比较长,会增加消息延迟,消息到达消费者时间会加长。这样时间一长会导致MQ中消息的堆积,而消息长时间堆积就会导致一系列的问题:

  1. 如果积压了几个小时的数据,有几千万的数据量,消费端处理的压力会越来越大
  2. 如果是带有过期时间的消息,可能这些消息已经到了过期时间,因为积压时间太长,还没有被消费段处理
  3. 如果持续积压消息,达到了MQ容量的上限,会导致新来的数据丢失,或者丢弃最久的数据,取决于策略,但是都会导致数据丢失的问题

如果每一次pull的时间比较短,假设有一段时间内MQ没有消息,则会产生很多无效请求pull ,导致了一定的网络开销

预防

  • 优化消费逻辑,也就是减少消费的时间
  • 水平扩容,增加消费者的并发能力,也就是增加消费者数量

解决

如果已经发生了消息堆积的问题,需要排查三个方面

  1. 大量的数据,如何处理
    • 找到积压的原因,一般是消费者消费能力不足,排查代码耗时长的地方
    • 扩容,增加多机器消费。新建一个 topic, partition是原来的10倍,建立原来10倍的queue。然后写临时的消费程序,去转移积压的数据,再将积压的数据均匀轮询到写好的10倍queue中。然后征用10倍的消费者消费当前队列。简单来说就是增大消费速度,同时增大容量防止新涌入的数据丢失。等消费完了,排查原因恢复。
  2. 积压时间过长,某些消息过期了
    程序找出过期的数据,恢复时间重新导入到MQ消费
  3. MQ已经写满了
    没有好处理的办法,只能增加消费者,消费一条消息,然后立刻丢掉,或者靠上面的大量消息处理方案,移动。已经丢失的数据就可能很难恢复了

消息队列高可用

RabbitMQ 只有主从复制的功能,实际上高可用并不明显,也就是将主服务器的数据拷贝的从服务器当中
RocketMQ 具有分布式集群的优势,

参考

消息队列常见问题分析
消息队列顺序性

你可能感兴趣的:(消息队列,java-rabbitmq,rabbitmq,java)