MQ
kafka是以吞吐量高而闻名,不过其数据稳定性一般,而且无法保证消息有序性。
阿里巴巴的RocketMQ基于Kafka的原理,利用Java代码打造,弥补了Kafka的缺点,继承了其高吞吐的优势,其客户端目前以Java为主。
RabbitMQ基于面向并发的语言Erlang开发,吞吐量不如Kafka,但是消息可靠性较好。也能有效的保证消息的有序性。因为Erlang的原因,集群搭建比较方便。支持多种协议,并且有各种语言的客户端,比较灵活。
相关资料:
这个问题其实时问3种MQ的差别,先看一张图:
ActiveMQ现在已经很少使用,社区不太活跃,放弃。
RabbitMQ并发能力强、消息延时低、高可用、管理界面丰富,并且最重要的是:社区非常活跃,出现BUG都能及时解决。
Kafka和RocketMQ的特点都是高吞吐量,但是kafka消息可靠性比较一般,而且消息不保证有序性。RocketMQ弥补了Kafka的缺点,不过是阿里开源,社区不太活跃,文档也不够丰富。
RabbitMQ可以 干嘛
例如系统A执行完业务,系统B需要得到系统A的业务结果,此时可以系统A中调用系统B(系统A中耦合了系统B的业务)。
此时如果系统C、系统D都有类似需求,那么系统A的业务逻辑还要继续修改,违反了开闭原则。
此时,可以利用MQ来解耦,让商品微服务发送消息通知,而相关的其它系统监听MQ即可:
数据库的并发能力有限,往往称为业务执行的性能瓶颈。
例如我们的服务只能支持500的并发,然而又每秒1000甚至更高的服务流量涌入,服务肯定会崩溃的。
此时,利用MQ来作为缓冲,就像大坝一样,高并发流量涌入,先放到MQ中缓存起来,后续系统再慢慢取出并处理即可:
如果一个业务执行中,需要调用多个其它服务,业务链路很长,同步调用的用时就时多个服务执行的总耗时,如图:
但是,我们如果再B系统执行完成后,利用MQ通知系统C和系统D去完成,直接返回结果给用户,就可以减少业务耗时。这样就把同步阻塞调用,变成了异步调用:
例如定时清理订单的需求,我们在下单完成后,如果用户超过半小时未支付,需要关闭订单。此时我们就需要发送延迟消息,即:发送消息半小时后,消费者才能收到消息,这就是延迟队列。
RabbitMQ的死信队列可以实现延迟队列效果,如图:
RocketMQ也可以实现延迟队列,但是RocketMQ的延迟队列的时间只能是固定时间间隔。
RabbitMQ
RabbitMQ底层基于Erlang语言,对分布式支持较好。并且官方也给出了搭建镜像机器的方式,可以把队列及其中的数据同步到镜像节点中,当队列所在节点故障时,镜像队列可以继续提供服务。
另外,MQ数据可以持久化,当节点恢复时,可以恢复数据。
RocketMQ
RocketMQ中的核心组件是NameServer和Broker,这两部分都可以建立主从集群,保证高可用:
我们针对MQ消息丢失的几种不同情况,采用不同的应对方案:
rabbitmq中消息消费后自动删除,不会永久保留,无法实现消息回溯。
消息重复消费产生的原因:
解决思路:业务处理时,保证处理消息接口的幂等性。
RabbitMQ支持多消费者绑定同一队列,消息Broker会把消息轮询的发送给每一个消费者。通过同一个队列多消费者监听,实现消息的争抢,加快消息消费速度。
RabbitMQ也可以做集群,集群数据会分片效果,从而能堆积更多消息。
备选方案:也可以给单个消费者接收消息后放入队列,交给线程池去处理。
某个业务发出了3条消息,要求这3条消息按照发送时的顺序执行。
RabbitMQ中有一个死信队列设定:我们可以给一个队列设置过期时间,或者发送消息时给消息设置过期时间。过期的消息称为死信,队列会把死信转发给提前设置的死信交换机,而与死信交换机绑定的队列就可以拿到这些消息。
因为发送消息超过一定时间(过期)后,才会被队列拿到,这样就实现了延迟队列效果。
实现起来非常简单,不过也有一些缺陷:
如果对上述问题有要求,可以利用Redis来实现延迟队列。
参考官方网站:https://www.rabbitmq.com/dlx.html
首先来看死信的概念。
死信
死信的英文是(Dead Letter),满足下列条件的消息被称为死信:
要实现延迟队列,我们肯定需要人为控制一个消息变为死信,因此我们一般采用上述的第二种方式:让一个消息在一段时间后过期,这种过期可以通过两种策略实现:
x-message-ttl
属性给消息所在队列设置TTL(Time To Live),当队列中的消息存在时间超过TTL后就自动成为死信由上面的概念可以知道,一个消息是不是死信,最终是由消息所在的队列来判断和处理的。当一个消息被判定为死信,它所在的队列会做怎样的处理呢?
队列会把死信交给提前指定的死信交换机(Dead Letter Exchange)。
死信交换机Dead Letter Exchanges
**死信交换机(Dead Letter Exchange)**其实就是一个普通交换机,也具备以前学习的交换机的所有特征,例如可以设置交换机类型为:topic、direct等。它负责把消息根据routing key转发给绑定的队列。
那什么样的交换机才可以叫死信交换机?需要队列在声明的时候,通过x-dead-letter-exchange
属性指定一个交换机,被指定的交换机就是死信交换机(Dead Letter Exchange)。同时队列还可以指定一个x-dead-letter-routing-key
(死信路由)作为死信的routing_key
,死信交换机转发消息时会根据这个routing_key
来转发消息。
死信交换机接收到消息以后,会根据消息的routing_key
再次转发消息到绑定的队列,如果队列绑定到死信交换机时,会根据队列指定的x-dead-letter-routing-key
来转发,如果队列没有绑定,则会根据消息来源时指定的routing_key
来转发。
例如:现在publisher发送消息时指定routing_key
为foo
,队列绑定死信交换机时指定了死信路由为:bar
,则死信交换机转发时,会使用bar
作为routing_key
,如图:
现在publisher发送消息时指定routing_key
为foo
,队列绑定死信交换机时没有指定**死信路由,则死信交换机转发时,会使用foo
作为routing_key
,如图:
现在,如果我们发送一个routingKey为foo的消息到达设置了过期时间为30秒的队列(图中的MessageQueue),30秒后消息过期,就会转发到死信交换机,然后就会发送到Queue1这个队列,我们的任务执行者监听Queue1,即可实现延迟队列了。
打开RabbitMQ的管理界面,然后先创建两个交换机:
然后创建两个队列:
dead.order.queue
:死信队列,设置过期时间为20秒,
normal.topic
交换机绑定,接收消息,routing_key为 order.evict
x-dead-letter-exchange
为dead.topic
这个死信交换机x-message-ttl
设置消息过期时间evict.order.queue
:普通任务队列,接收死信交换机转发过来的消息,将来推送给消费者
dead.topic
交换机绑定,接收消息,routing_key为 order.evict
进入交换机界面,点击要绑定的交换机,例如:normal.topic
:
在点开的界面填写要绑定的队列及routing_key:
然后还要绑定evict.order.queue
到dead.topic
这个交换机:
现在,向normal.topic
交换机发送消息,指定routing_key
为:order.evict
可以看到dead.order.queue
中已经有消息了:
然后等待20秒后,看到消息到了evict.order.queue
:
RabbitMQ实现延迟队列的优缺点:
优点:
缺点: