一、架构图
rabbit五种模式你了解吗?一学就会的代码不确定不来看看吗?
rabbitmq由以下组成部分构成:
- Producer:生产者
- Connection:连接器。内部有各个channel,比Connection更轻量
- Broker:mq的核心部分。内部的各个虚拟机彼此独立、隔离
- Virtual Host:虚拟机。内部有交换机和队列,交换机用于分发消息到不同队列中,一个交换机可以绑定到多个队列,不同的路由key决定了消息将被发送到哪个queue
- Consumer:消费者
分为五个模式(具体可见官网www.rabbitmq.com/getstarted.… )
1. hello world模式
最简单的模式,生产者直接将消息发到队列中,消费者直接从队列中拉取消息进行消费。
2. work queue模式
工作队列模式:生产者将消息发送到队列中,多个消费者去竞争进行消费。同一条消息只会被消费一次,主要应用于任务较多时提高消费效率。
3. 发布-订阅模式(pub-sub)
这个模型主要的变化在于多了一个交换机的组件和队列进行绑定(routing key为"",相当于和所有队列都绑定)。交换机有两个功能:
- 接收来自生产者的消息
- 对消息进行处理,如丢弃、交给某个队列、交给全部队列等。
交换机主要有三种类型:
- Fanout:广播
- Direct:直接定向传输到指定的routing key的队列
- Topic:通配符,把消息传输给符合routing pattern的队列
需要注意的是,Exchange本身只转发消息,并不储存消息。所以如果没有queue与exchange绑定,或者路由规则均不符合,消息将没法发送,导致消息会丢失。
4. 路由模式(routing)
- 此模式下,队列和交换机的绑定需要指定routing key,而不是全都进行绑定
- 在生产者发送消息时,也需要指定routing key
- 交换机的转发不再是广播,而是根据消息的routing key进行选择转发。只有消息的key和队列的key一致,channel中才会收到来自exchange的消息。
5. 主题模式(topic)
- 网上花里胡哨的说辞一堆,其实就是定向模式本身改为了通配符进行模糊匹配。
- 定向模式和主题模式的exchange在声明和channel的绑定关系时需要指定routing key,在投递消息给exchange时也需要指定routing key。当二者匹配成功,消息被投递到channel中,进而发到queue中。
二、安装rabbitmq
$ docker search rabbit
$ docker pull rabbitmq
$ docker run -e RABBITMQ_DEFAULT_USER=guest -e RABBITMQ_DEFAULT_PASS=guest --name mq --hostname localhost -p 15672:15672 -p 5672:5672 -d rabbitmq
安装好rabbitmq并启动后,可以通过访问localhost:15672进入mq的web界面。用户名、密码均为guest。
注意:15672是访问web界面的端口,5672是程序连接mq的端口,千万别弄混。
初次进入时无法访问,原因是rabbitmq默认web界面管理插件是关闭的,通过命令开启即可
解决方法:进入容器中打开rabbitmq_management,重新刷新浏览器
$ docker exec -it 65d4c09220f5 /bin/bash
root@localhost:/# rabbitmq-plugins enable rabbitmq_management
有可能,进去web界面后看不到消息条数,或者点击交换机想查看详细信息,报错:Stats in management UI are disabled on this node
解决方法:进入docker容器中修改mq的conf文件,然后重启docker容器,刷新页面后即可恢复
---以下内容在外部机器中----
$ docker exec -it 65d4c09220f5a615a3add30944db27b5ef50efb857003eab59cf0d1dd6e14651 /bin/bash
---以下内容在容器中敲----
root@localhost:/# cd /etc/rabbitmq/conf.d/
root@localhost:/etc/rabbitmq/conf.d# vim management_agent.disable_metrics_collector.conf
----以下内容在vim中---
management_agent.disable_metrics_collector = false(然后保存,并退出到容器外)
---以下内容在外部机器中----
$ docker restart 65d4c09220f5
三、各个模式的代码实现
各个模式的实现思路已经按顺序写在注释中了,可见注释中的序号。
1. hello world模式
1)生产者
package com.msq.producer;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
/**
* 用于发送消息
* Author: masiqi
* Created on: 2022/10/02
*/
public class Producer_HelloWorld {
public static void main(String[] args) throws IOException, TimeoutException {
//1. 创建连接工厂(用于新建连接)
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("127.0.0.1");
factory.setPort(5672); // 默认值:5672
factory.setVirtualHost("/msq"); // 虚拟机 默认值:/
factory.setUsername("guest"); // 默认值:guest
factory.setPassword("guest"); // 默认值:guest
//3. 创建Connection
Connection connection = factory.newConnection();
//4. 创建Channel(一个Connection中有多个channel)
Channel channel = connection.createChannel();
//5. 创建队列Queue(hello_world模式下没有exchange,所以直接创建queue即可 )
/**
* 各个参数含义:
* - 队列名
* - 是否持久化
* - 是否独占:只能有一个消费者监听队列;当Connection关闭时,是否删除队列
* - 是否自动删除:当没有Consumer时,自动删除
* - 其余参数
* !!! 如果没有一个名叫hello_world的队列,则会创建队列;否则就不会创建,直接使用
*/
channel.queueDeclare("hello_world", true, false, false, null);
String message = "hello rabbitmq~~~";
//6. 发送消息
/**
* - 交换机名称
* - 路由键名称
* - 配置信息
* - 消息数据(字节数据)
*/
channel.basicPublish("", "hello_world", null, message.getBytes(StandardCharsets.UTF_8));
//7. 释放资源
channel.close();
connection.close();
}
}
发送报错:原因是不存在对应的虚拟机。在管理界面中添加虚拟机后报错就会消失
解决方法:www.cnblogs.com/max1995/p/1…
2)消费者
package com.msq.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* Author: masiqi
* Created on: 2022/10/03
*/
public class Consumer_HelloWorld {
public static void main(String[] args) throws IOException, TimeoutException {
//1. 创建连接工厂(用于新建连接)
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("127.0.0.1");
factory.setPort(5672); // 默认值:5672
factory.setVirtualHost("/msq"); // 虚拟机 默认值:/
factory.setUsername("guest"); // 默认值:guest
factory.setPassword("guest"); // 默认值:guest
//3. 创建Connection
Connection connection = factory.newConnection();
//4. 创建Channel(一个Connection中有多个channel)
Channel channel = connection.createChannel();
//5. 创建队列Queue(hello_world模式下没有exchange,所以直接创建queue即可 )
/**
* 各个参数含义:
* - 队列名
* - 是否持久化
* - 是否独占:只能有一个消费者监听队列;当Connection关闭时,是否删除队列
* - 是否自动删除:当没有Consumer时,自动删除
* - 其余参数
* !!! 如果没有一个名叫hello_world的队列,则会创建队列;否则就不会创建,直接使用
*/
channel.queueDeclare("hello_world", true, false, false, null);
//6. 发送消息
Consumer consumer = new DefaultConsumer(channel){
/**
* 回调方法,收到消息后会自动调用
* @param consumerTag:消息标识
* @param envelope:获取信息,交换机、路由key等
* @param properties:配置信息
* @param body:消息数据
*/
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body){
System.out.println("consumerTag:" + consumerTag);
System.out.println("exchange:" + envelope.getExchange());
System.out.println("RoutingKey:" + envelope.getRoutingKey());
System.out.println("收到的消息:" + new String(body));
}
};
/**
* 队列名
* 是否自动确认
* 回调函数
*/
channel.basicConsume("hello_world", true, consumer);
// 7.不关闭资源
}
}
2. work-queue模式
1)生产者
package 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.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
/**
* Author: masiqi
* Created on: 2022/10/03
*/
public class Producer_PubSub {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建工厂
ConnectionFactory factory = new ConnectionFactory();
// 2.设置参数
factory.setHost("localhost");
factory.setVirtualHost("/msq");
// 3.创建connection
Connection connection = factory.newConnection();
// 4.创建channel
Channel channel = connection.createChannel();
/**
* 1.exchange:交换机名称
* 2.type:交换机类型
* - Direct:定向
* - Fanout:广播
* - Topic:通配符
* - Headers:参数匹配(用得少)
* 3.durable:是否持久化
* 4.autoDelete:是否自动删除
* 5.internal:是否内部使用,一般赋值false
* 6.arguments:参数
*/
// 5.创建exchange
String exchangeName = "test_fanout";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT, true, false, false, null);
// 6.创建queue
channel.queueDeclare("fanout_queue1", true, false, false, null);
channel.queueDeclare("fanout_queue2", true, false, false, null);
// 7.绑定queue和exchange
/**
* 1.queue:队列名称
* 2.exchange:交换机名称
* 3.routingkey:路由键,绑定规则
* - exchange的type为FANOUT,则key为""
* -
*/
channel.queueBind("fanout_queue1", exchangeName, "");
channel.queueBind("fanout_queue2", exchangeName, "");
String message = "2022-10-03 msq在学习rabbitmq";
channel.basicPublish(exchangeName, "", null, message.getBytes(StandardCharsets.UTF_8));
channel.close();
connection.close();
}
}
2)消费者
消费者1:使用fanout_queue1队列
package com.msq.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* Author: masiqi
* Created on: 2022/10/03
*/
public class Consumer_WorkQueue1 {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setVirtualHost("/msq");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("work_queue", true, false, false, null);
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body){
System.out.println("收到的消息:" + new String(body));
}
};
channel.basicConsume("work_queue", true, consumer);
}
}
消费者2
package com.msq.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* Author: masiqi
* Created on: 2022/10/03
*/
public class Consumer_WorkQueue2 {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setVirtualHost("/msq");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("work_queue", true, false, false, null);
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body){
System.out.println("收到的消息:" + new String(body));
}
};
channel.basicConsume("work_queue", true, consumer);
}
}
3. pub-sub模式
1)生产者
package 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.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
/**
* Author: masiqi
* Created on: 2022/10/03
*/
public class Producer_PubSub {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建工厂
ConnectionFactory factory = new ConnectionFactory();
// 2.设置参数
factory.setHost("localhost");
factory.setVirtualHost("/msq");
// 3.创建connection
Connection connection = factory.newConnection();
// 4.创建channel
Channel channel = connection.createChannel();
/**
* 1.exchange:交换机名称
* 2.type:交换机类型
* - Direct:定向
* - Fanout:广播
* - Topic:通配符
* - Headers:参数匹配(用得少)
* 3.durable:是否持久化
* 4.autoDelete:是否自动删除
* 5.internal:是否内部使用,一般赋值false
* 6.arguments:参数
*/
// 5.创建exchange
String exchangeName = "test_fanout";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT, true, false, false, null);
// 6.创建queue
channel.queueDeclare("fanout_queue1", true, false, false, null);
channel.queueDeclare("fanout_queue2", true, false, false, null);
// 7.绑定queue和exchange
/**
* 1.queue:队列名称
* 2.exchange:交换机名称
* 3.routingkey:路由键,绑定规则
* - exchange的type为FANOUT,则key为""
* -
*/
channel.queueBind("fanout_queue1", exchangeName, "");
channel.queueBind("fanout_queue2", exchangeName, "");
String message = "2022-10-03 msq在学习rabbitmq";
channel.basicPublish(exchangeName, "", null, message.getBytes(StandardCharsets.UTF_8));
channel.close();
connection.close();
}
}
2)消费者
消费者1:
package com.msq.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* Author: masiqi
* Created on: 2022/10/04
*/
public class Consumer_PubSub1 {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setVirtualHost("/msq");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// channel.queueDeclare("fanout_queue1", true, false, false, null); 无需重复声明
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body){
System.out.println("收到的消息:" + new String(body));
System.out.println("消费者1将保存此信息到数据库...");
}
};
channel.basicConsume("fanout_queue1", true, consumer);
}
}
消费者2:
package com.msq.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* Author: masiqi
* Created on: 2022/10/04
*/
public class Consumer_PubSub2 {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setVirtualHost("/msq");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// channel.queueDeclare("fanout_queue1", true, false, false, null); 无需重复声明
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body){
System.out.println("收到的消息:" + new String(body));
System.out.println("消费者2将打印此信息到控制台...");
}
};
channel.basicConsume("fanout_queue2", true, consumer);
}
}
4. routing模式
1)生产者
package 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.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
/**
* Author: masiqi
* Created on: 2022/10/04
*/
public class Producer_Routing {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建工厂
ConnectionFactory factory = new ConnectionFactory();
// 2.设置参数
factory.setHost("localhost");
factory.setVirtualHost("/msq");
// 3.创建connection
Connection connection = factory.newConnection();
// 4.创建channel
Channel channel = connection.createChannel();
/**
* 1.exchange:交换机名称
* 2.type:交换机类型
* - Direct:定向
* - Fanout:广播
* - Topic:通配符
* - Headers:参数匹配(用得少)
* 3.durable:是否持久化
* 4.autoDelete:是否自动删除
* 5.internal:是否内部使用,一般赋值false
* 6.arguments:参数
*/
// 5.创建exchange
String exchangeName = "test_direct";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT, true, false, false, null);
// 6.创建queue
channel.queueDeclare("direct_queue1", true, false, false, null);
channel.queueDeclare("direct_queue2", true, false, false, null);
// 7.绑定queue和exchange
/**
* 1.queue:队列名称
* 2.exchange:交换机名称
* 3.routingkey:路由键,绑定规则
* - exchange的type为FANOUT,则key为""
* - exchange的type为DIRECT,key则根据需要进行指定
*/
channel.queueBind("direct_queue1", exchangeName, "error"); //error日志额外发往queue1
channel.queueBind("direct_queue2", exchangeName, "info"); //info、warn、error日志发往queue2
channel.queueBind("direct_queue2", exchangeName, "warn");
channel.queueBind("direct_queue2", exchangeName, "error ");
String message = "2022-10-04 msq在学习rabbitmq,发了一条warn消息";
channel.basicPublish(exchangeName, "warn", null, message.getBytes(StandardCharsets.UTF_8));
message = "2022-10-04 msq在学习rabbitmq,发了一条info消息";
channel.basicPublish(exchangeName, "info", null, message.getBytes(StandardCharsets.UTF_8));
channel. close();
connection.close();
}
}
2)消费者
消费者1:
package com.msq.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* Author: masiqi
* Created on: 2022/10/04
*/
public class Consumer_Direct1 {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setVirtualHost("/msq");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// channel.queueDeclare("fanout_queue1", true, false, false, null); 无需重复声明
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body){
System.out.println("收到的消息:" + new String(body));
System.out.println("消费者1将保存error信息到数据库...");
}
};
channel.basicConsume("direct_queue1", true, consumer);
}
}
消费者2
package com.msq.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* Author: masiqi
* Created on: 2022/10/04
*/
public class Consumer_Direct2 {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setVirtualHost("/msq");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// channel.queueDeclare("fanout_queue1", true, false, false, null); 无需重复声明
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body){
System.out.println("收到的消息:" + new String(body));
System.out.println("消费者2将打印日志信息到数据库...");
}
};
channel.basicConsume("direct_queue2", true, consumer);
}
}
3)运行结果
5. topic模式
1)生产者
package 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.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
/**
* Author: masiqi
* Created on: 2022/10/04
*/
public class Producer_Topic {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建工厂
ConnectionFactory factory = new ConnectionFactory();
// 2.设置参数
factory.setHost("localhost");
factory.setVirtualHost("/msq");
// 3.创建connection
Connection connection = factory.newConnection();
// 4.创建channel
Channel channel = connection.createChannel();
/**
* 1.exchange:交换机名称
* 2.type:交换机类型
* - Direct:定向
* - Fanout:广播
* - Topic:通配符
* - Headers:参数匹配(用得少)
* 3.durable:是否持久化
* 4.autoDelete:是否自动删除
* 5.internal:是否内部使用,一般赋值false
* 6.arguments:参数
*/
// 5.创建exchange
String exchangeName = "test_topic";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC, true, false, false, null);
// 6.创建queue
channel.queueDeclare("topic_queue1", true, false, false, null);
channel.queueDeclare("topic_queue2", true, false, false, null);
// 7.绑定queue和exchange
/**
* 1.queue:队列名称
* 2.exchange:交换机名称
* 3.routingkey:路由键,绑定规则
* - exchange的type为FANOUT,则key为""
* - exchange的type为DIRECT,key则根据需要进行指定
* - exchange的type为TOPIC,key为正则表达式
*/
// 场景:1、所有error级别的日志存入数据库;2、sale模块的所有级别日志存入数据库; ===》队列1
// 3、所有日志打印到控制台 ==》队列2
channel.queueBind("topic_queue1", exchangeName, "#.error");
channel.queueBind("topic_queue1", exchangeName, "sale.*");
channel.queueBind("topic_queue2", exchangeName, "*.*");
String message = "2022-10-05 sale.info:销售了10件商品";
channel.basicPublish(exchangeName, "sale.info", null, message.getBytes(StandardCharsets.UTF_8));
message = "2022-10-05 sale.error:销售出错";
channel.basicPublish(exchangeName, "sale.error ", null, message.getBytes(StandardCharsets.UTF_8));
message = "2022-10-05 sale.warn:销售有风险";
channel.basicPublish(exchangeName, "sale.warn ", null, message.getBytes(StandardCharsets.UTF_8));
channel. close();
connection.close();
}
}
2)消费者
消费者1:
package com.msq.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* Author: masiqi
* Created on: 2022/10/04
*/
public class Consumer_Topic1 {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setVirtualHost("/msq");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// channel.queueDeclare("fanout_queue1", true, false, false, null); 无需重复声明
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body){
String msg = new String(body);
System.out.println("收到的消息:" + msg);
if(msg.contains("error")){
System.out.println("消费者1将错误日志信息保存到数据库...");
}else if(msg.contains("sale")){
System.out.println("消费者1将sale日志信息保存到数据库...");
}
}
};
channel.basicConsume("topic_queue1", true, consumer);
}
}
消费者2:
package com.msq.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* Author: masiqi
* Created on: 2022/10/04
*/
public class Consumer_Topic2 {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setVirtualHost("/msq");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// channel.queueDeclare("fanout_queue1", true, false, false, null); 无需重复声明
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body){
String msg = new String(body);
System.out.println("收到的消息:" + msg);
System.out.println("消费者2将打印所有日志到控制台...");
}
};
channel.basicConsume("topic_queue2", true, consumer);
}
}