介绍
MQ全称为Message Queue,即消息队列, RabbitMQ是由erlang语言开发,基于AMQP(Advanced Message Queue 高级消息队列协议)协议实现的消息队列,它是一种应用程序之间的通信方法,消息队列在分布式系统开发中应用非常广泛。RabbitMQ官方地址:http://www.rabbitmq.com/
1.开发中消息队列通常有如下应用场景:
1.1、任务异步处理。
将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间。
1.2、应用程序解耦合
MQ相当于一个中介,生产方通过MQ与消费方交互,它将应用程序进行解耦合。
1.3、程序削峰。
2.为什么使用RabbitMQ呢?
2.1、使得简单,功能强大。
2.2、基于AMQP协议。
2.3、社区活跃,文档完善。
2.4、高并发性能好,这主要得益于Erlang语言。
2.5、Spring Boot默认已集成RabbitMQ
2 RabbitMQ 的工作原理
1.RabbitMQ的基本结构:
Broker :消息队列服务进程,此进程包括两个部分:Exchange和Queue。
Exchange :消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过虑。
Queue :消息队列,存储消息的队列,消息到达队列并转发给指定的消费方。
Producer :消息生产者,即生产方客户端,生产方客户端将消息发送到MQ。
Consumer :消息消费者,即消费方客户端,接收MQ转发的消息。
2操作流程
消息发布接收流程:
-----发送消息-----
1、生产者和MQ建立TCP连接。
2、生产者和MQ建立通道。
3、生产者通过通道消息发送给MQ,由Exchange将消息进行转发。
4、Exchange将消息转发到指定的Queue(队列)
----接收消息-----
1、消费者和MQ建立TCP连接
2、消费者和MQ建立通道
3、消费者监听指定的Queue(队列)
4、当有消息到达Queue时MQ默认将消息推送给消费者。
消费者接收到消息。
2.2 Hello World
安装RabbitMQ的下载地址:http://www.rabbitmq.com/download.html
按照官方教程(http://www.rabbitmq.com/getstarted.html)测试hello world:
2.2.1搭建环境
1 )创建maven工程
创建生产者工程和消费者工程,分别加入RabbitMQ java client的依赖。
test-rabbitmq-producer:生产者工程
test-rabbitmq-consumer:消费者工程
<dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>4.0.3</version><!--此版本与spring boot 1.5.6版本匹配--> </dependency> |
2.2.2 生产者
在生产者工程下的test中创建测试类如下:
public class Producer01 { //队列名称 private static final String QUEUE = "helloworld"; public static void main(String[] args) throws IOException, TimeoutException { Connection connection = null; Channel channel = null; try { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); factory.setPort(5672); factory.setUsername("guest"); factory.setPassword("guest"); //rabbitmq默认虚拟机名称为“/”,虚拟机相当于一个独立的mq服务器 factory.setVirtualHost("/"); //创建与RabbitMQ服务的TCP连接 connection = factory.newConnection(); //创建与Exchange的通道,每个连接可以创建多个通道,每个通道代表一个会话任务 channel = connection.createChannel(); /** * 声明队列,如果Rabbit中没有此队列将自动创建 * param1:队列名称 * param2:是否持久化 * param3:队列是否独占此连接 * param4:队列不再使用时是否自动删除此队列 * param5:队列参数 */ channel.queueDeclare(QUEUE, true, false, false, null); String message = "helloworld小明"+System.currentTimeMillis(); /** * 消息发布方法 * param1:Exchange的名称,如果没有指定,则使用Default Exchange * param2:routingKey,消息的路由Key,是用于Exchange(交换机)将消息转发到指定的消息队列 * param3:消息包含的属性 * param4:消息体 */ /** * 这里没有指定交换机,消息将发送给默认交换机,每个队列也会绑定那个默认的交换机,但是不能显示绑定或解除绑定 * 默认的交换机,routingKey等于队列名称 */ channel.basicPublish("", QUEUE, null, message.getBytes()); System.out.println("Send Message is:'" + message + "'"); } catch(Exception ex) { ex.printStackTrace(); } finally { if(channel != null) { channel.close(); } if(connection != null) { connection.close(); } } } } |
2.2.3 消费者
在消费者工程下的test中创建测试类如下:
public class Consumer01 { private static final String QUEUE = "helloworld"; public static void main(String[] args) throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); //设置MabbitMQ所在服务器的ip和端口 factory.setHost("127.0.0.1"); factory.setPort(5672); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); //声明队列 channel.queueDeclare(QUEUE, true, false, false, null); //定义消费方法 DefaultConsumer consumer = new DefaultConsumer(channel) { /** * 消费者接收消息调用此方法 * @param consumerTag 消费者的标签,在channel.basicConsume()去指定 * @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送) * @param properties * @param body * @throws IOException */ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { //交换机 String exchange = envelope.getExchange(); //路由key String routingKey = envelope.getRoutingKey(); //消息id long deliveryTag = envelope.getDeliveryTag(); //消息内容 String msg = new String(body,"utf-8"); System.out.println("receive message.." + msg); } }; /** * 监听队列String queue, boolean autoAck,Consumer callback * 参数明细 * 1、队列名称 * 2、是否自动回复,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动回复 * 3、消费消息的方法,消费者接收到消息后调用此方法 */ channel.basicConsume(QUEUE, true, consumer); } } |
2.2.4 总结
1、发送端操作流程
1)创建连接
2)创建通道
3)声明队列
4)发送消息
2、接收端
1)创建连接
2)创建通道
3)声明队列
4)监听队列
5)接收消息
6 )ack回复
3工作模式
RabbitMQ有以下几种工作模式:
1、Work queues
2、Publish/Subscribe
3、Routing
4、Topics
5、Header
6、RPC
3.1 Work queues
work queues与入门程序相比,多了一个消费端,两个消费端共同消费同一个队列中的消息。
应用场景:对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
测试:
1、使用入门程序,启动多个消费者。
2、生产者发送多个消息。
结果:
1、一条消息只会被一个消费者接收;
2、rabbit采用轮询的方式将消息是平均发送给消费者的;
消费者在处理完某条消息后,才会收到下一条消息。
3.2 Publish/subscribe
3.2.1 工作模式
发布订阅模式:
1、每个消费者监听自己的队列。
2、生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收
到消息
3.3 Routing
3.3.1 工作模式
路由模式:
1、每个消费者监听自己的队列,并且设置routingkey。
2、生产者将消息发给交换机,由交换机根据routingkey来转发消息到指定的队列
3.3.2代码
1、生产者
声明exchange_routing_inform交换机。
声明两个队列并且绑定到此交换机,绑定时需要指定routingkey
发送消息时需要指定routingkey
package com.zb.test.rabbitmq; 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.util.concurrent.TimeoutException; public class Producer03_routing { //队列名称 private static final String QUEUE_INFORM_EMAIL = "queue_inform_email"; private static final String QUEUE_INFORM_SMS = "queue_inform_sms"; private static final String EXCHANGE_ROUTING_INFORM="exchange_routing_inform"; public static void main(String[] args) { Connection connection = null; Channel channel = null; try { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setPort(5672); factory.setUsername("guest"); factory.setPassword("guest"); factory.setVirtualHost("/"); connection = factory.newConnection(); channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT); channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null); channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null); //交换机和队列绑定String queue, String exchange, String routingKey /** * 参数明细 * 1、队列名称 * 2、交换机名称 * 3、路由key */ channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_ROUTING_INFORM,QUEUE_INFORM_EMAIL); channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_ROUTING_INFORM,QUEUE_INFORM_SMS); //发送邮件消息 for (int i=0;i<10;i++){ String message = "email inform to user"+i; channel.basicPublish(EXCHANGE_ROUTING_INFORM, QUEUE_INFORM_EMAIL, null, message.getBytes()); System.out.println("Send Message is:'" + message + "'"); } //发送短信消息 for (int i=0;i<10;i++){ String message = "sms inform to user"+i; channel.basicPublish(EXCHANGE_ROUTING_INFORM, QUEUE_INFORM_SMS, null, message.getBytes()); System.out.println("Send Message is:'" + message + "'"); } } catch (IOException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); }finally{ if(channel!=null){ try { channel.close(); } catch (IOException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } } if(connection!=null){ try { connection.close(); } catch (IOException e) { e.printStackTrace(); } } } } } |
2、邮件发送消费者
package com.zb.test.rabbitmq; import com.rabbitmq.client.*; import java.io.IOException; public class Consumer03_routing_email { //队列名称 private static final String QUEUE_INFORM_EMAIL = "inform_queue_email"; private static final String EXCHANGE_ROUTING_INFORM="inform_exchange_routing"; public static void main(String[] args) throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setPort(5672); factory.setUsername("guest"); factory.setPassword("guest"); factory.setVirtualHost("/"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT); channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null); channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_ROUTING_INFORM,QUEUE_INFORM_EMAIL); DefaultConsumer defaultConsumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { long deliveryTag = envelope.getDeliveryTag(); String exchange = envelope.getExchange(); String message = new String(body, "utf-8"); System.out.println(message); } }; channel.basicConsume(QUEUE_INFORM_EMAIL, true, defaultConsumer); } } |
3 、短信发送消费者
参考邮件发送消费者的代码流程,编写短信通知的代码
3.3.3测试
打开RabbitMQ的管理界面,观察交换机绑定情况:
使用生产者发送若干条消息,交换机根据routingkey转发消息到指定的队列。
3.3.4思考
1、Routing模式和Publish/subscibe有啥区别?
Routing模式要求队列在绑定交换机时要指定routingkey,消息会转发到符合routingkey的队列。
3.2.2代码
案例:
用户通知,当用户充值成功或转账完成系统通知用户,通知方式有短信、邮件多种方法。
1、生产者
声明Exchange_fanout_inform交换机。
声明两个队列并且绑定到此交换机,绑定时不需要指定routingkey
发送消息时不需要指定routingkey
package com.zb.test.rabbitmq; import com.rabbitmq.client.*; import java.io.IOException; import java.util.concurrent.TimeoutException; public class Producer02_publish { //队列名称 private static final String QUEUE_INFORM_EMAIL = "queue_inform_email"; private static final String QUEUE_INFORM_SMS = "queue_inform_sms"; private static final String EXCHANGE_FANOUT_INFORM="exchange_fanout_inform"; public static void main(String[] args) { Connection connection = null; Channel channel = null; try { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setPort(5672); factory.setUsername("guest"); factory.setPassword("guest"); factory.setVirtualHost("/"); connection = factory.newConnection(); channel = connection.createChannel(); //声明交换机 String exchange, BuiltinExchangeType type /** * 参数明细 * 1、交换机名称 * 2、交换机类型,fanout、topic、direct、headers */ channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT); channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null); channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null); //交换机和队列绑定String queue, String exchange, String routingKey /** * 参数明细 * 1、队列名称 * 2、交换机名称 * 3、路由key */ channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_FANOUT_INFORM,""); channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_FANOUT_INFORM,""); //发送消息 for (int i=0;i<10;i++){ String message = "inform to user"+i;
/** 向交换机发送消息 String exchange, String routingKey, BasicProperties props, byte[] body * 参数明细 * 1、交换机名称,不指令使用默认交换机名称 Default Exchange * 2、routingKey(路由key),根据key名称将消息转发到具体的队列,这里填写队列名称表示消息将发到此队列 * 3、消息属性 * 4、消息内容 */ channel.basicPublish(EXCHANGE_FANOUT_INFORM, "", null, message.getBytes()); System.out.println("Send Message is:'" + message + "'"); } } catch (IOException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); }finally{ if(channel!=null){ try { channel.close(); } catch (IOException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } } if(connection!=null){ try { connection.close(); } catch (IOException e) { e.printStackTrace(); } } } } } |
2、邮件发送消费者
package com.zb.test.rabbitmq; import com.rabbitmq.client.*; import java.io.IOException; import java.util.concurrent.TimeoutException; public class Consumer02_subscribe_email { //队列名称 private static final String QUEUE_INFORM_EMAIL = "inform_queue_email"; private static final String EXCHANGE_FANOUT_INFORM="inform_exchange_fanout"; public static void main(String[] args) throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setPort(5672); factory.setUsername("guest"); factory.setPassword("guest"); factory.setVirtualHost("/"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT); channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null); channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_FANOUT_INFORM,""); //定义消费方法 DefaultConsumer defaultConsumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { long deliveryTag = envelope.getDeliveryTag(); String exchange = envelope.getExchange(); //消息内容 String message = new String(body, "utf-8"); System.out.println(message); } }; channel.basicConsume(QUEUE_INFORM_EMAIL, true, defaultConsumer); } } |
按照上边的代码,编写邮件通知的消费代码。
3、短信发送消费者
参考上边的邮件发送消费者代码编写。
3.2.3测试
打开RabbitMQ的管理界面,观察交换机绑定情况:
使用生产者发送若干条消息,每条消息都转发到各各队列,每消费者都接收到了消息。
3.2.4 思考
1、publish/subscribe与work queues有什么区别。
区别:
1)work queues不用定义交换机,而publish/subscribe需要定义交换机。
2)publish/subscribe的生产方是面向交换机发送消息,work queues的生产方是面向队列发送消息(底层使用默认交换机)。
3)publish/subscribe需要设置队列和交换机的绑定,work queues不需要设置,实质上work queues会将队列绑定到默认的交换机。
相同点:
所以两者实现的发布/订阅的效果是一样的,多个消费端监听同一个队列不会重复消费消息。
2、实质工作用什么 publish/subscribe还是work queues。
建议使用publish/subscribe,发布订阅模式比工作队列模式更强大,并且发布订阅模式可以指定自己专用的交换
机。
3.4 Topics
3.4.1工作模式
路由模式:
1、每个消费者监听自己的队列,并且设置带统配符的routingkey。
2、生产者将消息发给broker,由交换机根据routingkey来转发消息到指定的队列。
3.4.2代码
案例:
根据用户的通知设置去通知用户,设置接收Email的用户只接收Email,设置接收sms的用户只接收sms,设置两种
通知类型都接收的则两种通知都有效。
1、生产者
声明交换机,指定topic类型:
/** * 声明交换机 * param1:交换机名称 * param2:交换机类型 四种交换机类型:direct、fanout、topic、headers */ channel.exchangeDeclare(EXCHANGE_TOPICS_INFORM, BuiltinExchangeType.TOPIC); //Email通知 channel.basicPublish(EXCHANGE_TOPICS_INFORM, "inform.email", null, message.getBytes()); //sms通知 channel.basicPublish(EXCHANGE_TOPICS_INFORM, "inform.sms", null, message.getBytes()); //两种都通知 channel.basicPublish(EXCHANGE_TOPICS_INFORM, "inform.sms.email", null, message.getBytes()); |
完整代码:
package com.zb.test.rabbitmq; 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.util.concurrent.TimeoutException; public class Producer04_topics { private static final String QUEUE_INFORM_EMAIL = "queue_inform_email"; private static final String QUEUE_INFORM_SMS = "queue_inform_sms"; private static final String EXCHANGE_TOPICS_INFORM="exchange_topics_inform";
public static void main(String[] args) { Connection connection = null; Channel channel = null; try { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setPort(5672); factory.setUsername("guest"); factory.setPassword("guest"); factory.setVirtualHost("/"); connection = factory.newConnection(); channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_TOPICS_INFORM, BuiltinExchangeType.TOPIC); channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null); channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null); //发送邮件消息 for (int i=0;i<10;i++){ String message = "email inform to user"+i; channel.basicPublish(EXCHANGE_TOPICS_INFORM, "inform.email", null, message.getBytes()); System.out.println("Send Message is:'" + message + "'"); } //发送短信消息 for (int i=0;i<10;i++){ String message = "sms inform to user"+i; channel.basicPublish(EXCHANGE_TOPICS_INFORM, "inform.sms", null, message.getBytes()); System.out.println("Send Message is:'" + message + "'"); } //发送短信和邮件消息 for (int i=0;i<10;i++){ String message = "sms and email inform to user"+i; channel.basicPublish(EXCHANGE_TOPICS_INFORM, "inform.sms.email", null, message.getBytes()); System.out.println("Send Message is:'" + message + "'"); } } catch (IOException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); }finally{ if(channel!=null){ try { channel.close(); } catch (IOException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } } if(connection!=null){ try { connection.close(); } catch (IOException e) { e.printStackTrace(); } } } } } |
2、消费端
队列绑定交换机指定通配符:
统配符规则:
中间以“.”分隔。
符号#可以匹配多个词,符号*可以匹配一个词语。
//声明队列 channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null); channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null); //声明交换机 channel.exchangeDeclare(EXCHANGE_TOPICS_INFORM, BuiltinExchangeType.TOPIC); //绑定email通知队列 channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_TOPICS_INFORM,"inform.#.email.#"); //绑定sms通知队列 channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_TOPICS_INFORM,"inform.#.sms.#"); |
3.4.3测试
使用生产者发送若干条消息,交换机根据routingkey统配符匹配并转发消息到指定的队列。
3.4.4思考
1、本案例的需求使用Routing工作模式能否实现?
使用Routing模式也可以实现本案例,共设置三个 routingkey,分别是email、sms、all,email队列绑定email和
all,sms队列绑定sms和all,这样就可以实现上边案例的功能,实现过程比topics复杂。
Topic模式更多加强大,它可以实现Routing、publish/subscirbe模式的功能。
3.5 Header 模式
header模式与routing不同的地方在于,header模式取消routingkey,使用header中的 key/value(键值对)匹配队列。
案例:
根据用户的通知设置去通知用户,设置接收Email的用户只接收Email,设置接收sms的用户只接收sms,设置两种
通知类型都接收的则两种通知都有效。
代码:
1)生产者
队列与交换机绑定的代码与之前不同,如下:
public class Producer { private static final String QUEUE_INFORM_EMAIL = "queue_inform_email"; private static final String QUEUE_INFORM_SMS = "queue_inform_sms"; private static final String EXCHANGE_HEADER_INFORM="exchange_header_inform"; public static void main(String[] args)throws Exception { Connection connection = MyConnectionFactory.getConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_HEADER_INFORM, BuiltinExchangeType.HEADERS); channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null); Map<String, Object> headers_email = new Hashtable<>(); headers_email.put("inform_type", "email"); channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_HEADER_INFORM ,"",headers_email); for (int i = 0; i < 10; i++) { String msg= "email send i------"+i; AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder(); builder.headers(headers_email); channel.basicPublish(EXCHANGE_HEADER_INFORM,"", builder.build(),msg.getBytes()); } channel.close(); connection.close(); } } |
2)发送邮件消费者
public class Consumer01 { private static final String QUEUE_INFORM_EMAIL = "queue_inform_email"; private static final String EXCHANGE_HEADER_INFORM="exchange_header_inform"; public static void main(String[] args)throws Exception { Connection connection = MyConnectionFactory.getConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_HEADER_INFORM, BuiltinExchangeType.HEADERS); channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null); Map<String, Object> headers_email = new Hashtable<>(); headers_email.put("inform_type", "email"); channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_HEADER_INFORM,"",headers_email); DefaultConsumer defaultConsumer =new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(new String(body,"utf-8")); } }; channel.basicConsume(QUEUE_INFORM_EMAIL, true, defaultConsumer); }
}
|
3)测试
生产端
public class Producer { public static void main(String[] args) throws Exception { String exchange = "confirm.exchange"; String routingKey = "confirm.key"; Connection con = MyConnection.getConnection(); Channel channel = con.createChannel(); channel.confirmSelect(); String msg="hello rabbitmq send confirm send"; channel.basicPublish(exchange,routingKey,null,msg.getBytes()); channel.addConfirmListener(new ConfirmListener() { public void handleAck(long l, boolean b) throws IOException { System.out.println("返回确认"); } public void handleNack(long l, boolean b) throws IOException { System.out.println("没有确认"); } }); }
} |
消费端
public class Consumer { public static void main(String[] args) throws Exception { String exchange = "confirm.exchange"; Connection con = MyConnection.getConnection(); final Channel channel = con.createChannel(); String queueName = "test_confirm_queue"; String routingKey = "confirm.#"; channel.exchangeDeclare(exchange, "topic", true, false, null); channel.queueDeclare(queueName, true, false, false, null); channel.queueBind(queueName, exchange, routingKey); 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, "utf-8")); } }; channel.basicConsume(queueName, true, consumer); }
} |
生产者
public class Producer { public static void main(String[] args) throws Exception { String exchange = "return.exchange"; String routingKey = "return.key"; String errorroutingKey = "abc.key";//错误的routingkey Connection con = MyConnection.getConnection(); Channel channel = con.createChannel(); channel.confirmSelect(); String msg="hello rabbitmq send return send"; channel.addReturnListener(new ReturnListener() { public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException { System.err.println("replyCode"+replyCode); System.err.println("replyText"+replyText); System.err.println("exchange"+exchange); System.err.println("routingKey"+routingKey); System.err.println("basicProperties"+basicProperties); } });
// channel.basicPublish(exchange,routingKey,true,null,msg.getBytes()); channel.basicPublish(exchange,errorroutingKey,true,null,msg.getBytes()); }
}
|
消费者
public class Consumer { public static void main(String[] args) throws Exception { String exchange = "return.exchange"; Connection con = MyConnection.getConnection(); final Channel channel = con.createChannel(); String queueName = "test_return_queue"; String routingKey = "return.#"; channel.exchangeDeclare(exchange, "topic", true, false, null); channel.queueDeclare(queueName, true, false, false, null); channel.queueBind(queueName, exchange, routingKey); 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, "utf-8")); } }; channel.basicConsume(queueName, true, consumer); }
} |
3.6消费端限流
生产端
public class Producer { public static void main(String[] args) throws Exception { Connection connection = MyConnectionFactory.getConnection(); Channel channel = connection.createChannel(); String exchange = "test_qos_exchange"; String routingKey = "qos.save"; for (int i = 0; i < 5; i++) { String msg = "Hello rabbitMQ Qos message:"+i; channel.basicPublish(exchange, routingKey, true, null, msg.getBytes()); } }
} |
消费端
public class Consumer { public static void main(String[] args)throws Exception { Connection connection = MyConnectionFactory.getConnection(); final Channel channel = connection.createChannel(); String exchange = "test_qos_exchange"; String queueName="test_qos_queue"; String routingKey="qos.#"; channel.exchangeDeclare(exchange,"topic",true,false,null); channel.queueDeclare(queueName,true,false,false,null); channel.queueBind(queueName,exchange,routingKey); //1.限流方式, channel.basicQos(0,2,false); DefaultConsumer defaultConsumer=new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("===========服务端发送的信息==============="); System.out.println("consumerTag;"+consumerTag); System.out.println("envelope;"+envelope); System.out.println("properties;"+properties); System.out.println("body;"+new String(body)); //手动回复ack,告诉服务端, 以处理完当前的消息, //你可以继续发下一条消息了 //测试程序演示,注释掉改代码, 检查consumer的控制台, //只有一条消息信息,开打注释发现所有的消息一条一条的都接收到了 //false :是否批量处理多条消息 channel.basicAck(envelope.getDeliveryTag(),false); } }; //第一件事就是autoAck设置为false channel.basicConsume(queueName,false,defaultConsumer); }
} |
3.7消费端ACK与重回队列
生产端
public class Producer { public static void main(String[] args) throws Exception { Connection connection = MyConnectionFactory.getConnection(); Channel channel = connection.createChannel(); String exchange = "test_ack_exchange"; String routingKey = "ack.save"; for (int i = 0; i < 5; i++) { String msg = "Hello rabbitMQ ACK message:" + i; Map<String, Object> header = new HashMap<String, Object>(); header.put("num",i); AMQP.BasicProperties properties = new AMQP.BasicProperties() .builder() .deliveryMode(2)// 设置消息是否持久化,1: 非持久化 2:持久化 .contentEncoding("UTF-8") .headers(header).build(); channel.basicPublish(exchange, routingKey, true, properties, msg.getBytes()); //mandatory true时,交换器无法根据自动的类型和路由键找到一个符合条件的队列,那么RabbitMq会调用Basic.Ruturn命令将消息返回给生产都,为false时,出现上述情况消息被直接丢弃 //mandatory 都是消息传递过程中,不可达目的地是,将消息返回给生产者的功能 } }
} |
消费端
public class Consumer { public static void main(String[] args)throws Exception { Connection connection = MyConnectionFactory.getConnection(); final Channel channel = connection.createChannel(); String exchange = "test_ack_exchange"; String queueName="test_ack_queue"; String routingKey="ack.#"; channel.exchangeDeclare(exchange,"topic",true,false,null); channel.queueDeclare(queueName,true,false,false,null); channel.queueBind(queueName,exchange,routingKey); DefaultConsumer defaultConsumer=new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("===========服务端发送的信息==============="); System.out.println("body;"+new String(body)); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } //检查consumer的控制台,发现map中的i=0的消息没有 //ack,consumer 一直能接收到producer的发过来的消息, //basicNack的第三个参数: //true将消息继续放到队列的末尾在此发送, false 不在放到队列中 if((Integer)properties.getHeaders().get("num")==0){ channel.basicNack(envelope.getDeliveryTag(),false,true); }else{ channel.basicAck(envelope.getDeliveryTag(),false); } } }; //手动签收,必须要关闭autoAck= false channel.basicConsume(queueName,false,defaultConsumer); }
}
|