RabbitMQ
RabbitMQ是一个基于AMQP(Advance Message Queue Protocol)通信协议的一种消息队列技术
JMS(Java Message Service) 是Java程序操作MQ的API规范,jms是在java应用API层面定义的一个标准,而AMQP为协议层定义的标准(具备跨语言的特性)。
RabbitMQ属于服务间的异步通信技术(不同于Dubbo和SpringCloud,它们属于同步通信技术),分布式系统的远程通信技术有以下两种方式:
远程过程调用(RPC)
借助消息中间件(MQ)
MQ具有以下几种优点:
- 应用解耦 :
- 消峰填谷 : 通过MQ中可以将短时间内大量的请求放入MQ的队列,处理消息的服务可以在自己能力范围内对队列中的消息进行处理;
- 应用提速 : 得益于MQ的异步通信属性,可以节省同步通信中调用服务等待返回值的时间,进而提升了系统之间的运行速度;
MQ同时也具有一下几种使用的局限性(缺点):
- 系统的复杂性提高了
- 系统的可用性降低了
- 系统内事务会有一段短暂的不一致
综上,当服务调用者不需要返回值(短时间内),且能够容忍一定时间的不一致性,及使用MQ能带来一定的收益才选择使用MQ;
RabbitMQ的基本操作
1. 导入rabbitMQ的依赖Jar包
2. 利用ConnectionFactory对象创建Connection对象(ConnectionFactory对象需设置连接rabbitMQ的相关配置)
3. 利用Connection对象创建Channel对象
4. 利用Channel对象可以声明Queue(队列),Exchange(交换机) 及队列和交换机的绑定
5. 利用channel对象的basicPublish&basicConsumer方法分别可进行消息的发送和接收
RabbitMQ的消息发送
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");//设置rabbitMQ服务所在主机地址
connectionFactory.setPort(5672);//设置端口
connectionFactory.setUsername("andrew");//设置用户名
connectionFactory.setPassword("123");//设置密码
connectionFactory.setVirtualHost("/testVH");//设置rabbitMQ的虚拟机
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("myQueue1",true,false,false,null);//申明队列 参数见源码
channel.queueDeclare("myQueue2",true,false,false,null);
channel.exchangeDeclare("topicExchange1", BuiltinExchangeType.TOPIC);//申明交换机
channel.queueBind("myQueue1","topicExchange1","routingKey.*.aa");//队列与交换机的绑定
channel.queueBind("myQueue2","topicExchange1","routingKey.*.bb");
//向rabbitMQ发送消息
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
channel.basicPublish("topicExchange1","routingKey.aa",null,("Hi this is a new message from andrew by directExchange1"+new Date().toLocaleString()).getBytes());
channel.basicPublish("topicExchange1","routingKey.bb",null,("Hi this is a new message from andrew by directExchange1"+new Date().toLocaleString()).getBytes());
System.out.println("provider send message successfully!!!");
}
channel.close();
connection.close();
RabbitMQ的消息接收
rabbitMQ的消息接收代码大致与发送代码相同,调用channel对象的basicConsume方法即可监听指定队列
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("andrew");
connectionFactory.setPassword("123");
connectionFactory.setVirtualHost("/testVH");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("myQueue2",true,false,false,null);
//创建一个DefaultConsumer类并重写其消费方法
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("consumer receive the message successfully!!! message is :");
System.out.println(new String(body));
System.out.println("*****************************");
System.out.println();
}
};
//监听myqQUeue2队列 当该队列有消息时 会触发 consumer对象的消费方法
channel.basicConsume("myQueue2", true,consumer);
RbbitMQ的消息工作模式
Work Queue:
最简单的模式
队列绑定直接通过队列名称绑定,即指定交换机和队列名称即可完成绑定(交换机默认为空字符串)
Publish&Subscribe
相较于Work Queue模式,需指定交换机且该交换机类型为Fanout,交换机通过队列名称相互绑定即一个交换机可绑定多个队列,进而实现一个消息被多个消费者消费;
Routing
在publish&subscribe模式的基础上,增加了RoutingKey的限制条件,进而实现了更加灵活的队列 交换机的绑定规则;
Topic
相较于Routing模式,topic模式在声明队列的时候所定义的RoutingKey支持通配符 # *,且交换机类型为Topic ;
#:匹配一个或多个词(词与词之间采用 . 分隔)
:匹配一个词
(aa..bb)即可匹配aa.bb或者aa.cc.bb和aa.cc.ee.bb
Spring整合RabbitMQ
Spring整合RabbitMQ主要需要在spring配置文件中配置对应的声明队列&交换机以及ConnectionFactory和RabbitTemplate;
使用步骤:
-
导入对应的jar包:
spring-rabbit
-
编写配置文件:
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"
publisher-returns="true"
publisher-confirms="true"/>
<!--定义管理交换机、队列 AdminTemplate对象与connectionFactory绑定-->
<rabbit:admin connection-factory="connectionFactory"/>
<!--创建声明队列对象-->
<rabbit:queue id="queue1" name="queue1" auto-declare="true">
<rabbit:queue-arguments>
设置队列的TTL(Time To Live)
<entry key="x-message-ttl" value="20000" value-type="java.lang.Integer"/>
设置队列的DLX 当消息成为死消息(DeadMessage)使消息进入DLX
<entry key="x-dead-letter-exchange" value="deadLetterExchange" value-type="java.lang.String"/>
设置队列最大长度(超出长度部分的消息会成为死消息)
<entry key="x-max-length" value="10" value-type="java.lang.Integer"/>
</rabbit:queue-arguments>
</rabbit:queue>
<!--死信队列 使用fanout类型 并绑定一个队列-->
<rabbit:fanout-exchange id="deadLetterExchange" name="deadLetterExchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding queue="deadLetterQueue"></rabbit:binding>
</rabbit:bindings>
</rabbit:fanout-exchange>
<rabbit:queue id="queue2" name="queue2" auto-declare="true"/>
<rabbit:queue id="deadLetterQueue" name="deadLetterQueue" auto-declare="true"/>
<rabbit:queue id="queue3" name="queue3" auto-declare="true"/>
<rabbit:queue id="queue4" name="queue4" auto-declare="true"/>
<!--交换机为FANOUT 模式 交换机直接将消息路由给所有绑定的队列-->
<!-- <rabbit:fanout-exchange name="fanoutExchange" id="fanoutExchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding queue="queue1"></rabbit:binding>
<rabbit:binding queue="queue2"></rabbit:binding>
<rabbit:binding queue="queue3"></rabbit:binding>
<rabbit:binding queue="queue4"></rabbit:binding>
</rabbit:bindings>
</rabbit:fanout-exchange>-->
<!--交换机为Direct模式 会把消息路由给匹配RoutingKey的队列-->
<!-- <rabbit:direct-exchange name="directExchange" id="directExchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding key="key1" queue="queue1"></rabbit:binding>
<rabbit:binding key="key2" queue="queue2"></rabbit:binding>
<rabbit:binding key="key3" queue="queue3"></rabbit:binding>
<rabbit:binding key="key4" queue="queue4"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>-->
<!--交换机为Topic模式 会把消息路由给和routingKey匹配带有通配符的队列-->
<rabbit:topic-exchange name="topicExchange" id="topicExchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding pattern="keys.*" queue="queue1"></rabbit:binding>
<rabbit:binding pattern="key.aa.*" queue="queue2"></rabbit:binding>
<rabbit:binding pattern="key.#" queue="queue3"></rabbit:binding>
<rabbit:binding pattern="keytest" queue="queue4"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
<!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
<!--定义队列监听对象-->
<bean id="queue1Listener1" class="com.andrew.rabbitmq.listener.Listen1"/>
<bean id="queue1Listener2" class="com.andrew.rabbitmq.listener.Listen2"/>
<bean id="queue1Listener3" class="com.andrew.rabbitmq.listener.Listen3"/>
<bean id="queue1Listener4" class="com.andrew.rabbitmq.listener.Listen4"/>
<!--监听器容器 把监听对象和队列相互绑定-->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">
<rabbit:listener ref="queue1Listener1" queue-names="deadLetterQueue"/>
<!-- <rabbit:listener ref="queue1Listener2" queue-names="queue2"/>
<rabbit:listener ref="queue1Listener3" queue-names="queue3"/>
<rabbit:listener ref="queue1Listener4" queue-names="queue4"/>-->
</rabbit:listener-container>
-
使用rabbitTemplate对象可以发送消息:
rabbitTemplate.convertAndSend("", "queue1", "this message will be expired in 5 seconds");
-
定义实现了MessageListener接口的监听类,并注入容器(当需要实现消息拒收功能时实现ChannelAwareMesageListener)
消息的可靠投递
RabbitMQ提供三种方法能保证消息的可靠投递,分别为:
- 发送消息端 Confirm : 当消息发送至交换机(Exchange)失败时,通过设置connectionFactory的 属性publisher-confirms=“true” 并通过RabbitTemlate的setConfimCallback方法定义当消息发送至Exchange失败的执行的回调函数
- 发送消息端 Return : 当消息经由Exchange路由至Queue时失败时,通过设置connectionFactory的属性publisher-returns=“true”,设置RabbitTemplate的setMantory为true(false发送失败会默认将该消息丢弃),并通过RabbitTemplate的setReturnCallback方法来定义消息发送到queue失败的回调函数;
- **监听端 ** :通过实现ChannelAwareMessageListener接口,并通过RabbitTemplate的setMantory方法,由channel的baiscAck&basicNack方法对消息进行接收和拒收;
RabbitMQ的死队列(DeadLetterExchange)
当消息成为死消息(DeadMessage)使,会被定义的DLX接收并路由至绑定的队列,消息成为死消息原因有以下三种:
- 消息过期: 通过队列的x-message-ttl属性可以设置队列内消息的有效期限,过期后队列内的消息会被设置的DLX接收,单个消息也可设置自己的有效期(当队列有效期和消息有效期重叠时以时间短的为准),且单个消息到达自身设置的有效期时,不会直接被DLX接收(当监听者消费该消息时才会检查该消息是否过期,进而才会移除无效的消息)
- 队列溢出: 当队列的消息数量超出自身设置的消息数量上限时,超出的部分也会成为死消息
- 被监听者拒收 : 当监听者使用拒收消息时,该消息也会成为一条死消息
延时队列 :
延时队列中的消息,只有在指定的时间后才能被监听者消费,在RabbitMQ通过使用死队列可以实现延时队列的功能:
即设置一条队列,并不为该队列设置监听者,设置该队列的消息有效期,期限过后便会把消息转至死队列,监听死队列即可实现延时队列的效果;
SpringBoot整合RabbitMQ
需引入spring-boot-starter-amqp和spring-rabbit的Jar包依赖并配置application…yml
#配置rabbitMQ连接信息
spring:
rabbitmq:
host: 192.168.142.1
username: andrew
password: 123
virtual-host: /testVH
port: 5672
publisher-confirm-type: CORRELATED #设置对发布端回调函数的支持
template:
mandatory: true #设置发布端 支持Return回调函数
通过注入RabbitTemplate对象可完成消息的指定发送
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void test() throws InterruptedException {
while (true){
Thread.sleep(1000);
rabbitTemplate.convertAndSend("topicExchange","routingKey1","this message is send by rabbitTemplate");
}
}
通过使用@RabbitListener注解标记指定方法来处理监听队列的消息
@Component
public class RabbitMqConsumer {
//message与channel会被自动传递进consumer方法
@RabbitListener(queues = {"queue1"})
public void consumer(Message message, Channel channel){
System.out.println("message received successfully!!!");
System.out.println("message content is : "+new String(message.getBody()));
}
}
RabbitMQ动态监听
通过SimpleMessageListenerContainner对象,可为容器动态添加监听器:
@Bean("Listener")//将该对象注入容器
public SimpleMessageListenerContainer getSmlc(){
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
//设置队列的ConnectionFactory
container.setConnectionFactory(rabbitTemplate.getConnectionFactory());
//设置消息手动签收
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
//设置监听队列
container.addQueueNames(singer);
//设置监听器
container.setMessageListener(new MyListener());
return container;
}
此方法可用于动态的给容器内的指定监听器添加指定的监听队列:
通过FactoryBean对象可以动态的向容器注入指定Bean 从而实现动态像容器添加指定数量的监听器:
@PostConstruct
public void createListenerContainer(){
//获取FactoryBean对象
DefaultListableBeanFactory factory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();
//像容器注入指定数量的监听器
for (String singer : singers) {
//获取bean的构造器
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(SimpleMessageListenerContainer.class);
//设置bean的构造方法
beanDefinitionBuilder.addConstructorArgValue(rabbitTemplate.getConnectionFactory());
//设置bean的属性
beanDefinitionBuilder.addPropertyReference("messageListener","listenerToTest");
beanDefinitionBuilder.addPropertyValue("QueueNames",singer);
//将bean注入容器
factory.registerBeanDefinition(singer,beanDefinitionBuilder.getBeanDefinition());
}
}