目录
初级
一.MQ的基本概念
二.RabbitMQ的安装和配置
三.RabbitMQ快速入门
1.入门程序
简单模式----RabbitMQ的六大工作模式之一。
需求:使用简单模式完成消息传递
①创建工程(生成者、消费者)
项目架构:
②分别添加依赖
生产者依赖:
<dependencies>
<!--rabbitMq的java客户端-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.6.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
消费者依赖:
消费者依赖与消费者相同,复制生成者依赖即可。
③编写生产者发送消息
package cn.zcj.producer;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 发消息的生产者
*/
public class producer01 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工场
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("127.0.0.1");//ip,默认为localhost
factory.setPort(5672);//端口,默认为5672
factory.setVirtualHost("/zcj");//虚拟机,默认为/
factory.setUsername("ZCJZCJ");//用户名,默认为guest
factory.setPassword("ZCJZCJ");//密码,默认为guest
//3.创建连接Connection
Connection connection = factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
//5.创建队列Queue
/*
* queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
* 参数解释:
* 1.queue:队列名称
* 2.durable:是否持久化,当rabbitMq重启后还在
* 3.exclusive:
* *是否独占:只能有一个消费者监听这个队列
* *当Connection关闭时,是否删除这个队列
* 4.autoDelete:是否自动删除。当没有Consumer时,自动删除掉
* 5.arguments:参数信息,暂时设置为null
* */
//如果没有一个名字叫helloWord的队列,则会创建该队列,有就不创建
channel.queueDeclare("helloWord",true,false,false,null);
String body = "hello,rabbitMq";
//6.发送消息
/*
* basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
* 参数解释:
* 1.exchange:交换机名称。简单模式不需要使用交换机,交换机名默认使用""
* 2.routingKey:路由名称
* 3.props:配置信息
* 4.body:发送消息数据
* */
channel.basicPublish("","helloWord",null,body.getBytes());
//7.释放资源
channel.close();
connection.close();
}
}
【Bug总结】代码中,生产者方的队列名与路由名不同,不会报错,但是生产者会拿不到消息。
④编写消费者接收消
【注意】消费者端不必关流释放资源
package cn.zcj.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
public class consumer01 {
public static void main(String[] args) {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("127.0.0.1");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/zcj");//虚拟机 默认值/
factory.setUsername("ZCJZCJ");//用户名 默认 guest
factory.setPassword("ZCJZCJ");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = null;
//4. 创建Channel
Channel channel = null;
try {
connection= factory.newConnection();
channel = connection.createChannel();
// 接收消息
Consumer consumer = new DefaultConsumer(channel){
/*
回调方法,当收到消息后,会自动执行该方法
1. consumerTag:标识
2. envelope:获取一些信息,交换机,路由key...
3. properties:配置信息
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);
System.out.println("body:"+new String(body));
}
};
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1. queue:队列名称
2. autoAck:是否自动确认,后面再讲
3. callback:回调对象
*/
channel.basicConsume("helloWord",true,consumer);
} catch (Exception e){
e.printStackTrace();
}
}
}
四.RabbitMQ的工作模式
1.简单模式(上文已实现)
2.工作队列模式
依赖和简单模式导入的依赖相同(复制上文依赖即可)
2.1.生产者:
生产者(1个)
代码:
package cn.zcj.producer;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 发消息的生产者
*/
public class producerWorkQueue {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工场
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("127.0.0.1");//ip,默认为localhost
factory.setPort(5672);//端口,默认为5672
factory.setVirtualHost("/zcj");//虚拟机,默认为/
factory.setUsername("ZCJZCJ");//用户名,默认为guest
factory.setPassword("ZCJZCJ");//密码,默认为guest
//3.创建连接Connection
Connection connection = factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
//5.创建队列Queue
//如果没有一个名字叫helloWord的队列,则会创建该队列,有就不创建
channel.queueDeclare("WorkQueue",true,false,false,null);
for (int i = 0 ;i<10;i++){
String body = "WorkQueue模式消息"+i;
//6.发送消息
channel.basicPublish("","WorkQueue",null,body.getBytes());
}
//7.释放资源
channel.close();
connection.close();
}
}
2.2.消费者
消费者(2个)
消费者1代码:
package cn.zcj.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
public class consumerWorkQueue01 {
public static void main(String[] args) {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("127.0.0.1");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/zcj");//虚拟机 默认值/
factory.setUsername("ZCJZCJ");//用户名 默认 guest
factory.setPassword("ZCJZCJ");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = null;
//4. 创建Channel
Channel channel = null;
try {
connection= factory.newConnection();
channel = connection.createChannel();
channel.queueDeclare("WorkQueue",true,false,false,null);
// 接收消息
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("body:"+new String(body));
}
};
channel.basicConsume("WorkQueue",true,consumer);
} catch (Exception e){
e.printStackTrace();
}
}
}
消费者2代码:将消费者1的代码复制一份后改下类名即可
【注意】启动时先启动两个消费者,再去启动生产者
测试结果:
从测试结果不难看出:消费者不会重复消费同一条消息,并且默认是两个消费者轮流(轮询)消费消息 。
总结:
3.Pub/Sub模式
模式说明:
依赖和简单模式导入的依赖相同(复制上文依赖即可)
生产者(1个):
package cn.zcj.producer;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 发消息的生产者
*/
public class producerPubSub {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工场
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("127.0.0.1");//ip,默认为localhost
factory.setPort(5672);//端口,默认为5672
factory.setVirtualHost("/zcj");//虚拟机,默认为/
factory.setUsername("ZCJZCJ");//用户名,默认为guest
factory.setPassword("ZCJZCJ");//密码,默认为guest
//3.创建连接Connection
Connection connection = factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
//5.创建交换机
/*
* exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete,
* boolean internal, Map<String, Object> arguments)
* 参数解析:
* 1.exchange:交换机名称
* 2.type:交换机类型----很重要,如果类型不同,那么分发消息的规则也就不同
* DIRECT("direct"):定向
FANOUT("fanout"):扇形(广播),发送消息到每一个与之绑定的队列
TOPIC("topic"):统配符的方式
HEADERS("headers"):参数匹配,使用较少,不做讲解
* 3.durable:是否持久化
* 4.autoDelete:自动删除
* 5.internal:内部使用,一般false
* 6.arguments:参数,暂时为null
* */
String exchangeName = "test_fanout";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT,true,false,false,null);
//6.创建2个队列Queue
String queueName01 = "test_fanout_queue01";
channel.queueDeclare(queueName01,true,false,false,null);
String queueName02 = "test_fanout_queue02";
channel.queueDeclare(queueName02,true,false,false,null);
//7.绑定队列和交换机(非常重要)
/*
* queueBind(String queue, String exchange, String routingKey)
* 参数解释:
* 1.queue:队列名称
* 2.exchange:交换机名称
* 3.routingKey:路由键,绑定规则
* 如果交换机的类型为fanout,routingKey的值为"",交换机会将消息发送给每一个与之绑定的Queue容器
*
* */
channel.queueBind(queueName01,exchangeName,"");
channel.queueBind(queueName02,exchangeName,"");
//8.发送消息
String message = "日志信息:张三查询了数据库......";
channel.basicPublish(exchangeName,"",null,message.getBytes());
//9.释放资源
channel.close();
connection.close();
}
}
【补充】交换机会将消息发送给每一个与之绑定的Queue容器。
消费者(2个)
消费者1
package cn.zcj.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class consumerPubSub01 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("127.0.0.1");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/zcj");//虚拟机 默认值/
factory.setUsername("ZCJZCJ");//用户名 默认 guest
factory.setPassword("ZCJZCJ");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
String queueName01 = "test_fanout_queue01";
String queueName02 = "test_fanout_queue02";
// 接收消息
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("body:" + new String(body));
System.out.println("收到消息了,将数据打印到控制台...");
}
};
channel.basicConsume(queueName01, true, consumer);
}
}
消费者2
复制消费者1的代码,然后在此处将名字改为queueName02
测试结果:
总结:
4.路由模式
模式介绍:
依赖和简单模式导入的依赖相同(复制上文依赖即可)
生产者代码实现
package cn.zcj.producer;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 发消息的生产者
*/
public class producerRouting {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工场
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("127.0.0.1");//ip,默认为localhost
factory.setPort(5672);//端口,默认为5672
factory.setVirtualHost("/zcj");//虚拟机,默认为/
factory.setUsername("ZCJZCJ");//用户名,默认为guest
factory.setPassword("ZCJZCJ");//密码,默认为guest
//3.创建连接Connection
Connection connection = factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
//5.创建交换机
/*
* exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete,
* boolean internal, Map<String, Object> arguments)
* 参数解析:
* 1.exchange:交换机名称
* 2.type:交换机类型----很重要,如果类型不同,那么分发消息的规则也就不同
* DIRECT("direct"):定向
FANOUT("fanout"):扇形(广播),发送消息到每一个与之绑定的队列
TOPIC("topic"):统配符的方式
HEADERS("headers"):参数匹配,使用较少,不做讲解
* 3.durable:是否持久化
* 4.autoDelete:自动删除
* 5.internal:内部使用,一般false
* 6.arguments:参数,暂时为null
* */
String exchangeName = "test_direct";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT,true,false,false,null);
//6.创建2个队列Queue
String queueName01 = "test_direct_queue01";
channel.queueDeclare(queueName01,true,false,false,null);
String queueName02 = "test_direct_queue02";
channel.queueDeclare(queueName02,true,false,false,null);
//7.绑定队列和交换机(非常重要)
/*
* queueBind(String queue, String exchange, String routingKey)
* 参数解释:
* 1.queue:队列名称
* 2.exchange:交换机名称
* 3.routingKey:路由键,绑定规则
* 如果交换机的类型为fanout,routingKey的值为"",交换机会将消息发送给每一个与之绑定的Queue容器
*
* */
//队列1绑定1次,绑定error
channel.queueBind(queueName01,exchangeName,"error");
//队列2绑定3次,绑定info、error、warning
channel.queueBind(queueName02,exchangeName,"info");
channel.queueBind(queueName02,exchangeName,"error");
channel.queueBind(queueName02,exchangeName,"warning");
//8.发送消息
String message = "日志信息:张三查询了数据库......";
channel.basicPublish(exchangeName,"error",
null,message.getBytes());
//9.释放资源
channel.close();
connection.close();
}
}
生产者测试效果:
从测试结果不难看出,交换机只会为队列1分配类型为error的消息,但是会为队列2分配了类型为error、info、warning类型的消息。
【Bug总结】代码中交换机名字写错,后台编写代码的控制台不会报错,但是消息却无法成功发出。
消费者代码实现(2个)
消费者1
package cn.zcj.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class consumerRouting01 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("127.0.0.1");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/zcj");//虚拟机 默认值/
factory.setUsername("ZCJZCJ");//用户名 默认 guest
factory.setPassword("ZCJZCJ");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
String directName01 = "test_direct_queue01";
String directName02 = "test_direct_queue02";
// 接收消息
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("body:" + new String(body));
System.out.println("收到消息了,将数据打印到控制台...");
}
};
channel.basicConsume(directName01, true, consumer);
}
}
消费者2
复制消费者1的代码,改下名字即可
channel.basicConsume(directName02, true, consumer);//只改这行和类名即可
消费者代码测试 :
5.通配符模式(最强大的模式)
模式介绍:
依赖和简单模式导入的依赖相同(复制上文依赖即可)
生产者代码:
【注意】"*"代表一个单词,"#"代表0至多个单词
package cn.zcj.producer;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 发消息的生产者
*/
public class producerTopics {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工场
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("127.0.0.1");//ip,默认为localhost
factory.setPort(5672);//端口,默认为5672
factory.setVirtualHost("/zcj");//虚拟机,默认为/
factory.setUsername("ZCJZCJ");//用户名,默认为guest
factory.setPassword("ZCJZCJ");//密码,默认为guest
//3.创建连接Connection
Connection connection = factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
//5.创建交换机
/*
* exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete,
* boolean internal, Map<String, Object> arguments)
* 参数解析:
* 1.exchange:交换机名称
* 2.type:交换机类型----很重要,如果类型不同,那么分发消息的规则也就不同
* DIRECT("direct"):定向
FANOUT("fanout"):扇形(广播),发送消息到每一个与之绑定的队列
TOPIC("topic"):统配符的方式
HEADERS("headers"):参数匹配,使用较少,不做讲解
* 3.durable:是否持久化
* 4.autoDelete:自动删除
* 5.internal:内部使用,一般false
* 6.arguments:参数,暂时为null
* */
String exchangeName = "test_topics";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC,true,false,false,null);
//6.创建2个队列Queue
String queueName01 = "test_topics_queue01";
channel.queueDeclare(queueName01,true,false,false,null);
String queueName02 = "test_topics_queue02";
channel.queueDeclare(queueName02,true,false,false,null);
//7.绑定队列和交换机(非常重要)
/*
* queueBind(String queue, String exchange, String routingKey)
* 参数解释:
* 1.queue:队列名称
* 2.exchange:交换机名称
* 3.routingKey:路由键,绑定规则
* 如果交换机的类型为fanout,routingKey的值为"",交换机会将消息发送给每一个与之绑定的Queue容器
*
* */
//routing key----系统的名称.日志的级别
//需求:所有error级别的日志存入数据库,所有order系统的日志存入数据库
channel.queueBind(queueName01,exchangeName,"#.error");
channel.queueBind(queueName01,exchangeName,"order.*");
//需求:任何级别任何系统的消息都打印在控制台
channel.queueBind(queueName02,exchangeName,"*.*");
//8.发送消息
String message = "日志信息:张三查询了数据库......";
channel.basicPublish(exchangeName,"order.info",null,message.getBytes());
//9.释放资源
channel.close();
connection.close();
}
}
生产者测试:
消费者:
代码实现:
package cn.zcj.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class consumerTopics01 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("127.0.0.1");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/zcj");//虚拟机 默认值/
factory.setUsername("ZCJZCJ");//用户名 默认 guest
factory.setPassword("ZCJZCJ");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
String topicsName01 = "test_topics_queue01";
String topicsName02 = "test_topics_queue02";
// 接收消息
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("body:" + new String(body));
System.out.println("收到消息了,将数据存储到数据库...");
}
};
channel.basicConsume(topicsName01, true, consumer);
}
}
消费者2
//复制消费者1的代码,并只改这一行
channel.basicConsume(topicsName02, true, consumer);
测试:
总结:
6.远程操纵模式
略。
五.Spring整合RabbitMQ
Spring整合rabbitMQ生产者
1.依赖导入
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>rabbitMq</artifactId>
<groupId>cn.zcj</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-rabbitMq-Proudcer</artifactId>
<dependencies>
<!--spring上下文-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
<!--spring整合amqp的插件包-->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<!---->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
</dependencies>
<!--编译插件-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.resource下两个配置文件
第一个:rabbitmq.properties
rabbitmq.host=127.0.0.1
rabbitmq.port=5672
rabbitmq.username=ZCJZCJ
rabbitmq.password=ZCJZCJ
rabbitmq.virtual-host=/zcj
第二个:spring-rabbitmq-producer.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<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}"/>
<!--定义管理交换机、队列-->
<rabbit:admin connection-factory="connectionFactory"/>
<!--定义持久化队列,不存在则自动创建;不绑定到交换机则绑定到默认交换机
默认交换机类型为direct,名字为:"",路由键为队列的名称
-->
<!--
参数解析:
id:bean的名字
name:queue的名字,可以与id不同
auto-declare:是否自动创建
auto-delete:自动删除,最后一个消费者和该队列断开连接后,自动删除该队列
exclusive:受否独占
durable:是否持久化
-->
<rabbit:queue id="spring_queue" name="spring_queue" auto-declare="true"/>
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~广播;所有队列都能收到消息~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_fanout_queue_1" name="spring_fanout_queue_1" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_fanout_queue_2" name="spring_fanout_queue_2" auto-declare="true"/>
<!--定义广播类型为交换机;并绑定上述两个队列-->
<rabbit:fanout-exchange id="spring_fanout_exchange" name="spring_fanout_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding queue="spring_fanout_queue_1"/>
<rabbit:binding queue="spring_fanout_queue_2"/>
</rabbit:bindings>
</rabbit:fanout-exchange>
<rabbit:direct-exchange name="aa">
<rabbit:bindings>
<!--direct类型的交换机绑定队列,key:路由key,queue:队列名称-->
<rabbit:binding queue="spring_queue" key="XXX"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~通配符;*匹配一个单词,#匹配多个单词 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_star" name="spring_topic_queue_star" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_well" name="spring_topic_queue_well" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_well2" name="spring_topic_queue_well2" auto-declare="true"/>
<rabbit:topic-exchange id="spring_topic_exchange" name="spring_topic_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding pattern="zcj.*" queue="spring_topic_queue_star"/>
<rabbit:binding pattern="zcj.#" queue="spring_topic_queue_well"/>
<rabbit:binding pattern="fengche.#" queue="spring_topic_queue_well2"/>
</rabbit:bindings>
</rabbit:topic-exchange>
<!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>
项目结构:
生产者测试:
package cn.zcj;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")
public class Proudcer {
//1.注入RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testHelloWorld(){
//2.发送消息
rabbitTemplate.convertAndSend("spring_queue","hello,spring-rabbitMQ");
}
@Test
public void testFanout(){
//2.发送消息
rabbitTemplate.convertAndSend("spring_fanout_exchange","","rabbitMQ-Fanout");
}
@Test
public void testTopics(){
//2.发送消息
rabbitTemplate.convertAndSend("spring_topic_exchange","zcj.test.MQ","rabbitMQ-Topics");
}
}
测试结果:
整合消费者
消费者端的依赖和生产者相同,复制即可
2.resource下两个配置文件
第一个:rabbitmq.properties
rabbitmq.host=127.0.0.1
rabbitmq.port=5672
rabbitmq.username=ZCJZCJ
rabbitmq.password=ZCJZCJ
rabbitmq.virtual-host=/zcj
第二个:spring-rabbitmq-consumer.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<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}"/>
<bean id="springQueueListener" class="cn.zcj.rabbitmq.listener.SpringQueueListener"/>
<!--<bean id="fanoutListener1" class="cn.zcj.rabbitmq.listener.FanoutListener1"/>
<bean id="topicListenerWell2" class="cn.zcj.rabbitmq.listener.TopicListenerWell2"/>-->
<rabbit:listener-container connection-factory="connectionFactory" auto-declare="true">
<rabbit:listener ref="springQueueListener" queue-names="spring_queue"/>
<!-- <rabbit:listener ref="fanoutListener1" queue-names="spring_fanout_queue_1"/>
<rabbit:listener ref="topicListenerWell2" queue-names="spring_topic_queue_well2"/>-->
</rabbit:listener-container>
</beans>
5
package cn.zcj.rabbitmq.listener;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
public class SpringQueueListener implements MessageListener {
@Override
public void onMessage(Message message) {
System.out.println(new String(message.getBody()));
}
}
5
package cn.zcj;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-consumer.xml")
public class ConsumerTest {
@Test
public void testHelloWorld(){
boolean flag = true;
while (true){
}
}
}
5
六.SpringBoot整合RabbitMQ
1.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.zcj</groupId>
<artifactId>SpringBootAndRabbitMQ</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<!--SpringBoot父工程依赖-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>
<dependencies>
<!--SpringBoot整合RabbitMq的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!--SpringBoot单元测试依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
</project>
2
spring:
rabbitmq:
host: 127.0.0.1
username: ZCJZCJ
password: ZCJZCJ
virtual-host: /zcj
port: 5672
2.启动类
package cn.zcj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RabbitMqApplication {
public static void main(String[] args) {
SpringApplication.run(RabbitMqApplication.class);
}
}
2
package cn.zcj.rabbitMqConfig;
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
public class RabbitMQConfig {
public static final String EXCHANGE_NAME = "BootTopicExchange";
public static final String QUEUE_NAME = "BootQueue";
//1.交换机
@Bean("bootExchange")
public Exchange bootExchange(){
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
}
//2.队列
@Bean("bootQueue")
public Queue bootQueue(){
return QueueBuilder.durable(QUEUE_NAME).build();
}
//3.让队列和交换机进行绑定
/*
* 1.知道哪个队列
* 2.知道哪个交换机
* 3.设置Routing Key
* */
@Bean
public Binding bindQueueExchange(@Qualifier("bootExchange") Exchange exchange, @Qualifier("bootQueue") Queue queue){
return BindingBuilder.bind(queue).to(exchange).with("boot.#").noargs();
}
}
2
package cn.zcj;
import cn.zcj.rabbitMqConfig.RabbitMQConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@SpringBootTest
@RunWith(SpringRunner.class)
public class ProducerTest {
//注入RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSendMessage(){
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"boot.zcj","helloBoot");
}
}
2测试结果
项目结构:
消费者 :
1.依赖
<parent>
<!--SpringBoot父工程依赖-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>
<dependencies>
<!--SpringBoot整合RabbitMq的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!--SpringBoot单元测试依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
2
spring:
rabbitmq:
host: 127.0.0.1
username: ZCJZCJ
password: ZCJZCJ
virtual-host: /zcj
port: 5672
2监听器
package cn.zcj.MqListener;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class RabbitMqListener {
//队列名必须要正确对应
@RabbitListener(queues = "BootQueue")
public void ListenerQueue(Message message){
System.out.println(new String(message.getBody()));
}
}
2启动类
package cn.zcj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RabbitMqConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(RabbitMqConsumerApplication.class);
}
}
2开启启动类后,控制台的输出结果:
2项目结构
高级
一.RabbitMQ高级特性
1.确认模式和回退模式
消息可靠性投递:
【补充】确认模式回退模式都是生产者到Broker的可靠性保障,其中确认模式发生再Producer到exchange之间,回退模式发生在exchange到Queue容器之间。
代码实现
<dependencies>
<!--spring上下文-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
<!--spring整合amqp的插件包-->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<!---->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
</dependencies>
<!--编译插件-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
1
rabbitmq.host=127.0.0.1
rabbitmq.port=5672
rabbitmq.username=ZCJZCJ
rabbitmq.password=ZCJZCJ
rabbitmq.virtual-host=/zcj
1
核心配置:
完整配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<!--publisher-confirms==>开启确认模式-->
<!--publisher-returns==>开启回退模式-->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"
publisher-confirms="true"
publisher-returns="true"
/>
<!--定义管理交换机、队列-->
<rabbit:admin connection-factory="connectionFactory"/>
<!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
<!--消息可靠性投递(生产端)-->
<rabbit:queue id="test_queue_confirm" name="test_queue_confirm"></rabbit:queue>
<rabbit:direct-exchange name="test_exchange_confirm">
<rabbit:bindings>
<rabbit:binding queue="test_queue_confirm" key="confirm"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
</beans>
1
package cn.zcj;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")
public class Proudcer {
//1.注入RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
/*
* 确认模式:
* 步骤:
* 1.确认模式开启:在配置文件里ConnectionFactory中开启publisher-confirms="true"
* 2.在rabbitTemplate定义ConfirmCallback回调函数
* */
@Test
public void testConfig(){
//2.定义回调函数
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
*
* @param correlationData:相关配置信息
* @param ack:exchange交换机,是否收到了消息。成功为true,失败为false
* @param cause:失败原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("confirm方法执行了");
if (ack){
System.out.println("收到消息成功");
}else {
System.out.println("收到消息失败"+cause);
}
}
});
//3.发送消息
//手动制造失败场景,故意写错交换机名字
rabbitTemplate.convertAndSend("test_exchange_confirm","confirm","hello");
}
}
1
成功测试:
失败测试:
2.回退模式
/**
* 回退模式:当消息发送给Exchange后,Exchange路由到Queue失败时,才会执行ReturnCallback
* 步骤:
* 1.开启回退模式:publisher-returns="true"
* 2.设置ReturnCallback
* 3.设置Exchange处理消息的模式(即此时路由消息已经失败了)
* 1.如果消息没有路由到Queue,默认情况下直接丢弃这个消息
* 2.如果消息没有路由到Queue,将消息返回给发送方ReturnCallback,这种方式要手动设置
*/
@Test
public void testReturn(){
//1.设置交换机处理失败消息的模式,下面代码的意思是将发送失败的消息返回给生产者
rabbitTemplate.setMandatory(true);
//2.设置ReturnCallback
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
* 只有当消息从交换机发送到Queue容器过程中失败时,下面这个回调函数才会执行
* @param message:消息对象
* @param replyCode:错误码
* @param replyText:错误信息
* @param exchange:交换机名
* @param routingKey:路由键
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println(message);
System.out.println(replyCode);
System.out.println(replyText);
System.out.println(exchange);
System.out.println(routingKey);
}
});
//3.发送消息
rabbitTemplate.convertAndSend("test_exchange_confirm","confirm222","HELLO");
}
1失败测试
总结:
2.Consumer ACK
介绍
消费端确认收到消息的三种模式------自动模式、手动模式、根据异常情况确认模式。
【补充】上文确认模式和回退模式均是生产者端的操作,是生产者到RabbitMQ Broker的可靠消息保障;而Consumer Ack均是消费者端的操作,是RabbitMQ Broker到消费者端的可靠消息保障。
环境准备
<dependencies>
<!--spring上下文-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
<!--spring整合amqp的插件包-->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<!---->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
</dependencies>
<!--编译插件-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
5
rabbitmq.host=127.0.0.1
rabbitmq.port=5672
rabbitmq.username=ZCJZCJ
rabbitmq.password=ZCJZCJ
rabbitmq.virtual-host=/zcj
5
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<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}"/>
<context:component-scan base-package="cn.zcj.rabbitmq.listener"></context:component-scan>
<!--定义监听器容器-->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">
<rabbit:listener ref="" queue-names="test_queue_confirm"/>
</rabbit:listener-container>
</beans>
5
自动签收模式:
自动模式监听器
package cn.zcj.rabbitmq.listener;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
import org.springframework.stereotype.Component;
@Component
public class AckListener implements MessageListener {
@Override
public void onMessage(Message message) {
System.out.println(new String(message.getBody()));
}
}
5将监听器类的类名首字母小写后写在配置文件里面
5测试
package cn.zcj;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-consumer.xml")
public class ConsumerTest {
@Test
public void testHelloWorld(){
while (true){
}
}
}
5测试结果
5
手动签收模式:
1.设置手动签收。acknowledge="manual"
2.让监听器实现ChannelAwareMessageListener接口
3.如果消息处理成功,则调用channel的basicAck()签收
4.如果消息处理失败,则调用channel的basicNack()拒绝签收,broker重新给消费者consumer发消息
手动模式监听器:
package cn.zcj.rabbitmq.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class AckListenerManual implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
//收到的当前消息的标签
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//1.接收转换消息
System.out.println(new String(message.getBody()));
//2.处理业务
System.out.println("处理业务逻辑");
//手动制造异常
//int i = 3/0;
//3.手动签收,true表示允许多条消息同时签收
channel.basicAck(deliveryTag,true);
} catch (Exception e) {
//4.拒绝签收
/*
第三个参数:重回队列。如果设置为true,则消息重新回到queue,broker会重新发送该消息给消费端
*/
channel.basicNack(deliveryTag,true,true);
//这个方法和上面的方法效果一样,但是不能一次签收多条消息
//channel.basicReject(deliveryTag,true);
}
}
}
5将监听器类的类名首字母小写后写在配置文件里面
5测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-consumer.xml")
public class ConsumerTest {
@Test
public void testHelloWorld(){
while (true){
}
}
}
5正常测试与异常测试:
根据异常情况确认模式:略。
消费端限流
介绍
2
Consumer限流机制:
1.确保消费者消息确认机制为手动确认机制
2.listener-container配置属性
prefetch="10",表示消费者每次去MQ拉取10条消息来消费,直到10条消息都确认消费完毕后,再去拉取下一批消息(10条)
1.先改动配置(2处)
2.准备监听器
package cn.zcj.rabbitmq.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
/**
* Consumer限流机制:
* 1.确保消费者消息确认机制为手动确认机制
* 2.listener-container配置属性
* prefetch="10",表示消费者每次去MQ拉取10条消息来消费,直到10条消息都确认消费完毕后,再去拉取下一批消息(10条)
*/
@Component
public class QosListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
//1.获取消息
System.out.println(new String(message.getBody()));
//2.处理业务:略
//3.消息手动签收,true表示允许多条消息同时签收;如果不签收,那么就无法拉取下一条消息消费
long deliveryTag = message.getMessageProperties().getDeliveryTag();
channel.basicAck(deliveryTag,true);
}
}
5测试:
@Test
public void testHelloWorld(){
while (true){
}
}
5测试结果:
总结:
3.TTL
TTL----是生产者方的操作
介绍:
1.队列统一过期
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<!--publisher-confirms==>开启确认模式-->
<!--publisher-returns==>开启回退模式-->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"
publisher-confirms="true"
publisher-returns="true"
/>
<!--定义管理交换机、队列-->
<rabbit:admin connection-factory="connectionFactory"/>
<!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
<!--ttl-->
<rabbit:queue name="test_queue_ttl" id="test_queue_ttl">
<!--设置queue的参数-->
<rabbit:queue-arguments>
<!--x-message-ttl指队列的过期时间10秒后过期-->
<entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"></entry>
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:topic-exchange name="test_exchange_ttl">
<rabbit:bindings>
<rabbit:binding pattern="ttl.#" queue="test_queue_ttl"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
</beans>
2核心配置
2测试
@Test
public void testTtl(){
for (int i = 1;i<=20;i++){
rabbitTemplate.convertAndSend("test_exchange_ttl","ttl.zcj","ttl"+i);
}
}
测试结果(10秒后,20条消息全部过期):
2.消息单独过期
@Test
public void testTtl(){
//消息处理对象,设置一些消息的参数信息
MessagePostProcessor p = new MessagePostProcessor(){
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//1.设置message的信息,5秒后过期
message.getMessageProperties().setExpiration("5000");
//2.返回该消息
return message;
}
};
//消息单独过期
rabbitTemplate.convertAndSend("test_exchange_ttl","ttl.zcj","ttl",p);
}
测试:
【注意1】如果设置了消息过期时间,又设置了队列过期时间,以过期时间短的为准。
【注意2】队列消息过期后,会将队列这中的消息全部移除。
【注意3】如果某一个队列中有10条消息,其中只有1条消息单独设置了消息过期,即便消息已经过期了,队列中显示的依旧是存在10条消息,而不是9条,只有当消费者消费那条已经过期的消息时(此时消息会到达队列顶端),队列才会取判断那条消息是否过期,过期了就从队列中移除。
【补充】一般都是针对队列整体设置过期时间,而不是针对消息设置过期时间。
总结:
4.死信队列
如果某一个队列中有10条消息,其中只有1条消息单独设置了消息过期,即便消息已经过期了,队列中显示的依旧是存在10条消息,而不是9条,只有当消费者消费那条已经过期的消息时(此时消息会到达队列顶端),队列才会取判断那条消息是否过期,过期了就从队列中移除丢弃,如果此时这个队列绑定了一个死信队列,那么就将这条消息存储到死信队列中,死信队列又可以去和其他队列绑定,将这条过期消息存储到其他队列中。
介绍:
2个核心问题
1.普通队列如何绑定死信队列?
2.消息什么时候称为死信?
消息称为死信的3中情况:
死信的交换机队列和正常的交换机队列其实是没有区别的
生产者方配置文件:
rabbitmq.host=127.0.0.1
rabbitmq.port=5672
rabbitmq.username=ZCJZCJ
rabbitmq.password=ZCJZCJ
rabbitmq.virtual-host=/zcj
绑定:
5
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<!--publisher-confirms==>开启确认模式-->
<!--publisher-returns==>开启回退模式-->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"
publisher-confirms="true"
publisher-returns="true"
/>
<!--定义管理交换机、队列-->
<rabbit:admin connection-factory="connectionFactory"/>
<!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
<!--
死信队列:
1.声明正常队列(test_queue_dlx)和交换机(test_exchange_dlx)
2.声明死信队列(queue_dlx)和死信交换机(exchange_dlx)
3.正常队列绑定死信交换机
设置两个参数
*x-dead-letter-exchange:死信交换机名称
*x-dead-letter-routing-key:发送给死信交换机的routingkey
-->
<!--1.声明正常队列(test_queue_dlx)和交换机(test_exchange_dlx)-->
<rabbit:queue name="test_queue_dlx" id="test_queue_dlx">
<!--3.正常队列绑定死信交换机-->
<rabbit:queue-arguments>
<!--3.1.设置死信交换机名称-->
<entry key="x-dead-letter-exchange" value="exchange_dlx"></entry>
<!--3.2.设置发送给死信交换机的routingkey-->
<entry key="x-dead-letter-routing-key" value="dlx.ZCJ"></entry>
<!--4.1.设置队列过期时间ttl,5秒过期-->
<entry key="x-message-ttl" value="5000" value-type="java.lang.Integer"></entry>
<!--4.2.设置队列的长度限制x-max-length,Queue容器里面只能存10条消息-->
<entry key="x-max-length" value="10" value-type="java.lang.Integer"></entry>
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:topic-exchange name="test_exchange_dlx">
<rabbit:bindings>
<rabbit:binding pattern="test_dlx.#" queue="test_queue_dlx"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
<!--2.声明死信队列(queue_dlx)和死信交换机(exchange_dlx)-->
<rabbit:queue name="queue_dlx" id="queue_dlx"></rabbit:queue>
<rabbit:topic-exchange name="exchange_dlx">
<rabbit:bindings>
<rabbit:binding pattern="dlx.#" queue="queue_dlx"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
</beans>
5测试:
/**
* 发送测试死信消息
* 1.过期时间
* 2.长度限制
* 3.消息拒收
*/
@Test
public void testDlx(){
/*
1.测试过期时间,死信消息
rabbitTemplate.convertAndSend("test_exchange_dlx","test_dlx.zcj","死信");
*/
/*
//2.测试长度超过Queue容器限制,死信消息
//测试结果:Queue容器只能存10条消息,一次发送20条消息有10条消息直接进入死信队列,剩下10条过期后进入死信队列
for(int i = 0;i<=20;i++){
rabbitTemplate.convertAndSend("test_exchange_dlx","test_dlx.zcj","死信"+i);
}*/
//3.测试消息拒收,死信消息
//注意:测试消息拒收时,消费者端一定不要让被拒收的消息重回Queue并让Queue重发,而是进入死信队列
rabbitTemplate.convertAndSend("test_exchange_dlx","test_dlx.zcj","拒收");
}
第三种测试的注意注意点:
测试结果(仅展示第一种):
总结:
5.延迟队列
介绍:
生产者端
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<!--publisher-confirms==>开启确认模式-->
<!--publisher-returns==>开启回退模式-->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"
publisher-confirms="true"
publisher-returns="true"
/>
<!--定义管理交换机、队列-->
<rabbit:admin connection-factory="connectionFactory"/>
<!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
<!--
延迟队列:
1.声明正常队列(order_queue)和交换机(order_exchange)
2.声明死信队列(queue_dlx)和死信交换机(exchange_dlx)
3.正常队列绑定死信交换机
设置两个参数
*x-dead-letter-exchange:死信交换机名称
*x-dead-letter-routing-key:发送给死信交换机的routingkey
-->
<!--1.声明正常队列(test_queue_dlx)和交换机(test_exchange_dlx)-->
<rabbit:queue name="order_queue" id="order_queue">
<!--3.正常队列绑定死信交换机-->
<rabbit:queue-arguments>
<!--3.1.设置死信交换机名称-->
<entry key="x-dead-letter-exchange" value="order_exchange_dlx"></entry>
<!--3.2.设置发送给死信交换机的routingkey-->
<entry key="x-dead-letter-routing-key" value="dlx_order.cancel"></entry>
<!--3.3.设置队列过期时间ttl,5秒过期-->
<entry key="x-message-ttl" value="5000" value-type="java.lang.Integer"></entry>
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:topic-exchange name="order_exchange">
<rabbit:bindings>
<rabbit:binding pattern="order.#" queue="order_queue"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
<!--2.声明死信队列(order_queue_dlx)和死信交换机(order_exchange_dlx)-->
<rabbit:queue name="order_queue_dlx" id="order_queue_dlx"></rabbit:queue>
<rabbit:topic-exchange name="order_exchange_dlx">
<rabbit:bindings>
<rabbit:binding pattern="dlx_order.#" queue="order_queue_dlx"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
5测试
@Test
public void testDelay() throws InterruptedException {
//1.发送订单消息。订单系统下单成功后,发送消息
rabbitTemplate.convertAndSend("order_exchange","order.MSG","订单id=1");
//2.倒计时10秒
for (int i = 10;i>0;i--){
System.out.println("倒计时:"+i);
Thread.sleep(1000);
}
}
5消费者端
<!--加载配置文件-->
<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}"/>
<context:component-scan base-package="cn.zcj.rabbitmq.listener"></context:component-scan>
<!--定义监听器容器-->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="10">
<!--延迟队列实现,这里消费者监听的必然是死信队列-->
<rabbit:listener ref="orderListener" queue-names="order_queue_dlx"/>
</rabbit:listener-container>
5
package cn.zcj.rabbitmq.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
@Component
public class OrderListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
//收到的当前消息的标签
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//1.接收转换消息
System.out.println(new String(message.getBody()));
//2.处理业务
System.out.println("处理业务逻辑");
System.out.println("1.根据订单id查询订单");
System.out.println("2.判断订单是否支付成功");
System.out.println("3.支付成功不做处理,支付失败回滚库存");
//3.手动签收,true表示允许多条消息同时签收
channel.basicAck(deliveryTag,true);
} catch (Exception e) {
System.out.println("出现异常,拒绝接收");
channel.basicNack(deliveryTag,true,false);
}
}
}
5
@Test
public void testHelloWorld(){
while (true){
}
}
测试结果描述:
订单模块(生产者端)发送一个订单消息(库存-1)到订单队列(正常队列),订单队列里面的消息10秒后过期,过期的消息会因为订单队列绑定了死信队列而进入死信队列;库存模块(消费者端)会去监听订单队列绑定的那个死信队列,并将死信队列的消息取出来进行业务判断,如果订单已经支付,则不做处理,如果订单没有支付,就回滚库存(库存+1)。
项目结构:
5