本章重点:
1.三种主要的交换机介绍
2.SpringBoot整合RabbitMQ三种交换机
3.死信队列
4.优先级队列和消息
5.服务端流控
6.消费端限流
RabbitMQ的特性
RabbitMQ使用Erlang语言编写,使用Mnesia数据库存储消息。
1.可靠性(Reliability) RabbitMQ 使用一些机制来保证可靠性,如持久化、传输确认、发布确认。
2.灵活的路由(Flexible Routing) 在消息进入队列之前,通过 Exchange 来路由消息的。对于典型的路由功 能,RabbitMQ 已经提供了一些内置的 Exchange 来实现。针对更复杂的路由功能,可以将多个 Exchange 绑定在 一起,也通过插件机制实现自己的 Exchange 。
3.消息集群(Clustering) 多个 RabbitMQ 服务器可以组成一个集群,形成一个逻辑 Broker 。
4.高可用(Highly Available Queues) 队列可以在集群中的机器上进行镜像,使得在部分节点出问题的情况下 队列仍然可用。
5.多种协议(Multi-protocol) RabbitMQ 支持多种消息队列协议,比如 AMQP、STOMP、MQTT 等等。
6.多语言客户端(Many Clients) RabbitMQ 几乎支持所有常用语言,比如 Java、.NET、Ruby、PHP、C#、 JavaScript 等等。
7.管理界面(Management UI) RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息、集群 中的节点。
8.插件机制(Plugin System) RabbitMQ提供了许多插件,以实现从多方面扩展,当然也可以编写自己的插件。
工作模型
RabbitMQ的术语介绍
Broker:即RabbitMQ的实体服务器。提供一种传输服务,维护一条从生产者到消费者的传输线路, 保证消息数据能按照指定的方式传输。
Exchange:消息交换机。指定消息按照什么规则路由到哪个队列Queue。
Queue:消息队列。消息的载体,每条消息都会被投送到一个或多个队列中。
Binding:绑定。作用就是将Exchange和Queue按照某种路由规则绑定起来。
Routing Key:路由关键字。Exchange根据Routing Key进行消息投递。定义绑定时指定的关键字称为 Binding Key。
Vhost:虚拟主机。一个Broker可以有多个虚拟主机,用作不同用户的权限分离。一个虚拟主机持有 一组Exchange、Queue和Binding。
Producer:消息生产者。主要将消息投递到对应的Exchange上面。一般是独立的程序。
Consumer:消息消费者。消息的接收者,一般是独立的程序。
Connection: Producer 和 Consumer 与Broker之间的TCP长连接。
Channel:消息通道,也称信道。在客户端的每个连接里可以建立多个Channel,每个Channel代表一 个会话任务。在RabbitMQ Java Client API中,channel上定义了大量的编程接口。
三种主要的交换机
- Direct Exchange 直连交换机
- Topic Exchange 主题交换机
- Fanout Exchange 广播交换机
RabbitMQ基本使用:
本文直接使用SpringBoot来进行开发.
消息消费者:
项目结构:
添加pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
添加连接配置 application.properties
spring.application.name=spring-boot-rabbitmq
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
创建配置类:RabbitConfig
在这里创建了三种类型的交换机,四个队列
package com.zbb.rabbitmq_consumer.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置类,需要使用configuration注解
*/
@Configuration
public class RabbitConfig {
//定义三个交换机
/**
* 直连交换机
* @return
*/
@Bean
public DirectExchange directExchange(){
return new DirectExchange("DIRECT_EXCHANGE");
}
/**
* 主题交换机
* @return
*/
@Bean
public TopicExchange topicExchange(){
return new TopicExchange("TOPIC_EXCHANGE");
}
/**
* 广播交换机
* @return
*/
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("FANOUT_EXCHANGE");
}
//定义四个队列
@Bean
public Queue firsQueue(){
return new Queue("FIST_QUEUE");
}
@Bean
public Queue secondQueue(){
return new Queue("SECOND_QUEUE");
}
@Bean
public Queue thirdQueue(){
return new Queue("THIRD_QUEUE");
}
@Bean
public Queue fourthQueue(){
return new Queue("FOURTH_QUEUE");
}
//定义四个绑定关系
@Bean
public Binding bindFirst(@Qualifier("firsQueue") Queue queue,
@Qualifier("directExchange") DirectExchange directExchange){
return BindingBuilder.bind(queue).to(directExchange).with("zbb.test");
}
@Bean
public Binding bindSecond(@Qualifier("secondQueue") Queue queue,
@Qualifier("topicExchange") TopicExchange topicExchange) {
return BindingBuilder.bind(queue).to(topicExchange).with("*.zbb.*");
}
@Bean
public Binding bindThird(@Qualifier("thirdQueue") Queue queue,
@Qualifier("fanoutExchange") FanoutExchange fanoutExchange) {
return BindingBuilder.bind(queue).to(fanoutExchange);
}
@Bean
public Binding bindFourth(@Qualifier("fourthQueue") Queue queue,
@Qualifier("fanoutExchange") FanoutExchange fanoutExchange) {
return BindingBuilder.bind(queue).to(fanoutExchange);
}
}
创建消费者,四个,分别监听4个队列
1、FirstConsumer
package com.zbb.rabbitmq_consumer.consumer;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = "FIST_QUEUE")
public class FirstConsumer {
@RabbitHandler
public void process(String msg) {
System.out.println("消费者一收到消息:"+msg);
}
}
2、SecondConsumer
package com.zbb.rabbitmq_consumer.consumer;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = "SECOND_QUEUE")
public class SecondConsumer {
@RabbitHandler
public void process(String msg) {
System.out.println("消费者二收到消息:"+msg);
}
}
3、ThirdConsumer
package com.zbb.rabbitmq_consumer.consumer;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = "THIRD_QUEUE")
public class ThirdConsumer {
@RabbitHandler
public void process(String msg) {
System.out.println("消费者三收到消息:"+msg);
}
}
4、FourthConsumer
package com.zbb.rabbitmq_consumer.consumer;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = "FOURTH_QUEUE")
public class FourthConsumer {
@RabbitHandler
public void process(String msg) {
System.out.println("消费者四收到消息:"+msg);
}
}
消息生产者:
引入pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
创建生产者类,给三个交换机发送消息
package com.zbb.rabbitmq_producer.producer;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MyProducer {
@Autowired
RabbitTemplate rabbitTemplate;
public void send(){
//直连
rabbitTemplate.convertAndSend("DIRECT_EXCHANGE", "zbb.test",
"this is a direct msg");
//主题
rabbitTemplate.convertAndSend("TOPIC_EXCHANGE","kaifa.zbb.IT",
"this is a Topic msg");
//广播
rabbitTemplate.convertAndSend("FANOUT_EXCHANGE", "",
"this is a fanout msg");
}
}
修改测试类:
package com.zbb.rabbitmq_producer;
import com.zbb.rabbitmq_producer.producer.MyProducer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitmqProducerApplicationTests {
@Autowired
MyProducer myProducer;
@Test
public void contextLoads() {
myProducer.send();
}
}
-
我们先启动消息生产者,打开管理界面可以看到四个队列里都有一条消息堆积
2.现在去启动消息消费者之后会发现控制台打印出消息,并且管理界面发生改变:
这个就是RabbitMQ在java中的简单使用,具体在项目中的使用需要结合业务来进行消息的收发。
下面将介绍一下RabbitMQ中一个重要的东西:交换机Exchange
这里只介绍三种主要的交换机。
Direct Exchange 直连交换机
直连类型的交换机与一个队列绑定时,需要指定一个明确的绑定关键字(binding key)。
路由规则:发送消息到直连类型的交换机时,只有路由关键字(routing key)跟绑定关键字(binding key)完全匹配时,绑定的队列才能收到消息。
队列:
@Bean
public Queue firsQueue(){
return new Queue("FIST_QUEUE");
}
交换机:
@Bean
public DirectExchange directExchange(){
return new DirectExchange("DIRECT_EXCHANGE");
}
绑定:
@Bean
public Binding bindFirst(@Qualifier("firsQueue") Queue queue,
@Qualifier("directExchange") DirectExchange directExchange){
return BindingBuilder.bind(queue).to(directExchange).with("zbb.test");
}
发送消息:
rabbitTemplate.convertAndSend("DIRECT_EXCHANGE", "zbb.test",
"this is a direct msg");
Topic Exchange 主题交换机
定义:主题类型的交换机与一个队列绑定时,可以指定按模式匹配的routing key。
通配符有两个,*代表匹配一个单词。#代表匹配零个或者多个单词。单词与单词之间用 . 隔开。
路由规则:发送消息到主题类型的交换机时,routing key符合binding key的模式时,绑定的队列才能收到消息
队列:
@Bean
public Queue secondQueue(){
return new Queue("SECOND_QUEUE");
}
交换机:
@Bean
public TopicExchange topicExchange(){
return new TopicExchange("TOPIC_EXCHANGE");
}
绑定:
@Bean
public Binding bindSecond(@Qualifier("secondQueue") Queue queue,
@Qualifier("topicExchange") TopicExchange topicExchange) {
return BindingBuilder.bind(queue).to(topicExchange).with("*.zbb.*");
}
发送消息:
//主题
rabbitTemplate.convertAndSend("TOPIC_EXCHANGE","kaifa.zbb.IT",
"this is a Topic msg");
Fanout Exchange 广播交换机,绑定两个队列thirdQueue fourthQueue
定义:广播类型的交换机与一个队列绑定时,不需要指定binding key。
路由规则:当消息发送到广播类型的交换机时,不需要指定routing key,所有与之绑定的队列都能收到消息。
队列:
@Bean
public Queue thirdQueue(){
return new Queue("THIRD_QUEUE");
}
@Bean
public Queue fourthQueue(){
return new Queue("FOURTH_QUEUE");
}
交换机:
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("FANOUT_EXCHANGE");
}
发送消息:
//广播
rabbitTemplate.convertAndSend("FANOUT_EXCHANGE", "",
"this is a fanout msg");
进阶知识 1、TTL(Time To Live) a、消息的过期时间
有两种设置方式:
通过队列属性设置消息过期时间:
Map<String, Object> argss = new HashMap<String, Object>();
argss.put("x-message-ttl",6000);
channel.queueDeclare("TEST_TTL_QUEUE", false, false, false, argss);
设置单条消息的过期时间:
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
// 持久化消息
.deliveryMode
.contentEncoding("UTF-8")
.expiration("10000") // TTL
.build();
channel.basicPublish("", "TEST_TTL_QUEUE", properties, msg.getBytes());
b、队列的过期时间:
Map<String, Object> argss = new HashMap<String, Object>();
argss.put("x-message-ttl",6000);
channel.queueDeclare("TEST_TTL_QUEUE", false, false, false, argss);
队列的过期时间决定了在没有任何消费者以后,队列可以存活多久。
2、死信队列
有三种情况消息会进入DLX(Dead Letter Exchange)死信交换机。
- (NACK || Reject ) && requeue == false
- 消息过期
- 队列达到最大长度(先入队的消息会被发送到DLX)
可以设置一个死信队列(Dead Letter Queue)与DLX绑定,即可以存储Dead Letter,消费者可以监听这个队列取走消息。
Map<String,Object> arguments = new HashMap<String,Object>();
arguments.put("x-dead-letter-exchange","DLX_EXCHANGE");
// 指定了这个队列的死信交换机
channel.queueDeclare("TEST_DLX_QUEUE", false, false, false, arguments);
// 声明死信交换机
channel.exchangeDeclare("DLX_EXCHANGE","topic", false, false, false, null);
// 声明死信队列
channel.queueDeclare("DLX_QUEUE", false, false, false, null);
// 绑定
channel.queueBind("DLX_QUEUE","DLX_EXCHANGE","#");
3、优先级队列
设置一个队列的最大优先级:
Map<String, Object> argss = new HashMap<String, Object>();
argss.put("x-max-priority",10);
// 队列最大优先级
channel.queueDeclare("ORIGIN_QUEUE", false, false, false, argss);
发送消息时指定消息当前的优先级:
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.priority(5)
// 消息优先级
.build();
channel.basicPublish("", "ORIGIN_QUEUE", properties, msg.getBytes());
优先级高的消息可以优先被消费,但是:只有消息堆积(消息的发送速度大于消费者的消费速度)的情况下优先级 才有意义。
4、延迟队列
RabbitMQ本身不支持延迟队列。可以使用TTL结合DLX的方式来实现消息的延迟投递,即把DLX跟某个队列绑定, 到了指定时间,消息过期后,就会从DLX路由到这个队列,消费者可以从这个队列取走消息。 另一种方式是使用rabbitmq-delayed-message-exchange插件。
当然,将需要发送的信息保存在数据库,使用任务调度系统扫描然后发送也是可以实现的。
5、RPC
RabbitMQ实现RPC的原理:服务端处理消息后,把响应消息发送到一个响应队列,客户端再从响应队列取到结 果。
其中的问题:Client收到消息后,怎么知道应答消息是回复哪一条消息的?所以必须有一个唯一ID来关联,就是 correlationId。
6、服务端流控(Flow Control)
RabbitMQ 会在启动时检测机器的物理内存数值。默认当 MQ 占用 40% 以上内存时,MQ 会主动抛出一个内存警告并阻塞所有连接(Connections)。
可以通过修改 rabbitmq.config 文件来调整内存阈值,默认值是 0.4,如下 所示: [{rabbit, [{vm_memory_high_watermark, 0.4}]}]. 默认情况,如果剩余磁盘空间在 1GB 以下,RabbitMQ 主动阻塞所有的生产者。这个阈值也是可调的。
注意队列长度只在消息堆积的情况下有意义,而且会删除先入队的消息,不能实现服务端限流。
7、消费端限流
在AutoACK为false的情况下,如果一定数目的消息(通过基于consumer或者channel设置Qos的值)未被确认 前,不进行消费新的消息。
channel.basicQos(2);
// 如果超过2条消息没有发送ACK,当前消费者不再接受队列消息
channel.basicConsume(QUEUE_NAME, false, consumer);
UI管理界面的使用
管理插件提供了更简单的管理方式。 启用管理插件
Windows启用管理插件
cd C:\Program Files\RabbitMQ Server\rabbitmq_server-3.6.6\sbin
rabbitmq-plugins.bat enable rabbitmq_management
Linux启用管理插件
cd /usr/lib/rabbitmq/bin
./rabbitmq-plugins enable rabbitmq_management
管理界面访问端口:
默认端口是15672,默认用户guest,密码guest。guest用户默认只能在本机访问。