RabbitMQ快速学习之WorkQueues模型、三种交换机、消息转换器(基于SpringBoot)

文章目录

  • 前言
  • 一、WorkQueues模型
    • 消息发送
    • 消息接收
    • 能者多劳
  • 二、交换机类型
    • 1.Fanout交换机
      • 消息发送
      • 消息接收
    • 2.Direct交换机
      • 消息接收
      • 消息发送
    • 3.Topic交换机
      • 消息发送
      • 消息接收
  • 三、编程式声明队列和交换机
    • fanout示例
    • direct示例
    • 基于注解
  • 四、消息转换器
  • 总结


前言

WorkQueues模型、Fanout交换机、Direct交换机、Topic交换机、基于SpringBoot注解声明队列和交换机、消息转换器。


一、WorkQueues模型

  • Work queues,任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息。
  • 当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work 模型,多个消费者共同处理消息处理,消息处理的速度就能大大提高了。

在这里插入图片描述
在控制台创建一个work.queue的队列:
在这里插入图片描述

消息发送

循环发送,模拟大量消息堆积现象。

/**
     * workQueue
     * 向队列中不停发送消息,模拟消息堆积。
     */
@Test
public void testWorkQueue() throws InterruptedException {
    // 队列名称
    String queueName = "simple.queue";
    // 消息
    String message = "hello, message_";
    for (int i = 0; i < 50; i++) {
        // 发送消息,每20毫秒发送一次,相当于每秒发送50条消息
        rabbitTemplate.convertAndSend(queueName, message + i);
        Thread.sleep(20);
    }
}

消息接收

模拟多个消费者绑定同一个队列,我们添加2个方法,并且设置不同睡眠时间模拟不同性能读取

@RabbitListener(queues = "work.queue")
public void listenWorkQueue1(String msg) throws InterruptedException {
    System.out.println("消费者1接收到消息:【" + msg + "】" + LocalTime.now());
    Thread.sleep(20);
}

@RabbitListener(queues = "work.queue")
public void listenWorkQueue2(String msg) throws InterruptedException {
    System.err.println("消费者2........接收到消息:【" + msg + "】" + LocalTime.now());
    Thread.sleep(200);
}

在这里插入图片描述

  • 消费者1很快完成了自己的25条消息
  • 消费者2却在缓慢的处理自己的25条消息。

也就是说消息是平均分配给每个消费者,并没有考虑到消费者的处理能力。导致1个消费者空闲,另一个消费者忙的不可开交。没有充分利用每一个消费者的能力,最终消息处理的耗时远远超过了1秒。这样显然是有问题的。

能者多劳

在spring中有一个简单的配置,可以解决这个问题。我们修改consumer服务的application.yml文件,添加配置:

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息

再次测试,发现结果如下:
在这里插入图片描述

可以发现,由于消费者1处理速度较快,所以处理了更多的消息;消费者2处理速度较慢,只处理了6条消息。而最终总的执行耗时也在1秒左右,大大提升。
正所谓能者多劳,这样充分利用了每一个消费者的处理能力,可以有效避免消息积压问题。

Work模型的使用:

  • 多个消费者绑定到一个队列,同一条消息只会被一个消费者处理
  • 通过设置prefetch来控制消费者预取的消息数量

二、交换机类型

1.Fanout交换机

发送流程:
在这里插入图片描述

  • 可以有多个队列
  • 每个队列都要绑定到Exchange(交换机)
  • 生产者发送的消息,只能发送到交换机
  • 交换机把消息发送给绑定过的所有队列
  • 订阅队列的消费者都能拿到消息

在控制台创建fanout.queue1、fanoutqueue2队列和dragon.fanout交换机,并将队列绑定到交换机:
在这里插入图片描述

在这里插入图片描述

消息发送

@Test
public void testFanoutExchange() {
    // 交换机名称
    String exchangeName = "dragon.fanout";
    // 消息
    String message = "hello, everyone!";
    rabbitTemplate.convertAndSend(exchangeName, "", message);
}

消息接收

@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg) {
    System.out.println("消费者1接收到Fanout消息:【" + msg + "】");
}

@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue2(String msg) {
    System.out.println("消费者2接收到Fanout消息:【" + msg + "】");
}

交换机的作用是什么?

  • 接收publisher发送的消息
  • 将消息按照规则路由到与之绑定的队列
  • 不能缓存消息,路由失败,消息丢失
  • FanoutExchange的会将消息路由到每个绑定的队列

2.Direct交换机

在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。

在这里插入图片描述
在Direct模型下:

  • 队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)
  • 消息的发送方在 向 Exchange发送消息时,也必须指定消息的 RoutingKey。
  • Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routingkey与消息的 Routing key完全一致,才会接收到消息

在控制台声明两个队列direct.queue1和direct.queue2,然后声明一个direct类型的交换机,绑定队列和交换机(使用red和blue作为key,绑定direct.queue1到dragon.direct;使用red和yellow作为key,绑定direct.queue2到dragon.direct):
在这里插入图片描述

在这里插入图片描述

消息接收

@RabbitListener(queues = "direct.queue1")
public void listenDirectQueue1(String msg) {
    System.out.println("消费者1接收到direct.queue1的消息:【" + msg + "】");
}

@RabbitListener(queues = "direct.queue2")
public void listenDirectQueue2(String msg) {
    System.out.println("消费者2接收到direct.queue2的消息:【" + msg + "】");
}

消息发送

@Test
public void testSendDirectExchange() {
    // 交换机名称
    String exchangeName = "dragon.direct";
    // 消息
    String message = "红色警报!日本乱排核废水,导致海洋生物变异,惊现哥斯拉!";
    // 发送消息
    rabbitTemplate.convertAndSend(exchangeName, "red", message);
}

绑定红色的都会接收到信息:
在这里插入图片描述

描述下Direct交换机与Fanout交换机的差异?

  • Fanout交换机将消息路由给每一个与之绑定的队列
  • Direct交换机根据RoutingKey判断路由给哪个队列
  • 如果多个队列具有相同的RoutingKey,则与Fanout功能类似

3.Topic交换机

  • Topic类型的Exchange与Direct相比,都是可以根据RoutingKey把消息路由到不同的队列。
    只不过Topic类型Exchange可以让队列在绑定BindingKey 的时候使用通配符!
  • BindingKey 一般都是有一个或多个单词组成,多个单词之间以.分割,例如: item.insert
  • 通配符规则:
    • #:匹配一个或多个词
    • *:匹配不多不少恰好1个词
    • 举例:
      dragon.#:能够匹配dragon.stu.insert 或者 dragon.stu
      dragon.*:只能匹配dragon.stu

在这里插入图片描述
假如此时publisher发送的消息使用的RoutingKey共有四种:

  • china.news 代表有中国的新闻消息;
  • china.weather 代表中国的天气消息;
  • japan.news 则代表日本新闻
  • japan.weather 代表日本的天气消息;

解释:

  • topic.queue1:绑定的是china.# ,凡是以 china.开头的routing key 都会被匹配到,包括:
    • china.news
    • china.weather
  • topic.queue2:绑定的是#.news ,凡是以 .news结尾的 routing key 都会被匹配。包括:
    • china.news
    • japan.news

在这里插入图片描述

消息发送

/**
 * topicExchange
 */
@Test
public void testSendTopicExchange() {
    // 交换机名称
    String exchangeName = "dragon.topic";
    // 消息
    String message = "喜报!孙悟空大战哥斯拉,胜!";
    // 发送消息
    rabbitTemplate.convertAndSend(exchangeName, "china.news", message);
}

消息接收

@RabbitListener(queues = "topic.queue1")
public void listenTopicQueue1(String msg){
    System.out.println("消费者1接收到topic.queue1的消息:【" + msg + "】");
}

@RabbitListener(queues = "topic.queue2")
public void listenTopicQueue2(String msg){
    System.out.println("消费者2接收到topic.queue2的消息:【" + msg + "】");
}

描述下Direct交换机与Topic交换机的差异?

  • Topic交换机接收的消息RoutingKey必须是多个单词,以 . 分割
  • Topic交换机与队列绑定时的bindingKey可以指定通配符
  • #:代表0个或多个词
  • *:代表1个词

三、编程式声明队列和交换机

SpringAMQP提供了一个Queue类,用来创建队列:

fanout示例

@Configuration
public class FanoutConfiguration {
    /**
     * 声明交换机
     * @return Fanout类型交换机
     */
    @Bean
    public FanoutExchange fanoutExchange(){
//    还可以是  return  ExchangeBuilder.fanoutExchange("dragon.fanout").build();
        return new FanoutExchange("dragon.fanout");
    }

    /**
     * 第1个队列
     */
    @Bean
    public Queue fanoutQueue1(){
//   还可以是  return  QueueBuilder.durable("fanout.queue1").build();
        return new Queue("fanout.queue1");
    }

    /**
     * 绑定队列和交换机
     */
    @Bean
    public Binding bindingQueue1(Queue fanoutQueue1, FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
    }

    /**
     * 第2个队列
     */
    @Bean
    public Queue fanoutQueue2(){
        return new Queue("fanout.queue2");
    }

    /**
     * 绑定队列和交换机
     */
    @Bean
    public Binding bindingQueue2(Queue fanoutQueue2, FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
    }
//绑定队列和交换机的另一方法
//    @Bean
//    public Binding bindingQueue2(){
//        return BindingBuilder.bind(fanoutQueue2()).to(fanoutExchange());
//    }
}

direct示例

@Configuration
public class DirectConfiguration {

    /**
     * 声明交换机
     * @return Direct类型交换机
     */
    @Bean
    public DirectExchange directExchange(){
        return ExchangeBuilder.directExchange("dragon.direct").build();
    }

    /**
     * 第1个队列
     */
    @Bean
    public Queue directQueue1(){
        return new Queue("direct.queue1");
    }

    /**
     * 绑定队列和交换机
     */
    @Bean
    public Binding bindingQueue1WithRed(Queue directQueue1, DirectExchange directExchange){
        return BindingBuilder.bind(directQueue1).to(directExchange).with("red");
    }
    /**
     * 绑定队列和交换机
     */
    @Bean
    public Binding bindingQueue1WithBlue(Queue directQueue1, DirectExchange directExchange){
        return BindingBuilder.bind(directQueue1).to(directExchange).with("blue");
    }

    /**
     * 第2个队列
     */
    @Bean
    public Queue directQueue2(){
        return new Queue("direct.queue2");
    }

    /**
     * 绑定队列和交换机
     */
    @Bean
    public Binding bindingQueue2WithRed(Queue directQueue2, DirectExchange directExchange){
        return BindingBuilder.bind(directQueue2).to(directExchange).with("red");
    }
    /**
     * 绑定队列和交换机
     */
    @Bean
    public Binding bindingQueue2WithYellow(Queue directQueue2, DirectExchange directExchange){
        return BindingBuilder.bind(directQueue2).to(directExchange).with("yellow");
    }
}

基于注解

上面的代码还是很多的,基于注解的方式也能够代替上面的繁杂配置,下面演示direct和topic交换机,队列的代码

@RabbitListener(bindings = @QueueBinding(
    value = @Queue(name = "direct.queue1"),
    exchange = @Exchange(name = "dragon.direct", type = ExchangeTypes.DIRECT),
    key = {"red", "blue"}
))
public void listenDirectQueue1(String msg){
    System.out.println("消费者1接收到direct.queue1的消息:【" + msg + "】");
}

@RabbitListener(bindings = @QueueBinding(
    value = @Queue(name = "direct.queue2"),
    exchange = @Exchange(name = "dragon.direct", type = ExchangeTypes.DIRECT),
    key = {"red", "yellow"}
))
public void listenDirectQueue2(String msg){
    System.out.println("消费者2接收到direct.queue2的消息:【" + msg + "】");
}
@RabbitListener(bindings = @QueueBinding(
    value = @Queue(name = "topic.queue1"),
    exchange = @Exchange(name = "hmall.topic", type = ExchangeTypes.TOPIC),
    key = "china.#"
))
public void listenTopicQueue1(String msg){
    System.out.println("消费者1接收到topic.queue1的消息:【" + msg + "】");
}

@RabbitListener(bindings = @QueueBinding(
    value = @Queue(name = "topic.queue2"),
    exchange = @Exchange(name = "hmall.topic", type = ExchangeTypes.TOPIC),
    key = "#.news"
))
public void listenTopicQueue2(String msg){
    System.out.println("消费者2接收到topic.queue2的消息:【" + msg + "】");
}

四、消息转换器

随便创建一个队列,然后发送Map对象,你会发现消息格式很不友好

@Test
public void testSendMap() throws InterruptedException {
    // 准备消息
    Map<String,Object> msg = new HashMap<>();
    msg.put("name", "柳岩");
    msg.put("age", 21);
    // 发送消息
    rabbitTemplate.convertAndSend("object.queue", msg);
}

控制台查看:
在这里插入图片描述

  • Spring的消息发送代码接收的消息体是一个Object,而在数据传输时,它会把你发送的消息序列化为字节发送给MQ,接收消息的时候,还会把字节反序列化为Java对象。
  • 只不过,默认情况下Spring采用的序列化方式是JDK序列化。众所周知,JDK序列化存在下列问题:
    • 数据体积过大
      有安全漏洞
      可读性差
  • 根据上面测试,显然,JDK序列化方式并不合适。我们希望消息体的体积更小、可读性更高,因此可以使用JSON方式来做序列化和反序列化。

j解决:
引入依赖:

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.9.10</version>
</dependency>

注意,如果项目中引入了spring-boot-starter-web依赖,则无需再次引入Jackson依赖。其转换器配置。
添加配置:

@Bean
public MessageConverter messageConverter(){
    // 1.定义消息转换器
    Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
    // 2.配置自动创建消息id,用于识别不同消息,也可以在业务中基于ID判断是否是重复消息
    jackson2JsonMessageConverter.setCreateMessageIds(true);
    return jackson2JsonMessageConverter;
}

总结

以上就是所有讲解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/200489.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于UDP的TFTP文件传输

代码&#xff1a; #include <myhead.h>//实现下载功能 int download(int cfd,struct sockaddr_in sin) {char buf[516] ""; //定义资源包char fileName[128] ""; //定义文件名printf("请输入文件名:");scanf("%s",fileName…

Rocky Linux 9.3 为 PowerPC 64 位带回云和容器镜像

RHEL 克隆版 Rocky Linux 9.3 今天发布了&#xff0c;作为红帽企业 Linux 发行版 CentOS Stream 和 Red Hat Enterprise Linux 的免费替代版本&#xff0c;现在可供下载。 Rocky Linux 9.3 是在 Rocky Linux 9.2 发布 6 个月之后发布的&#xff0c;它带回了 PowerPC 64 位 Lit…

Java核心知识点整理大全22-笔记

目录 19.1.14. CAP 一致性&#xff08;C&#xff09;&#xff1a; 可用性&#xff08;A&#xff09;&#xff1a; 分区容忍性&#xff08;P&#xff09;&#xff1a; 20. 一致性算法 20.1.1. Paxos Paxos 三种角色&#xff1a;Proposer&#xff0c;Acceptor&#xff0c;L…

MySQL修改已存在数据的字符集

在实际应用中&#xff0c;如果一开始没有正确的设置字符集&#xff0c;在运行一段时间以后&#xff0c;才发现当前字符集不能满足要求&#xff0c;需要进行调整&#xff0c;但又不想丢弃这段时间的数据&#xff0c;这个时候就需要修改字符集。 在MySQL设置默认字符集和校对规则…

【飞桨星河社区五周年线下工坊-杭州站】

? 欢迎大家参加杭州极客工坊&#xff0c;深入了解大模型前沿技术和创新应用&#xff0c;一站式体验AI原生应用开发? 精彩议程敬请期待&#xff5e; ? 时间&#xff1a;2023年12月3日 14:00-17:30 ? 地点&#xff1a;杭州西湖区花蒋路3号西溪润泽园度假酒店 ? 主题&#xf…

C++中用于动态内存的new和delete操作符

文章目录 1、动态分配内存的应用2、动态分配内存与分配给普通变量的内存有什么不同?3、C 中如何分配/释放内存4、new 操作符4.1 使用new的语法4.2 初始化内存4.3 分配内存块4.4 普通数组声明 Vs 使用new4.5 如果运行时没有足够内存可用怎么办&#xff1f; 5、delete 操作符 C/…

第二十章Java博客

如果一次只完成一件事情&#xff0c;很容易实现。但现实生活中&#xff0c;很多事情都是同时进行的。Java中为了模拟这种状态&#xff0c;引入了线程机制。简单地说&#xff0c;当程序同时完成多件事情时&#xff0c;就是所谓的多线程。多线程应用相当广泛&#xff0c;使用多线…

喜报 | 再获影响力产品奖!擎创科技实力亮相GOPS全球运维大会

10月26日-27日&#xff0c;为期两天&#xff0c;共1100余人签到的 GOPS 全球运维大会 2023 上海站已经圆满落幕。 此次会议的“2023 IT技术领导力年度颁奖典礼”中&#xff0c;擎创夏洛克AIOps数智运维管理平台凭借成熟的产品能力及广泛且优异的落地实践效益&#xff0c;得到了…

记 Doris 回归测试S3导入load_parallelism > 1

增加load_parallelism > 1的S3导入用例&#xff0c;测试导入时切分输入文件的逻辑。 这里有几个隐性的问题点&#xff08;坑&#xff09;&#xff1a; 1、导入的文件一定要大&#xff0c;一般大于128M&#xff0c;否则&#xff0c;即使设置了 load_parallelism > 1 也不…

AI搜索相关性在网站和APP上的应用

设定场景&#xff1a;您在寻找一件新衣服&#xff0c;所以在浏览最喜欢的网店。您跳到搜索栏上&#xff0c;输入您要找的东西。您期待出现什么结果&#xff1f; 高度准确、相关和即时的结果。 无论在什么网站上搜索&#xff0c;寻找什么&#xff0c;甚至在打错字或使用了错误的…

JAVA进阶之路JVM-3:JVM内存模型,运行时数据区域划分,程序计数器,虚拟机栈,本地方法栈,堆,元空间,字符串常量池

JVM内存模型 对于 Java 程序员来说&#xff0c;在虚拟机自动内存管理机制下&#xff0c;不再需要像 C/C 程序开发程序员这样为每一个操作去写对应的 delete / free 操作&#xff0c;不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序把内new存控制权利交给JVM虚拟机。一旦…

从源码解析Containerd容器启动流程

从源码解析Containerd容器启动流程 本文从源码的角度分析containerd容器启动流程以及相关功能的实现。 本篇containerd版本为v1.7.9。 更多文章访问 https://www.cyisme.top 本文从ctr run命令出发&#xff0c;分析containerd的容器启动流程。 ctr命令 查看文件cmd/ctr/comman…

高档建筑覆膜板,胶水足表面光滑

在建筑材料行业&#xff0c;选择高质量的建筑覆膜板至关重要。贵港市能强优品木业是专业从事建筑覆膜板生产销售25年的源头工厂。这家工厂一直以来致力于生产出色的覆膜板&#xff0c;以确保建筑物外观精美&#xff0c;持久耐用。 无论是商业大楼还是家庭住宅&#xff0c;外墙装…

气膜建筑助力体育场馆快速普及

传统的室内体育馆投入资金庞大&#xff0c;建设强度高&#xff0c;建设周期漫长。而气膜体育馆的出现&#xff0c;不仅显著降低了建设成本和缩短了建设周期&#xff0c;更符合节能环保的需求&#xff0c;成为推动场馆快速普及的创新建筑形式。 对于校园设施的建设而言&#xff…

可以免费使用的Axure在线版来了

Axure作为一种功能强大的原型设计工具&#xff0c;一直受到设计师的青睐。然而&#xff0c;其高昂的价格可能成为一个门槛&#xff0c;限制了一些设计师的选择。但不用担心&#xff0c;现在有一个免费的Axure在线工具即时设计&#xff0c;功能更完整&#xff0c;更划算&#xf…

『 MySQL数据库 』插入查询结果

文章目录 &#x1f39f;️ 前言&#x1f39f;️ 创建一张结构相同的表&#x1f39f;️ 表内插入查询结果&#x1f3ab; 对表内数据进行去重&#x1f3ab; 配合ORDER BY排序后以及LIMIT分页对数据进行插入 &#x1f39f;️ 前言 在MySQL数据库中不仅可以直接根据字段类型等对数据…

信而泰 SSL测试方法介绍

[本文介绍在ALPS平台上进行SSL测试的内容和方法] 什么是SSL SSL全称是Secure Sockets Layer&#xff0c;指安全套接字协议&#xff0c;为基于TCP的应用层协议提供安全连接&#xff1b;SSL介于TCP/IP协议栈的第四层和第五层之间&#xff0c;广泛用于电子商务、网上银行等。 SSL…

String你知道多少细节(含面试题)

1 字符串初始化 常见的初始化方式有以下3种 public static void main(String[] args) {String s1 "abc";System.out.println(s1);String s2 new String("abc");System.out.println(s2);char[] s3 {a,b,c};System.out.println(s3);} 【注意】 1.Strin…

【带头学C++】----- 八、C++面向对象编程 ---- 8.10 函数的默认参数

8.10 函数的默认参数 C在声明函数原型的时可为一个或者多个参数指定默认(缺省)的参数值&#xff0c;当函数调用的时候如果没有指定这个值&#xff0c;编器会自动用默认值代替。 通过为函数参数指定默认值&#xff0c;可以在调用函数时省略相应的参数&#xff0c;而该参数将使用…

自定义Windows服务启动失败

文章目录 自定义Windows服务启动失败报错内容解决方案管理员身份运行cmd进入到InstallUtil.exe的路径&#xff0c;使用cd命令。使用InstallUtil.exe工具安装服务。 自定义Windows服务启动失败 报错内容 “无法从命令行或调试器启动服务&#xff0c;必须首先安装Windows服务(使…