RabbitMQ传输原理、五种模式
本文代码基于SpringBoot,文末有代码连接 。首先是一些在Spring Boot的一些配置和概念,然后跟随代码看下五种模式
MQ两种消息传输方式,点对点(代码中的简单传递模式),发布/订阅(代码中路由模式)。要是你熟悉RabbitMQ SpringBoot配置的话,就是simple和direct。
MQ安装指南:https://blog.csdn.net/qq_19006223/article/details/89421050
0.消息队列运转过程
生产者生产过程:
(1)生产者连接到 RabbitMQ Broker 建立一个连接( Connection) ,开启 个信道 (Channel)
(2) 生产者声明一个交换器 ,并设置相关属性,比如交换机类型、是否持久化等
(3)生产者声明 个队列井设置相关属性,比如是否排他、是否持久化、是否自动删除等
(4)生产者通过路由键将交换器和队列绑定起来。
(5)生产者发送消息至 RabbitMQ Broker ,其中包含路由键、交换器等信息。
(6) 相应的交换器根据接收到的路由键查找相匹配的队列 如果找到 ,则将从生产者发送过来的消息存入相应的队列中。
(7) 如果没有找到 ,则根据生产者配置的属性选择丢弃还是回退给生产者
(8) 关闭信道。
(9) 关闭连接。
消费者接收消息的过程:
(1)消费者连接到 RabbitMQ Broker ,建立一个连接(Connection ,开启 个信道(Channel)
(2) 消费者向 RabbitMQ Broker 请求消费相应队列中的消息,可能会设置相应的回调函数, 以及做 些准备工作。
(3)等待 RabbitMQ Broker 回应并投递相应队列中的消息, 消费者接收消息。
(4) 消费者确认 ack) 接收到的消息
(5) RabbitMQ 从队列中删除相应己经被确认的消息
(6) 关闭信道。
(7)关闭连接。
1.项目结构
common是工具,receiver是消费者,sender是生产者
具体各自的pom.xml文件请看项目,都有注释。
2.sender(生产者的配置)
#确认机制
publisher-confirms: true 消息有没有到达MQ(会返回一个ack确认码)
publisher-returns: true 消息有没有找到合适的队列
主要是为了生产者和mq之间的一个确认机制,当消息到没到mq,会提供相应的回调,在项目中 RabbitSender 这个类中进行了相应的配置
private final RabbitTemplate.ConfirmCallback confirmCallback = (correlationData, ack, s) -> {
if (ack) {
System.out.println(correlationData.getId());
} else {
log.error("ConfirmCallback消息发送失败: {}", s);
}
}; private final RabbitTemplate.ReturnCallback returnCallback = (message, replyCode, replyText, exchange, routingKey)
-> log.error("ReturnCallback消息发送失败: {}", new String(message.getBody(), StandardCharsets.UTF_8)); public <T> void sendMsg(String exchangeName, String routingKeyName, T content) {
// 设置每个消息都返回一个确认消息
this.rabbitTemplate.setMandatory(true);
// 消息确认机制
this.rabbitTemplate.setConfirmCallback(confirmCallback);
// 消息发送失败机制
this.rabbitTemplate.setReturnCallback(returnCallback);
// 异步发送消息
CorrelationData correlationData = new CorrelationData();
correlationData.setId("123");
this.rabbitTemplate.convertAndSend(exchangeName, routingKeyName, content, correlationData);
}
还可以根据需求设置发送时CorrelationData 的值
#mandatory
参数设为 true 时,交换器无法根据自身的类型和路由键找到一个符合条件 的队列,那么 RabbitM 会调用 Basic.Return 命令将消息返回给生产者。
默认为false,直接丢弃 3.receiver(消费者配置)
这里主要说一下 listerner 的相关配置
一共有两种模式:simple和direct模式
simple主要包括两种工作模式,direct主要包括四种,待会代码会详解。
先说主要配置(以direct为例)
#acknowledge-mode: manual
手动确认模式,推荐使用这种。就是说当消息被消费者消费时,需要手动返回信息告诉mq。如果是自动的话,mq会自动确认,不管你消费者是否完成消费(比如说抛出异常)
#prefetch: 1
一个消费者一次拉取几条消息,本demo一条一条来。
#consumers-per-queue: 2
一个队列可以被多少消费者消费(这个配置,我测试的时候没测试出来,如果有朋友了解的话,可以评论下。)
还有其他配置,看下源码,两种模式共有的
simple特有的
direct特有的
4.各种模式详解
---------simple方式下的两种
打开上面的listener配置
4.1 simple
一个生产者,一个消费者
生产者发送消息都在SenderTest里面
生产者:
/**简单模式*/
@Test
public void senderSimple() throws Exception {
String context = "simple---> " + new Date();
this.rabbitTemplate.convertAndSend("simple", context);
}
消费者
@RabbitListener(queues = "simple")
public void simple(Message message, Channel channel){
String messageRec = new String(message.getBody());
System.out.println("simple模式接收到了消息:"+messageRec);
try {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
System.out.println("报错了------------------"+e.getMessage());
}
}
输出
simple模式接收到了消息:simple---> Sat Apr 20 20:40:16 CST 2019
4.2 work 模式
一个生产者,多个消费者
生产者
private static final List<Integer> ints = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
/**work模式*/
@Test
public void senderWork() throws Exception {
ints.forEach((i)->{
String context = "work---> " + i;
this.rabbitTemplate.convertAndSend("work", context);
});
}
消费者
@RabbitListener(queues = "work")
public void work1(Message message, Channel channel){
try{
Thread.sleep(500);
}catch (Exception e){
System.out.println(e.getMessage());
}
String messageRec = new String(message.getBody());
System.out.println("work1接收到了消息:"+messageRec);
try {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
System.out.println("work1报错了------------------"+e.getMessage());
}
} @RabbitListener(queues = "work")
public void work2(Message message, Channel channel){
try{
Thread.sleep(1000);
}catch (Exception e){
System.out.println(e.getMessage());
}
String messageRec = new String(message.getBody());
System.out.println("work2接收到了消息:"+messageRec);
try {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
System.out.println("work2报错了------------------"+e.getMessage());
}
}
输出
work1接收到了消息:work---> 2
work2接收到了消息:work---> 0
work1接收到了消息:work---> 1
work1接收到了消息:work---> 4
work2接收到了消息:work---> 3
work1接收到了消息:work---> 6
work1接收到了消息:work---> 7
work2接收到了消息:work---> 5
work1接收到了消息:work---> 8
work1接收到了消息:work---> 10
work2接收到了消息:work---> 9
-----direct方式下的
切换listener配置
4.3direct交换机
生产者发送消息给指定交换机,绑定的某个队列。
消费者通过监听某交换机绑定的某个队列接受消息。
生产者
/**direct交换机*/
@Test
public void senderDirect() throws Exception {
rabbitSender.sendMsg("direct","directKey1","directContent1");
rabbitSender.sendMsg("direct","directKey2","directContent2");
}
消费者
@RabbitListener(bindings = @QueueBinding(exchange = @Exchange("direct"), key = "directKey1"
, value = @Queue(value = "directQueue1", durable = "true", exclusive = "false", autoDelete = "false")))
public void direct1(String str, Channel channel, Message message) throws IOException {
try {
System.out.println("directQueue1接收到了:"+str);
}catch (Exception e){
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
}
} @RabbitListener(bindings = @QueueBinding(exchange = @Exchange("direct"), key = "directKey2"
, value = @Queue(value = "directQueue2", durable = "true", exclusive = "false", autoDelete = "false")))
public void direct2(String str, Channel channel, Message message) throws IOException {
try {
System.out.println("directQueue2接收到了:"+str);
}catch (Exception e){
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
}
}
输出
directQueue1接收到了:directContent1
directQueue2接收到了:directContent2
4.4 topic交换机
指定主题
# :匹配一个或者多级路径
*: 匹配一级路径
生产者
@Test
public void senderTopic() throws Exception {
String contexta = "topic.a";
rabbitSender.sendMsg("topic","topicKey.a",contexta);
String contextb = "topic.b";
rabbitSender.sendMsg("topic","topicKey.b",contextb);
String contextc = "topic.c";
rabbitSender.sendMsg("topic","topicKey.c",contextc);
String contextz = "topic.z";
rabbitSender.sendMsg("topic","topicKey.c.z",contextz);
}
消费者
/**
* topic交换机
* */
@RabbitListener(bindings = @QueueBinding(exchange = @Exchange(name = "topic",type = "topic"), key = "topicKey.#"
, value = @Queue(value = "topicQueue", durable = "true", exclusive = "false", autoDelete = "false")))
public void topicQueue(String str, Channel channel, Message message) throws Exception {
try {
System.out.println("topicQueue接收到了:"+str);
}catch (Exception e){
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
}
}
输出
topicQueue接收到了:topic.a
4.5 Fanout 交换机
广播模式,一个消息可以给多个消费者消费
生产者
/**Fanout 交换机*/
@Test
public void senderFanout() throws Exception {
String contexta = "Fanout";
rabbitSender.sendMsg("fanout","fanoutKey1",contexta);
//写不写KEY都无所谓
}
消费者
/**
* Fanout 交换机
* */
@RabbitListener(bindings = @QueueBinding(exchange = @Exchange(name = "fanout",type = "fanout"), key = "fanoutKey1"
, value = @Queue(value = "fanoutQueue1", durable = "true", exclusive = "false", autoDelete = "false")))
public void fanoutQueue1(String str, Channel channel, Message message) throws Exception {
try {
System.out.println("fanoutQueue1接收到了:"+str);
}catch (Exception e){
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
}
} @RabbitListener(bindings = @QueueBinding(exchange = @Exchange(name = "fanout",type = "fanout"), key = "fanoutKey2"
, value = @Queue(value = "fanoutQueue2", durable = "true", exclusive = "false", autoDelete = "false")))
public void fanoutQueue2(String str, Channel channel, Message message) throws Exception {
try {
System.out.println("fanoutQueue2接收到了:"+str);
}catch (Exception e){
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
}
}
输出
fanoutQueue2接收到了:Fanout
fanoutQueue1接收到了:Fanout
4.6 Headers 交换机
代码:https://github.com/majian1994/rabbitMQ_Study
RabbitMQ传输原理、五种模式的更多相关文章
- RabbitMQ详解(三)------RabbitMQ的五种模式
RabbitMQ详解(三)------RabbitMQ的五种模式 1.简单队列(模式) 上一篇文章末尾的实例给出的代码就是简单模式. 一个生产者对应一个消费者!!! pom.xml 必须导入Rab ...
- rabbitmq五种模式详解(含实现代码)
一.五种模式详解 1.简单模式(Queue模式) 当生产端发送消息到交换机,交换机根据消息属性发送到队列,消费者监听绑定队列实现消息的接收和消费逻辑编写.简单模式下,强调的一个队列queue只被一个消 ...
- qemu-kvm磁盘读写的缓冲(cache)的五种模式
qemu-kvm磁盘读写的缓冲(cache)模式一共有五种,分别是writethrough, wirteback, none, unsafe, directsync当你对VM读写磁盘的性能有不同的要求 ...
- FTP文件传输协议两种模式 ftp协议集,错误码集,ftp客户端命令集
TCP/IP协议中,FTP标准命令TCP端口号为21,Port方式数据端口为20.FTP协议的任务是从一台计算机将文件传送到另一台计算机,它与这两台计算机所处的位置.联接的方式.甚至是是否使用相同的操 ...
- RabbitMQ 详解 五种队列-SpiritMark
上次带大家看了一下RabbitMQ的基本概念,今天我们来详解一下 RabbitMQ的五种队列,也算是一个笔记,如果对您有帮助,可以关注一下,便于下次光顾! 文章目录 1.简单队列 2.work 模式 ...
- SPI总线传输的4种模式
概述 在芯片的资料上,有两个非常特殊的寄存器配置位,分别是 CPOL (Clock POlarity)和 CPHA (Clock PHAse). CPOL配置SPI总线的极性 CPHA配置SPI总线的 ...
- Rabbitmq的五种模式和案例
消息生产者p将消息放入队列 消费者监听队列,如果队列中有消息,就消费掉,消息被拿走后,自动从队列删除 (缺点:消息可能没有被消费者正确处理,已经消失了,无法恢复) 应用场景:聊天室 1.引入依赖 &l ...
- RabbitMQ学习笔记之五种模式及消息确认机制
本文详细介绍简单模式Simple.工作模式Work.发布订阅模式Publish/Subscribe.Topic.Routing. Maven依赖引用 <dependencies> < ...
- TP传输的两种模式
主动模式(active): 我们知道,FTP是由TCP封包的模式连接,TCP 这种封包由于需要经过 Server 端与 Client 端两边的『三次握手』之后,才能确定联机,也就是需要执行AC ...
随机推荐
- Neo4j安装配置(mac)
Neo4j安装配置(mac) 1.下载APP 注意:无需配置变量 下载地址:https://neo4j.com/download/ 2.安装程序并启动 3.创建数据库(local) 选择版本 4.启动 ...
- 图形化编程娱乐于教,Kittenblock实例,为背景添加音乐
图形化编程娱乐于教,Kittenblock实例,为背景添加音乐 跟很多学生聊过,很多学生不是不努力,只是找不到感觉.有一点不可否认,同样在一个教室上课,同样是一个老师讲授,学习效果迥然不同.关键的问题 ...
- subprocess.Popen stdout重定向内容实时获取
python 打开一个新进程执行系统命令, test 执行完才能获取返回, test1 实时获取返回结果 import subprocess def test(cmd): p = subprocess ...
- LA 6621 /ZOJ 3736 Pocket Cube 打表+暴力
这道题是长沙区域赛的一道简单题,当时题目在ZOJ重现的时候就做了一次,但是做的好复杂,用的BFS暴力,而且还没打表,最后还是莫名其妙的爆栈错误,所以就一直没弄出来,昨天做到大白书上例题05年东京区域赛 ...
- 吴裕雄--天生自然Linux操作系统:Linux 系统目录结构
登录系统后,在当前命令窗口下输入命令: ls / 你会看到如下图所示: 树状目录结构: 以下是对这些目录的解释: /bin:bin是Binary的缩写, 这个目录存放着最经常使用的命令. /boot: ...
- 第一行代码新闻例子报错 Unable to start activity ComponentInfo 原因
E/AndroidRuntime: FATAL EXCEPTION: main Process: com.timemanager.jason.fragmentbestpractice, PID: 56 ...
- P2P平台爆雷不断到底是谁的过错?
早在此前,范伟曾经在春晚上留下一句经典台词,"防不胜防啊".而将这句台词用在当下的P2P行业,似乎最合适不过了.就在这个炎热夏季,P2P行业却迎来最冷冽的寒冬. 引发爆雷潮的众多P ...
- 如何判断Office是32位还是64位?
对于持续学习VBA的老铁们,有必要了解Office的位数. 如果系统是32位的,则不需要判断Office位数了,因为只能安装32位Office. 下面只讨论64位系统中,Office的位数判断问题. ...
- StartDT AI Lab | 需求预测引擎如何助力线下零售业降本增效?
在当下经济明显进入存量博弈的阶段,大到各经济体,小到企业,粗放的增长模式已不适宜持续,以往高增长的时代已经成为过去,亟需通过变革发掘新的增长点.对于竞争激烈的线下零售行业而言,则更需如此. 零售行业一 ...
- 13)PHP,文件加载(include和require)
有四种文件加载的语法形式(注意,不是函数): include, include_once, require, require_once; 他们的本质是一样的,都是用于加载/引入/包含/载入一个外部 ...