目录
一些基本概念:
消息队列三大功能:
MQ的四大核心概念:
其他的一些:
基础代码:
生产者部分:
消费者部分:
工作队列:
消息应答:
自动应答:
手动应答:
消息自动重新入队:
代码部分:
RabbitMQ持久化:
队列的持久化:
消息持久化:
发布确认:
不公平发布:
预取值:
发布确认原理:
单个发布确认:
批量发布确认:
异步发布确认:
异步发布确认中未确认消息如何处理:
交换机:
死信队列:
概念:
产生死信的来源/原因:
代码:
延迟队列:
/**
* 生产者
*/
public class Producer {
public static final String Queue_Name="hello";
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//工厂ip 连接mq
connectionFactory.setHost("172.20.10.6");
connectionFactory.setUsername("ljw");
connectionFactory.setPassword("666666");
Connection connection = connectionFactory.newConnection();
//连接需要通过信道发送消息
Channel channel = connection.createChannel();
//通过信道获取队列
//1.队列名、队列中的消息是否需要持久化---默认存内存,持久化后存磁盘;
//2.是否进行消息共享,是否可以被多个消费者共享,true:不共享,false:共享;
//3.是否自动删除
channel.queueDeclare(Queue_Name,false,false,false,null);
String message="hello world";
//1.交换机
//2.路由的key
channel.basicPublish("",Queue_Name,null,message.getBytes(StandardCharsets.UTF_8));
System.out.println("消息发送完毕");
}
}
/**
* 消费者接收消息
*/
public class Consumer {
public static final String Queue_Name = "hello";
//接收消息
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//工厂ip 连接mq
connectionFactory.setHost("172.20.10.6");
connectionFactory.setUsername("ljw");
connectionFactory.setPassword("666666");
Connection connection = connectionFactory.newConnection();
//连接需要通过信道接受消息
Channel channel = connection.createChannel();
//声明 接收消息
DeliverCallback deliverCallback = (var1, var2) -> {
//将byte数组转化为String类型
String message = new String(var2.getBody(), StandardCharsets.UTF_8);;
System.out.println(message);
};
//声明 取消消息
CancelCallback cancelCallback=(var1)->{
System.out.println("消费被中断");
};
//1.消费哪个队列
//2.消费成功以后是否自动应答
//3.消费者收到消息的回调
//4.消费者取消消费的回调
channel.basicConsume(Queue_Name, true,deliverCallback, cancelCallback);
}
}
综上,可以将获取信道的过程封装到工具类里面:
public class RabbitUtils {
public Channel getChannel() throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//工厂ip 连接mq
connectionFactory.setHost("172.20.10.6");
connectionFactory.setUsername("ljw");
connectionFactory.setPassword("666666");
Connection connection = connectionFactory.newConnection();
//连接需要通过信道接受消息
Channel channel = connection.createChannel();
return channel;
}
}
/**
* 生产者
*/
public class Producer {
public static final String QUEUE_NAME="hello";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitUtils.getChannel();
//通过信道获取队列
//1.队列名、队列中的消息是否需要持久化---默认存内存,持久化后存磁盘;
//2.是否进行消息共享,是否可以被多个消费者共享,true:不共享,false:共享;
//3.是否自动删除
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
String message="hello world";
//1.交换机
//2.路由的key
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
scanner=new Scanner(System.in);
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println("消息发送完毕");
}
}
}
运行会发现,消费者是轮询消费多条消息的;
消息发送以后立即被认为已经传送成功,这种情况下,如果消息在接收到之前,消费者的channel关闭了,那么消息就会丢失;另一方面,这种情况没有对传递消息的数量进行限制,那么可能会导致消费者来不及处理消息,导致消息积压内存耗尽,最终使得消费者线程被操作系统杀死。所以这种应答方式仅适用于消费者可以高效并以某种速率处理这些消息的情况下。
public class Producer {
//队列名称
private static final String QUEUE_NAME = "ack_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitUtils.getChannel();
//申明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
Scanner scanner = new Scanner(System.in);
String message = "hello world";
while (scanner.hasNext()) {
scanner = new Scanner(System.in);
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println("消息已发送");
}
}
}
public class Consumer01 {
//队列名称
private static final String QUEUE_NAME = "ack_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitUtils.getChannel();
System.out.println("C1等待接收消息处理时间较短");
//这里写收到消息后如何消费
DeliverCallback deliverCallback = (var1, var2) -> {
try {
Thread.sleep(10000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
String message = new String(var2.getBody(), StandardCharsets.UTF_8);
System.out.println("接收到的消息是:"+message);
//手动应答
//1.消息的标记 tag -->envelope是属性
//2.批量应答
channel.basicAck(var2.getEnvelope().getDeliveryTag(),false);
};
//声明 取消消息
CancelCallback cancelCallback=(var1)->{
System.out.println("消费被中断");
};
//关闭自动应答,采用手动应答
boolean autoAck=false;
channel.basicConsume(QUEUE_NAME,autoAck,deliverCallback,cancelCallback);
}
}
public class Consumer02 {
//队列名称
private static final String QUEUE_NAME = "ack_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitUtils.getChannel();
System.out.println("C2等待接收消息处理时间较长");
//这里写收到消息后如何消费
DeliverCallback deliverCallback = (var1, var2) -> {
try {
Thread.sleep(300000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
String message = new String(var2.getBody(), StandardCharsets.UTF_8);
System.out.println("接收到的消息是:"+message);
//手动应答
//1.消息的标记 tag -->envelope是属性
//2.批量应答
channel.basicAck(var2.getEnvelope().getDeliveryTag(),false);
};
//声明 取消消息
CancelCallback cancelCallback=(var1)->{
System.out.println("消费被中断");
};
//关闭自动应答,采用手动应答
boolean autoAck=false;
channel.basicConsume(QUEUE_NAME,autoAck,deliverCallback,cancelCallback);
}
}
测试后发现,如果在消息处理的过程中某个消费者挂掉了,那么该条消息会重新入队并且很快分给其他消费者;并且如果开启手动应答以后,会在手动应答执行以后才将消息从队列里删除。
将队列申明中的durable参数改为true,那么也就是开启持久化;如果没有开启持久化,那么重启mq以后该队列就不存在了,但是持久化的仍旧存在。
//申明队列
//第二个参数即durable,true则为开启持久化
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
(开启持久化的队列在feature一列会显示“D”)
是在生产者发布消息的时候开启持久化,也就是在props参数加上开启持久化属性(MessageProperties.PERSISTENT_TEXT_PLAIN)。
//设置开启消息持久化(MessageProperties.PERSISTENT_TEXT_PLAIN)
channel.basicPublish("",QUEUE_NAME,
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes(StandardCharsets.UTF_8));
轮询分发相当于是一种公平发布,在这种情况下,如果一个消费者处理消息特别快,一个消费者处理消息特别慢,那么处理消息快的消费者大部分时间是空闲的,而处理消息慢的消费者则是一直处于工作状态,所以这样的发布方式不是非常合理,那么为了避免这种情况,我们可以在每个消费者端设置参数channel.basicQos(int prefetchCount=1)(默认是0,也就是轮询分发),从而变为不公平发布,也就相当于能者多劳,也就是一个消费者只能同一时刻只能处理一个消息,要是目前的消息还没处理好,就不会分给他新的消息;注意:此设置需要在手动应答的时候才生效;
//consumer01
int prefetchCount=3;
channel.basicQos(prefetchCount);
//consumer02
int prefetchCount=3;
channel.basicQos(prefetchCount);
//开启发布确认
channel.confirmSelect();
//单个确认
public static void publishMessageIndividually() throws IOException, TimeoutException, InterruptedException {
Channel channel = RabbitUtils.getChannel();
//通过uuid获取一个随机的队列名
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName, true, false, false, null);
//开启发布确认
channel.confirmSelect();
//开始时间
long begin = System.currentTimeMillis();
//批量发消息
for (int i=0;i
//批量确认
public static void publishMessageBatch() throws IOException, TimeoutException, InterruptedException {
Channel channel = RabbitUtils.getChannel();
//通过uuid获取一个随机的队列名
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName, true, false, false, null);
//开启发布确认
channel.confirmSelect();
//开始时间
long begin = System.currentTimeMillis();
//设置批量确认消息的数量
int batchCount = 100;
//批量发消息
for (int i = 1; i <= MESSAGE_COUNTS; i++) {
String message = i + "";
channel.basicPublish("", queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
//判断每到达100条的时候,批量确认一次
if (i % batchCount == 0) {
boolean flag = channel.waitForConfirms();
if (flag) {
System.out.println("发布成功");
}
}
}
//结束时间
long end = System.currentTimeMillis();
System.out.println("发布" + MESSAGE_COUNTS + "个批量发布确认消息用时:" + (end - begin) + "ms");
}
代码如图
//异步发布确认
public static void publishMessageAsync() throws IOException, TimeoutException, InterruptedException {
Channel channel = RabbitUtils.getChannel();
//通过uuid获取一个随机的队列名
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName, true, false, false, null);
//开启发布确认
channel.confirmSelect();
//开始时间
long begin = System.currentTimeMillis();
//设置批量确认消息的数量
int batchCount = 100;
//消息确认成功 回调函数
ConfirmCallback ackCallback=(var1,var3)->{
System.out.println("已确认消息:"+var1);
};
//消息确认失败,回调函数
ConfirmCallback nackCallback=(var1,var3)->{
System.out.println("未确认消息:"+var1);
};
//准备消息监听器,发送的时候就开始监听--->异步的
channel.addConfirmListener(ackCallback,nackCallback);
//批量发消息
for (int i = 1; i <= MESSAGE_COUNTS; i++) {
String message = i + "";
channel.basicPublish("", queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
}
//结束时间
long end = System.currentTimeMillis();
System.out.println("发布" + MESSAGE_COUNTS + "个异步发布确认消息用时:" + (end - begin) + "ms");
}
//异步发布确认
public static void publishMessageAsync() throws IOException, TimeoutException, InterruptedException {
Channel channel = RabbitUtils.getChannel();
//通过uuid获取一个随机的队列名
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName, true, false, false, null);
//开启发布确认
channel.confirmSelect();
/**线程安全有序的一个哈希表,适用于高并发的情况
* 1.将消息和序号进行关联
* 2.通过给到序号从而删除消息
* 3.支持高并发
*/
ConcurrentSkipListMap outstandingConfirms=new ConcurrentSkipListMap<>();
//设置批量确认消息的数量
int batchCount = 100;
//消息确认成功 回调函数
ConfirmCallback ackCallback = (var1, var3) -> {
//批量确认
if (var3){
//2.删除掉已经确认的消息 剩下未确认的消息
ConcurrentNavigableMap longStringConcurrentNavigableMap
= outstandingConfirms.headMap(var1);
longStringConcurrentNavigableMap.clear();
}else {
//单条确认
outstandingConfirms.remove(var1);
}
System.out.println(outstandingConfirms);
System.out.println("已确认消息:" + var1);
};
//消息确认失败,回调函数
ConfirmCallback nackCallback = (var1, var3) -> {
//3.可以在这里打印未确认的消息
System.out.println("未确认消息:" + var1);
};
//准备消息监听器,发送的时候就开始监听--->异步的
channel.addConfirmListener(ackCallback, nackCallback);
//开始时间,因为是异步的,所以放在发送上面开始记录;
long begin = System.currentTimeMillis();
//批量发消息
for (int i = 1; i <= MESSAGE_COUNTS; i++) {
String message = i + "";
//1.记录所有发布的消息
outstandingConfirms.put(channel.getNextPublishSeqNo(),message);
channel.basicPublish("", queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
}
//结束时间
long end = System.currentTimeMillis();
System.out.println("发布" + MESSAGE_COUNTS + "个异步发布确认消息用时:" + (end - begin) + "ms");
}
channel.queueDeclare().getQueue();
交换机和队列通过路由键进行绑定;
public class Producer {
private final static String EXCHANGE_NAME = "log";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitUtils.getChannel();
/**
* 1.交换机名称
* 2.交换机类型
*/
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String message = scanner.next();
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes(StandardCharsets.UTF_8));
System.out.println("生产者已成功发送消息:" + message);
scanner = new Scanner(System.in);
}
}
}
public class Consumer01 {
private final static String EXCHANGE_NAME = "log";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitUtils.getChannel();
/**
* 1.交换机名称
* 2.交换机类型
*/
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
//声明一个临时队列
String queue = channel.queueDeclare().getQueue();
/**
* 绑定交换机和队列
* 1.队列名称
* 2.交换机名称
* 3.路由键(fanout型可以不需要路由键,因为所有绑定的都会收到)
*/
channel.queueBind(queue, EXCHANGE_NAME, "");
DeliverCallback deliverCallback = (var1, var2) -> {
String message = new String(var2.getBody(), StandardCharsets.UTF_8);
System.out.println(message);
};
CancelCallback cancelCallback = (var1) -> {
System.out.println("消费被中断");
};
channel.basicConsume(queue, true, deliverCallback, cancelCallback);
}
}
public class Consumer02 {
private final static String EXCHANGE_NAME="log";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitUtils.getChannel();
/**
* 1.交换机名称
* 2.交换机类型
*/
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
//声明一个临时队列
String queue = channel.queueDeclare().getQueue();
/**
* 绑定交换机和队列
* 1.队列名称
* 2.交换机名称
* 3.路由键(fanout型可以不需要路由键,因为所有绑定的都会收到)
*/
channel.queueBind(queue,EXCHANGE_NAME,"");
DeliverCallback deliverCallback = (var1, var2) -> {
String message = new String(var2.getBody(), StandardCharsets.UTF_8);
System.out.println(message);
};
CancelCallback cancelCallback = (var1) -> {
System.out.println("消费被中断");
};
channel.basicConsume(queue,true,deliverCallback,cancelCallback);
}
}
此时一条消息就可以被两个消费者所消费;
死信也就是无法被消费的信息,一般是指生产者将消息投递到了broker或者是queue中了,但是由于某些原因消费者无法消费这些消息。如果没有对这些消息进行处理,那么它们就会变成死信。
消息TTL(存活时间)过期;
队列达到最大长度;
消息被拒绝(手动应答的是拒绝应答/否定应答);
/**
* 死信队列
*/
public class Producer {
private final static String NORMAL_EXCHANGE_NAME = "normal_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitUtils.getChannel();
//设置过期时间
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build();
for (int i = 0; i < 11; i++) {
String message = "info:" + i;
channel.basicPublish(NORMAL_EXCHANGE_NAME, "zhangsan",properties,message.getBytes(StandardCharsets.UTF_8));
System.out.println("生产者已成功发送消息:" + message);
}
}
}
//是在这个消费者中声明的交换机,所以应该先执行这个消费者
public class Consumer01 {
private final static String NORMAL_EXCHANGE_NAME = "normal_exchange";
private final static String DEAD_EXCHANGE_NAME = "dead_exchange";
private final static String NORMAL_QUEUE_NAME = "normal-queue";
private final static String DEAD_QUEUE_NAME = "dead-queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitUtils.getChannel();
/**
* 1.交换机名称
* 2.交换机类型
*/
channel.exchangeDeclare(NORMAL_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//声明队列
//普通队列需要设arguments参数,这样才能转发给死信交换机
HashMap arguments = new HashMap();
//过期时间,也可以在生产者那里设置
//arguments.put("x-message-ttl",10000);
//正常队列设置对应的死信交换机
arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
arguments.put("x-dead-letter-routing-key", "lisi");
//还要设置死信的路由键
channel.queueDeclare(NORMAL_QUEUE_NAME, false, false, false, arguments);
///
//死信队列
channel.queueDeclare(DEAD_QUEUE_NAME, false, false, false, null);
/**
* 绑定交换机和队列
* 1.队列名称
* 2.交换机名称
* 3.路由键
*/
channel.queueBind(NORMAL_QUEUE_NAME, NORMAL_EXCHANGE_NAME, "zhangsan");
channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE_NAME, "lisi");
DeliverCallback deliverCallback = (var1, var2) -> {
String message = new String(var2.getBody(), StandardCharsets.UTF_8);
System.out.println(message);
};
CancelCallback cancelCallback = (var1) -> {
System.out.println("消费被中断");
};
channel.basicConsume(NORMAL_QUEUE_NAME, true, deliverCallback, cancelCallback);
}
}
//这个消费者是用来消费死信队列中的消息的
public class Consumer02 {
private final static String DEAD_QUEUE_NAME = "dead-queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitUtils.getChannel();
DeliverCallback deliverCallback = (var1, var2) -> {
String message = new String(var2.getBody(), StandardCharsets.UTF_8);
System.out.println(message);
};
CancelCallback cancelCallback = (var1) -> {
System.out.println("消费被中断");
};
channel.basicConsume(DEAD_QUEUE_NAME, true, deliverCallback, cancelCallback);
}
}
//设置队伍最大长度
//在队列声明的arguments参数中加入
//这是指消息堆积了6条的情况,如果消息处理的够快就不会堆积,也就不会分给死信
arguments.put("x-max-length",6);
情况3:消息被拒绝:
//手动应答,拒绝符合条件的消息
DeliverCallback deliverCallback = (var1, var2) -> {
String message = new String(var2.getBody(), StandardCharsets.UTF_8);
System.out.println(message);
if (message.equals("info:5")){
System.out.println("拒绝此消息:"+message);
channel.basicReject(var2.getEnvelope().getDeliveryTag(),false);
}else {
System.out.println("接受此消息"+message);
channel.basicAck(var2.getEnvelope().getDeliveryTag(),false);
}
};
CancelCallback cancelCallback = (var1) -> {
System.out.println("消费被中断");
};
channel.basicConsume(NORMAL_QUEUE_NAME, false, deliverCallback, cancelCallback);
}