文章目录
- 工作模型
- 流量控制
- 服务端控制
- 简单队列
- 生产者
- 消费者
- 工作队列
- 原理
- 消息应答
- 自动应答
- 手动应答
- 持久化
- 消息分发
- 发布确认
- 发布确认方法
- 单个确认发布
- 批量确认发布
- 异步批量确认发布
- 交换机
- Exchanges
- 类型
- 无名exchange
- 临时队列
- 绑定
- fanout
- 生产者代码
- 消费者代码
- Direct exchange
- 生产者代码
- 消费者代码
- Topic
- 生产者代码
- 消费者1代码
- 消费者2代码
- 死信队列
- 生产者
- 正常消费者
- 死信消费者
- 延迟队列
- 基于死信
- 基于插件
- 总结
- 发布确认高级
- 确认机制方案
- 回退消息
- 备份交换机
- 幂等性
- 优先级队列
- 惰性队列
工作模型
流量控制
服务端控制
达到一定长度或者容量后,删除最老的数据
- x-max-length
- x-max-length-bytes
- 内存控制:vm_memory_high_watermark
- 磁盘控制:disk_free_limit.relative disk_free_limit.absolute
简单队列
生产者发消息到队列,消费者去队列中取消息
生产者
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* 生产者
*
* @author: 轩辕龙儿
* @date: 2021/7/20 11:22
* @Description: No Description
*/
public class Product {
// 队列名称
private static final String QUEUE_NAME = "test";
/**
* 发消息
* @param args
*/
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
/**
* rabbitmq的连接地址、用户名、密码
*/
factory.setHost("rabbitmq的连接地址");
factory.setUsername("rabbitmq的用户名");
factory.setPassword("rabbitmq的密码");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
/**
* 生成一个队列
* 1、队列名称
* 2、队列里面的消息算法持久化
* 3、该队列算法进行消息共享
* 4、是否自动删除,最后一个消费者断开连接后,该队列是否自动删除
* 5、其他参数
*/
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
String message = "Hello World";
/**
* 发送一个消费
* 1、发送到哪一个交换机
* 2、路由的key值是哪个
* 3、其他参数信息
* 4、发送消息的消息体
*/
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
System.out.println("发送完!");
}
}
消费者
import com.rabbitmq.client.*;
/**
* 消费者
*
* @author: 轩辕龙儿
* @date: 2021/7/20 13:19
* @Description: No Description
*/
public class Consumer {
// 队列名称
private static final String QUEUE_NAME = "test";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
/**
* rabbitmq的连接地址、用户名、密码
*/
factory.setHost("rabbitmq的连接地址");
factory.setUsername("rabbitmq的用户名");
factory.setPassword("rabbitmq的密码");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 声明接收消息
DeliverCallback deliverCallback = (consumTag,message)->{
System.out.println(new String(message.getBody()));
};
// 取消消息时的回调
CancelCallback cancelCallback = consumTag->{
System.out.println("消息消费呗回调");
};
/**
* 消费者消费消息
* 1、消费哪个队列
* 2、消费成功后是否要自动应答
* 3、消费者未成功消费的回调
* 4、消费者取录消费的回调
*/
channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
}
}
工作队列
原理
消息应答
自动应答
消息发送后立即被认为已经传送成功,这种模式需要在高吞吐量和数据传输安全性方面做权衡,因为这种模式如果消息在接收到之前,消费者那边出现连接或者 channel 关闭,那么消息就丢失了,当然另一方面这种模式消费者那边可以传递过载的消息,没有对传递的消息数量进行限制, 当然这样有可能使得消费者这边由于接收太多还来不及处理的消息,导致这些消息的积压,最终使得内存耗尽,最终这些消费者线程被操作系统杀死,所以这种模式仅适用在消费者可以高效并以某种速率能够处理这些消息的情况下使用。
手动应答
手动应答的好处是可以批量应答并且减少网络拥堵
- Channel.basicAck(用于肯定确认)
RabbitMQ 已知道该消息并且成功的处理消息,可以将其丢弃了 - Channel.basicNack(用于否定确认)
- Channel.basicReject(用于否定确认)
与 Channel.basicNack 相比少一个参数,不处理该消息了直接拒绝,可以将其丢弃了
持久化
- 队列持久化
- 消息持久化
消息分发
- 轮训
- 不公平分发
- 预取值
发布确认
发布确认方法
- 单个确认发布
- 批量确认发布
- 异步批量确认发布
单个确认发布
public static void publishMessageIndividually() throws Exception {
try (Channel channel = RabbitMqUtils.getChannel()) {
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName, false, false, false, null);
//开启发布确认
channel.confirmSelect();
long begin = System.currentTimeMillis();
for (int i = 0; i < MESSAGE_COUNT; i++) {
String message = i + "";
channel.basicPublish("", queueName, null, message.getBytes());
//服务端返回 false 或超时时间内未返回,生产者可以消息重发
boolean flag = channel.waitForConfirms();
if (flag) {
System.out.println("消息发送成功");
}
}
long end = System.currentTimeMillis();
System.out.println("发布" + MESSAGE_COUNT + "个单独确认消息,耗时" + (end - begin) +
"ms");
}
}
批量确认发布
public static void publishMessageBatch() throws Exception {
try (Channel channel = RabbitMqUtils.getChannel()) {
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName, false, false, false, null);
//开启发布确认
channel.confirmSelect();
//批量确认消息大小
int batchSize = 100;
//未确认消息个数
int outstandingMessageCount = 0;
long begin = System.currentTimeMillis();
for (int i = 0; i < MESSAGE_COUNT; i++) {
String message = i + "";
channel.basicPublish("", queueName, null, message.getBytes());
outstandingMessageCount++;
if (outstandingMessageCount == batchSize) {
channel.waitForConfirms();
outstandingMessageCount = 0;
}
}
//为了确保还有剩余没有确认消息 再次确认
if (outstandingMessageCount > 0) {
channel.waitForConfirms();
}
long end = System.currentTimeMillis();
System.out.println("发布" + MESSAGE_COUNT + "个批量确认消息,耗时" + (end - begin) +
"ms");
}
}
异步批量确认发布
public static void publishMessageAsync() throws Exception {
try (Channel channel = RabbitMqUtils.getChannel()) {
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName, false, false, false, null);
//开启发布确认
channel.confirmSelect();
/**
* 线程安全有序的一个哈希表,适用于高并发的情况
* 1.轻松的将序号与消息进行关联
* 2.轻松批量删除条目 只要给到序列号
* 3.支持并发访问
*/
ConcurrentSkipListMap<Long, String> outstandingConfirms = new
ConcurrentSkipListMap<>();
/**
* 确认收到消息的一个回调
* 1.消息序列号
* 2.true 可以确认小于等于当前序列号的消息
* false 确认当前序列号消息
*/
ConfirmCallback ackCallback = (sequenceNumber, multiple) -> {
if (multiple) {
//返回的是小于等于当前序列号的未确认消息 是一个 map
ConcurrentNavigableMap<Long, String> confirmed = outstandingConfirms.headMap(sequenceNumber, true);
//清除该部分未确认消息
confirmed.clear();
} else {
//只清除当前序列号的消息
outstandingConfirms.remove(sequenceNumber);
}
};
ConfirmCallback nackCallback = (sequenceNumber, multiple) -> {
String message = outstandingConfirms.get(sequenceNumber);
System.out.println("发布的消息" + message + "未被确认,序列号" + sequenceNumber);
};
/**
* 添加一个异步确认的监听器
* 1.确认收到消息的回调
* 2.未收到消息的回调
*/
channel.addConfirmListener(ackCallback, nackCallback);
long begin = System.currentTimeMillis();
for (int i = 0; i < MESSAGE_COUNT; i++) {
String message = "消息" + i;
/**
* channel.getNextPublishSeqNo()获取下一个消息的序列号
* 通过序列号与消息体进行一个关联
* 全部都是未确认的消息体
*/
outstandingConfirms.put(channel.getNextPublishSeqNo(), message);
channel.basicPublish("", queueName, null, message.getBytes());
}
long end = System.currentTimeMillis();
System.out.println("发布" + MESSAGE_COUNT + "个异步确认消息,耗时" + (end - begin) + "ms");
}
}
交换机
Exchanges
类型
- 直接(direct)
- 主题(topic)
- 标题(headers)
- 扇出(fanout)
无名exchange
临时队列
绑定
fanout
一个生产者发送,多个消费者接收
生产者代码
import com.huangge.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
* Created with IntelliJ IDEA.
*
* @author: 轩辕龙儿
* @date: 2021/7/21 10:11
* @Description: No Description
*/
public class Product {
public static final String EXCHANGE_NAME = "logs";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
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);
}
}
}
消费者代码
import com.huangge.rabbitmq.utils.RabbitMqUtils;
import com.huangge.rabbitmq.utils.SleepUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.nio.charset.StandardCharsets;
/**
* Created with IntelliJ IDEA.
*
* @author: 轩辕龙儿
* @date: 2021/7/21 10:05
* @Description: No Description
*/
public class Consumer01 {
public static final String EXCHANGE_NAME = "logs";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, EXCHANGE_NAME, "");
System.out.println("等待接收消息01---------------");
DeliverCallback deliverCallback = (consumTag, message) -> {
System.out.println("接收到的消息:" + new String(message.getBody(), StandardCharsets.UTF_8));
};
// 取消消息时的回调
CancelCallback cancelCallback = consumTag -> {
System.out.println("消息被消费者取消的回调");
};
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
});
}
}
Direct exchange
指定接收者
生产者代码
import com.huangge.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
* Created with IntelliJ IDEA.
*
* @author: 轩辕龙儿
* @date: 2021/7/21 10:11
* @Description: No Description
*/
public class Product {
public static final String EXCHANGE_NAME = "direct";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String message = scanner.next();
channel.basicPublish(EXCHANGE_NAME, "error", null, message.getBytes(StandardCharsets.UTF_8));
System.out.println("发出消息:" + message);
}
}
}
消费者代码
import com.huangge.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.nio.charset.StandardCharsets;
/**
* Created with IntelliJ IDEA.
*
* @author: 轩辕龙儿
* @date: 2021/7/21 10:05
* @Description: No Description
*/
public class Consumer01 {
public static final String EXCHANGE_NAME = "direct";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.queueDeclare("console",false,false,false,null);
channel.queueBind("console", EXCHANGE_NAME, "info");
channel.queueBind("console", EXCHANGE_NAME, "warning");
DeliverCallback deliverCallback = (consumTag, message) -> {
System.out.println("接收到的消息01:" + new String(message.getBody(), StandardCharsets.UTF_8));
};
channel.basicConsume("console", true, deliverCallback, consumerTag -> {
});
}
}
Topic
发送到类型是 topic 交换机的消息的 routing_key 不能随意写,必须满足一定的要求,它必须是一个单词列表,以点号分隔开。这些单词可以是任意单词,比如说:“stock.usd.nyse”, “nyse.vmw”,
“quick.orange.rabbit”.这种类型的。当然这个单词列表最多不能超过 255 个字节。
在这个规则列表中,其中有两个替换符是大家需要注意的
*(星号):代替一个单词
#(井号):替代零个或多个单词**
生产者代码
import com.huangge.rabbitmq.utils.RabbitMqUtils;import com.rabbitmq.client.Channel;import java.nio.charset.StandardCharsets;import java.util.HashMap;import java.util.Map;import java.util.Scanner;/** * Created with IntelliJ IDEA. * * @author: 轩辕龙儿 * @date: 2021/7/21 10:11 * @Description: No Description */public class Product { public static final String EXCHANGE_NAME = "topic"; public static void main(String[] args) throws Exception { Channel channel = RabbitMqUtils.getChannel(); Map<String, String> bindingKeyMap = new HashMap<>(); bindingKeyMap.put("quick.orange.rabbit", "被队列Q1Q2接收到"); bindingKeyMap.put("lazy.orange.elephant", "被队列Q1Q2接收到"); bindingKeyMap.put("quick.orange.fox", "被队列Q1接收到"); bindingKeyMap.put("lazy.brown.fox", "被队列Q2接收到"); bindingKeyMap.put("lazy.pink.rabbit", "虽然满足两个绑定但只被队列Q2接收一次"); bindingKeyMap.put("quick.brown.fox", "不匹配任何绑定不会被任何队列接收到会被丢弃"); bindingKeyMap.put("quick.orange.male.rabbit", "是四个单词不匹配任何绑定会被丢弃"); bindingKeyMap.put("lazy.orange.male.rabbit", "是四个单词但匹配Q2"); for (Map.Entry<String, String> bindingKeyEntity : bindingKeyMap.entrySet()) { channel.basicPublish(EXCHANGE_NAME, bindingKeyEntity.getKey(), null, bindingKeyEntity.getValue().getBytes(StandardCharsets.UTF_8)); System.out.println("生产者发出消息:"+bindingKeyEntity.getValue()); } }}
消费者1代码
import com.huangge.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.nio.charset.StandardCharsets;
/**
* @author: 轩辕龙儿
* @date: 2021/7/21 10:05
* @Description: No Description
*/
public class Consumer01 {
public static final String EXCHANGE_NAME = "topic";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
String queueName = "queue1";
channel.queueDeclare(queueName, false, false, false, null);
channel.queueBind(queueName, EXCHANGE_NAME, "*.orange.*");
System.out.println("队列1等待接收消息。。。。。。。。。");
DeliverCallback deliverCallback = (consumTag, message) -> {
System.out.println("接收到的消息01:" + new String(message.getBody(), StandardCharsets.UTF_8));
System.out.println("接收队列:" + queueName + "绑定键" + message.getEnvelope().getRoutingKey());
};
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
});
}
}
消费者2代码
import com.huangge.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.nio.charset.StandardCharsets;
/**
* Created with IntelliJ IDEA.
*
* @author: 轩辕龙儿
* @date: 2021/7/21 10:05
* @Description: No Description
*/
public class Consumer02 {
public static final String EXCHANGE_NAME = "topic";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
String queueName = "queue2";
channel.queueDeclare(queueName, false, false, false, null);
channel.queueBind(queueName, EXCHANGE_NAME, "*.*.rabbit");
channel.queueBind(queueName, EXCHANGE_NAME, "lazy.#");
System.out.println("队列2等待接收消息。。。。。。。。。");
DeliverCallback deliverCallback = (consumTag, message) -> {
System.out.println("接收到的消息02:" + new String(message.getBody(), StandardCharsets.UTF_8));
System.out.println("接收队列:" + queueName + "绑定键" + message.getEnvelope().getRoutingKey());
};
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
});
}
}
死信队列
无法被消费的消息
怎么进入死信队列:
- 消息过期了:消息从普通交换机普通队列放到死信交换机死信队列
- 消费者拒绝消息并且不让消息回队列
- 队列中达到消息存储的上限,先进入队列的消息会到达死信队列
生产者
import com.huangge.rabbitmq.utils.RabbitMqUtils;import com.rabbitmq.client.AMQP;import com.rabbitmq.client.Channel;import java.nio.charset.StandardCharsets;import java.util.Scanner;/** * Created with IntelliJ IDEA. * * @author: 轩辕龙儿 * @date: 2021/7/21 10:11 * @Description: No Description */public class Product { public static final String NORMAL_EXCHANGE = "normal_exchange"; public static void main(String[] args) throws Exception { Channel channel = RabbitMqUtils.getChannel(); AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build(); for (int i = 0; i < 10; i++) { String message = "info" + i; channel.basicPublish(NORMAL_EXCHANGE, "zhangsan", properties, message.getBytes(StandardCharsets.UTF_8)); } }}
正常消费者
import com.huangge.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import org.omg.CORBA.PUBLIC_MEMBER;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* Created with IntelliJ IDEA.
*
* @author: 轩辕龙儿
* @date: 2021/7/21 10:05
* @Description: No Description
*/
public class Consumer01 {
public static final String NORMAL_EXCHANGE = "normal_exchange";
public static final String DEAD_EXCHANGE = "dead_exchange";
public static final String NORMAL_QUEUE = "normal_queue";
public static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
Map<String, Object> arguments = new HashMap<>();
// arguments.put("x-message-ttl",10000);
arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
arguments.put("x-dead-letter-routing-key","lisi");
channel.queueDeclare(NORMAL_QUEUE,false,false,false,arguments);
channel.queueDeclare(DEAD_QUEUE,false,false,false,null);
channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "zhangsan");
channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, "lisi");
DeliverCallback deliverCallback = (consumTag, message) -> {
System.out.println("接收到的消息01:" + new String(message.getBody(), StandardCharsets.UTF_8));
};
channel.basicConsume(NORMAL_QUEUE, true, deliverCallback, consumerTag -> {
});
}
}
死信消费者
import com.huangge.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* Created with IntelliJ IDEA.
*
* @author: 轩辕龙儿
* @date: 2021/7/21 10:05
* @Description: No Description
*/
public class Consumer02 {
public static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
DeliverCallback deliverCallback = (consumTag, message) -> {
System.out.println("接收到的消息02:" + new String(message.getBody(), StandardCharsets.UTF_8));
};
channel.basicConsume(DEAD_QUEUE, true, deliverCallback, consumerTag -> {
});
}
}
延迟队列
队列内部是有序的,最重要的特性就体现在它的延时属性上,延时队列中的元素是希望在指定时间到了以后或之前取出和处理,简单来说,延时队列就是用来存放需要在指定时间被处理的元素的队列。
基于死信
基于插件
在官网上下载 https://www.rabbitmq.com/community-plugins.html,下载
rabbitmq_delayed_message_exchange 插件,然后解压放置到 RabbitMQ 的插件目录。进入 RabbitMQ 的安装目录下的 plgins 目录,执行下面命令让该插件生效,然后重启 RabbitMQ
/usr/lib/rabbitmq/lib/rabbitmq_server-xxx/plugins
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
总结
延时队列在需要延时处理的场景下非常有用,使用 RabbitMQ 来实现延时队列可以很好的利RabbitMQ 的特性,如:消息可靠发送、消息可靠投递、死信队列来保障消息至少被消费一次以及未被正确处理的消息不会被丢弃。
发布确认高级
在生产环境中由于一些不明原因,导致 rabbitmq 重启,在 RabbitMQ 重启期间生产者消息投递失败, 导致消息丢失,需要手动处理和恢复。于是,我们开始思考,如何才能进行 RabbitMQ 的消息可靠投递呢? 特别是在这样比较极端的情况,RabbitMQ 集群不可用的时候,无法投递的消息该如何处理呢
- 交换机收不到消息
- 队列收不到消息
确认机制方案
回退消息
在仅开启了生产者确认机制的情况下,交换机接收到消息后,会直接给消息生产者发送确认消息,如果发现该消息不可路由,那么消息会被直接丢弃,此时生产者是不知道消息被丢弃这个事件的。那么如何让无法被路由的消息帮我想办法处理一下?最起码通知我一声,我好自己处理啊。通过设置 mandatory 参数可以在当消息传递过程中不可达目的地时将消息返回给生产者。
备份交换机
幂等性
解决办法:
- 唯一 ID+指纹码机制,利用数据库主键去重
- 利用 redis 的原子性去实现( 执行 setnx 命令,天然具有幂等性)
优先级队列
0~255,越大越先执行
惰性队列
RabbitMQ 从 3.6.0 版本开始引入了惰性队列的概念。惰性队列会尽可能的将消息存入磁盘中,而在消费者消费到相应的消息时才会被加载到内存中,它的一个重要的设计目标是能够支持更长的队列,即支持更多的消息存储。当消费者由于各种各样的原因(比如消费者下线、宕机亦或者是由于维护而关闭等)而致使长时间内不能消费消息造成堆积时,惰性队列就很有必要了。