目录
RabbitMQ工作模式
1. 简单模式(Simple)
P:生产者
C:消费者
Queue:消息队列
特点:一个生产者,一个消费者,消息只能被消费一次,也称为点对点模式
适用场景:消息只能被单个消费者处理
2. 工作队列模式(WorkQueue)
一个生产者P,多个消费者C1、C2,在多个消息的情况下,队列会将消息分派给不同的消费者,每个消费者都会收到不同的消息
特点:消息不会重复(C1,C2共同消费队列中的消息),分配给不同的消费者
适用场景:集群环境中做异步处理
3. 发布/订阅模式(Publish/Subscribe)
一个生产者,多个消费者,X表示交换机,交换机会复制多份消息到不同的队列,每个消费者都接收相同的消息
适用场景:消息需要被多个消费者同时接收的场景
关于交换机
交换机转发消息有一套转发的规则,不同的规则对应不同的交换机类型
RabbitMQ实现了4种交换机类型(AMOP协议定义的)
1、Direct(直接交换机):生产者发送消息时,会指定一个“目标队列”的名字,交换机收到消息后会查看绑定的队列中是否有名字匹配的队列,如果有则将消息塞给对应的队列,如果没有则丢弃消息。
2、Fanout(扇出交换机):
3、Topic(主题交换机):
2个概念
1)bindingKey:队列和交换机绑定的时候指定一个单词(暗号)
2)routingKey:生产者发送的消息的时候也指定一个单词
如果当前routingKey和bindingKey能够对上暗号,则发送消息
匹配规则:
4、Header:消息头交换机(不常见)
4. 路由模式
路由模式是发布订阅模式的变种,在发布订阅的基础上增加路由Key。发布订阅模式是无条件把所有的消息分发给所有的消费者,路由模式是Exchange根据RoutingKey的规则将数据筛选后发送给对应的消费者队列
适用场景:需要根据特定的规则分发消息的场景
5. 通配符模式
路由模式的升级版,增加了通配符功能,交换机根据RoutingKey将消息转发给与RoutingKey匹配的队列
6. RPC模式
1、客户端发送消息到一个指定的队列,并在消息属性中设置replyTo字段,这个字段指定了一个回调队列,用于接收服务端的响应
2、服务端接收到请求后,处理请求并发送响应消息到replyTo指定的队列
7. 发布确认模式
发布确认模式是RabbitMQ提供的一种确保消息可靠发送到RabbitMQ服务器的机制。这种模式下,生产者可以等待RabbitMQ服务器的确认,确保消息已经被服务器接收并处理。(保证消息到达服务器,不是保证消息到达消费者)
1、生产者将Channel设置为confirm模式(调用channel.confirmSelect()完成)后,发布的每一条消息都会获得员工唯一的ID,生产者可以将这些序号于消息关联,以便跟踪消息的状态
2、当消息被RabbitMQ服务器接收并处理后,服务器会异步的向生产者发送一个ACK给生产者,表示消息已经送达
RabbitMQ工作模式使用案例
1. 简单模式
一个生产者,一个消费者
生产者:
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//1. 建立连接(Connection)
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接参数:ip,端口号,用户名,密码,虚拟机
connectionFactory.setHost("");//ip
connectionFactory.setPort(5672);//端口号,默认是5672
connectionFactory.setUsername("Test");//用户名
connectionFactory.setPassword("123");//密码
connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接
//2. 开启通道(Channel)
Channel channel = connection.createChannel();//通过connection开启通道
//3.声明队列
channel.queueDeclare("testQueue", true, false, false, null);
String msg = "HelloRabbitMQ";//消息的内容
//4.发送消息
channel.basicPublish("", "testQueue", null, msg.getBytes());
//5.释放资源
channel.close();
connection.close();
}
消费者:
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//1. 建立连接(Connection)
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接参数:ip,端口号,用户名,密码,虚拟机
connectionFactory.setHost("");//ip
connectionFactory.setPort(5672);//端口号,默认是5672
connectionFactory.setUsername("Test");//用户名
connectionFactory.setPassword("123");//密码
connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接
//2. 开启通道(Channel)
Channel channel = connection.createChannel();//通过connection开启通道
//3. 声明队列(如果生产者声明过了,可以省略)
channel.queueDeclare("testQueue", true, false, false, null);
//4.消费消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接收到消息" + new String(body));
}
};
channel.basicConsume("testQueue", true, consumer);
//5.释放资源
channel.close();
connection.close();
}
}
2. 工作队列模式
生产者代码:
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//1. 建立连接(Connection)
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接参数:ip,端口号,用户名,密码,虚拟机
connectionFactory.setHost("");//ip
connectionFactory.setPort(5672);//端口号,默认是5672
connectionFactory.setUsername("Test");//用户名
connectionFactory.setPassword("123");//密码
connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接
//2. 开启通道(Channel)
Channel channel = connection.createChannel();//通过connection开启通道
//3.声明队列
channel.queueDeclare("work.queue", true, false, false, null);
//发送10条消息
for (int i = 0; i < 10; i++) {
String msg = "HelloWorkQueue" + i;//消息的内容
channel.basicPublish("", "work.queue", null, msg.getBytes());
}
System.out.println("消息发送成功");
//6.释放资源
channel.close();
connection.close();
}
}
编写两个消费者代码
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1. 建立连接(Connection)
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接参数:ip,端口号,用户名,密码,虚拟机
connectionFactory.setHost("");//ip
connectionFactory.setPort(5672);//端口号,默认是5672
connectionFactory.setUsername("Test");//用户名
connectionFactory.setPassword("123");//密码
connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接
//2. 开启通道(Channel)
Channel channel = connection.createChannel();//通过connection开启通道
//4.声明队列
channel.queueDeclare("work.queue", true, false, false, null);
//5. 消费消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接收到消息" + new String(body));
}
};
channel.basicConsume("work.queue", true, consumer);
}
}
public class Consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
//1. 建立连接(Connection)
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接参数:ip,端口号,用户名,密码,虚拟机
connectionFactory.setHost("");//ip
connectionFactory.setPort(5672);//端口号,默认是5672
connectionFactory.setUsername("Test");//用户名
connectionFactory.setPassword("123");//密码
connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接
//2. 开启通道(Channel)
Channel channel = connection.createChannel();//通过connection开启通道
//4.声明队列
channel.queueDeclare("work.queue", true, false, false, null);
//5. 消费消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接收到消息" + new String(body));
}
};
channel.basicConsume("work.queue", true, consumer);
}
}
首先运行两个消费者,观察RabbitMQ管理界面
接着运行生产者,观察两个消费者程序的日志
3. 发布订阅模式
参数
参数:
1. exchange:交换机名称
2. type:交换机类型
DIRECT("direct"), 定向,直连,routing
FANOUT("fanout"),扇形(⼴播), 每个队列都能收到消息
TOPIC("topic"),通配符
HEADERS("headers") 参数匹配(⼯作⽤的较少)
3. durable: 是否持久化.
true-持久化,false⾮持久化.
持久化可以将交换器存盘,在服务器重启的时候不会丢失相关信息
4. autoDelete: ⾃动删除.
⾃动删除的前提是⾄少有⼀个队列或者交换器与这个交换器绑定, 之后所有与这个交换器绑定的
队列或者交换器都与此解绑.
⽽不是这种理解: 当与此交换器连接的客⼾端都断开时,RabbitMQ会⾃动删除本交换器.
5. internal: 内部使⽤, ⼀般falase.
如果设置为true, 表⽰内部使⽤.
客⼾端程序⽆法直接发送消息到这个交换器中,只能通过交换器路由到交换器这种⽅式
6. arguments: 参数
编写生产者代码
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//1. 建立连接(Connection)
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接参数:ip,端口号,用户名,密码,虚拟机
connectionFactory.setHost("");//ip
connectionFactory.setPort(5672);//端口号,默认是5672
connectionFactory.setUsername("Test");//用户名
connectionFactory.setPassword("123");//密码
connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接
//2. 开启通道(Channel)
Channel channel = connection.createChannel();//通过connection开启通道
//3. 声明交换机
/**
* 参数:
* 1. exchange:交换机名称
* 2. type:交换机类型
* DIRECT("direct"), 定向,直连,routing
* FANOUT("fanout"),扇形(⼴播), 每个队列都能收到消息
* TOPIC("topic"),通配符
* HEADERS("headers") 参数匹配(⼯作⽤的较少)
* 3. durable: 是否持久化,true-持久化,false⾮持久化.持久化可以将交换器存盘,在服务器重启的时候不会丢失相关信息
* 4. autoDelete: ⾃动删除.
* ⾃动删除的前提是⾄少有⼀个队列或者交换器与这个交换器绑定, 之后所有与这个交换器绑定的
* 队列或者交换器都与此解绑.
* ⽽不是这种理解: 当与此交换器连接的客⼾端都断开时,RabbitMQ会⾃动删除本交换器.
* 5. internal: 内部使⽤, ⼀般falase.
* 如果设置为true, 表⽰内部使⽤.
* 客⼾端程序⽆法直接发送消息到这个交换器中,只能通过交换器路由到交换器这种⽅式
* 6. arguments: 参数
*/
channel.exchangeDeclare("FANOUT_EXCHANGE", BuiltinExchangeType.FANOUT, false);
//4.声明队列
/**
* queueDeclare方法的参数
* queue:队列名
* durable:可持久化
* exclusive:是否独占(是否只能由一个消费者进行消费)
* autoDelete:当没有人消费时,是否自动删除
* arguments:参数
*/
channel.queueDeclare("Queue1", true, false, false, null);
channel.queueDeclare("Queue2", true, false, false, null);
//5.交换机和队列进行绑定
/**
* 参数1:queue,队列名
* 参数2:exchange,交换机名
* 参数3:routingkey:路由key
* 如果交换机类型为fanout,routingkey设置为""表示每个消费者都可以接受到全部信息
*/
channel.queueBind("Queue1", "FANOUT_EXCHANGE", "");
channel.queueBind("Queue2", "FANOUT_EXCHANGE", "");
String msg = "HelloFANOUT";//消息的内容
//发送消息
/**
* basicPublish方法的参数
* exchange:交换机名称
* routingKey:routingKey和队列名保持一致
* props:属性配置
* body:消息的内容
*/
channel.basicPublish("FANOUT_EXCHANGE", "", null, msg.getBytes());
System.out.println("消息发送成功");
//6.释放资源
channel.close();
connection.close();
}
}
运行,观察控制台
编写消费者代码
两个消费者代码一致,只需要把队列名改一下即可
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1. 建立连接(Connection)
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接参数:ip,端口号,用户名,密码,虚拟机
connectionFactory.setHost("");//ip
connectionFactory.setPort(5672);//端口号,默认是5672
connectionFactory.setUsername("Test");//用户名
connectionFactory.setPassword("123");//密码
connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接
//2. 开启通道(Channel)
Channel channel = connection.createChannel();//通过connection开启通道
//3.声明队列,如果已经声明过则不创建
channel.queueDeclare("Queue1", true, false, false, null);
//4.消费消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接收到消息" + new String(body));
}
};
/**
* 方法参数
* queue:队列名
* autoAck:是否自动确认
* Consumer:接收到消息后执行的逻辑
* **/
channel.basicConsume("Queue1", true, consumer);
}
}
运行两个消费者程序
4. 路由模式
用代码实现如图的结构
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//1. 建立连接(Connection)
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接参数:ip,端口号,用户名,密码,虚拟机
connectionFactory.setHost("");//ip
connectionFactory.setPort(5672);//端口号,默认是5672
connectionFactory.setUsername("Test");//用户名
connectionFactory.setPassword("123");//密码
connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接
//2. 开启通道(Channel)
Channel channel = connection.createChannel();//通过connection开启通道
//3. 声明交换机
channel.exchangeDeclare("DIRECT_EXCHANGE", BuiltinExchangeType.DIRECT, true);
//4.声明队列
channel.queueDeclare("DIRQueue1", true, false, false, null);
channel.queueDeclare("DIRQueue2", true, false, false, null);
//5.交换机和队列进行绑定
channel.queueBind("DIRQueue1", "DIRECT_EXCHANGE", "a");
channel.queueBind("DIRQueue2", "DIRECT_EXCHANGE", "a");
channel.queueBind("DIRQueue2", "DIRECT_EXCHANGE", "b");
channel.queueBind("DIRQueue2", "DIRECT_EXCHANGE", "c");
String msgA = "HelloDirA";//消息的内容
String msgB = "HelloDirB";//消息的内容
String msgC = "HelloDirC";//消息的内容
//发送消息
channel.basicPublish("DIRECT_EXCHANGE", "a", null, msgA.getBytes());
channel.basicPublish("DIRECT_EXCHANGE", "b", null, msgB.getBytes());
channel.basicPublish("DIRECT_EXCHANGE", "c", null, msgC.getBytes());
System.out.println("消息发送成功");
//6.释放资源
channel.close();
connection.close();
}
}
运行代码,观察控制台
可以看到,队列1收到了一条消息,队列2收到了3条消息,接着编写消费者代码,两个消费者代码类似,只需要把队列名改改
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1. 建立连接(Connection)
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接参数:ip,端口号,用户名,密码,虚拟机
connectionFactory.setHost("");//ip
connectionFactory.setPort(5672);//端口号,默认是5672
connectionFactory.setUsername("Test");//用户名
connectionFactory.setPassword("123");//密码
connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接
//2. 开启通道(Channel)
Channel channel = connection.createChannel();//通过connection开启通道
//3.声明队列,如果已经声明过则不创建
channel.queueDeclare("DIRQueue1", true, false, false, null);
//4.消费消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接收到消息" + new String(body));
}
};
/**
* 方法参数
* queue:队列名
* autoAck:是否自动确认
* Consumer:接收到消息后执行的逻辑
* **/
channel.basicConsume("DIRQueue1", true, consumer);
}
}
运行两个消费者代码
5. 通配符模式
用代码实现如图结构
编写生产者代码:
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//1. 建立连接(Connection)
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接参数:ip,端口号,用户名,密码,虚拟机
connectionFactory.setHost("");//ip
connectionFactory.setPort(5672);//端口号,默认是5672
connectionFactory.setUsername("Test");//用户名
connectionFactory.setPassword("123");//密码
connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接
//2. 开启通道(Channel)
Channel channel = connection.createChannel();//通过connection开启通道
//3. 声明交换机
channel.exchangeDeclare("TOPIC_EXCHANGE", BuiltinExchangeType.TOPIC, true);
//4.声明队列
channel.queueDeclare("TOPICQueue1", true, false, false, null);
channel.queueDeclare("TOPICQueue2", true, false, false, null);
//5.交换机和队列进行绑定
channel.queueBind("TOPICQueue1", "TOPIC_EXCHANGE", "*.a.*");
channel.queueBind("TOPICQueue2", "TOPIC_EXCHANGE", "*.*.b");
channel.queueBind("TOPICQueue2", "TOPIC_EXCHANGE", "c.#");
String msgA = "HelloTopic_ae.a.f";//消息的内容
String msgB = "HelloTopic_ef.a.b";//消息的内容
String msgC = "HelloTopic_c.ef.d";//消息的内容
//发送消息
channel.basicPublish("TOPIC_EXCHANGE", "ae.a.f", null, msgA.getBytes());//转发到Queue1
channel.basicPublish("TOPIC_EXCHANGE", "ef.a.b", null, msgB.getBytes());//转发到Queue1和Queue2
channel.basicPublish("TOPIC_EXCHANGE", "c.ef.d", null, msgC.getBytes());//转发到Queue2
System.out.println("消息发送成功");
//6.释放资源
channel.close();
connection.close();
}
}
观察控制台
两个队列都有两条消息
接下来编写消费者代码,两个消费者代码一致,只需要修改队列名
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//1. 建立连接(Connection)
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接参数:ip,端口号,用户名,密码,虚拟机
connectionFactory.setHost("");//ip
connectionFactory.setPort(5672);//端口号,默认是5672
connectionFactory.setUsername("Test");//用户名
connectionFactory.setPassword("123");//密码
connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接
//2. 开启通道(Channel)
Channel channel = connection.createChannel();//通过connection开启通道
//3.声明队列,如果已经声明过则不创建
channel.queueDeclare("TOPICQueue2", true, false, false, null);
//4.消费消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接收到消息" + new String(body));
}
};
/**
* 方法参数
* queue:队列名
* autoAck:是否自动确认
* Consumer:接收到消息后执行的逻辑
* **/
channel.basicConsume("TOPICQueue2", true, consumer);
}
}
6. RPC模式
客户端代码
public class Client {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//1. 建立连接(Connection)
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接参数:ip,端口号,用户名,密码,虚拟机
connectionFactory.setHost("");//ip
connectionFactory.setPort(5672);//端口号,默认是5672
connectionFactory.setUsername("Test");//用户名
connectionFactory.setPassword("123");//密码
connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接
//2. 开启通道(Channel)
Channel channel = connection.createChannel();//通过connection开启通道
String msg = "HelloRPC";
String correlationId = UUID.randomUUID().toString();
//
channel.queueDeclare("RPC_RESPONSE_QUEUE",true,false,false,null);
channel.queueDeclare("RPC_REQUEST_QUEUE",true,false,false,null);
//发送请求
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
.correlationId(correlationId)//设置correlationId
.replyTo("RPC_RESPONSE_QUEUE")//设置响应的队列
.build();
channel.basicPublish("", "RPC_REQUEST_QUEUE", properties, msg.getBytes());
//使用阻塞队列,来存储响应信息
final BlockingQueue<String> response = new LinkedBlockingQueue<>(1);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String responseMsg = new String(body);
System.out.println("接收到回调消息" + responseMsg);
if (correlationId.equals(properties.getCorrelationId())) {
//如果correlationId相等,把消息放进阻塞队列中
response.offer(responseMsg);
}
}
};
channel.basicConsume("RPC_RESPONSE_QUEUE", true, consumer);
//从阻塞队列中
System.out.println("响应结果"+response.take());
//6.释放资源
channel.close();
connection.close();
}
}
服务端代码:
public class Server {
public static void main(String[] args) throws IOException, TimeoutException {
//1. 建立连接(Connection)
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接参数:ip,端口号,用户名,密码,虚拟机
connectionFactory.setHost("");//ip
connectionFactory.setPort(5672);//端口号,默认是5672
connectionFactory.setUsername("Test");//用户名
connectionFactory.setPassword("123");//密码
connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接
//2. 开启通道(Channel)
Channel channel = connection.createChannel();//通过connection开启通道
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String request = new String(body);
String response = "响应成功!!!!!!!!!" + request;
System.out.println(response);
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties()
.builder()
.correlationId(properties.getCorrelationId())
.build();
channel.basicPublish("", "RPC_RESPONSE_QUEUE",basicProperties, response.getBytes());
channel.basicAck(envelope.getDeliveryTag(),false);//手动确认
}
};
//3.接受请求
channel.basicConsume("RPC_REQUEST_QUEUE", false, consumer);
//6.释放资源
channel.close();
connection.close();
}
}
先运行客户端代码,观察控制台
接着运行服务端代码
7. 发布确认
有三种策略
1、单独确认:每发送一条消息,就等待确认,确认完毕再发送下一条消息
2、批量确认:发送消息的数量达到一定数量的时候再确认
3、异步确认:
代码案例:
public class PublisherConfirms {
private static final Integer MESSAGE_COUNT = 10000;
public static Connection createConnection() throws IOException, TimeoutException {
//1. 建立连接(Connection)
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接参数:ip,端口号,用户名,密码,虚拟机
connectionFactory.setHost("116.205.165.92");//ip
connectionFactory.setPort(5672);//端口号,默认是5672
connectionFactory.setUsername("Test");//用户名
connectionFactory.setPassword("123");//密码
connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
//从连接工厂中获取到连接
return connectionFactory.newConnection();
}
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//1.单独确认
// publishMessagesIndividually();
//2.批量确认
publishMessagesInBatches();
//3.异步确认
handlingPublisherConfirmsAsynchronously();
}
//1.单独确认,每发送一条消息,就等待确认,确认完毕再发送下一条消息
private static void publishMessagesIndividually() throws IOException, TimeoutException, InterruptedException {
try (Connection connection = createConnection()) {
//1.开启信道
Channel channel = connection.createChannel();
//2.设置信道为confirm模式,开启之后,基于该信道发送的消息都会生成一个id,用于消息的确认
channel.confirmSelect();
//3.声明队列
channel.queueDeclare("PUBLISHER_CONFIRM_QUEUE1", true, false, false, null);
//4.发送消息并等待确认
long start = System.currentTimeMillis();
for (int i = 0; i < MESSAGE_COUNT; i++) {
String msg = "HelloMessage" + i;
channel.basicPublish("", "PUBLISHER_CONFIRM_QUEUE1", null, msg.getBytes());
//等待确认
channel.waitForConfirmsOrDie(5000);//如果超时了会抛出异常
}
long end = System.currentTimeMillis();
System.out.println("批量确认策略,消息条数" + MESSAGE_COUNT + "耗时" + (end - start) + "ms");
}
}
//2.批量确认
private static void publishMessagesInBatches() throws IOException, TimeoutException, InterruptedException {
try (Connection connection = createConnection()) {
//1.开启信道
Channel channel = connection.createChannel();
//2.设置信道为confirm模式,开启之后,基于该信道发送的消息都会生成一个id,用于消息的确认
channel.confirmSelect();
//3.声明队列
channel.queueDeclare("PUBLISHER_CONFIRM_QUEUE2", true, false, false, null);
//4.发送消息并等待确认
int count = 0;
int batchSize = 50;//批量处理的消息数量
long start = System.currentTimeMillis();
for (int i = 0; i < MESSAGE_COUNT; i++) {
String msg = "HelloMessage" + i;
channel.basicPublish("", "PUBLISHER_CONFIRM_QUEUE2", null, msg.getBytes());
count++;
// 等待确认
if (count == batchSize) {//如果发送的消息达到50条,就批量确认
channel.waitForConfirmsOrDie(5000);//如果超时了会抛出异常
count = 0;
}
}
//如果count>0再整体进行确认
if (count > 0) {
channel.waitForConfirmsOrDie(5000);//如果超时了会抛出异常
}
long end = System.currentTimeMillis();
System.out.println("批量确认策略,消息条数" + MESSAGE_COUNT + "耗时" + (end - start) + "ms");
}
}
//3.异步确认
private static void handlingPublisherConfirmsAsynchronously() throws IOException, TimeoutException, InterruptedException {
try (Connection connection = createConnection()) {
//1.开启信道
Channel channel = connection.createChannel();
//2.设置信道为confirm模式,开启之后,基于该信道发送的消息都会生成一个id,用于消息的确认
channel.confirmSelect();
//3.声明队列
channel.queueDeclare("PUBLISHER_CONFIRM_QUEUE3", true, false, false, null);
//
long start = System.currentTimeMillis();
SortedSet<Long> confirmSeqNo = Collections.synchronizedSortedSet(new TreeSet<>());//存储未确认的消息的id
//4.异步监听
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
//处理确认
if (multiple) {
//如果是批量处理,清除掉当前id之前的消息
confirmSeqNo.headSet(deliveryTag + 1).clear();
} else {
//如果不是批量处理,清除当前的这个id就可以了
confirmSeqNo.remove(deliveryTag);
}
}
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
//处理
if (multiple) {
//如果是批量处理,清除掉当前id之前的消息
confirmSeqNo.headSet(deliveryTag + 1).clear();
} else {
//如果不是批量处理,清除当前的这个id就可以了
confirmSeqNo.remove(deliveryTag);
}
//重发消息
}
});
//5.发送消息
for (int i = 0; i < MESSAGE_COUNT; i++) {
String msg = "HelloMessage" + i;
long id = channel.getNextPublishSeqNo();
channel.basicPublish("", "PUBLISHER_CONFIRM_QUEUE3", null, msg.getBytes());
confirmSeqNo.add(id);//id加入集合中,表示消息发送完了,但是还没确认
}
while (!confirmSeqNo.isEmpty()) {//如果存储id的集合消息不为空,说明还有消息没有确认
Thread.sleep(10);
}
long end = System.currentTimeMillis();
System.out.println("异步确认策略,消息条数" + MESSAGE_COUNT + "耗时" + (end - start) + "ms");
}
}
}
使用Spring Boot完成RabbitMQ的通信
工作队列模式
创建SpringBoot项目,添加RabbitMQ相关的依赖(为了方便学习,把SpringWeb也加上~)
添加相关配置
spring.rabbitmq.host=ip地址
spring.rabbitmq.password=密码
spring.rabbitmq.port=端口号
spring.rabbitmq.username=用户名
1、声明队列
/**
* 声明队列
* @return
*/
@Bean("workQueue")
public Queue workQueue() {
return QueueBuilder.durable(Constants.WORK_QUEUE).build();
}
注意,这里导入的不是Java的Queue而是org.springframework.amqp.core的Queue
2、编写生产者代码
@RestController
@RequestMapping("/producer")
public class ProducerController {
//使用RabbitTemplate,进行消息的发送
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/work")
public String work() {
rabbitTemplate.convertAndSend("", "WORK_QUEUE", "Hello Spring AMQP");
return "发送成功";
}
}
在浏览器中访问这个接口即可完成消息的发送
3、编写消费者代码
@Component
public class WorkListener {
@RabbitListener(queues = Constants.WORK_QUEUE)//要从哪个队列中获取消息
public void queueListener(Message message) {
System.out.println("listener1[" + Constants.WORK_QUEUE + "] 接收到消息: " + message);
}
//一个RabbitListener就是一个消费者,如果只有一个RabbitListener,那就是简单模式了
@RabbitListener(queues = Constants.WORK_QUEUE)//要从哪个队列中获取消息
public void queueListener2(Message message) {
System.out.println("listener2[" + Constants.WORK_QUEUE + "] 接收到消息: " + message);
}
}
@RabbitListener是Spring框架中用于监听RabbitMQ队列的注解,通过这个注解可以定义一个方法,以便从RabbitMQ中接收消息,参数类型
程序一启动就会监听队列中有没有消息,如果有就消费
发布订阅模式
生产者代码
1、声明队列
/**
* 声明队列-发布订阅模式
*
* @return
*/
@Bean("fanoutQueue1")
public Queue fanoutQueue1() {
return QueueBuilder.durable(Constants.FANOUT_QUEUE1).build();
}
@Bean("fanoutQueue2")
public Queue fanoutQueue2() {
return QueueBuilder.durable(Constants.FANOUT_QUEUE2).build();
}
2、声明交换机
/**
* 声明交换机-发布订阅模式
* @return
*/
@Bean("fanoutExchange")
public FanoutExchange fanoutExchange() {
return ExchangeBuilder.fanoutExchange(Constants.FANOUT_EXCHANGE).durable(true).build();
}
3、交换机和队列的绑定
/**
* 交换机和队列的绑定
* @param exchange
* @param queue
* @return
*/
@Bean("fanoutQueueBinding1")
public Binding fanoutQueueBinding1(@Qualifier("fanoutExchange") FanoutExchange exchange,@Qualifier("fanoutQueue1") Queue queue){
return BindingBuilder.bind(queue).to(exchange);
}
/**
* 交换机和队列的绑定
* @param exchange
* @param queue
* @return
*/
@Bean("fanoutQueueBinding1")
public Binding fanoutQueueBinding2(@Qualifier("fanoutExchange") FanoutExchange exchange,@Qualifier("fanoutQueue2") Queue queue){
return BindingBuilder.bind(queue).to(exchange);
}
4、发送消息
@RequestMapping("/fanout")
public String fanout() {
rabbitTemplate.convertAndSend(Constants.FANOUT_EXCHANGE,"","Hello Spring qmqp:fanout");
return "发送成功";
}
编写消费者代码
@Component
public class FanoutListener {
@RabbitListener(queues = Constants.FANOUT_QUEUE1)//要从哪个队列中获取消息
public void queueListener1(Message message) {
System.out.println("[" + Constants.FANOUT_QUEUE1 + "] 接收到消息: " + message);
}
@RabbitListener(queues = Constants.FANOUT_QUEUE2)//要从哪个队列中获取消息
public void queueListener2(Message message) {
System.out.println("[" + Constants.FANOUT_QUEUE2 + "] 接收到消息: " + message);
}
}
路由模式
1、声明队列
@Bean("directQueue1")
public Queue directQueue1() {
return QueueBuilder.durable(Constants.DIRECT_QUEUE1).build();
}
@Bean("directQueue2")
public Queue directQueue2() {
return QueueBuilder.durable(Constants.DIRECT_QUEUE2).build();
}
2、声明交换机
@Bean("directExchange")
public DirectExchange directExchange() {
return ExchangeBuilder.directExchange(Constants.DIRECT_EXCHANGE).durable(true).build();
}
3、绑定
@Bean("directQueueBinding1")
public Binding directQueueBinding1(@Qualifier("directExchange") DirectExchange directExchange, @Qualifier("directQueue1") Queue queue) {
return BindingBuilder.bind(queue).to(directExchange).with("orange");
}
@Bean("directQueueBinding2")
public Binding directQueueBinding2(@Qualifier("directExchange") DirectExchange directExchange, @Qualifier("directQueue2") Queue queue) {
return BindingBuilder.bind(queue).to(directExchange).with("black");
}
@Bean("directQueueBinding3")
public Binding directQueueBinding3(@Qualifier("directExchange") DirectExchange directExchange, @Qualifier("directQueue2") Queue queue) {
return BindingBuilder.bind(queue).to(directExchange).with("orange");
}
4、发送消息
@RequestMapping("/direct/{routingKey}")
public String direct(@PathVariable("routingKey") String routingKey) {
rabbitTemplate.convertAndSend(Constants.DIRECT_EXCHANGE, routingKey, "Hello Spring qmqp:direct" + "routingKey is " + routingKey);
return "发送成功";
}
5、消费者代码
@Component
public class DirectListener {
@RabbitListener(queues = Constants.DIRECT_QUEUE1)//要从哪个队列中获取消息
public void queueListener1(Message message) {
System.out.println("[" + Constants.DIRECT_QUEUE1 + "] 接收到消息: " + message);
}
@RabbitListener(queues = Constants.DIRECT_QUEUE2)//要从哪个队列中获取消息
public void queueListener2(Message message) {
System.out.println("[" + Constants.DIRECT_QUEUE2 + "] 接收到消息: " + message);
}
}
通配符模式
1、声明队列
//通配符模式
//声明队列
@Bean("topicQueue1")
public Queue topicQueue1() {
return QueueBuilder.durable(Constants.TOPIC_QUEUE1).build();
}
@Bean("topicQueue2")
public Queue topicQueue2() {
return QueueBuilder.durable(Constants.TOPIC_QUEUE2).build();
}
2、声明交换机
//声明交换机
@Bean("topicExchange")
public TopicExchange topicExchange() {
return ExchangeBuilder.topicExchange(Constants.TOPIC_EXCHANGE).durable(true).build();
}
3、绑定
//绑定
@Bean("topicQueueBinding1")
public Binding topicQueueBinding1(@Qualifier("topicExchange") TopicExchange topicExchange, @Qualifier("topicQueue1") Queue queue) {
return BindingBuilder.bind(queue).to(topicExchange).with("*.orange.*");
}
//绑定
@Bean("topicQueueBinding2")
public Binding topicQueueBinding2(@Qualifier("topicExchange") TopicExchange topicExchange, @Qualifier("topicQueue2") Queue queue) {
return BindingBuilder.bind(queue).to(topicExchange).with("*.*.rabbit");
}
//绑定
@Bean("topicQueueBinding3")
public Binding topicQueueBinding3(@Qualifier("topicExchange") TopicExchange topicExchange, @Qualifier("topicQueue2") Queue queue) {
return BindingBuilder.bind(queue).to(topicExchange).with("lazy.#");
}
4、发送消息
@RequestMapping("/topic/{routingKey}")
public String topic(@PathVariable("routingKey") String routingKey) {
rabbitTemplate.convertAndSend(Constants.TOPIC_EXCHANGE, routingKey, "Hello Spring qmqp:topic" + "routingKey is " + routingKey);
return "发送成功";
}
5、消费消息
@Component
public class TopicListener {
@RabbitListener(queues = Constants.TOPIC_QUEUE1)//要从哪个队列中获取消息
public void queueListener1(Message message) {
System.out.println("[" + Constants.TOPIC_QUEUE1 + "] 接收到消息: " + message);
}
@RabbitListener(queues = Constants.TOPIC_QUEUE2)//要从哪个队列中获取消息
public void queueListener2(Message message) {
System.out.println("[" + Constants.TOPIC_QUEUE2 + "] 接收到消息: " + message);
}
}
RabbitListener注解
RabbitListener是类注解,也是方法注解,如果用在类上,需要搭配RabbitHandler注解
@Component
@RabbitListener(queues = "queue")
public class Test {
@RabbitHandler
public void handMessage(String message) {
System.out.println("接受到消息" + message);
}
}
RabbitListener会根据消息的内容选择不同的方法,例如
@Component
@RabbitListener(queues = "queue")
public class TestListener {
@RabbitHandler
public void handMessage(String message) {
System.out.println("接受到消息" + message);
}
@RabbitHandler
public void handMessage(Integer message) {
System.out.println("接受到消息" + message);
}
}
如果消息是String类型的,就会调用第一个方法,如果是Integer类型的,则调用第二个方法
发送对象
定义OrderInfo对象
@Data
public class OrderInfo implements Serializable {
private String id;
private String name;
}
@RestController
@RequestMapping("/order")
public class TestController {
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/create2")
public String create2() {
OrderInfo info = new OrderInfo();
info.setId(UUID.randomUUID().toString());
info.setName("Order1");
rabbitTemplate.convertAndSend("", "order.create", info);
return "成功";
}
}
将消息转换成json格式,需要调用setMessageConverter方法:
@RequestMapping("/create2")
public String create2() {
OrderInfo info = new OrderInfo();
info.setId(UUID.randomUUID().toString());
info.setName("Order1");
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
rabbitTemplate.convertAndSend("", "order.create", info);
return "下单成功";
}
这样做不太美观,我们可以自己配置RabbitTemplate,设置转换格式,同时交给Spring管理,这样注入进来的就是我们自己的RabbitTemplate了
@Configuration
public class RabbitMQConfig {
@Bean
public Jackson2JsonMessageConverter jackson2JsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory factory, Jackson2JsonMessageConverter jackson2JsonMessageConverter) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(factory);
rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter);
return rabbitTemplate;
}
}
消费对象
消费对象只需要监听即可,可以在类上加RabbitListener注解搭配RabbitHandler一起使用,也可以只把RabbitListener加在方法上,而方法参数则是需要消费的对象,例如
@Component
@RabbitListener(queues = "order.create")//queues是对应的队列
public class OrderListener {
@RabbitHandler
public void handMessage(String orderInfo) {
System.out.println("接收到消息" + orderInfo);
}
@RabbitHandler
public void handMessage(OrderInfo orderInfo) {
System.out.println("接收到消息" + orderInfo);
}
}