RabbitMQ进阶篇【理解?应用】

发布时间:2024年01月24日

🥳🥳Welcome 的Huihui's Code World ! !🥳🥳

接下来看看由辉辉所写的关于RabbitMQ的相关操作吧

目录

🥳🥳Welcome 的Huihui's Code World ! !🥳🥳

?

一.什么是交换机

1.概念释义?

2.例子理解

3.例子分析

二.交换机有哪些类型

1.扇形交换机?Fanout? Exchange

2.直连交换机?Direct Exchange

3.主题交换机?Topic?Exchange

4.首部交换机?Headers Exchange

5.默认交换机?Default? Exchange

6.死信交换机 Dead Letter Exchange

三.为什么要使用交换机

四.怎么使用交换机【demo演示】

1.直连交换机

生产者

消费者

测试

效果

2.主题交换机

生产者

消费者

测试

效果

3.扇形交换机

生产者

消费者

测试

效果


一.什么是交换机

1.概念释义?

????????RabbitMQ交换机是RabbitMQ消息队列中间件的一部分,它负责接收生产者发送的消息并将其路由到相应的队列中,以便消费者可以消费这些消息。

????????交换机通过绑定键(Binding Key)将自身与一个或多个队列关联起来,并根据消息的路由键(Routing Key)将消息发送到匹配的队列中。它起到了消息路由的作用,类似于邮局中的分拣员,根据信件的地址将信件分发到正确的邮箱中。

??言简意赅的总结一下:交换机就是作为一个中间过渡的角色,通过绑定键和队列产生关联,然后再通过路由键与具体匹配到的队列进行信息传递


2.例子理解

????????我们知道,邮局是一个信件传递的中介,它负责将寄信人发送的信件分发到收信人的邮箱中。

但是,如果邮局没有分拣员的话,就无法将信件准确地分发到相应的邮箱中,这时候,很可能会出现信件丢失或者送错邮箱的情况。

那么,在邮局中,分拣员的作用就是将寄信人发送的信件按照地址、邮编等信息进行分类和筛选,然后将信件分发到相应的邮箱中。这样,每个收信人就可以收到自己的信件了。


3.例子分析

在RabbitMQ中,交换机的作用就类似于邮局中的分拣员。它接收生产者(寄信人)发送的消息(信件),并根据消息的路由键(地址、邮编等信息)将消息分发到相应的队列(邮箱)中,以便消费者(收信人)可以消费这些消息。

二.交换机有哪些类型

1.扇形交换机?Fanout? Exchange

????????扇形交换机是最基本的交换机类型,它所能做的事情非常简单———广播消息。扇形交换机会把能接收到的消息全部发送给绑定在自己身上的队列。因为广播不需要“思考”,所以扇形交换机处理消息的速度也是所有的交换机类型里面最快的。【就相当于我们群发祝福消息给他人一样】

2.直连交换机?Direct Exchange

直连交换机是最简单的交换机类型,它的算法超级简单,将消息中的 Routing Key 匹配队列中的Binding Key。如果没有匹配的队列,消息将会被丢弃。

比如下面的这张图,如果消息的路由键是orange,就会将消息给到Q1队列处理,如果路由键是blank或者green,就会将消息给到Q2队列处理。

????????而且同一个绑定键可以绑定多个不同的队列,当绑定了多个队列时,消息也会被发送到多个队列中,就像下面这张图一样,如果现在有一个消息的路由键时black,那么这个消息将会同时被推送到Q1和Q2这两个队列中。【有点像扇形交换机了】

? ? ? ? ??虽然说这样能够实现出像扇形交换机那样的群发效果,但是如果我们真的在实际开发中用直连交换机来完成群发的功能,那么我们就需要在这个交换机上面绑定非常多的路由键,如果每个交换机上面都绑定了一堆的路由键,那么到了消息管理的这趴就会变的很困难...

3.主题交换机?Topic?Exchange

????????主题交换机(Topic Exchange)是一种灵活而强大的交换机类型。它通过将消息的

路由键与绑定的队列的主题模式进行匹配来确定消息应该发送到哪些队列中。主题模式是一个字符串,可以包含通配符 "*" 和 "#"。它使用类似于正则表达式的主题匹配规则来将消息路由到一个或多个队列中。

  • "" 用于匹配一个单词,例如 "topic." 可以匹配 "topic.a"、"topic.b",但不匹配 "topic.a.b"。
  • "#" 用于匹配零个或多个单词,例如 "topic.#" 可以匹配 "topic.a"、"topic.a.b"、"topic.a.b.c" 等。

为了有画面感一点,好理解一点,这里也使用文字+图片的方式举个例子

  • ?RoutingKey:wh.orange.xw------Q1
  • ?RoutingKey:wh.orange.rabbit------Q1,Q2
  • ?RoutingKey:wh.bb.rabbit------Q2
  • ?RoutingKey:lazy.wh------Q2
  • ?RoutingKey:lazy.wh.rabbit------Q2
  • ?RoutingKey:lazy.orange.wh------Q1,Q2
  • ?RoutingKey:lazy.orange.rabbit------Q1,Q2

??上面说到了主题交换机是使用类似于正则表达式的主题匹配规则来将消息路由到一个或多个队列中,但是其中也有一些特殊的极限情况👇👇

  1. 当一个队列的绑定键是#,它将会接收所有的消息,而不再考虑所接收消息的路由键
  2. 当一个队列的绑定键没有用到#和*时,它就像直连交换机一样工作

4.首部交换机?Headers Exchange

????????首部交换机和扇形交换机都不需要路由键,交换机时通过Headers头部来将消息映射到队列的,有点像HTTP的Headers.

????????Hash结构中要求携带一个键"x-match",这个键的value可以是any或者all

  • all:在发布消息时携带的所有Entry必须和绑定在队列上的所有Entry完全匹配
  • any:只要发布消息时携带的有一对键值对headers满足队列定义的多个参数arguments的其中一个就能匹配上,注意这里是键值对的完全匹配,只要匹配到键,值却是不一样的

5.默认交换机?Default? Exchange

????????实际上默认交换机是一个由RabbitMQ预先声明好的名字为空字符串的直连交换机(direct exchange)。它有一个特殊的属性使得它对于简单应用特别有用处:那就是每个新建队列(queue)都会自动绑定到默认交换机上,绑定的路由键(routing key)名称与队列名称相同。

???????类似amq.*的名称的交换机:这些是RabbitMQ默认创建的交换机。这些队列名称被预留做RabbitMQ内部使用,不能被应用使用,否则抛出403?(ACCESS_REFUSED)错误?


例如当你声明了一个名为”hello”的队列,RabbitMQ会自动将其绑定到默认交换机上,绑定的路由键名称也是为”hello”。因此,当携带着名为”hello”的路由键的消息被发送到默认交换机的时候,此消息会被默认交换机路由至名为”hello”的队列中。

6.死信交换机 Dead Letter Exchange

????????在默认情况,如果消息在投递到交换机时,交换机发现此消息没有匹配的队列,则这个消息将被悄悄丢弃。为了解决这个问题,RabbitMQ中有一种交换机叫死信交换机。当消费者不能处理接收到的消息时,将这个消息重新发布到另外一个队列中,等待重试或者人工干预。这个过程中的exchange和queue就是所谓的”Dead Letter Exchange 和 Queue

????????通常以下这样的消息会变成死信消息

  • 消息被拒绝,并且设置 requeue 参数为 false
  • 消息过期(默认情況下 Rabbit 中的消息不过期,但是可以设置队列的过期时间和消息的过期时间以达到消息过期的效果)
  • 队列达到最大长度(一般当设置了最大队列长度或大小并达到最大值时)


例如下图:生产者生产一条1分钟后超时的订单消息到正常交换机exchange-a中,消息匹配到队列queue-a,但一分钟后仍未消费。 此时消息就会被投递到死信交换机dlxy-exchange中,并发送到死信队列中, 死信队列dlx-queue的消费者拿到消息后,根据消息去查询订单的状态,如果仍然是未支付状态,就将订单状态更新为超时状态。

三.为什么要使用交换机

使用交换机的主要目的是实现消息的路由和分发,确保消息能够准确地传递到目标队列中。

  1. 路由功能:交换机可以根据消息的路由键将消息发送到相应的队列中。这样,生产者就可以根据消息的属性或标签来指定消息要发送到哪个队列,从而实现灵活的消息路由和过滤。

  2. 解耦合:交换机可以将生产者和消费者解耦合。生产者只需要将消息发送到交换机,而不需要关注具体的队列。消费者只需要从队列中接收消息,而不需要知道消息是如何被发送到队列的。这样,生产者和消费者之间可以独立地进行开发和扩展,提高系统的可维护性和可扩展性。

  3. 多播和广播:使用交换机可以实现消息的多播和广播。多播是指将一条消息发送到多个队列,而广播是指将一条消息发送到所有绑定了交换机的队列。这样,在某些场景下,可以方便地实现消息的复制、分发和通知。

  4. 灵活性和可扩展性:通过使用交换机,可以轻松地增加、删除和修改队列之间的消息路由规则。这样,系统可以根据实际需求进行动态调整,而不需要修改生产者和消费者的代码。

四.怎么使用交换机【demo演示】

1.直连交换机

生产者

package com.example.publisher;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@SuppressWarnings("all")
public class RabbitExchange {

    // 创建一个名为"direct-queue"的队列
    @Bean
    public Queue directQueue() {
        return new Queue("direct_queue");
    }

    // 创建一个名为"direct-exchange"的直连交换机,设置为持久化,不发布消息到交换器
    @Bean
    public DirectExchange directExchange() {
        return new DirectExchange("direct_exchange", true, false);
    }

    // 将队列绑定到直连交换机,并指定路由键为"direct_routing_key"
    @Bean
    public Binding binding() {
        return BindingBuilder.bind(directQueue())
                .to(directExchange())
                .with("direct_routing_key");
    }

}

消费者

package com.example.consumer;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component 
@RabbitListener(queues = {"direct-queue"}) // 监听名为"direct-queue"的队列
public class DirectReceiver {

    @RabbitHandler
    public void handler(Map<String, Object> json) { // 参数为接收到的消息,类型为Map<String, Object>
        System.out.println(json); // 打印接收到的消息
    }
}

测试

package com.example.publisher.controller;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController // 声明这是一个Spring控制器
public class Direct {
    @Autowired // 自动注入AmqpTemplate实例
    private AmqpTemplate rabbitTemplate;

    // 定义一个请求映射,当访问"/direct"路径时,调用sendData方法
    @RequestMapping("/direct")
    public String sendData() {
        // 创建一个Map对象,用于存储要发送的消息数据
        Map<String, Object> data = new HashMap<>();
        data.put("msg", "直连交换机"); // 将消息内容放入Map中

        // 使用rabbitTemplate的convertAndSend方法发送消息,指定交换机名称、路由键和消息数据
        rabbitTemplate.convertAndSend("direct_exchange", "direct_routing_key", data);

        // 返回一个字符串表示消息已发送
        return "😙😙";
    }
}

效果

访问测试的方法

2.主题交换机

生产者

package com.example.publisher;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@SuppressWarnings("all")
public class RabbitExchange {

    // 定义路由规则
    public static String A_KEY = "*.orange.*";
    public static String B_KEY = "*.* rabbit";
    public static String C_KEY = "lazy.#";

    /**
     * 定义队列
     * @return
     */
    @Bean
    public Queue Topicqueue() {
        return new Queue("topic-queue", true);
    }

    /**
     * 定义主题交换机
     * @return
     */
    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange("topic-exchange", true, false);
    }

    /**
     * 将队列与交换机进行绑定,并设置路由键
     * @return
     */
    @Bean
    public Binding bindingA() {
        return BindingBuilder.bind(Topicqueue())
                .to(topicExchange())
                .with(A_KEY);
    }

}

消费者

package com.example.consumer.exchange;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Map;

// 定义一个名为TopicReceiver的类,用于接收RabbitMQ消息
@Component
@RabbitListener(queues = {"topic-queue"})
public class TopicReceiver {

    // 使用@RabbitHandler注解标记handler方法,用于处理接收到的消息
    @RabbitHandler
    public void handler(Map<String,Object> json){
        // 打印接收到的消息
        System.out.println(json);
    }

}

测试

package com.example.publisher.controller;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

// 定义一个名为Sender的类,用于发送消息到RabbitMQ
@RestController
public class Topic {
    // 自动注入AmqpTemplate对象,用于发送消息
    @Autowired
    private AmqpTemplate rabbitTemplate;

    // 定义一个名为sendTopic的方法,用于发送主题消息
    @RequestMapping("/sendTopic")
    public String sendTopic() {
        // 创建一个Map对象,用于存储消息内容
        Map<String,Object> data=new HashMap<>();
        data.put("msg","主题交换机");
        // 使用rabbitTemplate的convertAndSend方法发送消息,指定交换机名称、路由键和消息内容
        rabbitTemplate.convertAndSend("topic-exchange","aa.orange.bb", data);
        // 返回一个字符串表示发送成功
        return "😙😙";
    }
}

效果

3.扇形交换机

生产者

package com.example.publisher;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@SuppressWarnings("all")
public class RabbitExchange {
    // 定义一个名为queueX的队列,用于接收消息
    @Bean
    public Queue queueX() {
        return new Queue("queue-x");
    }

    // 定义一个名为queueY的队列,用于接收消息
    @Bean
    public Queue queueY() {
        return new Queue("queue-y");
    }

    // 定义一个名为queueZ的队列,用于接收消息
    @Bean
    public Queue queueZ() {
        return new Queue("queue-z");
    }

    /**
     * 定义扇形交换机,与路由键无关
     * @return
     */
    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange("fanout-exchange", true, false);
    }

    // 将queueX绑定到fanoutExchange上
    @Bean
    public Binding bindingX() {
        return BindingBuilder.bind(queueX())
                .to(fanoutExchange());
    }

    // 将queueY绑定到fanoutExchange上
    @Bean
    public Binding bindingY() {
        return BindingBuilder.bind(queueY())
                .to(fanoutExchange());
    }

    // 将queueZ绑定到fanoutExchange上
    @Bean
    public Binding bindingZ() {
        return BindingBuilder.bind(queueZ())
                .to(fanoutExchange());
    }

}

消费者

package com.example.consumer.exchange;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
public class FanoutReceiver {

    // 监听队列queue-x的消息,当接收到消息时,调用handlerY方法处理
    @RabbitListener(queues = {"queue-x"})
    @RabbitHandler
    public void handlerY(Map<String,Object> json){
        System.out.println("已接受到队列queue-x传递过来的消息:"+json);
    }

    // 监听队列queue-y的消息,当接收到消息时,调用handlerX方法处理
    @RabbitListener(queues = {"queue-y"})
    @RabbitHandler
    public void handlerX(Map<String,Object> json){
        System.out.println("已接受到队列queue-y传递过来的消息:"+json);
    }

    // 监听队列queue-z的消息,当接收到消息时,调用handlerZ方法处理
    @RabbitListener(queues = {"queue-z"})
    @RabbitHandler
    public void handlerZ(Map<String,Object> json){
        System.out.println("已接受到队列queue-z传递过来的消息:"+json);
    }
}

测试

package com.example.publisher.controller;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
public class Fanout {
    // 自动注入AmqpTemplate对象
    @Autowired
    private AmqpTemplate rabbitTemplate;

    // 定义发送扇形交换机消息的方法
    @RequestMapping("/sendFanout")
    public String sendFanout() {
        // 创建一个Map对象,用于存储消息数据
        Map<String,Object> data=new HashMap<>();
        data.put("msg","扇形交换机");
        // 使用rabbitTemplate的convertAndSend方法发送消息,指定交换机名称和路由键(这里为null,表示不指定路由键)
        rabbitTemplate.convertAndSend("fanout-exchange",null, data);
        // 返回一个字符串,表示发送成功
        return "😙😙;";
    }
}

效果

好啦,今天的分享就到这了,希望能够帮到你呢!😊😊??

文章来源:https://blog.csdn.net/m0_74315688/article/details/135782598
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。