1.RabbitMQ 的安装
1.1上传离线安装包
- rabbitmq-install 目录上传到 /root
1.2切换到rabbitmq-install目录
-
cd rabbitmq-install
1.3安装
-
rpm -ivh *.rpm
1.4启动rabbitmq服务器
# 设置服务,开机自动启动
systemctl enable rabbitmq-server
# 启动服务
systemctl start rabbitmq-server
1.5rabbitmq管理界面
- 启用管理界面
# 开启管理界面插件
rabbitmq-plugins enable rabbitmq_management
# 防火墙打开 15672 管理端口
firewall-cmd --zone=public --add-port=15672/tcp --permanent
firewall-cmd --reload
- 重启RabbitMQ服务
systemctl restart rabbitmq-server
1.6访问
- 访问服务器的15672端口,例如:
http://192.168.64.140:15672
1.7添加用户
# 添加用户
rabbitmqctl add_user admin admin
# 新用户设置用户为超级管理员
rabbitmqctl set_user_tags admin administrator
1.8设置访问权限
1.9开放客户端连接端口
# 打开客户端连接端口
firewall-cmd --zone=public --add-port=5672/tcp --permanent
firewall-cmd --reload
- 主要端口介绍
- 4369 – erlang发现口
- 5672 – client端通信口
- 15672 – 管理界面ui端口
- 25672 – server间内部通信口
2.RabbitMQ工作模式
2.1简单模式
RabbitMQ是一个消息中间件,你可以想象它是一个邮局。当你把信件放到邮箱里时,能够确信邮递员会正确地递送你的信件。RabbitMq就是一个邮箱、一个邮局和一个邮递员。
- 发送消息的程序是生产者
- 队列就代表一个邮箱。虽然消息会流经RbbitMQ和你的应用程序,但消息只能被存储在队列里。队列存储空间只受服务器内存和磁盘限制,它本质上是一个大的消息缓冲区。多个生产者可以向同一个队列发送消息,多个消费者也可以从同一个队列接收消息.
- 消费者等待从队列接收消息
2.1.1RabbitMQ—Demo
创建Project—>Enpty Project
2.1.2编辑pom.xml
<?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.tedu</groupId>
<artifactId>rabbitmq-api</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.4.3</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.1.3创建Producer(生产者)发送消息
package m1;
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 Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//建立连接
ConnectionFactory f=new ConnectionFactory();
f.setHost("192.168.64.140");
f.setPort(5672);
f.setUsername("admin");
f.setPassword("admin");
Connection conn=f.newConnection();
Channel c=conn.createChannel();
//让服务器创建队列 helloworld(已经存在则不重复创建)
//队列参数:helloworld,是否是持久队列;是否排他(独占)队列,是否自动删除
c.queueDeclare("helloworld",
false,
false,
false,
null);//其他参数属性,没有给null
//消息发送到helloworld队列
//参数:
// 1.默认的交换机
// 3.其他的消息属性配置,如果没有给null值
c.basicPublish("",
"helloworld",
null,
"hello world!".getBytes());
System.out.println("消息已发送!");
}
}
2.1.4启动main方法测试
- 点击helloworld查看消息具体内容:
2.1.5创建Consumer(消费者)接收消息
package m1;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) throws Exception {
// 连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140"); // wht6.cn
f.setUsername("admin");
f.setPassword("admin");
Channel c = f.newConnection().createChannel();
// 创建 helloworld 队列(队列如果已经存在,不会重复创建)
c.queueDeclare("helloworld", false, false, false, null);
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
byte[] a = message.getBody();
String msg = new String(a);
System.out.println("收到: "+msg);
}
};
CancelCallback cancelCallback = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
// 消费数据,等待从队列接收数据
// 第二个参数true: 由服务器对消息进行自动确认,确认后会直接删除消息
c.basicConsume("helloworld", true, deliverCallback, cancelCallback);
}
}
2.1.6启动main方法进行测试
- 点击helloworld查看消息发现队列为空:
2.2工作模式
- 工作队列(即任务队列)背后的主要思想是避免立即执行资源密集型任务,并且必须等待它完成。相反,我们将任务安排在稍后完成。
- 我们将任务封装为消息并将其发送到队列。后台运行的工作进程将获取任务并最终执行任务。当运行多个消费者时,任务将在它们之间分发。
- 使用任务队列的一个优点是能够轻松地并行工作。如果我们正在积压工作任务,我们可以添加更多工作进程,这样就可以轻松扩展。
2.2.1生产者发送消息
package m2;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
//f.setPort(5672);//默认端口可以省略
f.setUsername("admin");
f.setPassword("admin");
Channel c = f.newConnection().createChannel();
//定义队列
c.queueDeclare("helloworld",false,false,false,null);
//发送消息
//循环输入消息发送
/*
* 输入消息:ger.err.e.....er...........er......
*消费者受到消息后,遍历字符串,每个点字符,都暂停一秒,来模拟耗时消息
*/
while (true){
System.out.print("输入消息:");
String msg = new Scanner(System.in).nextLine();
c.basicPublish("","helloworld",null,msg.getBytes());
}
}
}
2.2.2消费者接收消息
package m2;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//建立连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
f.setUsername("admin");
f.setPassword("admin");
Channel c = f.newConnection().createChannel();
//定义队列(已经存在则不重复创建)
c.queueDeclare("helloworld", false, false, false, null);
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
//遍历消息字符串,每个点字符都暂停一秒
String msg = new String(message.getBody());
System.out.println("收到:"+msg);
for (int i =0;i<msg.length();i++){
if ('.' ==msg.charAt(i)){
try {
Thread.sleep(1000);
}catch (InterruptedException e){
}
}
}
System.out.println("消息处理结束!");
}
};
CancelCallback cancelCallback = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
//消费数据,等待从队列接收数据
//第二个参数true: 由服务器对消息进行自动确认,确认后会直接删除消息
c.basicConsume("helloworld",true,deliverCallback,cancelCallback);//处理消息的回调对象,取得消息处理的回调对象);
}
}
2.2.3运行测试
- 运行:
- 一个生产者
- 两个消费者
- 生产者发送多条消息,如: 1,2,3,4,5,6,7,8,9,0.
- 两个消费者分别收到:
消费者一:
消费者二:
rabbitmq在所有消费者中轮询分发消息,把消息均匀地发送给所有消费者
2.2.4合理地分发
- rabbitmq会一次把多个消息分发给消费者, 这样可能造成有的消费者非常繁忙, 而其它消费者空闲. 而rabbitmq对此一无所知, 仍然会均匀的分发消息;
- 我们可以使用 basicQos(1) 方法, 这告诉rabbitmq一次只向消费者发送一条消息, 在返回确认回执前, 不要向消费者发送新消息. 而是把消息发给下一个空闲的消费者。
- 1.手动确认模式
......
//第二个参数true: 由服务器对消息进行自动确认,确认后会直接删除消息
// false:手动确认
c.basicConsume("helloworld",false,deliverCallback,cancelCallback);
.....
- 2.发送回执
......
//向服务器发送消息的回执
//参数:1.回执(long类型的编码)2.是否一次确认多条消息(一般是false)
c.basicAck(message.getEnvelope().getDeliveryTag(),false);
System.out.println("消息处理结束!");
.....
- 3.设置Qos
....
//每次只接收处理1条消息,处理完成之前不能接收下一条消息
c.basicQos(1);
....
- 4.测试
生产者:
消费者1:
消费者2:
2.2.5消息持久化
- 当rabbitmq关闭时, 我们队列中的消息仍然会丢失, 除非明确要求它不要丢失数据
- 要求rabbitmq不丢失数据要做如下两点: 把队列和消息都设置为可持久化(durable)
1.队列持久化
//第二个参数是持久化参数durable
ch.queueDeclare("helloworld", true, false, false, null);
2.消息持久化
//第三个参数设置消息持久化
ch.basicPublish("", "task_queue",
MessageProperties.PERSISTENT_TEXT_PLAIN,
msg.getBytes());
3.生产者和消费者都要改(队列):
.....
//定义队列
//队列如果在服务器端已经存在.属性不可变
c.queueDeclare("task_queue",true,false,false,null);
//发送消息
//循环输入消息发送
/*
* 输入消息:ger.err.e.....er...........er......
*消费者受到消息后,遍历字符串,每个点字符,都暂停一秒,来模拟耗时消息
*/
while (true){
System.out.print("输入消息:");
String msg = new Scanner(System.in).nextLine();
c.basicPublish("","task_queue",null,msg.getBytes());
}
.....
4.消息:
while (true){
System.out.print("输入消息:");
String msg = new Scanner(System.in).nextLine();
c.basicPublish("","task_queue", MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes());
}
5.最终代码:
生产者:
package m2;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
//f.setPort(5672);//默认端口可以省略
f.setUsername("admin");
f.setPassword("admin");
Channel c = f.newConnection().createChannel();
//定义队列
//队列如果在服务器端已经存在.属性不可变
c.queueDeclare("task_queue",true,false,false,null);
//发送消息
//循环输入消息发送
/*
* 输入消息:ger.err.e.....er...........er......
*消费者受到消息后,遍历字符串,每个点字符,都暂停一秒,来模拟耗时消息
*/
while (true){
System.out.print("输入消息:");
String msg = new Scanner(System.in).nextLine();
c.basicPublish("","task_queue", MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes());
}
}
}
消费者:
package m2;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//建立连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
f.setUsername("admin");
f.setPassword("admin");
Channel c = f.newConnection().createChannel();
//定义队列(已经存在则不重复创建)
c.queueDeclare("task_queue", true, false, false, null);
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
//遍历消息字符串,每个点字符都暂停一秒
String msg = new String(message.getBody());
System.out.println("收到:"+msg);
for (int i =0;i<msg.length();i++){
if ('.' ==msg.charAt(i)){
try {
Thread.sleep(1000);
}catch (InterruptedException e){
}
}
}
//向服务器发送消息的回执
//参数:1.回执(long类型的编码)2.是否一次确认多条消息(一般是false)
c.basicAck(message.getEnvelope().getDeliveryTag(),false);
System.out.println("消息处理结束!");
}
};
CancelCallback cancelCallback = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
//每次只接收处理1条消息,处理完成之前不能接收下一条消息
c.basicQos(1);
//消费数据,等待从队列接收数据
//第二个参数true: 由服务器对消息进行自动确认,确认后会直接删除消息
// false:手动确认
c.basicConsume("task_queue",false,deliverCallback,cancelCallback);//处理消息的回调对象,取得消息处理的回调对象);
}
}
2.3发布订阅模式
即向多个消费者传递同一条消息;
生产者通过交换机发送消息,消费者只负责和交换机进行绑定。
2.3.1Exchanges 交换机
- RabbitMQ消息传递模型的核心思想是,生产者永远不会将任何消息直接发送到队列。实际上,通常生产者甚至不知道消息是否会被传递到任何队列。
- 相反,生产者只能向交换机(Exchange)发送消息。交换机是一个非常简单的东西。一边接收来自生产者的消息,另一边将消息推送到队列。交换器必须确切地知道如何处理它接收到的消息。exchange的类型定义。
- 类型:
Direct、Topic、Header和Fanout
Fanout交换机非常简单。它只是将接收到的所有消息广播给它所知道的所有队列。 - 创建Fanout类型的交换机,并称之为 logs:
ch.exchangeDeclare("logs", "fanout");
2.3.2绑定 Bindings
已经创建了一个fanout交换机和一个队列,现在我们需要告诉exchange向指定队列发送消息。exchange和队列之间的关系称为绑定。
//指定的队列,与指定的交换机关联起来
//成为绑定 -- binding
//第三个参数时 routingKey, 由于是fanout交换机, 这里忽略 routingKey
ch.queueBind(queueName, "logs", "");
2.3.3生产者
package m3;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
//f.setPort(5672);//默认端口可以省略
f.setUsername("admin");
f.setPassword("admin");
Channel c = f.newConnection().createChannel();
//定义交换机,服务器如果存在交换机,则不重复创建
//c.exchangeDeclare("logs","fanout");
c.exchangeDeclare("logs", BuiltinExchangeType.FANOUT);
//向交换机发送消息
while (true){
System.out.print("输入消息:");
String msg = new Scanner(System.in).nextLine();
//参数:1.交换机
// 2.选择队列,对fanout交换机无效
c.basicPublish("logs","",null,msg.getBytes());
}
}
}
2.3.4消费者
package m3;
import com.rabbitmq.client.*;
import com.sun.org.apache.bcel.internal.generic.NEW;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
//f.setPort(5672);//默认端口可以省略
f.setUsername("admin");
f.setPassword("admin");
Channel c = f.newConnection().createChannel();
//1.定义随机队列 2.定义交换机 3.绑定
String queue = UUID.randomUUID().toString();
c.queueDeclare(queue,false,true,true,null);
c.exchangeDeclare("logs", BuiltinExchangeType.FANOUT);
//对 fanout 交换机,第三个参数无效
c.queueBind(queue,"logs","");
//正常从队列接收消息
DeliverCallback deliverCallback=new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg = new String(message.getBody());
System.out.println("收到:"+msg);
}
};
CancelCallback cancelCallback=new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
c.basicConsume(queue,true,deliverCallback,cancelCallback);
}
}
2.3.5测试
生产者:
消费者1:
消费者2:
2.4路由模式
生产者需要携带路由键,消费者绑定需指定关键词。可以绑定多次,每次使用一个关键词。
2.4.1直连交换机 Direct exchange
- Fanout交换机,这并没有给我们太多的灵活性——它只能进行简单的广播。
- Direct交换机路由算法——消息传递到bindingKey与routingKey完全匹配的队列。
- 举例:
- 其中我们可以看到直连交换机
X
,它绑定了两个队列。第一个队列用绑定键orange
绑定,第二个队列有两个绑定,一个绑定black
,另一个绑定键green
。 - 这样设置,使用路由键
orange
发布到交换器的消息将被路由到队列Q1
。带有black或green
路由键的消息将转到Q2
。而所有其他消息都将被丢弃。
2.4.2绑定 Bindings
- 绑定是交换机和队列之间的关系。这可以简单地理解为:队列对来自此交换的消息感兴趣。
- 绑定可以使用额外的routingKey参数。为了避免与basic_publish参数混淆,我们将其称为bindingKey。这是我们如何创建一个键绑定:
ch.queueBind(queueName, EXCHANGE_NAME, "black");
2.4.3生产者
package m4;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
//f.setPort(5672);//默认端口可以省略
f.setUsername("admin");
f.setPassword("admin");
Channel c = f.newConnection().createChannel();
//定义交换机
c.exchangeDeclare("direct_logs", BuiltinExchangeType.DIRECT);
//发送消息,需要携带路由键
while (true){
System.out.print("输入消息:");
String msg = new Scanner(System.in).nextLine();
System.out.print("输入路由键:");
String key = new Scanner(System.in).nextLine();
//第二个参数是消息上携带的路由键
c.basicPublish("direct_logs",key,null,msg.getBytes());
}
}
}
2.4.4消费者
package m4;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
//f.setPort(5672);//默认端口可以省略
f.setUsername("admin");
f.setPassword("admin");
Channel c = f.newConnection().createChannel();
//1.定义随机队列 2.定义交换机 3.绑定,指定绑定的关键词
//定义随机队列
//由 rabbitmq 服务器自动命名,默认参数:false,true,true(非持久,独占,自动删除)
String queue = c.queueDeclare().getQueue();
//定义交换机
c.exchangeDeclare("direct_logs", BuiltinExchangeType.DIRECT);
//绑定(指定关键词)
System.out.println("输入绑定键,用空格隔开:");
String s = new Scanner(System.in).nextLine();
String[] a = s.split("\\s+");//["aa","bb","cc"]//“\s”:正则表达式,表示空格;“+”:可以匹配1到多个空格;第一个“\”,表示转义字符。
for (String key:a){
c.queueBind(queue,"direct_logs",key);
}
//正常的从队列消费数据
DeliverCallback deliverCallback=new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
//取出消息数据,和消息上携带的路由键
String msg = new String(message.getBody());
String key = message.getEnvelope().getRoutingKey();
System.out.println("收到:"+key+" - "+msg);
}
};
CancelCallback cancelCallback=new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
c.basicConsume(queue,true,deliverCallback,cancelCallback);
}
}
2.4.5测试
生产者:
消费者1:
消费者2:
2.5主题模式
2.5.1主题交换机 Topic exchange
- 发送到Topic交换机的消息,它的的routingKey,必须是由点分隔的多个单词。单词可以是任何东西,但通常是与消息相关的一些特性。几个有效的routingKey示例:“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”。routingKey可以有任意多的单词,最多255个字节。
- bindingKey也必须采用相同的形式。Topic交换机的逻辑与直连交换机类似——使用特定routingKey发送的消息将被传递到所有使用匹配bindingKey绑定的队列。bindingKey有两个重要的特殊点:
*
可以通配单个单词。#
可以通配零个或多个单词。
- 举例:
- 将routingKey设置为"
quick.orange.rabbit
"的消息将被发送到两个队列。 - 消息 "
lazy.orange.elephant
“也发送到它们两个。 - ”
quick.orange.fox
“只会发到第一个队列,”lazy.brown.fox
“只发给第二个。 - ”
lazy.pink.rabbit
“将只被传递到第二个队列一次,即使它匹配两个绑定。 - ”
quick.brown.fox
"不匹配任何绑定,因此将被丢弃。 - “
lazy.orange.male.rabbit
”,即使它有四个单词,也将匹配最后一个绑定,并将被传递到第二个队列。
2.5.2生产者
package m5;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
//f.setPort(5672);//默认端口可以省略
f.setUsername("admin");
f.setPassword("admin");
Channel c = f.newConnection().createChannel();
//定义交换机
c.exchangeDeclare("topic_logs", BuiltinExchangeType.TOPIC);
//发送消息,需要携带路由键
while (true){
System.out.print("输入消息:");
String msg = new Scanner(System.in).nextLine();
System.out.print("输入路由键:");
String key = new Scanner(System.in).nextLine();
//第二个参数是消息上携带的路由键
c.basicPublish("topic_logs",key,null,msg.getBytes());
}
}
}
2.5.3消费者
package m5;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
//f.setPort(5672);//默认端口可以省略
f.setUsername("admin");
f.setPassword("admin");
Channel c = f.newConnection().createChannel();
//1.定义随机队列 2.定义交换机 3.绑定,指定绑定的关键词
//定义随机队列
//由 rabbitmq 服务器自动命名,默认参数:false,true,true(非持久,独占,自动删除)
String queue = c.queueDeclare().getQueue();
//定义交换机
c.exchangeDeclare("topic_logs", BuiltinExchangeType.TOPIC);
//绑定(指定关键词)
System.out.println("输入绑定键,用空格隔开:");
String s = new Scanner(System.in).nextLine();
String[] a = s.split("\\s+");//["aa","bb","cc"]
// “\s”:正则表达式,表示空格;“+”:可以匹配1到多个空格;第一个“\”,表示转义字符。
for (String key:a){
c.queueBind(queue,"topic_logs",key);
}
//正常的从队列消费数据
DeliverCallback deliverCallback=new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
//取出消息数据,和消息上携带的路由键
String msg = new String(message.getBody());
String key = message.getEnvelope().getRoutingKey();
System.out.println("收到:"+key+" - "+msg);
}
};
CancelCallback cancelCallback=new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
c.basicConsume(queue,true,deliverCallback,cancelCallback);
}
}
2.5.4测试
生产者:
消费者1:
消费者2:
2.6RPC异步调用模式
附:https://blog.csdn.net/weixin_38305440/article/details/102810522
3.拼多商城整合 RabbitMQ
- 当用户下订单时,我们的业务系统直接与数据库通信,把订单保存到数据库中
- 当系统流量突然激增,大量的订单压力,会拖慢业务系统和数据库系统
- 我们需要应对流量峰值,让流量曲线变得平缓,如下图
3.1订单存储的解耦
- 为了进行流量削峰,我们引入 rabbitmq 消息队列,当购物系统产生订单后,可以把订单数据发送到消息队列;
- 而订单消费者应用从消息队列接收订单消息,并把订单保存到数据库。
- 这样,当流量激增时,大量订单会暂存在rabbitmq中,而订单消费者可以从容地从消息队列慢慢接收订单,向数据库保存。
3.2生产者----发送订单
3.2.1pom.xml 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
3.2.2编辑application.yml
spring:
rabbitmq:
host: 192.168.64.140
username: admin
password: admin
3.2.3修改主程序 RunPdAPP
在主程序中添加下面的方法创建Queue实例:
- 当创建RabbitMQ连接和信道后,Spring的RabbitMQ工具会自动在服务器中创建队列,代码在
RabbitAdmin.declareQueues()
方法中
//封装队列消息的对象
//rabbitmq 的自动配置类会自动发现这个Queue实例,
//根据其中封装的队列消息,在rabbitmq服务器上创建这个队列
@Bean
public Queue orderQueue(){
return new Queue("oderQueue");
}
3.2.4修改 OrderServiceImpl
.......
//RabbitAutoConfiguration中创建了AmpqTemplate实例
@Autowired
private AmqpTemplate amqpTemplate;
public String saveOrder(PdOrder pdOrder) throws Exception {
String orderId = generateId(); //生成订单ID
pdOrder.setOrderId(orderId);
//转换成byte[]数组再发送
amqpTemplate.convertAndSend("orderQueue",pdOrder);
return orderId;
}
.......
3.2.5查看 RabbitMQ 队列
3.3消费者----接收订单,并保存到数据库
3.3.1修改 application.yml 配置
server:
port: 81
......................
3.3.2新建 OrderConsumer
package com.pd;
import com.pd.pojo.PdOrder;
import com.pd.service.OrderService;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
//从 rabbitmq 服务器的 orderQueue 队列,接收订单消息,调用业务代码保存订单
/*
*消费者代码由消息来触发执行
*/
@Component//自动扫描,自动创建实例
@RabbitListener(queues = "orderQueue")//注册成为消费者,从队列"orderQueue"接收消息
public class OrderConsumer {
@Autowired
private OrderService orderService;
//用来处理消息的方法
@RabbitHandler//配合@RabbitListener使用,传递给相应的方法处理,只能有一个该注解
public void receive(PdOrder pdOrder) throws Exception {
orderService.saveOrder(pdOrder);
System.out.println("-----------订单已存储!----------");
}
}
3.3.3修改 OrderServiceImpl 的 saveOrder() 方法
public String saveOrder(PdOrder pdOrder) throws Exception {
/*String orderId = generateId(); //生成订单ID
pdOrder.setOrderId(orderId);
//转换成byte[]数组再发送
amqpTemplate.convertAndSend("orderQueue",pdOrder);*/
String orderId = pdOrder.getOrderId();
PdShipping pdShipping = pdShippingMapper.selectByPrimaryKey(pdOrder.getAddId());
pdOrder.setShippingName(pdShipping.getReceiverName());
pdOrder.setShippingCode(pdShipping.getReceiverAddress());
pdOrder.setStatus(1);
pdOrder.setPaymentType(1);
pdOrder.setPostFee(10D);
pdOrder.setCreateTime(new Date());
double payment = 0;
List<ItemVO> itemVOs = selectCartItemByUseridAndItemIds(pdOrder.getUserId(), pdOrder.getItemIdList());
for (ItemVO itemVO : itemVOs) {
PdOrderItem pdOrderItem = new PdOrderItem();
String id = generateId();
//String id="2";
pdOrderItem.setId(id);
pdOrderItem.setOrderId(orderId);
pdOrderItem.setItemId("" + itemVO.getPdItem().getId());
pdOrderItem.setTitle(itemVO.getPdItem().getTitle());
pdOrderItem.setPrice(itemVO.getPdItem().getPrice());
pdOrderItem.setNum(itemVO.getPdCartItem().getNum());
payment = payment + itemVO.getPdCartItem().getNum() * itemVO.getPdItem().getPrice();
pdOrderItemMapper.insert(pdOrderItem);
}
pdOrder.setPayment(payment);
pdOrderMapper.insert(pdOrder);
return orderId;
}
3.3.4 启动测试