概述
通过一些demo来演示RabbitMQ被整合后是如何使用的
配置
1、添加依赖 或 编辑起步依赖添加Spring for RabbitMQ
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
2、yml配置
spring:
rabbitmq:
host: 192.168.126.128
username: admin
password: admin
简单模式
在主程序中,创建Queue
的实例
- 注意Queue是Spring提供的
Spring提供的Queue类,是队列的封装对象,他封装了队列的参数信息。
RabbitMQ的自动配置类RabbitAutoConfiguration
会发现这些Queue实例,并在RabbitMQ服务器中定义这些队列。
import org.springframework.amqp.core.Queue;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import cn.tedu.RabbitmqSpringbootApplication;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
@Bean
public Queue helloworldQueue() {
return new Queue("helloworld", false);
}
}
生产者
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Sender {
@Autowired
AmqpTemplate t;
public void send() {
t.convertAndSend("helloworld", "helloworld"+System.currentTimeMillis());
System.out.println("消息已发送");
}
}
消费者
- 通过
@RabbitListener
注解从指定的队列接收消息 - 使用
@RebbitHandler
注解的方法来处理消息
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = "helloworld") // 通过注解自动加载自动配置,成为消费者,一直接收数据
public class Receiver {
// 如果把 @RabbitListener注解写道类上需要加@RabbitHandler,把接收到的数据交给这个方法处理
@RabbitHandler
public void receiver(String message) {
System.out.println("收到:" + message);
}
}
测试类
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class Test1 {
@Autowired
Sender sender;
@Test
public void test1() {
sender.send();
}
}
工作模式
1、主程序
- 创建名为
task_queue
的持久队列
import org.springframework.amqp.core.Queue;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
@Bean
public Queue task_queue() {
// 这个构造方法创建的队列参数为: 持久,非排他,非自动删除
return new Queue("task_queue");
}
}
生产者
import java.util.Scanner;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Sender {
@Autowired
AmqpTemplate t;
public void send() {
while (true) {
System.out.println("请输入消息:");
String s = new Scanner(System.in).nextLine();
t.convertAndSend("taskQueue", s); // 默认是持久消息
}
}
}
Spring Boot封装的 RabbitMQ API中,发送的消息默认是持久化消息。如果需要设置非持久化消息,则做一下设置
- 使用 MessagePostProcessor 前置处理器参数
- 从消息中获取消息的属性对象
- 在属性中把 DeliveryMode 设置为非持久化
t.convertAndSend("taskQueue", (Object) s, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
MessageProperties props = message.getMessageProperties();
props.setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
return message;
}
});
消费者
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class Receiver {
@RabbitListener(queues = "taskQueue")
public void receiver1(String message) throws InterruptedException {
System.out.println("消费者1 - 收到:" + message);
for (int i = 0; i < message.length(); i++) {
if ('.' == message.charAt(i)) {
Thread.sleep(1000);
}
}
System.out.println("消费者1 - 消息处理完毕");
}
@RabbitListener(queues = "taskQueue")
public void receiver2(String message) throws InterruptedException {
System.out.println("消费者2 - 收到:" + message);
for (int i = 0; i < message.length(); i++) {
if ('.' == message.charAt(i)) {
Thread.sleep(1000);
}
}
System.out.println("消费者2 - 消息处理完毕");
}
}
测试类
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class Test1 {
@Autowired
Sender sender;
@Test
public void test1() {
sender.send();
}
}
ack模式
在 Spring Boot 中提供了三种确认模式:
- NONE:使用 RabbitMQ 的自动确认
- AUTO:使用 RabbitMQ 的手动确认,Spring Boot 回自动发送确认回执 (默认)
- MANUAL:使用 RabbitMQ 的手动确认,且必须手动执行确认操作
默认的 AUTO 模式中,处理消息的方法抛出异常,则表示消息没有被正确处理,该消息会被重新发送
设置 ack 模式
spring:
rabbitmq:
listener:
simple:
# acknowledgeMode: NONE # rabbitmq的自动确认
acknowledgeMode: AUTO # rabbitmq的手动确认, springboot会自动发送确认回执 (默认)
# acknowledgeMode: MANUAL # rabbitmq的手动确认, springboot不发送回执, 必须自己编码发送回执
手动执行确认操作
如果设置为MANUAL
模式,必须手动执行确认操作
@RabbitListener(queues="taskQueue")
public void receive1(String s, Channel c, @Header(name=AmqpHeaders.DELIVERY_TAG) long tag) throws Exception {
System.out.println("receiver1 - 收到: "+s);
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '.') {
Thread.sleep(1000);
}
}
// 手动发送确认回执
c.basicAck(tag, false);
}
设置抓取数量
工作模式中,为了合理的分发数据,需要将 qos 设置成1,每次只接收一条消息处理完成后才接受下一条消息。
Spring Boot 中是通过```prefetch``属性进行设置,该属性默认值为 250.
spring:
rabbitmq:
listener:
simple:
prefetch: 1
发布和订阅模式
主程序
创建FanoutExcnahge
实例,封装fanout
类型交换机定义信息。
自动配置类会自动发现交换机实例,并在 RabbitMQ 服务器中定义该交换机。
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
@Bean
public FanoutExchange logsExchange() {
// 非持久,不自动删除
return new FanoutExchange("logs", false, false);
}
}
生产者
生产者向指定的交换机logs
发送数据
不需要指定队列名或路由键,即使指定也无效,因为fanout
交换机会向所有与之绑定的队列发送数据,并不是有选择的发送
import java.util.Scanner;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Sender {
@Autowired
AmqpTemplate t;
public void send() {
while (true) {
System.out.println("请输入消息:");
String s = new Scanner(System.in).nextLine();
t.convertAndSend("logs","", s); // 默认是持久消息 第二个参数是路由键
}
}
}
消费者
消费者需要完成一下操作:
- 1、定义水机队列(随即命名,非持久,排他,自动删除)
- 2、定义交换机(可以省略)
- 3、将队列绑定到交换机
Spring Boot 通过注解完成以上操作:
@RabbitListener(bindings = @QueueBinding( //这里进行绑定设置
value = @Queue, //这里定义随机队列,默认属性: 随机命名,非持久,排他,自动删除
exchange = @Exchange(name = "logs", declare = "false") //指定 logs 交换机,因为主程序中已经定义,这里不进行定义
))
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class Receiver {
@RabbitListener(bindings = @QueueBinding(
value = @Queue, // 如果@Queue注解不指定任何参数,则为服务器随即起名
exchange = @Exchange(name = "logs", declare = "false") // declare false 表时使用已有的交换机,而不是新创建
))
public void receiver1(String message) throws InterruptedException {
System.out.println("消费者1 - 收到:" + message);
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue, // 如果@Queue注解不指定任何参数,则为服务器随即起名
exchange = @Exchange(name = "logs", declare = "false") // declare false 表时使用已有的交换机,而不是新创建
))
public void receiver2(String message) throws InterruptedException {
System.out.println("消费者2 - 收到:" + message);
}
}
测试类
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class Test1 {
@Autowired
Sender sender;
@Test
public void test1() {
sender.send();
}
}
路由模式
与发布订阅者模式类似,只做以下几点调整:
- 1、使用直连交换机 (
direct
) - 2、队列与交换机绑定时,设置绑定键
- 3、发送消息时,设置路由键
主程序
主程序中使用DirectExchange
对象封装交换机信息,Spring Boot 自动配置类会自动发现这个对象,并在 RabbitMQ 服务器上定义这个交换机。
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
@Bean
public DirectExchange logsExchange() {
// 非持久,不自动删除
return new DirectExchange("direct_logs", false, false);
}
}
生产者
生产者向指定的交换机发送消息,并指定路由键。
import java.util.Scanner;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Sender {
// RabbitAutoConfiguration
@Autowired
AmqpTemplate t;
public void send() {
while (true) {
System.out.println("请输入消息:");
String s = new Scanner(System.in).nextLine();
System.out.println("请输入路由键:");
String key = new Scanner(System.in).nextLine();
t.convertAndSend("direct_logs",key, s); // 默认是持久消息 第二个参数是路由键
}
}
}
消费者
消费者通过注解来定义随机队列,绑定到交换机,并指定绑定键。
@RabbitListener(bindings = @QueueBinding( // 这里做绑定设置
value = @Queue, // 定义队列, 随机命名,非持久,排他,自动删除
exchange = @Exchange(name = "direct_logs", declare = "false"), // 指定绑定的交换机,主程序中已经定义过队列,这里不进行定义
key = {"error","info","warning"} // 设置绑定键
))
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class Receiver {
@RabbitListener(bindings = @QueueBinding(
value = @Queue, // 如果@Queue注解不指定任何参数,则为服务器随即起名
exchange = @Exchange(name = "direct_logs", declare = "false"), // declare false 表时使用已有的交换机,而不是新创建
key = {"erro"}
))
public void receiver1(String message) throws InterruptedException {
System.out.println("消费者1 - 收到:" + message);
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue, // 如果@Queue注解不指定任何参数,则为服务器随即起名
exchange = @Exchange(name = "direct_logs", declare = "false"),
key = {"info", "erro", "warning"}
))
public void receiver2(String message) throws InterruptedException {
System.out.println("消费者2 - 收到:" + message);
}
}
测试类
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class Test1 {
@Autowired
Sender sender;
@Test
public void test1() {
sender.send();
}
}
主题模式
主题模式是一种具有特殊规则的路由模式,代码与路由模式基本相同,只做一下两点调整:
- 1、使用
topic
交换机 - 2、使用特殊的绑定键和路由键
主程序
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
@Bean
public TopicExchange logsExchange() {
// 非持久,不自动删除
return new TopicExchange("topic_logs", false, false); // 默认持久
}
}
生产者
import java.util.Scanner;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Sender {
// RabbitAutoConfiguration
@Autowired
AmqpTemplate t;
public void send() {
while (true) {
System.out.println("请输入消息:");
String s = new Scanner(System.in).nextLine();
System.out.println("请输入路由键:");
String key = new Scanner(System.in).nextLine();
t.convertAndSend("topic_logs",key, s); // 默认是持久消息
}
}
}
消费者
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class Receiver {
@RabbitListener(bindings = @QueueBinding(
value = @Queue, // 如果@Queue注解不指定任何参数,则为服务器随即起名
exchange = @Exchange(name = "topic_logs", declare = "false"), // declare false 表时使用已有的交换机,而不是新创建
key = {"*.orange.*"}
))
public void receiver1(String message) throws InterruptedException {
System.out.println("消费者1 - 收到:" + message);
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue, // 如果@Queue注解不指定任何参数,则为服务器随即起名
exchange = @Exchange(name = "topic_logs", declare = "false"), // declare false 表时使用已有的交换机,而不是新创建
key = {"*.*.rabbit", "lazy.#"}
))
public void receiver2(String message) throws InterruptedException {
System.out.println("消费者2 - 收到:" + message);
}
}
测试类
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class Test1 {
@Autowired
Sender sender;
@Test
public void test1() {
sender.send();
}
}
RPC异步调用
主程序
主程序中定义两个队列
- 1、发送调用信息的队列
- 2、返回结果的队列
import java.util.UUID;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
/*
* 定义队列:
* 1、发送队列 rpc_queue
* 2、返回队列 自己起随机名 非持久,排他,自动删除
*/
@Bean
public Queue rpcQueue() {
return new Queue("rpc_queue", false);
}
@Bean
public Queue rndQueue() {
return new Queue(UUID.randomUUID().toString(), false, true, true);
}
}
服务端
从 rpc_queue
接收调用数据,执行运算,并返回计算结果
@RabbitListener
注解对于具有返回值的方法:
- 回自动获取返回队列名称(
replyTo
属性) - 会自动获取关联id(
correlationId
属性) - 向
replyTo
属性指定的队列发送计算结果,并携带correlationId
属性
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class RpcServer {
/*
* 1、接收调用数据
* 2、执行服务器端计算
* 3、发送计算结果
*
* spring boot 自动处理:
* 1、自动从rpc_queue接收消息,传到receive方法
* 2、自动获取返回队列的队列名
* 3、自动获取关联id
* 4、返回值,会自动发回到rpc客户端
*
*/
@RabbitListener(queues = "rpc_queue")
public long receive(int n) {
return f(n);
}
private long f(int n) {
if (n == 1 || n == 2) {
return 1;
}
return f(n-1) + f(n-2);
}
}
客户端
1、使用 SPEL 表达式 获取随机队列名:#{rndQueue.name}
2、发送调用数据时,携带随机队列名和关联id
3、从随机队列接收调用结果,并获取关联id
import java.util.UUID;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;
@Component
public class RpcClient {
@Autowired
AmqpTemplate t;
// SPEL表达式语言
@Value("#{rndQueue.name}")
String replyTo;
public void send(int n) {
// 携带返回队列名, 关联id
t.convertAndSend("rpc_queue", n, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
MessageProperties props = message.getMessageProperties();
props.setReplyTo(replyTo);
props.setCorrelationId(UUID.randomUUID().toString());
return message;
}
});
System.out.println("调用已发送");
}
@RabbitListener(queues = "#{rndQueue.name}")
public void recive(long r, @Header(name = AmqpHeaders.CORRELATION_ID) String cid) {
System.out.println(cid + " - 斐波那契数:" + r);
}
}
测试类
import java.util.Scanner;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class TopicTests {
@Autowired
RpcClient client;
@Test
void test1() throws Exception {
while (true) {
System.out.print("请输入要计算的值: ");
int n = new Scanner(System.in).nextInt();
client.send(n);
}
}
}