消息队列RabbitMQ
消息队列RabbitMQ
什么是消息队列
MQ全称为Message Queue,即消息队列。“消息队列”是在消息的传输过程中保存消息的容器。它是典型的:生产者、消费者模型。生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入,这样就实现了生产者和消费者的解耦。
消息队列应用场景
消息队列中间件是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构
JMS和AMQP协议
实现MQ的有两种主流方式:JMS和AMQP两种协议,下面是两种协议的对比图
RabbitMQ概述
RabbitMQ是由erlang语言开发,基于AMQP(Advanced Message Queue 高级消息队列协议)协议实现的消息队列。RabbitMQ官方地址:http://www.rabbitmq.com
RabbitMQ中的核心概念
1.Message
消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成, 这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可 能需要持久性存储)等。
2.Publisher
消息的生产者,也是一个向交换器发布消息的客户端应用程序。
3.Exchange
交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
Exchange有4种类型:direct(默认),fanout,topic,和headers,不同类型的Exchange转发消息的策略有所区别
4.Queue
消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直 在队列里面,等待消费者连接到这个队列将其取走
5.Routing key
路由键,生产者在将消息发送给Exchange的时候,一般会指定一个routing key,来指定这个消息的路由规则,而这个routing key需要与Exchange Type及binding key联合使用才能最终生效。(Exchange type 就是 exchange的路由类型, binding key就是exchange与queue绑定时的一个key,相当于给这个绑定取个名字.这个名字是跟 routing key 相同或者包含的)
6.Binding
绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交 换器理解成一个由绑定构成的路由表。
Exchange和Queue的绑定可以是多对多的关系。
7.Connection
网络连接,比如一个TCP连接。
8.Channel
信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内的虚拟连接,AMQP命令都是通过信道 发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁TCP都 是非常昂贵的开销,所以引入了信道的概念,以复用一条TCP连接。
9.Consumer
消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。
10.Virtual Host
虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加 密环境的独立服务器域。每个 vhost 本质上就是一个mini版的RabbitMQ 服务器,拥 有自己的队列、交换器、绑定和权限机制。vhost是AMQP概念的基础,必须在连接时指定,RabbitMQ 默认的vhost是/。
11.Broker
表示消息队列服务器实体
Exchange类型
Exchange分发消息时根据类型的不同分发策略有区别,目前共四种类型: direct、fanout、topic、headers。headers匹配AMQP消息的 header而不是路由键,headers交换器和direct交换器完全一致,但性能差很多,目前几乎用不到了,所以直接看另外三种类型:
1.Direct Exchange(直接匹配)
消息中的路由键(routing key)如果和Binding中的binding key一致,交换器就将消息发到对应的队列中。路由键与队列名完全匹配,如果一个队列绑定到交换机要求路由键为"dog”,则只转发routing key标记为“dog”的消息,不会转发 "dog.puppy”,也不会转发“dog.guard"等等。它是完全匹配、单播的模式。
2.Fanout Exchange(广播类型)
每个发到fanout类型交换器的消息都会分到所有绑定的队列上去。fanout交换器不处理路由键,只是简单的将队列绑定到交换器上,每个发送到交换器的消息都会被转发到与该交换器绑定的所有队列上。fanout类型转发消息是最快的。
3.Topic Exchange(主题类型)
Topic交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键和绑定键的字符串切分成单词,这些单词之间用点隔开。它同样也会识别两个通配符:符号“#”和符号"x"。#几个或多个单词,*匹配一个单词。
RabbitMQ的消息模型
在RabbitMQ的官方教程中,RabbitMQ为我们提供了6中消息模型,第6种是RPC(远程调用),不是MQ(消息队列),所以RabbitMQ真正的消息模型只有5种
1.基本消息模型
一个生产者对应一个消费者
2.work队列消息模型
work队列模型是为了弥补基本消息模型的缺陷而诞生的,是通过一个队列绑定多个消费者,以达到避免消息在队列中堆积的问题
3.订阅模型Fanout
-
订阅模型比基本消息模型和work队列消息模型,多出了一个交换机,每个队列都要绑定交换机
-
生产者不再直接将消息发送到队列中,而是发送到交换机中,然后由交换机根据不同的策略,发送到不同的队列中。订阅模型的三种不同类型,其实就是采用了不同的交换机分发策略
-
Fanout模型,交换机直接将所有消息以广播的形式,发送给所有队列。即生产者生产的一条消息,会被交换机发给所有的队列,所有和队列绑定的消费者均会消费这一条消息
4.订阅模型Direct
Direct(选择性发送),交换机不会将消息发送给所有队列,而是通过队列绑定到交换机上的RoutingKey(路由Key)发送到指定的队列中
5.订阅模型Topic
- Topic模型的交换机可以让队列在绑定的时候,使用通配符的方式进行模糊匹配。
*
表示正好匹配一个单词,#
表示匹配一个或多个单词
如何防止消息丢失
生产者没有成功把消息发送到RabbitMQ
因为网络传输的不稳定性,当生产者在向MQ发送消息的过程中,MQ没有接收到消息
解决办法:
1.使用事务(性能差)
使用AMQP协议提供的一个事务机制,在生产者发送消息之前,通过channel.txSelect开启一个事务,接着发送消息, 如果消息投递server失败,进行事务回滚channel.txRollback,然后重新发送, 如果server收到消息,就提交事务channel.txCommit
但是,很少有人这么干,因为这是同步操作,一条消息发送之后会使发送端阻塞,以等待RabbitMQ-Server的回应,之后才能继续发送下一条消息,生产者生产消息的吞吐量和性能都会大大降低
2.发送方确认机制(publisher confirm)
生产者开启confirm模式之后,你每次写的消息都会分配一个唯一的id,然后如果写入了rabbitmq中,rabbitmq会给你回传一个ack消息,告诉这个消息成功还是失败了。如果rabbitmq没能处理这个消息,会回调你一个nack接口,告诉你这个消息接收失败,可以重试。
3.做好容错方法(try-catch)
发送消息可能会网络失败,失败后要有重试机制,可记录到数据库,采用定期扫描重发的方式。每一条消息都做好日志记录,给数据库保存每一个消息的记录,定期扫描数据库将失败的消息重新发送。
RabbitMQ弄丢了消息
消息抵达消息队列,要将消息进行持久化,写入磁盘才算成功。此时mq没有持久化成功,mq宕机了造成消息丢失
解决办法:
发送方确认机制(publisher confirm)
生产者开启confirm模式之后,rabbitmq会给你回传一个ack消息,告诉这个消息成功还是失败了。如果rabbitmq没能处理这个消息,会回调你一个nack接口,告诉你这个消息接收失败,此时修改数据库中的mq状态,进行消息定期重试。配合上面的容错方法
消费端弄丢了数据
消费者刚拿到消息准备消费的时候,此时消费端没有消费消息,mq使用的是自动ack模式,mq就会认为你已经消费了,把消息丢掉
解决办法
一定开启手动ACK,消费成功才移除,失败或者没来得及处理就noAck并重新入队
如何防止消息重复
消息消费成功,事务已经提交,ack时,机器宕机。导致没有ack成功,Broker的消息 重新由unack变为ready,并发送给其他消费者
解决办法
消费者的业务消费接口应该设计为幂等性的。比如扣库存有 工作单的状态标志
使用防重表(redis/mysql),发送消息每一个都有业务的唯 一标识,处理过就不用处理
rabbitMQ的每一个消息都有redelivered字段,可以获取是否 是被重新投递过来的,而不是第一次投递过来的
如何防止消息积压
-
消费者宕机积压
-
消费者消费能力不足积压
-
发送者发送流量太大
解决办法
-
上线更多的消费者,进行正常消费
-
上线专门的队列消费服务,将消息先批量取出来,记录数据库,离线慢慢处理