目录:
- rabbitmq客户端使用:
- rabbitmq 生产者的基本步骤
- rabbitmq 消费者的基本步骤
- pom依赖
- simple简单模式
- 工作队列模式
- 工作队列模式(非公平模式)
- 发布者/订阅者模式
- 路由模式
- 主题模式
- rabbitmq事务
- 生产者确认消息到达队列,confirm机制
- 串行确认
- 异步确认
- rabbitmq客户端api,及常用类说明
- springboot整合rabbitmq
- 基本使用
- Queue队列
- Exchange交换器
- Binding绑定
- Message消息
- 简单模式,不绑交换机,直连队列
- 工作队列模式,多个消费者消费同一个队列
- 发布者/订阅者模式,绑定Fanout类型交换器
- 路由模式,绑定direct类型交换器
- 主题模式,绑定topic类型交换器
- confirm模式
- spring整合rabbitmq使用事务
- 消费者限流
- 快速启动多个消费者,concurrency属性
关于RabbitMQ的教程很多,我找了两篇个人感觉比较好的。
https://blog.csdn.net/hellozpc/article/details/81436980 RabbitMQ教程
https://www.cnblogs.com/yihuihui/p/12600797.html 【SpringBoot MQ 系列】RabbitListener 消费基本使用姿势介绍
我这里会放出这5中基本队列使用的代码。
rabbitmq客户端使用:
rabbitmq 生产者的基本步骤:
1、创建连接工程ConnectionFactory,设置相关配置,主要就是host、port、username、password、vhost这5个属性
2、根据ConnectionFactory获取连接Connection
3、声明通道Channel
4、声明交换机Exchange(可以没有Exchange,生产者直接将消息扔给队列,Exchange重复声明是没问题的,但是重复声明时决不能更改其声明属性,否则报错。)
5、声明队列Queue(Queue队列必须要有,如果队列存在了就可以不声明了,但重复声明也没啥问题,但是重复声明时决不能更改其声明属性,否则报错。)
6、队列和交换机绑定(如果是没有交换机那就没必要了)
7、发送消息。
8、关闭channel和connection,释放资源。
rabbitmq 消费者的基本步骤:
1、创建连接工程ConnectionFactory,设置相关配置,主要就是host、port、username、password、vhost这5个属性
2、根据ConnectionFactory获取连接Connection
3、声明通道Channel
4、声明队列Queue(Queue队列必须要有,如果队列存在了就可以不声明了,但重复声明也没啥问题,但是重复声明时决不能更改其声明属性,否则报错。)
5、定义一个消费回调(消费信息的api,新版rabbitmq和旧版的是不同的,我这里只用新版的)
6、将消费回调绑定到队列上,让其进行监听。(消费者千万不要在绑定监听后就关闭channel和connection,不然你是接收不到消息的,毕竟你都关闭了)
先上pom依赖:
com.rabbitmq amqp-client
1、simple简单模式:
这种模式是最简单的,就一个生产者,一个消费者,一个队列。
代码:
生产者 Provider
package com.hongcheng.rabbitmq_demo.simple; import java.io.IOException; import java.nio.charset.Charset; import java.util.concurrent.TimeoutException; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; public class Provider { private static final String queue_name = "hongcheng_queue"; public static void main(String[] args) throws IOException, TimeoutException { // 定义一个连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); // 设施服务地址 connectionFactory.setHost("127.0.0.1"); // 设置端口 connectionFactory.setPort(5672); // 设置vhost虚拟主机 connectionFactory.setVirtualHost("hongchengHost"); // 设置用户名 connectionFactory.setUsername("admin"); // 设置密码 connectionFactory.setPassword("admin"); // 获取连接 Connection connection = connectionFactory.newConnection(); // 从连接中获取一个通道channel Channel channel = connection.createChannel(); // 声明一个队列 channel.queueDeclare(queue_name, false, false, false, null); String msg = "大傻子"; // 发送消息 channel.basicPublish("", queue_name, null, msg.getBytes(Charset.forName("utf-8"))); channel.close(); connection.close(); } }
消费者 Consumer
package com.hongcheng.rabbitmq_demo.simple; import java.io.IOException; import java.util.concurrent.TimeoutException; import com.rabbitmq.client.AMQP.BasicProperties; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.DefaultConsumer; import com.rabbitmq.client.Envelope; public class Consumer { private static final String queue_name = "hongcheng_queue"; public static void main(String[] args) throws IOException, TimeoutException { // 定义一个连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); // 设施服务地址 connectionFactory.setHost("127.0.0.1"); // 设置端口 connectionFactory.setPort(5672); // 设置vhost虚拟主机 connectionFactory.setVirtualHost("hongchengHost"); // 设置用户名 connectionFactory.setUsername("admin"); // 设置密码 connectionFactory.setPassword("admin"); // 获取连接 Connection connection = connectionFactory.newConnection(); // 创建通道 Channel channel = connection.createChannel(); // 声明队列 channel.queueDeclare(queue_name, false, false, false, null); // 定义一个消费者 DefaultConsumer consumer = new DefaultConsumer(channel) { // 消费信息 @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { String string = new String(body,"utf-8"); System.err.println("消息队列收到的消息 ==>" + string); } }; // 监听队列 channel.basicConsume(queue_name, true,consumer); } }
有些参数会在后面会在代码注释里面进行说明。
因为获取Connection连接后面会经常用到,所以我抽取出来弄成一个类
ConnectionUtils
package com.hongcheng.rabbitmq_demo; import java.io.IOException; import java.util.concurrent.TimeoutException; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; public class ConnectionUtils { public static Connection getConnection() throws IOException, TimeoutException { // 定义一个连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); // 设施服务地址 connectionFactory.setHost("127.0.0.1"); // 设置端口 connectionFactory.setPort(5672); // 设置vhost虚拟主机 connectionFactory.setVirtualHost("hongchengHost"); // 设置用户名 connectionFactory.setUsername("admin"); // 设置密码 connectionFactory.setPassword("admin"); return connectionFactory.newConnection(); } }
2、工作队列模式
其实也就是在简单模式上多几个消费者而已
代码:
生产者 Provider:
连续生成50条消息
package com.hongcheng.rabbitmq_demo.work; import java.io.IOException; import java.nio.charset.Charset; import java.util.concurrent.TimeoutException; import com.hongcheng.rabbitmq_demo.ConnectionUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; public class Provider { private static final String queue_name = "work_queue"; public static void main(String[] args) throws IOException, TimeoutException { // 获取连接 Connection connection = ConnectionUtils.getConnection(); // 获取channel Channel channel = connection.createChannel(); // 声明队列 channel.queueDeclare(queue_name, false, false, false, null); for(int i = 1; i <= 50 ;i++) { String msg = "work消息" + i; channel.basicPublish("", queue_name, null, msg.getBytes(Charset.forName("utf-8"))); System.err.println(msg); try { Thread.sleep(400); } catch (InterruptedException e) { e.printStackTrace(); } } // 关闭 channel.close(); connection.close(); } }
消费者1 Consumer1
模拟处理一条消息需要1秒
package com.hongcheng.rabbitmq_demo.work; import java.io.IOException; import java.util.concurrent.TimeoutException; import com.hongcheng.rabbitmq_demo.ConnectionUtils; import com.rabbitmq.client.AMQP.BasicProperties; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.DefaultConsumer; import com.rabbitmq.client.Envelope; /*** * 这种情况下,Consumer1和Consumer2轮流获取到mq的消息 * 并不会因为Consumer2处理的快就能多获取几个消息 * */ public class Consumer1 { private static final String queue_name = "work_queue"; public static void main(String[] args) throws IOException, TimeoutException { // 获取连接 Connection connection = ConnectionUtils.getConnection(); // 获取channel Channel channel = connection.createChannel(); // 声明队列 channel.queueDeclare(queue_name, false, false, false, null); // 创建消费者 DefaultConsumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { String msg = new String(body,"utf-8"); System.err.println("Consumer1消费了消息 ===> " + msg); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }; // 消费者监听 boolean autoAck = true; // 自动确认消费 channel.basicConsume(queue_name, autoAck, consumer); // 注意,不要关闭 } }
消费者2 Consumer2
模拟处理一条消息需要2秒。
消费者2和消费者1其实代码基本都是一样的,只是消费者2每次休眠2秒,消费者1每次休眠1秒
package com.hongcheng.rabbitmq_demo.work; import java.io.IOException; import java.util.concurrent.TimeoutException; import com.hongcheng.rabbitmq_demo.ConnectionUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.DefaultConsumer; import com.rabbitmq.client.Envelope; import com.rabbitmq.client.AMQP.BasicProperties; /*** * 这种情况下,Consumer1和Consumer2轮流获取到mq的消息 * 并不会因为Consumer2处理的快就能多获取几个消息 * */ public class Consumer2 { private static final String queue_name = "work_queue"; public static void main(String[] args) throws IOException, TimeoutException { // 获取连接 Connection connection = ConnectionUtils.getConnection(); // 获取channel Channel channel = connection.createChannel(); // 声明队列 channel.queueDeclare(queue_name, false, false, false, null); // 创建消费者 DefaultConsumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { String msg = new String(body,"utf-8"); System.err.println("Consumer2消费了消息 ===> " + msg); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }; // 消费者监听 boolean autoAck = true; // 自动确认消费 channel.basicConsume(queue_name, autoAck, consumer); // 注意,不要关闭 } }
这种模式下,两个消费者会交替处理消息,mq不会管他们谁处理的的快,谁处理的慢。这算是一种公平模式吧。
工作队列的另外一种写法:非公平模式
非公平模式也就是说多个队列之间不存在交替处理,谁先处理完了,就给rabbitmq发送一条消息,告诉rabbitmq自己处理完了,那么rabbitmq就会给他发下一条消息。
这样子谁处理能力强,就能获得更多的消息。
生产者 Provider
package com.hongcheng.rabbitmq_demo.workunfair; import java.io.IOException; import java.nio.charset.Charset; import java.util.concurrent.TimeoutException; import com.hongcheng.rabbitmq_demo.ConnectionUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; /** * 公平模式,需要控制mq每次只给消费者一个消息,并且消费者发送确认后才给下一个消息 * 另外消费者必须手动发送ack确认 * */ public class Provider { private static final String queue_name = "work_queue_fair"; public static void main(String[] args) throws IOException, TimeoutException { // 获取连接 Connection connection = ConnectionUtils.getConnection(); // 获取channel Channel channel = connection.createChannel(); // 控制mq每次只发送一条消息 int prefetchCount = 1; channel.basicQos(prefetchCount ); // 声明队列 channel.queueDeclare(queue_name, false, false, false, null); for(int i = 1; i <= 50 ;i++) { String msg = "work消息" + i; /** * @param exchange 交换机 * @param routingKey 路由键 * @param props 消息路由的headers信息 * @param body 消息内容 * */ channel.basicPublish("", queue_name, null, msg.getBytes(Charset.forName("utf-8"))); System.err.println(msg); try { Thread.sleep(400); } catch (InterruptedException e) { e.printStackTrace(); } } // 关闭 channel.close(); connection.close(); } }
消费者 Consumer1
package com.hongcheng.rabbitmq_demo.workunfair; import java.io.IOException; import java.util.concurrent.TimeoutException; import com.hongcheng.rabbitmq_demo.ConnectionUtils; import com.rabbitmq.client.AMQP.BasicProperties; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.DefaultConsumer; import com.rabbitmq.client.Envelope; /** * 公平模式,需要控制mq每次只给消费者一个消息,并且消费者发送确认后才给下一个消息。 * 另外消费者必须手动发送ack确认。 * 这种情况下,那个消费者先消费完,就先获取下一个消息。 * 最后每个消费者获取到的消息数量是不同的。 * */ public class Consumer1 { private static final String queue_name = "work_queue_fair"; public static void main(String[] args) throws IOException, TimeoutException { // 获取连接 Connection connection = ConnectionUtils.getConnection(); // 获取channel Channel channel = connection.createChannel(); // 控制mq每次只发送一条消息 int prefetchCount = 1; channel.basicQos(prefetchCount ); // 声明队列 channel.queueDeclare(queue_name, false, false, false, null); // 创建消费者 DefaultConsumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { String msg = new String(body,"utf-8"); System.err.println("Consumer2消费了消息 ===> " + msg); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { // 手动执行确认 channel.basicAck(envelope.getDeliveryTag(), false); } } }; // 消费者监听 boolean autoAck = false; // 必须关闭自动确认消费 channel.basicConsume(queue_name, autoAck, consumer); // 注意,不要关闭 } }
消费者 Consumer2
package com.hongcheng.rabbitmq_demo.workunfair; import java.io.IOException; import java.util.concurrent.TimeoutException; import com.hongcheng.rabbitmq_demo.ConnectionUtils; import com.rabbitmq.client.AMQP.BasicProperties; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.DefaultConsumer; import com.rabbitmq.client.Envelope; /** * 公平模式,需要控制mq每次只给消费者一个消息,并且消费者发送确认后才给下一个消息。 * 另外消费者必须手动发送ack确认。 * 这种情况下,那个消费者先消费完,就先获取下一个消息。 * 最后每个消费者获取到的消息数量是不同的。 * */ public class Consumer2 { private static final String queue_name = "work_queue_fair"; public static void main(String[] args) throws IOException, TimeoutException { // 获取连接 Connection connection = ConnectionUtils.getConnection(); // 获取channel Channel channel = connection.createChannel(); // 控制mq每次只发送一条消息 int prefetchCount = 1; channel.basicQos(prefetchCount ); // 声明队列 /*** * 队列声明后,其队列的配置是不可以修改的,也就是说 * channel.queueDeclare(queue_name, true, true, true, null);是会报错了。 * 毕竟rabbitmq不允许重新定义(不同参数的)一个消息队列 * */ /** * queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Maparguments) * queue:消息队列名 * durable:是否持久化 * exclusive:是否独占队列,仅限此次连接进操作 * autoDelete:是否自动删除,服务器在不使用这个队列的时候会删除它 * arguments:队列的其他属性参数 * */ channel.queueDeclare(queue_name, false, false, false, null); // 创建消费者 DefaultConsumer consumer = new DefaultConsumer(channel) { /** * @param consumerTag 消费者标签 * @param envelope 消息信封,有四个属性:deliveryTag投递标签,redeliver是否重新投递,exchange交换机,routingKey路由键 * @param properties 基本属性 * @param body 消息内容 * */ @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { String msg = new String(body,"utf-8"); System.err.println("Consumer2消费了消息 ===> " + msg); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } finally { // 手动执行确认 /** * @param deliveryTag 投递标签 * @param multiple 是否确认多个消息,true会确认该标签以前的,被这个消费者接受的多个信息 * */ channel.basicAck(envelope.getDeliveryTag(), false); } } }; // 消费者监听 /** * autoAck = true :自动确认模式,一旦rabbitmq将消息分发给消费者,就会从内存中删除。可能存在消息分发给消费者后, * 消费者挂了,导致消息丢失。 * autoAck = false :手动确认,如果一个消费者挂了,就会把消息交付给其他消费者。 * 消费者手动发一个消息应答给rabbitmq,意义说这个消息已经处理完了,你可以删除了,此时rabbitmq就将消息从内存中删除 * */ boolean autoAck = false; // 必须关闭自动确认消费 channel.basicConsume(queue_name, autoAck, consumer); // 注意,不要关闭 } }
3、发布者/订阅者模式
相比于上面两种模式,发布者/订阅者模式要求必须有Exchange交换机。
生产者将消息发给exchange交换机,交换机根据不同的策略将消息转发给队列,消费者订阅队列,获取消息进行消费。
这种模式引入了交换机,同时也带出接下来的几种模式routing(路由模式)和topic(主体模式)
exchange:交换机。一方面接收消费者的消息,另一方面将消息发给绑定的队列
交换机有四种模式:
fanout:分散模式,也就是不处理路由键,选择广播消息,只要和该交换机绑定的队列都可以收到消息。
direct:路由模式,只有完全匹配路由键的队列才能收到消息,一个队列可以和一个交换机有多个routKey路由键。
topic:主题模式,用.将路由键分成不同的主题,只要路由键能和队列的模式进行匹配上,就分发消息。#表示匹配一个或多个主题,*表示只匹配一个主题。
header:header模式,很少用,通过发送消息是的header来进行匹配
我们这里所说的发布者/订阅者模式,其实就是用了fanout交换机,进行广播消息。
生产者 Provider
package com.hongcheng.rabbitmq_demo.pb; import java.io.IOException; import java.nio.charset.Charset; import java.util.concurrent.TimeoutException; import com.hongcheng.rabbitmq_demo.ConnectionUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; public class Provider { private final static String exchange_name = "exchange_fanout"; public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtils.getConnection(); Channel channel = connection.createChannel(); // 声明交换机 /** * 交换机是没有存储数据的能力的,rabbitmq里面只有队列才有存储数据的能力。 * 这里我们并没有给交换机绑定队列,所以数据肯定会丢失。 * */ /** * exchange:交换机。一方面接收消费者的消息,另一方面将消息发给绑定的队列 * 交换机有四种模式: * fanout:分散模式,也就是不处理路由键,选择广播消息,只要和该交换机绑定的队列都可以收到消息。 * direct:路由模式,只有完全匹配路由键的队列才能收到消息,一个队列可以和一个交换机有多个routKey路由键。 * topic:主题模式,用.将路由键分成不同的主题,只要路由键能和队列的模式进行匹配上,就分发消息。#表示匹配一个或多个主题,*表示只匹配一个主题。 * header:header模式,很少用,通过发送消息是的header来进行匹配 * */ channel.exchangeDeclare(exchange_name, "fanout"); // 发送消息 String msg = "这里是交换机"; // 发送消息去交换机,而不是队列 channel.basicPublish(exchange_name, "", null, msg.getBytes(Charset.forName("utf-8"))); System.err.println("成功发送消息 ==> " + msg); channel.close(); connection.close(); } }
消费者1 Consumer1
package com.hongcheng.rabbitmq_demo.pb; import java.io.IOException; import java.util.concurrent.TimeoutException; import com.hongcheng.rabbitmq_demo.ConnectionUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.DefaultConsumer; import com.rabbitmq.client.Envelope; import com.rabbitmq.client.AMQP.BasicProperties; public class Consumer1 { private final static String queue_name = "exchange_fanout_queue_1"; private final static String exchange_name = "exchange_fanout"; public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtils.getConnection(); Channel channel = connection.createChannel(); channel.queueDeclare(queue_name, false, false, false, null);
// 绑定队列和交换机,路由键不填 channel.queueBind(queue_name, exchange_name, "");// 创建消费者 DefaultConsumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { String msg = new String(body,"utf-8"); System.err.println("Consumer1消费了消息 ===> " + msg); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }; // 消费者监听 boolean autoAck = true; channel.basicConsume(queue_name, autoAck, consumer); // 注意,不要关闭 } }
消费者2 Consumer2
package com.hongcheng.rabbitmq_demo.pb; import java.io.IOException; import java.util.concurrent.TimeoutException; import com.hongcheng.rabbitmq_demo.ConnectionUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.DefaultConsumer; import com.rabbitmq.client.Envelope; import com.rabbitmq.client.AMQP.BasicProperties; public class Consumer2 { private final static String queue_name = "exchange_fanout_queue_2"; private final static String exchange_name = "exchange_fanout"; public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtils.getConnection(); Channel channel = connection.createChannel(); channel.queueDeclare(queue_name, false, false, false, null);
// 绑定队列和交换器,路由键不填 channel.queueBind(queue_name, exchange_name, "");// 创建消费者 DefaultConsumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { String msg = new String(body,"utf-8"); System.err.println("Consumer2消费了消息 ===> " + msg); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }; // 消费者监听 boolean autoAck = true; channel.basicConsume(queue_name, autoAck, consumer); // 注意,不要关闭 } }
4、路由模式
路由模式是基于direct类型的Exchange衍生出来的
direct:路由模式,只有完全匹配路由键的队列才能收到消息,一个队列可以和一个交换机有多个routKey路由键。
消息的路由键和 exchange与queue绑定的路由键必须完全匹配,交换机才会把消息丢给队列
生产者 Provider
package com.hongcheng.rabbitmq_demo.routing; import java.io.IOException; import java.nio.charset.Charset; import java.util.concurrent.TimeoutException; import com.hongcheng.rabbitmq_demo.ConnectionUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; public class Provider { private final static String exchange_name = "exchange_direct"; public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtils.getConnection(); Channel channel = connection.createChannel(); // 声明交换机 channel.exchangeDeclare(exchange_name, "direct"); // 发送消息 // String msg = "这里是direct交换机 ====> error消息"; // Consumer1和Consumer2都收到 // String msg = "这里是direct交换机 ====> info消息"; // 仅Consumer2都收到 String msg = "这里是direct交换机 ====> warning消息"; // 仅Consumer2都收到 // String routKey = "error"; // String routKey = "info"; String routKey = "warning"; channel.basicPublish(exchange_name, routKey, null, msg.getBytes(Charset.forName("utf-8"))); System.err.println("成功发送消息 ==> " + msg); channel.close(); connection.close(); } }
消费者1 Consumer1
package com.hongcheng.rabbitmq_demo.routing; import java.io.IOException; import java.util.concurrent.TimeoutException; import com.hongcheng.rabbitmq_demo.ConnectionUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.DefaultConsumer; import com.rabbitmq.client.Envelope; import com.rabbitmq.client.AMQP.BasicProperties; public class Consumer1 { private final static String queue_name = "exchange_direct_queue_1"; private final static String exchange_name = "exchange_direct"; public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtils.getConnection(); Channel channel = connection.createChannel(); channel.queueDeclare(queue_name, false, false, false, null); // 队列绑定交换机 channel.queueBind(queue_name, exchange_name, "error");// 创建消费者 DefaultConsumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { String msg = new String(body,"utf-8"); System.err.println("Consumer1消费了消息 ===> " + msg); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }; // 消费者监听 boolean autoAck = true; channel.basicConsume(queue_name, autoAck, consumer); // 注意,不要关闭 } }
消费者2 Consumer2
package com.hongcheng.rabbitmq_demo.routing; import java.io.IOException; import java.util.concurrent.TimeoutException; import com.hongcheng.rabbitmq_demo.ConnectionUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.DefaultConsumer; import com.rabbitmq.client.Envelope; import com.rabbitmq.client.AMQP.BasicProperties; public class Consumer2 { private final static String queue_name = "exchange_direct_queue_2"; private final static String exchange_name = "exchange_direct"; public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtils.getConnection(); Channel channel = connection.createChannel(); channel.queueDeclare(queue_name, false, false, false, null); // 队列和交换机绑定,可以帮多个 channel.queueBind(queue_name, exchange_name, "error"); channel.queueBind(queue_name, exchange_name, "info"); channel.queueBind(queue_name, exchange_name, "warning");// 创建消费者 DefaultConsumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { String msg = new String(body,"utf-8"); System.err.println("Consumer2消费了消息 ===> " + msg); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }; // 消费者监听 boolean autoAck = true; channel.basicConsume(queue_name, autoAck, consumer); // 注意,不要关闭 } }
5、主题模式
主题模式是基于topic类型的Exchange交换器出来的。
主题模式相比于路由模式的区别是:主体模式可以对路由键进行模糊匹配,而路由模式只能对路由键进行完全匹配
topic:主题模式,用.将路由键分成不同的主题,只要路由键能和队列的模式进行匹配上,就分发消息。#表示匹配一个或多个主题,*表示只匹配一个主题。
主题模式运行路由键按“.”(英文点)进行划分,分成多个主题topic,队列与交换器进行绑定时,绑定路由键可以使用通配符。
# :表示任意个topic
* :表示1个topic
例如:
exchange -------------- A.B.#.F.*.K ------------ queue
消息1(A.B.C.D.E.F.T.K)可以发到队列里面
消息2(A.B.C)不能发到队列里面
消息3(A.B.C.F.E.K)能发到队列里面
消息4(A.B.C.F.E)不能发到队列面
生产者 Provider
package com.hongcheng.rabbitmq_demo.topic; import java.io.IOException; import java.nio.charset.Charset; import java.util.concurrent.TimeoutException; import com.hongcheng.rabbitmq_demo.ConnectionUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; public class Provider { private final static String exchange_name = "exchange_topic"; public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtils.getConnection(); Channel channel = connection.createChannel(); // 声明交换机 channel.exchangeDeclare(exchange_name, "topic"); // Consumer1 ==> A.#.D *.C // Consumer2 ==> A.B.C.D A.*.D.# // 发送消息 // String msg = "这里是topic交换机 ====> A.B.C.D"; // Consumer2和Consumer1都收到 // String msg = "这里是topic交换机 ====> A.C"; // 仅Consumer1都收到 // String msg = "这里是topic交换机 ====> A.B.D.E.F"; // 仅Consumer2都收到 String msg = "这里是topic交换机 ====> A.B.D"; // Consumer1和Consumer2都收到 // String routKey = "A.B.C.D"; // String routKey = "A.C"; // String routKey = "A.B.D.E.F"; String routKey = "A.B.D"; channel.basicPublish(exchange_name, routKey, null, msg.getBytes(Charset.forName("utf-8"))); System.err.println("成功发送消息 ==> " + msg); channel.close(); connection.close(); } }
消费者1 consumer1
package com.hongcheng.rabbitmq_demo.topic; import java.io.IOException; import java.util.concurrent.TimeoutException; import com.hongcheng.rabbitmq_demo.ConnectionUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.DefaultConsumer; import com.rabbitmq.client.Envelope; import com.rabbitmq.client.AMQP.BasicProperties; public class Consumer1 { private final static String queue_name = "exchange_topic_queue_1"; private final static String exchange_name = "exchange_topic"; public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtils.getConnection(); Channel channel = connection.createChannel(); channel.queueDeclare(queue_name, false, false, false, null); // 队列绑定交换机 channel.queueBind(queue_name, exchange_name, "A.#.D"); channel.queueBind(queue_name, exchange_name, "*.C"); // 创建消费者 DefaultConsumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { String msg = new String(body,"utf-8"); System.err.println("Consumer1消费了消息 ===> " + msg); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }; // 消费者监听 boolean autoAck = true; channel.basicConsume(queue_name, autoAck, consumer); // 注意,不要关闭 } }
消费者2 consumer2
package com.hongcheng.rabbitmq_demo.topic; import java.io.IOException; import java.util.concurrent.TimeoutException; import com.hongcheng.rabbitmq_demo.ConnectionUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.DefaultConsumer; import com.rabbitmq.client.Envelope; import com.rabbitmq.client.AMQP.BasicProperties; public class Consumer2 { private final static String queue_name = "exchange_topic_queue_2"; private final static String exchange_name = "exchange_topic"; public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtils.getConnection(); Channel channel = connection.createChannel(); channel.queueDeclare(queue_name, false, false, false, null); // 队列和交换机绑定 channel.queueBind(queue_name, exchange_name, "A.B.C.D"); channel.queueBind(queue_name, exchange_name, "A.*.D.#");// 创建消费者 DefaultConsumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { String msg = new String(body,"utf-8"); System.err.println("Consumer2消费了消息 ===> " + msg); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }; // 消费者监听 boolean autoAck = true; channel.basicConsume(queue_name, autoAck, consumer); // 注意,不要关闭 } }
rabbitmq事务
rabbitmq像普通数据库一样,也支持事务。rabbitmq的事务是指消费者发送消息去rabbitmq,要么全部发送成功,要么全部失败。
rabbitmq的事务依赖三个方法:
channel.txSelect(); 开启事务
channel.txCommit(); 提交事务
channel.txRollback(); 回滚事务
rabbitmq的事务只针对生产者发布消息的时候,所以代码只在生产者上有变化,消费者没变化
生产者 Provider
package com.hongcheng.rabbitmq_demo.transitional; import java.io.IOException; import java.nio.charset.Charset; import java.util.concurrent.TimeoutException; import com.hongcheng.rabbitmq_demo.ConnectionUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; /** * rabbitmq支持事务,他的事务是指消费者发送消息去rabbitmq,要么全部发送成功,要么全部失败。 * 主要依靠下面三个方法: * channel.txSelect(); 开启事务 * channel.txCommit(); 提交事务 * channel.txRollback(); 回滚事务 * */ public class Provider { private static final String queue_name = "hongcheng_queue"; public static void main(String[] args) throws IOException, TimeoutException { // 获取一个连接 Connection connection = ConnectionUtils.getConnection(); // 从连接中获取一个通道channel Channel channel = connection.createChannel(); // 声明一个队列 channel.queueDeclare(queue_name, false, false, false, null); try { // 启动事务 channel.txSelect(); String msg = "大傻子"; // 发送消息 channel.basicPublish("", queue_name, null, msg.getBytes(Charset.forName("utf-8")));
// 模拟程序出错 int a = 1 / 0; // 提交事务 channel.txCommit(); } catch (IOException e) { // 回滚事务 channel.txRollback(); } channel.close(); connection.close(); } }
消费者 Consumer
package com.hongcheng.rabbitmq_demo.transitional; import java.io.IOException; import java.util.concurrent.TimeoutException; import com.rabbitmq.client.AMQP.BasicProperties; import com.hongcheng.rabbitmq_demo.ConnectionUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.DefaultConsumer; import com.rabbitmq.client.Envelope; public class Consumer { private static final String queue_name = "hongcheng_queue"; public static void main(String[] args) throws IOException, TimeoutException { // 获取连接 Connection connection = ConnectionUtils.getConnection(); // 创建通道 Channel channel = connection.createChannel(); // 声明队列 channel.queueDeclare(queue_name, false, false, false, null); // 定义一个消费者 DefaultConsumer consumer = new DefaultConsumer(channel) { // 消费信息 @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { String string = new String(body,"utf-8"); System.err.println("消息队列收到的消息 ==>" + string); } }; // 监听队列 channel.basicConsume(queue_name, true,consumer); } }
生产者确认消息到达队列,confirm机制:
生产者将消息发送给rabbitmq后,消息究竟有没有到目标队列?生产者有时候是需要确定的,如果没有发到目标队列,就要重传或者别的操作。
消息发送后会有3种情况:
1、找不到交换器。
2、找不到队列。
3、成功到达目标队列。
消息confirm确认机制也是可以代替事务机制。如果生产者收不到mq的确认消息,就重传。
不过有一点需要注意的是:
生产者收不到消息可能是因为网络问题,实际上mq是把消息发给队列了的,这时候生产者重新发送消息,就会导致消息重复。这里就要求消费者那边处理消息必须是幂等的。所谓幂等就是多次用同样的数据执行同样的操作,结果始终相同。例如删除操作,同样的数据也只能删除一条。但是如果是新增操作,就可能增加了两条或多条。
另外一点就是一个channel里面,confirm机制不能和事务机制同时存在。
confirm确认有3种写法。
1、普通串行阻塞单条确认:channel.waitForConfirms(),会一直阻塞,直到收到mq的确认
2、普通串行阻塞批量确认:channel.waitForConfirms(),会一直阻塞,直到收到mq的确认。返回true表示之前发的那一批消息都ok;false表示之前发的那一堆消息存在某些信息有问题,至于是哪些消息,有多少消息有问题是不确定的。
3、异步模式:rabbitmq提供了一个确认监听器,可以监听消息确认成功ack,和消息确认失败nack,需要自己维护自己发送过得消息。
confirm确认只针对生产者,所以这里我只给出生产者的代码。
1、普通串行阻塞单条确认
package com.hongcheng.rabbitmq_demo.confirm; import java.io.IOException; import java.nio.charset.Charset; import java.util.concurrent.TimeoutException; import com.hongcheng.rabbitmq_demo.ConnectionUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; /** * rabbitmq的confirm确认模式: * 生产者将channel设置成confirm模式,一旦信道进入confirm模式,所有在该信道上的发布的消息都会被指派一个唯一ID(从1开始) * 一旦消息被投递单所匹配的队列后,mq就会发送一个确认信息给生产者(包含消息的唯一ID),这使得生产者得知消息已经正确到达目标 * 队列。 * 如果消息是持久化的,那么确认消息将会在消息写入磁盘后发出。mq回传给生产者的确认消息中deliveryTag就是发布的消息的唯一ID。 * * ACK确认: 指消费者消费完消息后,由消费者发出,mq接收,用于通知mq删除消息的。 * confirm确认:指生产者发布消息后,有mq发出,生产者接收,用于通知生产者消息发送成功的。 * * confirm模式可以是异步的,通过回调来处理确认消息。也可以是同步的。 * * confirm确认有三种方法: * 1、普通串行阻塞单条确认:channel.waitForConfirms(),会一直阻塞,直到收到mq的确认 * 2、普通串行阻塞批量确认:channel.waitForConfirms(),会一直阻塞,直到收到mq的确认。 * 返回true表示之前发的那一批消息都ok;false表示之前发的那一堆消息存在某些信息有问题,至于是哪些消息,有多少消息有问题是不确定的。 * 3、异步模式:rabbitmq提供了一个确认监听器,可以监听消息确认成功ack,和消息确认失败nack,需要自己维护自己发送过得消息。 * */ public class Provider { private static final String queue_name = "hongcheng_confirm"; public static void main(String[] args) throws IOException, TimeoutException, InterruptedException { // 获取一个连接 Connection connection = ConnectionUtils.getConnection(); // 从连接中获取一个通道channel Channel channel = connection.createChannel(); // 声明一个队列 channel.queueDeclare(queue_name, false, false, false, null); // 生产者调用confirmSelect方法将channel设置成confirm模式。 /** * 注意,channel不同同时txSelect()和confirmSelect()方法。 * channel要么是confirm模式,要么是事务模式,只能二选一。 * */ channel.confirmSelect(); String msg = "大傻子"; /** * 关于1、普通串行阻塞单条确认和2、普通串行阻塞批量确认的唯一区别就是: * 普通串行阻塞批量确认是通过循环一次次发送消息的。 * * channel.waitForConfirms()本身就是对你上次发送的消息进行确认,至于你上次发了多少消息,你自己觉得,如果你发的多,那就确认多条消息。 * */ // 发送消息 channel.basicPublish("", queue_name, null, msg.getBytes(Charset.forName("utf-8")));/** * 阻塞接收mq的确认信息 * */ if(!channel.waitForConfirms()) { System.err.println("消息发送失败"); }else { System.err.println("消息发送成功"); } channel.close(); connection.close(); } }
2、普通串行阻塞批量确认
package com.hongcheng.rabbitmq_demo.confirm; import java.io.IOException; import java.nio.charset.Charset; import java.util.concurrent.TimeoutException; import com.hongcheng.rabbitmq_demo.ConnectionUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; /** * rabbitmq的confirm确认模式: * 生产者将channel设置成confirm模式,一旦信道进入confirm模式,所有在该信道上的发布的消息都会被指派一个唯一ID(从1开始) * 一旦消息被投递单所匹配的队列后,mq就会发送一个确认信息给生产者(包含消息的唯一ID),这使得生产者得知消息已经正确到达目标 * 队列。 * 如果消息是持久化的,那么确认消息将会在消息写入磁盘后发出。mq回传给生产者的确认消息中deliveryTag就是发布的消息的唯一ID。 * * ACK确认: 指消费者消费完消息后,由消费者发出,mq接收,用于通知mq删除消息的。 * confirm确认:指生产者发布消息后,有mq发出,生产者接收,用于通知生产者消息发送成功的。 * * confirm模式可以是异步的,通过回调来处理确认消息。也可以是同步的。 * * confirm确认有三种方法: * 1、普通串行阻塞单条确认:channel.waitForConfirms(),会一直阻塞,直到收到mq的确认 * 2、普通串行阻塞批量确认:channel.waitForConfirms(),会一直阻塞,直到收到mq的确认。 * 返回true表示之前发的那一批消息都ok;false表示之前发的那一堆消息存在某些信息有问题,至于是哪些消息,有多少消息有问题是不确定的。 * 3、异步模式:rabbitmq提供了一个确认监听器,可以监听消息确认成功ack,和消息确认失败nack,需要自己维护自己发送过得消息。 * */ public class Provider { private static final String queue_name = "hongcheng_confirm"; public static void main(String[] args) throws IOException, TimeoutException, InterruptedException { // 获取一个连接 Connection connection = ConnectionUtils.getConnection(); // 从连接中获取一个通道channel Channel channel = connection.createChannel(); // 声明一个队列 channel.queueDeclare(queue_name, false, false, false, null); // 生产者调用confirmSelect方法将channel设置成confirm模式。 /** * 注意,channel不同同时txSelect()和confirmSelect()方法。 * channel要么是confirm模式,要么是事务模式,只能二选一。 * */ channel.confirmSelect(); String msg = "大傻子"; /** * 关于1、普通串行阻塞单条确认和2、普通串行阻塞批量确认的唯一区别就是: * 普通串行阻塞批量确认是通过循环一次次发送消息的。 * * channel.waitForConfirms()本身就是对你上次发送的消息进行确认,至于你上次发了多少消息,你自己觉得,如果你发的多,那就确认多条消息。 * */// 批量发送 for (int i = 0; i < 5; i++) { channel.basicPublish("", queue_name, null, msg.getBytes(Charset.forName("utf-8"))); } /** * 阻塞接收mq的确认信息 * */ if(!channel.waitForConfirms()) { System.err.println("消息发送失败"); }else { System.err.println("消息发送成功"); } channel.close(); connection.close(); } }
3、异步模式
package com.hongcheng.rabbitmq_demo.confirm.asyn; import java.io.IOException; import java.nio.charset.Charset; import java.util.Collections; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.TimeoutException; import com.hongcheng.rabbitmq_demo.ConnectionUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.ConfirmListener; import com.rabbitmq.client.Connection; /** * rabbitmq的confirm确认模式: * 生产者将channel设置成confirm模式,一旦信道进入confirm模式,所有在该信道上的发布的消息都会被指派一个唯一ID(从1开始) * 一旦消息被投递单所匹配的队列后,mq就会发送一个确认信息给生产者(包含消息的唯一ID),这使得生产者得知消息已经正确到达目标 * 队列。 * 如果消息是持久化的,那么确认消息将会在消息写入磁盘后发出。mq回传给生产者的确认消息中deliveryTag就是发布的消息的唯一ID。 * * ACK确认: 指消费者消费完消息后,由消费者发出,mq接收,用于通知mq删除消息的。 * confirm确认:指生产者发布消息后,有mq发出,生产者接收,用于通知生产者消息发送成功的。 * * confirm模式可以是异步的,通过回调来处理确认消息。也可以是同步的。 * * confirm确认有三种方法: * 1、普通串行阻塞单条确认:channel.waitForConfirms(),会一直阻塞,直到收到mq的确认 * 2、普通串行阻塞批量确认:channel.waitForConfirms(),会一直阻塞,直到收到mq的确认。 * 返回true表示之前发的那一批消息都ok;false表示之前发的那一堆消息存在某些信息有问题,至于是哪些消息,有多少消息有问题是不确定的。 * 3、异步模式:rabbitmq提供了一个确认监听器,可以监听消息确认成功ack,和消息确认失败nack,需要自己维护自己发送过得消息。 * */ public class Provider { private static final String queue_name = "hongcheng_confirm"; public static void main(String[] args) throws IOException, TimeoutException, InterruptedException { // 获取一个连接 Connection connection = ConnectionUtils.getConnection(); // 从连接中获取一个通道channel Channel channel = connection.createChannel(); // 声明一个队列 channel.queueDeclare(queue_name, false, false, false, null); // 生产者调用confirmSelect方法将channel设置成confirm模式。 /** * 注意,channel不同同时txSelect()和confirmSelect()方法。 * channel要么是confirm模式,要么是事务模式,只能二选一。 * */ channel.confirmSelect(); // 保存未确认的消息标识,消息标识从1开始,依次递增,channel发送的第一条消息的deliveryTag就是1,第二条消息deliveryTag就是2,channel只是当前的channel SortedSetsortedSet = Collections.synchronizedSortedSet(new TreeSet ()); // 添加消息确认监听器 channel.addConfirmListener(new ConfirmListener() { /*** * 确认失败监听器 * @param deliveryTag 消息标识,消息标识从1开始,依次递增,channel发送的第一条消息的deliveryTag就是1,第二条消息deliveryTag就是2,channel只是当前的channel * @param multiple 是否确认该标识及其前面所有信息 * */ @Override public void handleNack(long deliveryTag, boolean multiple) throws IOException { if(multiple) { System.err.println("------handleNack-----multiple(true)-------"); sortedSet.headSet(deliveryTag + 1).clear(); }else { System.err.println("------handleNack-----multiple(false)-------"); sortedSet.remove(deliveryTag); } } /*** * 确认成功监听器 * @param deliveryTag 消息标识 * @param multiple 是否确认该标识及其前面所有信息 * */ @Override public void handleAck(long deliveryTag, boolean multiple) throws IOException { if(multiple) { System.err.println("------handleAck-----multiple(true)-------"); sortedSet.headSet(deliveryTag + 1).clear(); }else { System.err.println("------handleAck-----multiple(false)-------"); sortedSet.remove(deliveryTag); } } }); String msg = "大傻子"; // 批量发送 for (int i = 0; i < 500; i++) { String temp = msg + i; long nextPublishSeqNo = channel.getNextPublishSeqNo(); channel.basicPublish("", queue_name, null, temp.getBytes(Charset.forName("utf-8"))); sortedSet.add(nextPublishSeqNo); } channel.close(); connection.close(); } }
rabbitmq的客户端基本使用就这样了,接下来我们看一下上面用过的一些rabbitmq客户端api
ConnectionFactory.class
用于创建rabbitmq的连接Connection的工厂,里面可以进行各种参数的配置
下面的代码是最常用的:
// 定义一个连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); // 设施服务地址 connectionFactory.setHost("127.0.0.1"); // 设置端口 connectionFactory.setPort(5672); // 设置vhost虚拟主机 connectionFactory.setVirtualHost("hongchengHost"); // 设置用户名 connectionFactory.setUsername("admin"); // 设置密码 connectionFactory.setPassword("admin"); Connection connection = connectionFactory.newConnection();
Connection.class
与rabbitmq进行通信的连接,他的文档告诉了我们怎样快速获取一个connection实例,是不是和我们上面的代码一样
这是Connection的基本方法
Channel.class
表示通道,channel是我们直接与rabbitmq进行通信的桥梁。里面包含了大量方法。
DefaultConsumer.class
默认消费者回调,用于通过回调来异步处理消息。
// 定义一个消费者 DefaultConsumer consumer = new DefaultConsumer(channel) { /** * 接收到mq发的消息就会调用 * */ @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { String string = new String(body,"utf-8"); System.err.println("消息队列收到的消息 ==>" + string); } /** * channel.basicConsume(queue_name, true,consumer);被调用时才执行的回调 * */ @Override public void handleConsumeOk(String consumerTag) { System.err.println("handleConsumeOk ===> " + consumerTag); } /** * channel.basicCancel(consumerTag);被调用后才执行的回调 * */ @Override public void handleCancelOk(String consumerTag) { System.err.println("handleCancelOk ===> " + consumerTag); } /** * 这个实在不知道什么时候调用 * */ @Override public void handleCancel(String consumerTag) throws IOException { System.err.println("handleCancel ===> " + consumerTag); } /** * 当connection.close()或者channel.close()或者channel.abort();;被调用时才执行的回调 * */ @Override public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) { System.err.println("handleShutdownSignal ===> " + consumerTag); } /** * 当重新获取但之前已经发给消费者,但消费者为ack确认的消息,执行此回调 * */ @Override public void handleRecoverOk(String consumerTag) { System.err.println("handleRecoverOk ===> " + consumerTag); } };
springboot整合rabbitmq
基本使用
先上pom依赖
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"> 4.0.0 com.hongcheng rabbitmq-demo 0.0.1-SNAPSHOT jar rabbitmq-demo http://maven.apache.org org.springframework.boot spring-boot-starter-parent 2.3.0.RELEASE UTF-8 1.8 1.8 1.8 org.springframework.boot spring-boot-starter-amqp org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-devtools runtime true
application.yml
server:
port: 8021
spring:
#给项目来个名字
application:
name: rabbitmq-demo-springboot
#配置rabbitMq 服务器
rabbitmq:
host: 127.0.0.1
port: 5672
username: admin
password: admin
#虚拟host 可以不设置,使用server默认host
virtual-host: hongchengHost
RabbitConfig.java
package com.hongcheng.rabbitmq_demo.springboot.config; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.Queue; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * spring按照rabbitmq抽象出四个组件:Queue、Exchange、Binding、Message。 * 我们需要配置Queue、Exchange、Binding * */ @Configuration public class RabbitConfig { @Bean public Queue getQueue() { /** * private final String name; 队列名 private final boolean durable; 是否持久化,默认是false,持久化队列:会被存储在磁盘上,mq重启时仍然存在;非持久化仅在当前连接有效 private final boolean exclusive; exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable private final boolean autoDelete; autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。 private volatile String actualName; 队列实际名,如果队列名传入为空,就自动创建一个默认名 Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, Maparguments) * */ return new Queue("springboot_queue_direct",false,false,false,null); } @Bean public DirectExchange getDirectExchange() { /** * private final String name; 交换机名 private final boolean durable; 是否持久化,默认是false,持久化交换器:会被存储在磁盘上,mq重启时仍然存在;非持久化仅在当前连接有效 private final boolean autoDelete; autoDelete:是否自动删除,当没有生产者使用此交换机,该会自动删除。 5中交换器类型: direct:DirectExchange topic:HeadersExchange fanout:FanoutExchange headers:TopicExchange system: 自定义:CustomExchange AbstractExchange(String name, boolean durable, boolean autoDelete, Maparguments) * */ return new DirectExchange("springboot_exchange_direct",false,false,null); } @Bean public Binding bindingDirect() { /*** * Binding 用于将交换机和队列进行绑定 * * private final String destination; 目标对象名,可以是交换机,也可以是队列 private final String exchange; 源交换机名 private final String routingKey; 路由键 private final DestinationType destinationType; 目标对象类型,QUEUE、EXCHANGE rabbitmq支持交换机与队列/交换机进行绑定。 当Exchange和Exchange绑定时,可以形成复杂的拓扑结构。 Binding(String destination, DestinationType destinationType, String exchange, String routingKey, Maparguments) 创建Binding也可以使用BindingBuilder来构建 BindingBuilder.bind(目标对象,这里可以是队列,也可以是交换器).to(源交换机名,这里只能是交换器).with(路由键) 如果你要绑定多个路由键,就创建多个Binding吧 * */ return BindingBuilder.bind(getQueue()).to(getDirectExchange()).with("rabbitmq.springboot.direct"); } }
生产者
package com.hongcheng.rabbitmq_demo.springboot.controller; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.Map; import java.util.UUID; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/rabbitmq-springboot") public class SendMessageController { @Autowired RabbitTemplate rabbitTemplate; @GetMapping("/sendDirectMessage") public String sendDirectMessage() { String messageId = String.valueOf(UUID.randomUUID()); String messageData = "test message, hello!"; String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); Mapmap=new HashMap<>(); map.put("messageId",messageId); map.put("messageData",messageData); map.put("createTime",createTime); //将消息携带绑定键值:rabbitmq.springboot.direct 发送到交换机springboot_exchange_direct rabbitTemplate.convertAndSend("springboot_exchange_direct", "rabbitmq.springboot.direct", map); return "ok"; } @GetMapping("/sendDirectMessage/{num}") public String sendDirectMessage(@PathVariable("num")int num) { for(int i = 0; i< num;i++) { String messageId = String.valueOf(UUID.randomUUID()); String messageData = "test message, hello ==>" + i; String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); Map map=new HashMap<>(); map.put("messageId",messageId); map.put("messageData",messageData); map.put("createTime",createTime); //将消息携带绑定键值:rabbitmq.springboot.direct 发送到交换机springboot_exchange_direct rabbitTemplate.convertAndSend("springboot_exchange_direct", "rabbitmq.springboot.direct", map); } return "ok"; } }
消费者
package com.hongcheng.rabbitmq_demo.springboot.listener; import java.util.Map; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; @Component public class DirectRabbitListener2 { /** * @param testMessage springboot整合rabbitmq后,是可以发送java对象的,由spring自己序列化对象,默认是使用jdk序列化 * 也可以自己更换成Jackson2JsonMessageConverter * 我们也可以改成用org.springframework.amqp.core.Message来接收原始的消息对象,如public void process( Message testMessage) * */ @RabbitListener(queues = "springboot_queue_direct") @SuppressWarnings("rawtypes") public void process( Map testMessage) { System.out.println("DirectReceiver消费者2 ==> 收到消息 : " + testMessage.toString()); } }
相比以前直接使用rabbitmq的客户端,整合springboot后消费者和生产者更加简单了,代码量更少了。同时也将交换机、队列、绑定提取到配置类了。
spring按照rabbitmq抽象出四个组件:Queue、Exchange、Binding、Message。
下面我们将一一探讨这四个东东
1、Queue队列
private final String name; 队列名
private final boolean durable; 是否持久化,默认是false,持久化队列:会被存储在磁盘上,mq重启时仍然存在;非持久化仅在当前连接有效
private final boolean exclusive; exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
private final boolean autoDelete; autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
private volatile String actualName; 队列实际名,如果队列名传入为空,就自动创建一个默认名
构造函数 =====> Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, Map
2、Exchange交换器
他的类结构:
基本方法:
private final String name; 交换机名
private final boolean durable; 是否持久化,默认是false,持久化交换器:会被存储在磁盘上,mq重启时仍然存在;非持久化仅在当前连接有效
private final boolean autoDelete; autoDelete:是否自动删除,当没有生产者使用此交换机,该会自动删除。
5种交换器类型
交换器类型 : 实现类
direct : DirectExchange
topic : HeadersExchange
fanout : FanoutExchange
headers : TopicExchange
自定义 : CustomExchange
构造函数 ======> AbstractExchange(String name, boolean durable, boolean autoDelete, Map
3、Binding绑定
Binding 用于将交换机和队列进行绑定
private final String destination; 目标对象名,可以是交换机,也可以是队列
private final String exchange; 源交换机名
private final String routingKey; 路由键
private final DestinationType destinationType; 目标对象类型,QUEUE、EXCHANGE
rabbitmq支持交换机与队列/交换机进行绑定。
当Exchange和Exchange绑定时,可以形成复杂的拓扑结构。
构造函数 =======> Binding(String destination, DestinationType destinationType, String exchange, String routingKey, Map
创建Binding也可以使用BindingBuilder来构建
BindingBuilder.bind(目标对象,这里可以是队列,也可以是交换器).to(源交换机名,这里只能是交换器).with(路由键)
如果你要绑定多个路由键,就创建多个Binding吧
4、Message消息
spring在发送消息给rabbitmq时,会带上许多属性一起发送,同时我们也可以直接发送java对象,由spring自己序列化对象,默认是使用jdk序列化,也可以自己更换成Jackson2JsonMessageConverter
之前我们用rabbitmq的客户端实现了5种模式,简单模式,工作队列模式、发布者/订阅者模式、路由模式、主题模式,其实换成springboot整合后也差不多
1、简单模式,不绑交换机,直连队列
RabbitConfig.java增加下面代码
@Bean public Queue getQueue1() { // 普通队列,不绑定交换器 return new Queue("springboot_queue1",false,false,false,null); }
消费者增加下面代码
@RabbitListener(queues = "springboot_queue1") public void process2( Message message) { System.out.println("springboot_queue1队列 ==> 收到消息 : " + new String(message.getBody())); }
生产者增加下面代码,发送消息时,exchange填空,路由键填队列名
@GetMapping("/sendMessage") public String sendMessage() { String messageId = String.valueOf(UUID.randomUUID()); String messageData = "普通队列,不绑定交换器"; String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); Mapmap=new HashMap<>(); map.put("messageId",messageId); map.put("messageData",messageData); map.put("createTime",createTime); //将消息携带绑定键值:springboot_queue1 发送到队列springboot_queue1 rabbitTemplate.convertAndSend("", "springboot_queue1", map); return "ok"; }
2、工作队列模式
RabbitConfig.java增加下面代码
@Bean public Queue getQueue_work() { // 工作队列模式,一个队列多个消费者 return new Queue("springboot_queue_work",false,false,false,null); }
消费者增加下面代码
@RabbitListener(queues = "springboot_queue_work") public void process5( Message message) { System.out.println("springboot_queue_work消费者1 ==> 收到消息 : " + new String(message.getBody())); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @RabbitListener(queues = "springboot_queue_work") public void process6( Message message) { System.out.println("springboot_queue_work消费者2 ==> 收到消息 : " + new String(message.getBody())); try { Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
生产者增加下面代码
@GetMapping("/sendWorkMessage") public String sendWorkMessage() { for(int i = 0; i< 20;i++) { String messageId = String.valueOf(UUID.randomUUID()); String messageData = "工作队列,绑定Fanout交换器"; String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); Mapmap=new HashMap<>(); map.put("messageId",messageId); map.put("messageData",messageData); map.put("createTime",createTime); //将消息携带绑定键值: springboot_queue_work 发送到队列springboot_queue_work rabbitTemplate.convertAndSend("", "springboot_queue_work", map); } return "ok"; }
3、发布者/订阅者模式,绑定Fanout类型交换器
RabbitConfig.java增加下面代码
@Bean public Queue getQueue_fanout1() { // 广播队列,绑定广播交换器 return new Queue("springboot_queue_fanout1",false,false,false,null); } @Bean public Queue getQueue_fanout2() { // 广播队列,绑定广播交换器 return new Queue("springboot_queue_fanout2",false,false,false,null); } @Bean public FanoutExchange getFanoutExchange() { // 广播队列,绑定广播交换器 return new FanoutExchange("springboot_exchange_fanout",false,false,null); } @Bean public Binding bindingFanout1() { // 广播队列,绑定广播交换器 return BindingBuilder.bind(getQueue_fanout1()).to(getFanoutExchange()); } @Bean public Binding bindingFanout2() { // 广播队列,绑定广播交换器 return BindingBuilder.bind(getQueue_fanout2()).to(getFanoutExchange()); }
消费者增加下面代码
@RabbitListener(queues = "springboot_queue_fanout2") public void process3( Message message) { System.out.println("springboot_queue_fanout2队列 ==> 收到消息 : " + new String(message.getBody())); } @RabbitListener(queues = "springboot_queue_fanout1") public void process4( Message message) { System.out.println("springboot_queue_fanout1队列 ==> 收到消息 : " + new String(message.getBody())); }
生产者增加下面代码
@GetMapping("/sendFanoutMessage") public String sendFanoutMessage() { String messageId = String.valueOf(UUID.randomUUID()); String messageData = "工作队列,绑定Fanout交换器"; String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); Mapmap=new HashMap<>(); map.put("messageId",messageId); map.put("messageData",messageData); map.put("createTime",createTime); //将消息不携带绑定键, 发送到交换机springboot_exchange_fanout rabbitTemplate.convertAndSend("springboot_exchange_fanout", "", map); return "ok"; }
4、路由模式,绑定direct类型交换器
代码和发布者/订阅者模式类似,只是交换器换成DirectExchange,路由键换成完整的键值
RabbitConfig.java增加下面代码
@Bean public Queue getQueue() { return new Queue("springboot_queue_direct",false,false,false,null); } @Bean public DirectExchange getDirectExchange() { return new DirectExchange("springboot_exchange_direct",false,false,null); } @Bean public Binding bindingDirect() { return BindingBuilder.bind(getQueue()).to(getDirectExchange()).with("rabbitmq.springboot.direct"); }
消费者增加下面代码
@RabbitListener(queues = "springboot_queue_direct") public void process( Message message) { System.out.println("springboot_queue_direct队列 ==> 收到消息 : " + new String(message.getBody())); }
生产者增加下面代码
@GetMapping("/sendDirectMessage") public String sendDirectMessage() { String messageId = String.valueOf(UUID.randomUUID()); String messageData = "你还,rabbitmq!"; String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); Mapmap=new HashMap<>(); map.put("messageId",messageId); map.put("messageData",messageData); map.put("createTime",createTime); //将消息携带绑定键值:rabbitmq.springboot.direct 发送到交换机springboot_exchange_direct rabbitTemplate.convertAndSend("springboot_exchange_direct", "rabbitmq.springboot.direct", map); return "ok"; }
5、主题模式,绑定topic类型交换器
代码和路由模式类似,只是交换器换成TopicExchange,路由键换成完整的键值
RabbitConfig.java增加下面代码
@Bean
public Queue getQueue_Topic() {
return new Queue("springboot_queue_topic",false,false,false,null);
}
@Bean
public TopicExchange getTopicExchange() {
return new TopicExchange("springboot_exchange_topic",false,false,null);
}
@Bean
public Binding bindingTopic() { return BindingBuilder.bind(getQueue_Topic()).to(getTopicExchange()).with("#.topic"); }
消费者增加下面代码
@RabbitListener(queues = "springboot_queue_topic") public void process7( Message message) { System.out.println("springboot_queue_topic队列 ==> 收到消息 : " + new String(message.getBody())); }
生产者增加下面代码
@GetMapping("/sendTopicMessage")
public String sendTopicMessage() {
String messageId = String.valueOf(UUID.randomUUID());
String messageData = "你还,rabbitmq!";
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map map=new HashMap<>(); map.put("messageId",messageId); map.put("messageData",messageData); map.put("createTime",createTime); //将消息携带绑定键值:rabbitmq.springboot.topic 发送到交换机springboot_exchange_topic rabbitTemplate.convertAndSend("springboot_exchange_topic", "rabbitmq.springboot.topic", map); return "ok"; }
前面使用rabbitmq的客户端时我们用过confirm模式,在spring中如何实现呢?
application.yml增加
server: port: 8021 spring: #给项目来个名字 application: name: rabbitmq-demo-springboot #配置rabbitMq 服务器 rabbitmq: host: 114.115.255.201 port: 5672 username: admin password: hongcheng #虚拟host 可以不设置,使用server默认host virtual-host: hongchengHost #确认消息已发送到交换机(Exchange) publisher-confirms: true #确认消息已发送到队列(Queue) publisher-returns: true
RabbitConfig.java增加下面代码
@Bean public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){ RabbitTemplate rabbitTemplate = new RabbitTemplate(); rabbitTemplate.setConnectionFactory(connectionFactory); // 消息序列化,这个你可以加也可以用默认的jdk序列化,为了好看我用json序列化 rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter()); //设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数 rabbitTemplate.setMandatory(true); /** * 总体来说,消息发送有四种情况: * ①消息推送到server,但是在server里找不到交换机。 ②消息推送到server,找到交换机了,但是没找到队列。 ③消息推送到sever,交换机和队列啥都没找到。 ④消息推送成功。 1.如果消息没有到exchange,则ConfirmCallback回调,ack=false。 2.如果消息到达exchange,则ConfirmCallback回调,ack=true。 3.exchange到queue成功,则不回调ReturnCallback。 4.exchange到queue失败,则回调ReturnCallback(需设置mandatory=true,否则不回调,消息就丢了) 5.如果消息成功到达exchange和queue,两个回调都不调用 看配置文件 #确认消息已发送到交换机(Exchange) publisher-confirms: true #确认消息已发送到队列(Queue) publisher-returns: true * */ // Confirm是消息发到交换机时做的检测 rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() { @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { /** * correlationData,是会返回为null的,也就是说我们能知道消息发送到exchange失败,但是具体是哪个消息失败,我们不知道。 * 所以需要我们在发送消息的时候将Message 和 CorrelationData手动绑定,并自己缓存起来,当ConfirmCallback被调用时在 * 手动找到响应的消息。 * */ System.out.println("ConfirmCallback: "+"相关数据:"+correlationData); System.out.println("ConfirmCallback: "+"确认情况:"+ack); System.out.println("ConfirmCallback: "+"原因:"+cause); } }); // Return是消息发送到队列时做的检测 rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() { @Override public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) { /** * Message是会返回的 * */ System.out.println("ReturnCallback: "+"消息:"+message); System.out.println("ReturnCallback: "+"回应码:"+replyCode); System.out.println("ReturnCallback: "+"回应信息:"+replyText); System.out.println("ReturnCallback: "+"交换机:"+exchange); System.out.println("ReturnCallback: "+"路由键:"+routingKey); } }); return rabbitTemplate; }
spring整合rabbitmq使用事务
这里我只能找到的可以实现事务的方法只有rabbitmq客户端的事务三件套方法,但是要用rabbitTemplate创建一个新的channel,而且发送消息要用channel发送,不能用rabbitTemplate
@GetMapping("/sendWorkMessage") public String sendWorkMessage() throws IOException { Channel channel = rabbitTemplate.getConnectionFactory().createConnection().createChannel(true); try { channel.txSelect(); for(int i = 0; i< 20;i++) { String messageId = String.valueOf(UUID.randomUUID()); String messageData = "工作队列,绑定Fanout交换器"; String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); Mapmap=new HashMap<>(); map.put("messageId",messageId); map.put("messageData",messageData); map.put("createTime",createTime); //将消息携带绑定键值: springboot_queue_work 发送到队列springboot_queue_work channel.basicPublish("", "springboot_queue_work", null, map.toString().getBytes()); if( i == 8) { int a = 1/0; } } channel.txCommit(); }catch (Exception e) { e.printStackTrace(); channel.txRollback(); } return "ok"; }
消费者限流。
如果mq有充足的消息,那么消费者每次都会预取一定数量的消息(默认250),当消费者处理消息缓慢时,可能会造成消息积压,同时增大消费者的内存。
此时我们可以减少预取的消息数量,缓解当前消费者的压力,同时也将消息分摊给其他性能更好的消费者。
另外如果一个队列有多个消费者,那这些消费者会轮流平分处理这些消息,不管谁性能好谁性能坏,所以很有必要进行消费者限流。
RabbitConfig.java增加下面代码:
@Bean("myListenerFactory") public RabbitListenerContainerFactory getQueue_work(ConnectionFactory connectionFactory) { // 工作队列模式,一个队列多个消费者 SimpleRabbitListenerContainerFactory simpleMessageListenerContainer = new SimpleRabbitListenerContainerFactory(); simpleMessageListenerContainer.setConnectionFactory(connectionFactory); simpleMessageListenerContainer.setPrefetchCount(1); return simpleMessageListenerContainer; }
消费者代码(需要开启手动确认):
@RabbitListener(queues = "springboot_queue_work",ackMode = "MANUAL",containerFactory = "myListenerFactory") public void process5( Message message,Channel channel) throws IOException { System.out.println("springboot_queue_work消费者1 ==> 收到消息 : " + new String(message.getBody())); try { Thread.sleep(1000); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } catch (Exception e) { e.printStackTrace(); } } @RabbitListener(queues = "springboot_queue_work",ackMode = "MANUAL",containerFactory = "myListenerFactory") public void process6( Message message,Channel channel) throws IOException { System.err.println("springboot_queue_work消费者2 ==> 收到消息 : " + new String(message.getBody())); try { Thread.sleep(2000); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } catch (Exception e) { e.printStackTrace(); } }
如果是使用rabbitmq客户端的话,就只需要调用basicQos(int prefetchSize, int prefetchCount, boolean global)这个方法,同时开启手动ack确认就好了。
但是整合了spring后用了这个方法也不起作用。
快速启动多个消费者,concurrency属性
消费者代码(concurrency表示消费者个数,只要写这个属性就可以了):
@RabbitListener(queues = "springboot_queue_direct",ackMode = "MANUAL",concurrency = "4") public void process( Message message,Channel channel) throws IOException, ClassNotFoundException, InterruptedException { ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(message.getBody())); System.out.println("DirectReceiver消费者1 ==> 收到消息 : " + objectInputStream.readObject()); // 拒绝消息,并让他重回队列,要注意不要死循环了 // channel.basicReject(message.getMessageProperties().getDeliveryTag(), true); // 错误信息,并让他重回队列,要注意不要死循环了 // channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false); // 确认消息 channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); Thread.sleep(1000); }