目录
- RabbitMQ
- 消息中间件入门,AMQP 与 RabbitMQ
- RabbitMQ中消息发布 与 权衡
- RabbitMQ 中消息消费权衡 及 消息属性
- RabbitMQ 异步处理实战
- RabbitMQ 管理与高可用集群
- Kafka
- Kafka 入门,生产者 和 消费者实战
- Kafka 高级进阶,消费者分区再均衡
- 深入理解Kafka的内部机制
- Kafka 集群及流计算实战
- RocketMQ
- RocketMQ 入门
- 玩转RocketMQ 中的消息发送 与 消费
- RocketMQ 的高级特性
- RocketMQ 的存储以及高可用集群
- RocketMQ限时订单实战
RabbitMQ中 消息发布(生产者)权衡
1、使用 RabbitMQ 原生 java 客户端进行消息通讯
1.1 集成
客户端需要amqp-client-5.0.0.jar和slf4j-api-1.6.1.jar
建议使用Maven:
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.0.0</version>
</dependency>
注意:5系列的版本最好使用JDK8及以上, 低于JDK8可以使用4.x(具体的版本号到Maven的中央仓库查)的版本。本课程使用5系列版本,其余版本不予理会。
1.2 原生 java 客户端中使用 direct 交换器
生产者 和 消费者 一般用法:
生产者代码示例:
package com.h.rabbitmq.native2.exchange;
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;
/**
* @author h
* @Description 生产者(直连式交换器)
* @createTime 2020年06月13日 09:37
*/
public class DirectProduct {
public static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2.设置工厂连接的地址 和 端口(默认端口为5672)
connectionFactory.setHost("localhost");
// 3.获取连接
Connection connection = connectionFactory.newConnection();
// 4.创建信道
Channel channel = connection.createChannel();
// 5.在信道中设置交换器 名称 交换器类型
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 6.声明路由键、消息体
String routeKey = "king";
String message = "hello rabbitmq";
// 7.发布消息 交换器名称 路由键 消息体
channel.basicPublish(EXCHANGE_NAME, routeKey, null, message.getBytes());
System.out.println("发送消息成功。");
// 8.关闭信道、关闭连接
channel.close();
connection.close();
}
}
消费者代码示例:消费者启动后,会一直监听队列中是否有消息可以消费
package com.h.rabbitmq.native2.exchange;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description 消费者,消费者保持长连接,一直监听队列中是否有消息可消费
* @createTime 2020年06月13日 10:13
*/
public class NormalConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2.设置连接地址
connectionFactory.setHost("localhost");
// 4.获取连接
Connection connection = connectionFactory.newConnection();
// 5.创建信道
Channel channel = connection.createChannel();
// 6.在信道中设置交换器
channel.exchangeDeclare(DirectProduct.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 7.声明队列
String queueName = "queue_king";
channel.queueDeclare(queueName, false, false, false, null);
// 8.绑定,将队列 与 交换器、路由键绑定在一起
String routeKey = "king";
channel.queueBind(queueName, DirectProduct.EXCHANGE_NAME, routeKey);
System.out.println("等待生产者发布消息。。。");
// 9.声明一个消费者
final Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String s, Envelope envelope,
AMQP.BasicProperties basicProperties,
byte[] bytes) throws IOException {
String message = new String(bytes, "UTF-8");
System.out.println("routeKey: " + envelope.getRoutingKey() + ",message: " + message);
}
};
// 10.消费者订阅指定队列上的消息
channel.basicConsume(queueName, true, consumer);
}
}
一个队列绑定到交换器时可以多个路由键,也就是多重绑定
队列 和 交换器 的多重绑定
package com.h.rabbitmq.native2.exchange;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description 一个队列绑定到交换器时可以多个路由键,也就是多重绑定
* @createTime 2020年06月13日 14:32
*/
public class MultiBindConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
// 设置交换器类型
channel.exchangeDeclare(DirectProduct.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 声明一个随机队列
String queueName = channel.queueDeclare().getQueue();
// 队列绑定到交换器上时,时允许绑定多个路由键的,也就是多重绑定。
String[] routeKeys = {"king", "mark", "james"};
for (String routeKey : routeKeys) {
channel.queueBind(queueName, DirectProduct.EXCHANGE_NAME, routeKey);
}
System.out.println(" [*] 多重绑定演示,等待消息。。");
// 创建队列消费者
final Consumer consumerA = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("接收到," + envelope.getRoutingKey() + ", " + message);
}
};
// 通过信道将消费者与队列绑定建立绑定关系,即让消费者订阅队列
channel.basicConsume(queueName, true, consumerA);
}
}
一个连接多个信道
每个信道都可以收到同样的消息
package com.h.rabbitmq.native2.exchange;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description 一个连接多个信道
* 生产者发布的消息,每个信道都可以收到同样的消息
* @createTime 2020年06月13日 14:53
*/
public class MultiChannelConsumer {
private static class ConsumerWorker implements Runnable{
final Connection connection;
public ConsumerWorker(Connection connection){
this.connection = connection;
}
@Override
public void run() {
try {
// 创建一个信道,意味着每个线程单独一个信道
Channel channel = connection.createChannel();
// 信道设置交换器类型
channel.exchangeDeclare(DirectProduct.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 声明一个随机队列
String queueName = channel.queueDeclare().getQueue();
// 消费者名字,
final String consumerName = Thread.currentThread().getName() + "-all";
// 队列绑定到交换器上,是允许绑定多个路由键的,也就是多重绑定
String[] routeKeys = {"king", "mark", "james"};
System.out.println("["+consumerName+"]" + "等待消息。。");
for (String routeKey : routeKeys) {
channel.queueBind(queueName, DirectProduct.EXCHANGE_NAME, routeKey);
}
// 创建队列消费者
final Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(consumerName + " 接收到消息," + envelope.getRoutingKey()
+ ", " + message);
}
};
channel.basicConsume(queueName, true, consumer);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
for (int i = 0; i < 2; i++) {
ConsumerWorker consumerWorker = new ConsumerWorker(connection);
new Thread(consumerWorker).start();
}
}
}
打印结果如下:
[Thread-0-all]等待消息。。
[Thread-1-all]等待消息。。
Thread-0-all 接收到消息,king, hello rabbitmq
Thread-0-all 接收到消息,mark, hello rabbitmq
Thread-0-all 接收到消息,james, hello rabbitmq
Thread-1-all 接收到消息,king, hello rabbitmq
Thread-1-all 接收到消息,mark, hello rabbitmq
Thread-1-all 接收到消息,james, hello rabbitmq
一个队列多个消费者
一个队列有多个消费者,则会表现出消息在消费者之间的轮训发送
package com.h.rabbitmq.native2.exchange.direct;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description 一个队列有多个消费者,则会表现出消息在消费者之间的轮训发送
* @createTime 2020年06月13日 15:21
*/
public class MultiConsumerOneQueue {
private static class ConsumerWorker implements Runnable{
final Connection connection;
final String queueName;
public ConsumerWorker(Connection connection, String queueName) {
this.connection = connection;
this.queueName = queueName;
}
@Override
public void run() {
try {
final Channel channel = connection.createChannel();
channel.exchangeDeclare(DirectProduct.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.queueDeclare(queueName, false, false, false, null);
final String consumerName = Thread.currentThread().getName();
String[] routeKeys = {"king", "mark", "james"};
for (String routeKey : routeKeys) {
channel.queueBind(queueName, DirectProduct.EXCHANGE_NAME, routeKey);
}
System.out.println("等待消息中。。");
final Consumer consumerA = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(consumerName + ", " + envelope.getRoutingKey() + ", " + message);
}
};
channel.basicConsume(queueName, true, consumerA);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
// 3个线程共享一个队列,一个队列多个消费者
String queueName = "focusAll";
for (int i = 0; i < 3; i++) {
ConsumerWorker consumerWorker = new ConsumerWorker(connection, queueName);
new Thread(consumerWorker).start();
}
}
}
1.3 原生 java 客户端中使用 Fanout 交换器
生产者 和 消费者 一般用法
生产者
package com.h.rabbitmq.native2.exchange.fanout;
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;
/**
* @author h
* @Description 生产者,使用广播式交换器
* @createTime 2020年06月13日 15:39
*/
public class FanoutProduct {
public static final String EXCHANGE_NAME_FANOUT = "EXCHANGE_NAME_FANOUT";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME_FANOUT, BuiltinExchangeType.FANOUT);
String[] routeKeys = {"king", "mark", "james"};
// 将路由键与交换器绑定在一起
for(int i=0;i<3;i++){
String routeKey = routeKeys[i%3];
String message = "hello fanout " + i;
// 发布消息
channel.basicPublish(EXCHANGE_NAME_FANOUT, routeKey, null, message.getBytes());
System.out.println("已发送消息" + message);
}
channel.close();
connection.close();
}
}
消费者1
package com.h.rabbitmq.native2.exchange.fanout;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description fanout 类型交换器,一个队列绑定多个路由键
* @createTime 2020年06月13日 15:53
*/
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(FanoutProduct.EXCHANGE_NAME_FANOUT, BuiltinExchangeType.FANOUT);
String queueName = channel.queueDeclare().getQueue();
String[] routeKeys = {"king", "mark", "james"};
for (String routeKey : routeKeys) {
channel.queueBind(queueName, FanoutProduct.EXCHANGE_NAME_FANOUT, routeKey);
System.out.println("等待消息。。");
}
final Consumer consumerA = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(envelope.getRoutingKey() + " 消费到消息, " + message);
}
};
channel.basicConsume(queueName, true, consumerA);
}
}
消费者2
package com.h.rabbitmq.native2.exchange.fanout;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description fanout 类型交换器,一个队列绑定多个路由键
* @createTime 2020年06月13日 15:53
*/
public class Consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(FanoutProduct.EXCHANGE_NAME_FANOUT, BuiltinExchangeType.FANOUT);
String queueName = channel.queueDeclare().getQueue();
// 广播式交换器,不管消费者设置的绑定路由键是否正确,都可以消费到消息。
String routeKey = "xxx";
channel.queueBind(queueName, FanoutProduct.EXCHANGE_NAME_FANOUT, routeKey);
System.out.println("等待消息。。");
final Consumer consumerA = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(envelope.getRoutingKey() + " 消费到消息, " + message);
}
};
channel.basicConsume(queueName, true, consumerA);
}
}
看看路由键有无影响?
广播式交换器,不管消费者设置的绑定路由键是否正确,都可以消费到消息。
1.4 使用 RabbitMQ 原生 java 客户端进行消息通讯
路由键中的 “*” 和 “#”
”.”将路由键分为了几个标识符, “*”匹配1个,“#”匹配一个或多个
生成者 和 消费者使用 Topic,看看路由键中的 “*” 和 “#” 的实际效果
#
king.#
#.B
king.#.A 和 king.*.A
#.kafka.#
king.kafka.A
topic 类型交换器,生产者:
package com.h.rabbitmq.native2.exchange.topic;
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;
/**
* @author h
* @Description
* Topic类型的生产者
* 假设有交换器 topic_course,
* 讲课老师有king,mark,james,
* 技术专题有kafka,jvm,redis,
* 课程章节有 A、B、C,
* 路由键的规则为 讲课老师+“.”+技术专题+“.”+课程章节,如:king.kafka.A。
* 生产者--生产全部的消息3*3*3=27条消息
* @createTime 2020年06月13日 16:18
*/
public class TopicProduct {
public static final String EXCHANGE_NAME_TOPIC = "EXCHANGE_NAME_TOPIC";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME_TOPIC, BuiltinExchangeType.TOPIC);
String[] techers = {"king", "mark", "james"};
String[] modules = {"kafka", "jvm", "redis"};
String[] servers = {"A", "B", "C"};
for (int i = 0; i < 3; i++) {
for (int j = 0; j < modules.length; j++) {
for (int k = 0; k < servers.length; k++) {
String message = "hello topic_["+i+","+j+","+k + "_]";
String routeKey = techers[i%3] + "." + modules[j%3] + "." + servers[k%3];
channel.basicPublish(EXCHANGE_NAME_TOPIC, routeKey, null, message.getBytes());
System.out.println("[x] 发送 " + routeKey + ", " + message);
}
}
}
channel.close();
connection.close();
}
}
消费者:
package com.h.rabbitmq.native2.exchange.topic;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description 关注所有课程
* @createTime 2020年06月13日 16:50
*/
public class TopicConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
final Channel channel = connection.createChannel();
channel.exchangeDeclare(TopicProduct.EXCHANGE_NAME_TOPIC, BuiltinExchangeType.TOPIC);
String queueName = channel.queueDeclare().getQueue();
//绑定队列,# 号匹配所有的路由键
// channel.queueBind(queueName, TopicProduct.EXCHANGE_NAME_TOPIC, "#");
// 匹配所有 kafka 相关的路由键
// channel.queueBind(queueName, TopicProduct.EXCHANGE_NAME_TOPIC, "#.kafka.#");
// 匹配所有以 B 结尾的路由键
// channel.queueBind(queueName, TopicProduct.EXCHANGE_NAME_TOPIC, "#.B");
// 匹配所有以 king 开头, A 结尾的路由键
// channel.queueBind(queueName, TopicProduct.EXCHANGE_NAME_TOPIC, "king.#.A");
// 匹配所有以 king 开头 的路由键
// channel.queueBind(queueName, TopicProduct.EXCHANGE_NAME_TOPIC, "king.#");
// 匹配king.kafka.A的路由键
// channel.queueBind(queueName, TopicProduct.EXCHANGE_NAME_TOPIC, "king.kafka.A");
// 匹配 king.* 的路由键,不存在匹配的路由键
channel.queueBind(queueName, TopicProduct.EXCHANGE_NAME_TOPIC, "king.*");
System.out.println("等待生产者发送消息");
final Consumer consumerA = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(envelope.getRoutingKey() + ", " + message);
}
};
channel.basicConsume(queueName, true, consumerA);
}
}
2、生产者-消息发布时的权衡
不做任何配置的情况下,生产者是不知道消息是否真正到达RabbitMQ,也就是说消息发布操作不返回任何消息给生产者。怎么保证我们消息发布的可靠性?有以下几种常用机制。
2.1 失败通知
发送消息时,设置 mandatory 标志。
生产者代码示例:
package com.h.rabbitmq.native2.exchange.producer_balance.mandatory;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description 生产者,失败确认模式
* @createTime 2020年06月13日 17:46
*/
public class ProducerMandatory {
public static final String EXCHANGE_NAME = "mandatory_test";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 连接关闭,回调
connection.addShutdownListener(new ShutdownListener() {
@Override
public void shutdownCompleted(ShutdownSignalException cause) {
}
});
// 信道关闭,回调
channel.addShutdownListener(new ShutdownListener() {
@Override
public void shutdownCompleted(ShutdownSignalException cause) {
}
});
// 失败确认通知
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int replyCode, String replyText,
String exchange, String routingKey,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
System.out.println("返回的replyCode " + replyCode);
System.out.println("返回的replyText " + replyText);
System.out.println("返回的exchange " + exchange);
System.out.println("返回的routingKey " + routingKey);
}
});
String[] routeKets = {"king", "mark", "james"};
for (int i = 0; i < routeKets.length; i++) {
String routingKey = routeKets[i];
String message = "mandatory_test " + i;
channel.basicPublish(EXCHANGE_NAME, routingKey, true, null, message.getBytes());
System.out.println("---------------------------------");
System.out.println("发送消息成功。" + routingKey + ", " + message);
// 这里不休眠为什么会没有回调?
Thread.sleep(200);
}
channel.close();
connection.close();
}
}
消费者代码示例:
package com.h.rabbitmq.native2.exchange.producer_balance.mandatory;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description TODO
* @createTime 2020年06月13日 17:59
*/
public class ConsumerProducerMandatory {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(ProducerMandatory.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, ProducerMandatory.EXCHANGE_NAME, "king");
System.out.println("等待生产者,发布消息");
final Consumer consumerA = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(envelope.getRoutingKey() + ", " + message);
}
};
channel.basicConsume(queueName, consumerA);
}
}
2.2 事务
事务的实现主要是对信道(Channel)的设置
主要分为:启动事务、提交事务、回滚事务。
但是事务机制本身也会带来问题:
- 1,严重的性能问题,性能下降 2-10 倍不等
- 2,使生产者应用程序产生同步
生产者使用事务代码示例:
package com.h.rabbitmq.native2.exchange.producer_balance.transaction;
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;
/**
* @author h
* @Description 生产者-事务模式
* 使用事务的方式,性能会下降 2-10 倍不等
* @createTime 2020年06月13日 21:13
*/
public class ProducerTransaction {
public static final String EXCHANGE_NAME = "producer_transaction";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
String[] routeKeys = {"king", "mark", "james"};
try {
// 加入事务
channel.txSelect();
for (int i = 0; i < routeKeys.length; i++) {
String message = "hello world, " + i;
String routeKey = routeKeys[i];
channel.basicPublish(EXCHANGE_NAME, routeKey, null, message.getBytes());
System.out.println("发送消息," + routeKey + ", " + message);
Thread.sleep(200);
}
channel.txCommit();
} catch (Exception e) {
channel.txRollback();
System.out.println("事务回滚...");
}
channel.close();
connection.close();
}
}
消费者代码示例:
package com.h.rabbitmq.native2.exchange.producer_balance.transaction;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
*类说明:消费者——事务模式
*/
public class ConsumerTransaction {
public static void main(String[] argv)
throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
// 打开连接和创建频道,与发送端一样
Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.exchangeDeclare(ProducerTransaction.EXCHANGE_NAME,
BuiltinExchangeType.DIRECT);
String queueName = "producer_confirm";
channel.queueDeclare(queueName,false,false,
false,null);
//只关注king的日志
String routekey="king";
channel.queueBind(queueName, ProducerTransaction.EXCHANGE_NAME,
routekey);
System.out.println(" [*] Waiting for messages......");
// 创建队列消费者
final Consumer consumerB = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
//记录日志到文件:
System.out.println( "Received ["+ envelope.getRoutingKey()
+ "] "+message);
}
};
channel.basicConsume(queueName, true, consumerB);
}
}
2.3 发送方确认模式
疑问:
- 发布一个无法路由的路由键,走了失败通知,还会返回 ACK 这个是有问题的吗
发送方确认模式比事务模式更轻量,性能影响几乎可以忽略不计。
2.3.1 一般确认
生产者代码示例:
package com.h.rabbitmq.native2.exchange.producer_balance.producerconfirm;
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;
/**
* @author h
* @Description 发送方确认模式-一般确认(单个确认)
* @createTime 2020年06月14日 09:51
*/
public class ProducerConfirm {
public static final String EXCHANGE_NAME = "producer_confirm";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.confirmSelect();
for (int i = 0; i < 2; i++) {
String message = "hello producer confirm";
String routeKey = "king";
channel.basicPublish(EXCHANGE_NAME, routeKey, null, message.getBytes());
System.out.println("发送消息成功。。" + routeKey + ", " + message);
// 发送方确认
if(channel.waitForConfirms()) {
System.out.println("发送方确认, 发送成功");
}else {
System.out.println("发送方确认, 发送失败");
}
}
channel.close();
connection.close();
}
}
2.3.2 批量确认
package com.h.rabbitmq.native2.exchange.producer_balance.producerconfirm;
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;
/**
* @author h
* @Description 发送者确认-批量确认
* @createTime 2020年06月14日 10:15
*/
public class ProducerBatchConfirm {
public static final String EXCHANGE_NAME = "producer_batch_confirm";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.confirmSelect();
for (int i = 0; i < 2; i++) {
String message = "hello producer batch confirm" + i;
String routeKey = "king";
channel.basicPublish(EXCHANGE_NAME, routeKey, null, message.getBytes());
System.out.println("消息已发送 " + routeKey + ", " + message);
}
// 批量确认
channel.waitForConfirmsOrDie();
channel.close();
connection.close();
}
}
2.3.3 异步监听确认
package com.h.rabbitmq.native2.exchange.producer_balance.producerconfirm;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description 发送者确认-异步确认
* @createTime 2020年06月14日 10:29
*/
public class ProducerConfirmAsync {
public static final String EXCHANGE_NAME = "producer_confirm_async";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 开启发送者确认模式
channel.confirmSelect();
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("发送成功,回调ACK。。,id:" + deliveryTag + ", 是否批量确认:" + multiple);
}
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("发送失败,回调NACK。。。" + deliveryTag + ", " + multiple);
}
});
for (int i = 0; i < 2; i++) {
String message = "hello producer confirm async" + i;
String routeKey = "king";
channel.basicPublish(EXCHANGE_NAME, routeKey, null, message.getBytes());
System.out.println("消息已发送 " + routeKey + ", " + message);
}
// channel.close();
// connection.close();
}
}
2.4 备用交换器
如果主交换器无法路由消息,那么消息将被路由到这个新的备用交换器。
注意:如果有设置mandatory标志,会怎样?
- 不会走路由键失败通知,因为发布的消息无法路由时,都到备用交换器中了。
代码示例,生产者:
package com.h.rabbitmq.native2.exchange.producer_balance.backupexchange;
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.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description 备用交换器-生产者
* @createTime 2020年06月14日 11:08
*/
public class BackupExchangeProducer {
public static final String EXCHANGE_NAME = "main-exchange";
public static final String BAK_EXCHANGE_NAME = "bak-exchange";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
Map<String, Object> hashMap = new HashMap<>();
hashMap.put("alternate-exchange", BAK_EXCHANGE_NAME);
// 主交换器
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT,
false, false, hashMap);
// 备用交换器
channel.exchangeDeclare(BAK_EXCHANGE_NAME, BuiltinExchangeType.FANOUT,
true, false, null);
// 所有消息
String[] routeKeys = {"king", "mark", "james"};
for (int i = 0; i < 3; i++) {
String routeKey = routeKeys[i % 3];
String message = "hello back Exchange " + i;
channel.basicPublish(EXCHANGE_NAME, routeKey, null, message.getBytes());
System.out.println("消息已发送, " + routeKey + ", " + message);
}
channel.close();
connection.close();
}
}
代码示例,主消费者:
package com.h.rabbitmq.native2.exchange.producer_balance.backupexchange;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description 主消费者
* @createTime 2020年06月17日 20:57
*/
public class MainConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
final Channel channel = connection.createChannel();
// 声明一个队列
String queueName = "backupExchange";
String routingKey = "king";
channel.queueDeclare(queueName, false, false,
false, null);
channel.queueBind(queueName, BackupExchangeProducer.EXCHANGE_NAME, routingKey);
System.out.println("等待生产者发布消息。。。");
final Consumer consumerB = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("消费者消费消息:" + envelope.getRoutingKey() + ": " + message);
}
};
channel.basicConsume(queueName, true, consumerB);
}
}
代码示例,备用交换器:
package com.h.rabbitmq.native2.exchange.producer_balance.backupexchange;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description 备用交换器,消费者
* @createTime 2020年06月17日 20:48
*/
public class BackupExConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
final Channel channel = connection.createChannel();
// 声明一个队列
String queueName = "fetchOther";
channel.queueDeclare(queueName, false,
false, false, null);
channel.queueBind(queueName, BackupExchangeProducer.BAK_EXCHANGE_NAME, "#");
System.out.println("备用,等待生产者发布消息。。。");
final Consumer consumerB = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("消费者消费消息:" + envelope.getRoutingKey() + ": " + message);
}
};
channel.basicConsume(queueName, true, consumerB);
}
}
2.5 消息发布时的权衡-总结
3、消费者–消费消息时的权衡
3.1 消息消费时的方式
3.1.1 消息的获取方式
拉取 Get(不推荐)
代码示例,生产者:
package com.h.rabbitmq.native2.exchange.consumer_balance.geimessage;
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;
/**
* @author h
* @Description 普通生产者
* @createTime 2020年06月17日 22:27
*/
public class GetMessageProducer {
public static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
for (int i = 0; i < 3; i++) {
String message = "hello get message producer " + i;
channel.basicPublish(EXCHANGE_NAME, "error", null, message.getBytes());
System.out.println("发送消息," + message);
}
channel.close();
connection.close();
}
}
代码示例,消费者:
package com.h.rabbitmq.native2.exchange.consumer_balance.geimessage;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description 消费者-拉取模式
* @createTime 2020年06月17日 22:33
*/
public class GetMessageConsumer {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(GetMessageProducer.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// String queueName = channel.queueDeclare().getQueue(); // 这里不能使用随机队列,否则消息不进行手动确认,也不会有问题
String queueName = "focusError";
channel.queueDeclare(queueName, false, false, false, null);
channel.queueBind(queueName, GetMessageProducer.EXCHANGE_NAME, "error");
System.out.println("等待生产者发布消息。。。");
while (true) {
// 拉取一条,autoAck 设置为false表示需要手动确认,设置为true时表示自动确认
GetResponse response = channel.basicGet(queueName, false);
if(null != response) {
System.out.println("RoutingKey:" + response.getEnvelope().getRoutingKey()
+ ": " + new String(response.getBody(), "UTF-8"));
}
// 确认(手动),消息确认后rabbitMQ认为这条消息已经被消费,会从队列中删除
channel.basicAck(0, true);
Thread.sleep(1000);
}
}
}
推送 Consume
消息消费,避免拉取
消息的应答
自动确认
手动确认
Qos预取模式
代码示例,生产者:
package com.h.rabbitmq.native2.exchange.consumer_balance.qos;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description
* @createTime 2020年06月17日 22:59
*/
public class QosProducer {
public static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 发送210条消息,其中第210条数据表示被批消息结束
for (int i = 0; i < 210; i++) {
String message = "hello qos producer " + i;
if(i == 209) {
message = "stop";
}
channel.basicPublish(EXCHANGE_NAME, "error", null, message.getBytes());
System.out.println("发送消息,message: " + message);
}
channel.close();
connection.close();
}
}
代码示例,消费者,单个确认:
package com.h.rabbitmq.native2.exchange.consumer_balance.qos;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description Qos 普通消费者
* @createTime 2020年06月17日 23:04
*/
public class QosConsumerMain {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(QosProducer.EXCHANGE_NAME, "direct");
String queueName = "qos_consumer_main";
channel.queueDeclare(queueName, false, false,
false, null);
channel.queueBind(queueName, QosProducer.EXCHANGE_NAME, "error");
System.out.println("等待生产者发布消息。。。");
final Consumer consumerA = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("消费到消息," + envelope.getRoutingKey() + ":" + message);
// 单条确认
channel.basicAck(envelope.getDeliveryTag(), true);
}
};
// 100 表示一次预取的数量,最后不够100条时则全部预取出来
channel.basicQos(100, true);
// 消费者正式开始在指定的队列上消费消息
channel.basicConsume(queueName, consumerA);
}
}
代码示例,消费者,批量确认:
package com.h.rabbitmq.native2.exchange.consumer_balance.qos;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description 消费者 - Qos 批量确认
* @createTime 2020年06月17日 23:26
*/
public class AosBatchAckConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(QosProducer.EXCHANGE_NAME, "direct");
String queueName = "qos_consumer_main";
channel.queueDeclare(queueName, false, false,
false, null);
channel.queueBind(queueName, QosProducer.EXCHANGE_NAME, "error");
System.out.println("等待生产者发布消息。。。");
BatchAckConsumer batchAckConsumer = new BatchAckConsumer(channel);
channel.basicConsume(queueName, false, batchAckConsumer);
}
static class BatchAckConsumer extends DefaultConsumer {
private int messageCount = 0;
/**
* Constructs a new instance and records its association to the passed-in channel.
*
* @param channel the channel to which this consumer is attached
*/
public BatchAckConsumer(Channel channel) {
super(channel);
System.out.println("批量确认消费者启动。。。");
}
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
// System.out.println("批量消费者--- routeKey " + envelope.getRoutingKey() + " message " + message);
messageCount++;
// 批量确认,50一批
if(messageCount % 50 == 0) {
this.getChannel().basicAck(envelope.getDeliveryTag(), true);
System.out.println("批量消息消费进行消息确认。。。");
}
// 如果是最后一条消息,则把剩余的消息进行确认
if(message.equals("stop")) {
this.getChannel().basicAck(envelope.getDeliveryTag(), true);
System.out.println("批量消费者进行最后业务消息的确认......");
}
}
}
}
Qos 预取单个确认 和 批量确认 同时使用的场景:
注意: 如果Qos 的预取单个确认,和批量确认都同时使用,是轮训消费。
package com.h.rabbitmq.native2.exchange.consumer_balance.qos;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description 消费者 - Qos 批量确认
* @createTime 2020年06月17日 23:26
*/
public class AosOneAckAndBatchAckConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(QosProducer.EXCHANGE_NAME, "direct");
String queueName = "qos_consumer_main";
channel.queueDeclare(queueName, false, false,
false, null);
channel.queueBind(queueName, QosProducer.EXCHANGE_NAME, "error");
System.out.println("等待生产者发布消息。。。");
final Consumer consumerA = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("消费到消息," + envelope.getRoutingKey() + ":" + message);
// 单条确认
channel.basicAck(envelope.getDeliveryTag(), true);
}
};
// 100 表示一次预取的数量,最后不够100条时则全部预取出来
channel.basicQos(100, true);
// 消费者正式开始在指定的队列上消费消息
channel.basicConsume(queueName, consumerA);
BatchAckConsumer batchAckConsumer = new BatchAckConsumer(channel);
channel.basicConsume(queueName, false, batchAckConsumer);
}
static class BatchAckConsumer extends DefaultConsumer {
private int messageCount = 0;
/**
* Constructs a new instance and records its association to the passed-in channel.
*
* @param channel the channel to which this consumer is attached
*/
public BatchAckConsumer(Channel channel) {
super(channel);
System.out.println("批量确认消费者启动。。。");
}
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("批量消费者--- routeKey " + envelope.getRoutingKey() + " message " + message);
messageCount++;
// 批量确认,50一批
if(messageCount % 50 == 0) {
this.getChannel().basicAck(envelope.getDeliveryTag(), true);
System.out.println("批量消息消费进行消息确认。。。");
}
// 如果是最后一条消息,则把剩余的消息进行确认
if(message.equals("stop")) {
this.getChannel().basicAck(envelope.getDeliveryTag(), true);
System.out.println("批量消费者进行最后业务消息的确认......");
}
}
}
}
事务模式
3.2 消息消费的拒绝
3.2.1 消息的拒绝方式
Reject
单条拒绝
Nack
单条 或 批量拒绝
消息的重新投递
requeue=true
消费者拒绝流程示意图:
代码示例,普通生产者:
package com.h.rabbitmq.native2.rejectmsg;
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;
/**
* @author h
* @Description 普通生产者
* @createTime 2020年06月18日 20:25
*/
public class RejectProducer {
public static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
for (int i = 0; i < 10; i++) {
String message = "hello reject message " + (i+1);
String routeKey = "error";
channel.basicPublish(EXCHANGE_NAME, routeKey, null, message.getBytes());
System.out.println("发布消息," + message);
}
channel.close();
connection.close();
}
}
代码示例,普通消费者A:
package com.h.rabbitmq.native2.rejectmsg;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description 普通消费者
* 注意:三个消费者要绑定同一个队列
* @createTime 2020年06月18日 20:35
*/
public class NormalConsumerA {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(RejectProducer.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
String queueName = "focuserError";
channel.queueDeclare(queueName, false, false, false, null);
String routeKey = "error";
channel.queueBind(queueName, RejectProducer.EXCHANGE_NAME, routeKey);
System.out.println("等待生产者发布消息。。。");
final Consumer consumerA = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("收到消息," + envelope.getRoutingKey() + ": " + message);
// 手动确认
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
channel.basicConsume(queueName, false, consumerA);
}
}
代码示例,普通消费者B:
package com.h.rabbitmq.native2.rejectmsg;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description 普通消费者
* 注意:三个消费者要绑定同一个队列
* @createTime 2020年06月18日 20:35
*/
public class NormalConsumerB {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(RejectProducer.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
String queueName = "focuserError";
channel.queueDeclare(queueName, false, false, false, null);
String routeKey = "error";
channel.queueBind(queueName, RejectProducer.EXCHANGE_NAME, routeKey);
System.out.println("等待生产者发布消息。。。");
final Consumer consumerB = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("收到消息," + envelope.getRoutingKey() + ": " + message);
// 手动确认
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
channel.basicConsume(queueName, false, consumerB);
}
}
代码示例,拒绝消费者:
当发生异常时的解决措施
package com.h.rabbitmq.native2.rejectmsg;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description 拒绝消费者,消息重新回到队列中
* 注意:三个消费者要绑定同一个队列
* @createTime 2020年06月18日 20:35
*/
public class RejectlConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(RejectProducer.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
String queueName = "focuserError";
channel.queueDeclare(queueName, false, false, false, null);
String routeKey = "error";
channel.queueBind(queueName, RejectProducer.EXCHANGE_NAME, routeKey);
System.out.println("等待生产者发布消息。。。");
final Consumer rejectConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
try {
String message = new String(body, "UTF-8");
// 模拟业务发生异常
throw new RuntimeException("业务异常,消息消费失败。" + message);
} catch (Exception e) {
// requeue=true 表示,将消息放回到原队列中
channel.basicReject(envelope.getDeliveryTag(), true);
// requeue=false 消息会丢失
// channel.basicReject(envelope.getDeliveryTag(), false);
// Nack 既可以单条,也可以多条拒绝
// channel.basicNack(envelope.getDeliveryTag(), false, true);
e.printStackTrace();
}
}
};
channel.basicConsume(queueName, false, rejectConsumer);
}
}
3.2.2 死信交换器DLX
产生死信消息的场景:
- 消息过期
- 队列达到最大长度
- 消息被拒绝,并且requeue=false
- 示意图如下:
代码示例,普通生产者生产者:
package com.h.rabbitmq.native2.dlx;
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;
/**
* @author h
* @Description 普通生产者
* @createTime 2020年06月18日 21:49
*/
public class DlxProducer {
public static final String EXCAHNGE_NAME = "dlx_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCAHNGE_NAME, BuiltinExchangeType.TOPIC);
String[] routeKeys = {"king", "mark", "james"};
for (int i = 0; i < 3; i++) {
String routeKey = routeKeys[i];
String msg = "hello dlx exchange" + (i+1);
channel.basicPublish(EXCAHNGE_NAME, routeKey, null, msg.getBytes());
System.out.println("生产者发布消息。。。。" + routeKey + ", " + msg);
}
channel.close();
connection.close();
}
}
代码示例,普通消费者,
但是如果自己无法消费的消息,将投入到死信队列中
package com.h.rabbitmq.native2.dlx;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description 普通的消费者,但是自己无法消费的消息,将投入到死信队列中
* @createTime 2020年06月18日 21:58
*/
public class WillMakeDlxConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
final Channel channel = connection.createChannel();
channel.exchangeDeclare(DlxProducer.EXCAHNGE_NAME, BuiltinExchangeType.TOPIC);
// 绑定死信队列交换器
String queueName = "dlx_make";
Map<String, Object> param = new HashMap<>();
param.put("x-dead-letter-exchange", DlxProcessConsumer.DLX_EXCHANGE_NAME);
channel.queueDeclare(queueName, false, false, false, param);
channel.queueBind(queueName, DlxProducer.EXCAHNGE_NAME, "#");
System.out.println("等待生产者发布消息。。。");
final Consumer consumerA = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
// 如果是King的消息确认
if(envelope.getRoutingKey().equals("king")) {
System.out.println("消费消息," + envelope.getRoutingKey() + ", " + message);
channel.basicAck(envelope.getDeliveryTag(), false);
}else {
// 其他消息拒绝 并且设置 requeue=false,成为死信消息
// 这里是模拟异常情况,如何将消息放入死信队列中
System.out.println("will reject "+ envelope.getRoutingKey() + ", " + message);
channel.basicReject(envelope.getDeliveryTag(), false);
}
}
};
// 消费者正式开始消费指定队列上的消息,这里需要手动确认
channel.basicConsume(queueName, false, consumerA);
}
}
代码示例,死信消息消费者:
package com.h.rabbitmq.native2.dlx;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description 普通的消费者,负责消费死信队列 dlx_accept
* @createTime 2020年06月18日 22:02
*/
public class DlxProcessConsumer {
public static final String DLX_EXCHANGE_NAME = "dlx_accept";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(DLX_EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
String queueName = "dlx_accept";
channel.queueDeclare(queueName, false, false,
false, null);
channel.queueBind(queueName, DLX_EXCHANGE_NAME, "#");
System.out.println("等待死信消息产生。。。");
final Consumer consumerB = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("Received dead letter["
+envelope.getRoutingKey()
+"]"+message);
}
};
channel.basicConsume(queueName, true, consumerB);
}
}
4、队列的控制
4.1 临时队列
4.1.1 自动删除队列
- 删除时会把整个队列删除,autoDelete=true
- 自动删除判断条件
- 消费者断开
- 生产者断开
4.1.2 单消费者队列
- 不管一个队列绑定了多少个消费者,都只往一个消费者中投递消息及消息独占
- exclusive=true
4.1.3 自动过期队列
- 设置参数:x-expires
- 从什么时候开始计算这10秒钟?
- 队列没有 Get 操作
- 队列没有 consumer 消费者连上去
- 如果以上两个条件都满足,即使生产者在不断的往队列中送入消息,这个队列也会被删除。
4.2 永久队列
队列的持久性
- 设置参数:durable=true
- 队列每投递一个消息到消费者,都会将这个消息持久化到本地磁盘
- 效率低
4.3 队列级别消息过期
4.4 队列保留参数列表
5、消息的属性
Request - Response 模式
应答模式:
代码示例,生产者:
package com.h.rabbitmq.native2.replyto;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description 消息的属性的控制-生产者
* @createTime 2020年06月23日 21:45
*/
public class ReplyToProducer {
public static final String EXCHANGE_NAME = "replyto";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 声明一个响应队列,消费者将会把要返回的消息发送到该队列中
String responseQueue = channel.queueDeclare().getQueue();
String msgId = UUID.randomUUID().toString();
AMQP.BasicProperties properties = new AMQP.BasicProperties()
.builder()
.replyTo(responseQueue)
.messageId(msgId)
.build();
final Consumer consumerA = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
String msg = new String(body, "UTF-8");
System.out.println("生产者收到反馈:" + envelope.getRoutingKey()
+ ", " + msg);
}
};
// 消费者正式开始在指定的队列上消费消息
channel.basicConsume(responseQueue, true, consumerA);
String msg = "hello rabbitMQ reply to";
channel.basicPublish(EXCHANGE_NAME, "error",
properties,
msg.getBytes());
System.out.println("发送消息成功。。。");
}
}
代码示例,消费者:
package com.h.rabbitmq.native2.replyto;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author h
* @Description 消息的属性的控制-消费者
* @createTime 2020年06月23日 22:03
*/
public class ReplyToConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(ReplyToProducer.EXCHANGE_NAME,
BuiltinExchangeType.DIRECT, false);
String queueName = "replyTo";
channel.queueDeclare(queueName, false, false, false, null);
// 绑定,将队列和交换器通过路由键进行绑定
String routeKey = "error";
channel.queueBind(queueName, ReplyToProducer.EXCHANGE_NAME, routeKey);
System.out.println("等待生产者发布消息。。。");
final Consumer consumerB = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("收到生产者发布的消息:" + envelope.getRoutingKey() + ", "
+ message);
AMQP.BasicProperties respProperties
= new AMQP.BasicProperties
.Builder()
.replyTo(properties.getReplyTo())
.correlationId(properties.getMessageId())
.build();
// 收到生产者发布的消息,做出响应
channel.basicPublish("", respProperties.getReplyTo(),
respProperties, ("hi ," + message).getBytes("UTF-8"));
}
};
// 消费者正式开始在指定的队列上消费
channel.basicConsume(queueName, true, consumerB);
}
}