深入学习 RabbitMQ


提示:以下是本篇文章正文内容,RabbitMQ 系列学习将会持续更新

在这里插入图片描述

官网https://www.rabbitmq.com

一、简介和业务场景

简介
RabbitMQ 是一个实现了 AMQP (Advanced Message Queuing Protocol) 高级消息队列协议的消息队列服务,用 Erlang 语言,是面向消息的中间件。

主要流程

  1. 生产者 (Producer) 与消费者 (Consumer) 和 RabbitMQ 服务 (Broker) 建立连接;
  2. 然后生产者发布消息 (Message) 同时需要携带交换机 (Exchange) 名称以及路由规则 (Routing Key),这样消息会到达指定的交换机;
  3. 然后交换机根据路由规则匹配对应的 Binding,最终将消息发送到匹配的消息队列 (Quene);
  4. 最后 RabbitMQ 服务将队列中的消息投递给订阅了该队列的消费者,消费者也可以主动拉取消息。

业务场景一:异步处理

假如一个商城项目,在用户支付模块中,可能会涉及到其它业务,比如:积分折扣、消费券、短信验证等功能。我们传统的执行步骤是逐步执行,需要等待每个业务执行完毕才能支付成功,这样大大影响了用户的体验!

在这里插入图片描述
我们使用消息中间件进行异步处理,当用户下单支付同时我们创建消息队列进行异步的处理其它业务,在我们支付模块中最重要的是用户支付,我们可以将一些不重要的业务放入消息队列执行,这样可以大大提高了我们程序运行的速度,用户支付模块中也大大减少了支付时间,为用户带来了更好的体验感!

业务场景二:应用解耦

我们以商城项目为例,订单系统耦合调用支付、库存、物流系统。如果某天其中一个系统出现了异常就会造成订单系统故障!
在这里插入图片描述
使用中间件后,订单系统通过 队列 去访问支付、库存、物流系统,队列会监督各个系统完成,如果完不成队列会一直监督,直到完成为止!这样就不会因为一个子系统出现故障而造成整个系统瘫痪。

业务场景三:流量削峰

假设我们有一个订单系统,我们的订单系统最大承受访问量是每秒1万次,如果说某天访问量过大我们的系统承受不住了,会对服务器造成宕机,这样的话我们的系统就瘫痪了,为了解决该问题我们可以使用中间件对流量进行消峰。
在这里插入图片描述
服务器收到用户的请求后,首先写入消息队列,可以设置消息队列的 TTL 和最大长度,达到阈值则直接抛弃用户请求或跳转到错误页面。
这种方式的好处是可以避免系统的宕机瘫痪,坏处是系统速度变慢,但是总比不能使用好。

回到目录…

二、Java 操作消息队列

2.1 添加 RabbitMQ 依赖

<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.14.2</version>
</dependency>

2.2 创建生产者,负责将信息发送到消息队列

public class ProducerDemo {
    public static void main(String[] args) {
        // 使用ConnectionFactory来创建连接
        ConnectionFactory factory = new ConnectionFactory();

        // 设定连接信息
        factory.setHost("1.15.76.95");
        factory.setPort(5672);  // 这是amqp协议端口
        factory.setUsername("admin");
        factory.setPassword("123456");
        factory.setVirtualHost("/test"); // 虚拟主机

        // 创建连接
        try(Connection connection = factory.newConnection();
        	// 1.通过 Connection 创建新的 Channel
            Channel channel = connection.createChannel()) {
            // 2.声明队列,如果此队列不存在,会自动创建
            channel.queueDeclare("yyds", false, false, false, null);
            // 3.将队列绑定到交换机
            channel.queueBind("yyds", "amq.direct", "my-yyds");
            // 4.发布新的消息,注意消息需要转换为byte[]
            channel.basicPublish("amq.direct", "my-yyds", null, "Hello,World!".getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

a. 客户端需要通过连接创建一个新的通道(Channel),同一个连接下可以有很多个通道,这样就不用创建很多个连接也能支持分开发送了。

b. 其中 queueDeclare 方法的参数如下:

com.rabbitmq.client.AMQP.Queue.DeclareOk queueDeclare(
	String queue, // 队列的名称(默认创建后routingKey和队列名称一致)
	boolean durable, // 是否持久化
	boolean exclusive, // 是否排他,如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。排他队列是基于Connection可见,同一个Connection的不同Channel是可以同时访问同一个连接创建的排他队列,一旦Connection关闭或者客户端退出,该排他队列都会自动被删除。
	boolean autoDelete, // 是否自动删除
	Map<String, Object> arguments); // 设置队列的其他一些参数

c. 其中 queueBind 方法参数如下:

com.rabbitmq.client.AMQP.Queue.BindOk queueBind(
	String queue, // 需要绑定的队列名称
	String exchange, // 需要绑定的交换机名称
	String routingKey);

d. 其中 basicPublish 方法的参数如下:

void basicPublish(
	String exchange, // 对应的Exchange名称,我们这里就使用第二个直连交换机
	String routingKey, // 填写绑定时指定的routingKey
	BasicProperties props, // 其他的配置
	byte[] body); // 消息本体

e. 执行完成后,可以在管理页面中看到我们刚刚创建好的消息队列了:
在这里插入图片描述
在这里插入图片描述

回到目录…

2.3 创建消费者,读取消息

public class ConsumerDemo {
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("1.15.76.95");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("123456");
        factory.setVirtualHost("/test");

        // 这里不使用try-with-resource,因为消费者是一直等待新的消息到来,所以就不关闭连接了
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        // 创建一个基本的消费者
        channel.basicConsume("yyds",
                false, // 是否开启自动应答,默认采用 Ack message requeue false
                (s, delivery) -> {
                    System.out.println(new String(delivery.getBody()));
                    // 确认应答,第一个参数是当前的消息标签,第二个参数false表示不批量处理队列中所有的消息(只处理当前消息)
                    channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
                    // 拒绝应答,第二个参数false表示只处理当前消息,第三个参数false表示不把消息丢回队列(丢弃消息)
                    //channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, false);
                    // 拒绝此消息,第二个参数false表示是否重新排队
                    //channel.basicReject(delivery.getEnvelope().getDeliveryTag(), false);
                },
                s -> {});
    }
}

其中 basicConsume 方法参数如下:

String basicConsume(String var1, boolean var2, DeliverCallback var3, CancelCallback var4) throws IOException;
  • String queue - 消息队列名称,直接指定。
  • boolean autoAck - 自动应答,消费者从消息队列取出数据后,需要跟服务器进行确认应答,当服务器收到确认后,会自动将消息删除。如果开启自动应答,那么默认调用 ack 消息发出后会直接删除;如果不开启,则需要手动指定。
  • DeliverCallback deliver - 消息接收后的函数回调,我们可以在回调中对消息进行处理,处理完成后,需要给服务器确认应答。
    Ack Mode (应答模式有4种):
    • Nack message requeue true:拒绝消息,也就是说不会将消息从消息队列取出,并且重新排队,一次可以拒绝多个消息。
    • Ack message requeue false:确认应答,确认后消息会从消息队列中移除,一次可以确认多个消息。
    • Reject message requeue true/false:也是拒绝此消息,但是可以指定是否重新排队。
  • CancelCallback cancel - 当消费者取消订阅时进行的函数回调,这里暂时用不到。

执行完成后,可以在控制台看到我们刚刚获取到的消息了:
在这里插入图片描述

回到目录…

三、SpringBoot 整合消息队列

官方文档https://docs.spring.io/spring-amqp/docs/current/reference/html

前面我们已经完成了 RabbitMQ 的安装和简单使用,并且通过 Java 连接到服务器。现在我们来尝试在 SpringBoot 中整合消息队列客户端。

🍒3.1 依赖、yml、config配置类

①添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

②yml 配置文件,配置 RabbitMQ 的信息

spring:
  rabbitmq:
    addresses: 1.15.76.95
    username: admin
    password: 123456
    virtual-host: /test

③config 配置类,指定交换机、指定消息队列、进行交换机和队列的绑定

@Configuration
public class RabbitConfiguration {

    @Bean("directExchange") // 定义交换机Bean,可以很多个
    public Exchange exchange(){
        return ExchangeBuilder.directExchange("amq.direct").build();
    }

    @Bean("yydsQueue") // 定义消息队列
    public Queue queue(){
        return QueueBuilder
                .nonDurable("yyds") // 队列名称,非持久化类型
                .build();
    }

    @Bean("binding")
    public Binding binding(@Qualifier("directExchange") Exchange exchange,
                           @Qualifier("yydsQueue") Queue queue){
        // 将我们刚刚定义的交换机和队列进行绑定
        return BindingBuilder
                .bind(queue) // 绑定队列
                .to(exchange) // 到交换机
                .with("my-yyds") // 使用自定义的routingKey
                .noargs();
    }
}

回到目录…

🍒3.2 创建生产者

@SpringBootTest
class RabbitMqStudyApplicationTests {

    @Resource
    RabbitTemplate template;
    // RabbitTemplate为我们封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可

    @Test
    void test() {
        // 使用convertAndSend方法一步到位
        // public void convertAndSend(String exchange, String routingKey, Object object)
        // SimpleMessageConverter only supports String, byte[] and Serializable payloads
        template.convertAndSend("amq.direct", "my-yyds", "Hello,World");
    }
}

启动,查看控制台日志,可以看到yyds消息队列的详细信息:

2023-04-02 22:37:13.320  INFO 17512 --- [           main] c.w.r.RabbitMqStudyApplicationTests      : Started RabbitMqStudyApplicationTests in 1.662 seconds (JVM running for 2.725)
2023-04-02 22:37:13.469  INFO 17512 --- [           main] o.s.a.r.c.CachingConnectionFactory       : Attempting to connect to: [1.15.76.95:5672]
2023-04-02 22:37:13.661  INFO 17512 --- [           main] o.s.a.r.c.CachingConnectionFactory       : Created new connection: rabbitConnectionFactory#375b5b7f:0/SimpleConnection@34819867 [delegate=amqp://admin@1.15.76.95:5672//test, localPort= 64448]
2023-04-02 22:37:13.664  INFO 17512 --- [           main] o.s.amqp.rabbit.core.RabbitAdmin         : Auto-declaring a non-durable, auto-delete, or exclusive Queue (yyds) durable:false, auto-delete:false, exclusive:false. It will be redeclared if the broker stops and is restarted while the connection factory is alive, but all messages will be lost.

🍒3.3 创建消费者

因为消费者实际上就是一直等待消息然后进行处理的角色,这里我们只需要创建一个监听器就行了,它会一直等待消息到来然后再进行处理。

方式一@RabbitListener 标注在方法上,直接监听指定的队列,此时接收的参数需要与发送的类型一致。

@Component  //注册为Bean
public class ConsumeListener {

    @RabbitListener(queues = "yyds")  //定义此方法为队列yyds的监听器,一旦监听到新的消息,就会接受并处理
    public void receiver(Message message){
        System.out.println(new String(message.getBody()));
    }
}

方式二@RabbitListener 标注在类上面表示当有收到消息的时候,就交给 @RabbitHandler 的方法处理,根据接收的参数类型进入具体的方法中。

@Component
@RabbitListener(queues = "yyds")
public class ConsumeListener {
	
	@RabbitHandler
    public void receiver(Message message){ //接收所有类型的数据
        System.out.println(new String(message.getBody()));
    }

	@RabbitHandler
    public void processMessage1(String message) { //接收String类型的数据
        System.out.println(message);
    }
 
    @RabbitHandler
    public void processMessage2(byte[] message) { //接收byte[]类型的数据
        System.out.println(new String(message));
    }
}

接着我们启动服务器:
在这里插入图片描述
可以看到控制台成功输出了我们之前放入队列的消息,并且管理页面中也显示此消费者已经连接了:
在这里插入图片描述
此时,我们再通过管理页面添加新的消息,也是可以持续接收的。

回到目录…

🍒3.4 消费者反馈给生产者

如果我们需要确保消息能够被消费者接受并处理,就需要得到消费者的反馈。

// 生产者
@Test
void publisher() {
  	//会等待消费者消费然后返回响应结果
    Object res = template.convertSendAndReceive("amq.direct", "my-yyds", "Hello World!");
    System.out.println("收到消费者响应:" + res);
}
// 消费者
@RabbitListener(queues = "yyds")  // 定义此方法为队列yyds的监听器,一旦监听到新的消息,就会接受并处理
public String receiver(String message){
    System.out.println(message);
    return "收到!";
}

测试:先启动消费者,再启动生产者。(因为如果生产者在一定时间内未收到反馈,会自动返回null)

2023-04-02 22:59:03.591  INFO 8552 --- [           main] o.s.a.r.c.CachingConnectionFactory       : Attempting to connect to: [1.15.76.95:5672]
2023-04-02 22:59:03.763  INFO 8552 --- [           main] o.s.a.r.c.CachingConnectionFactory       : Created new connection: rabbitConnectionFactory#45f24169:0/SimpleConnection@769a58e5 [delegate=amqp://admin@1.15.76.95:5672//test, localPort= 64998]
2023-04-02 22:59:03.780  INFO 8552 --- [           main] o.s.amqp.rabbit.core.RabbitAdmin         : Auto-declaring a non-durable, auto-delete, or exclusive Queue (yyds) durable:false, auto-delete:false, exclusive:false. It will be redeclared if the broker stops and is restarted while the connection factory is alive, but all messages will be lost.
2023-04-02 22:59:04.110  INFO 8552 --- [           main] c.w.r.RabbitMqStudyApplicationTests      : Started RabbitMqStudyApplicationTests in 2.315 seconds (JVM running for 3.179)
2023-04-02 22:59:04.313  INFO 8552 --- [           main] .l.DirectReplyToMessageListenerContainer : Container initialized for queues: [amq.rabbitmq.reply-to]
2023-04-02 22:59:04.423  INFO 8552 --- [           main] .l.DirectReplyToMessageListenerContainer : SimpleConsumer [queue=amq.rabbitmq.reply-to, index=0, consumerTag=amq.ctag-ktaUOuTLGH-2_YYJnmdVwA identity=6a48a7f3] started
收到消费者响应:收到!

回到目录…

🍒3.5 发送/接收 JSON 数据

@Data
public class User {
    int id;
    String name;
}

注意:实体类一定要有默认的无参构造。

  • 不能使用 @AllArgsConstructor 注解;如果自定义有参构造,必须加上无参构造。
  • 否则会 JSON错误提示:(no Creators, like default constructor, exist): cannot deserialize from Object value

①先在配置类中添加 JSON 转换器:

@Configuration
public class RabbitConfiguration {
  	...

    @Bean("jacksonConverter") //直接创建一个用于JSON转换的Bean
    public Jackson2JsonMessageConverter converter(){
        return new Jackson2JsonMessageConverter();
    }
}

②生产者:有了 JSON 转换器,就可以直接发送 Object 类型的消息。

@Test
void publisher2() {
    template.convertAndSend("amq.direct", "my-yyds", new User(1, "张三"));
}

在这里插入图片描述

③消费者:将消息队列中取出来的JSON数据转为对象,需要指定转换器

@Component
public class TestListener {

  	// 指定messageConverter为我们刚刚创建的Bean名称
    @RabbitListener(queues = "yyds", messageConverter = "jacksonConverter")
    public void receiver(User user){ // 直接接收User类型
        System.out.println(user);
    }
}

我们也可以在网页端发布 JSON 数据,发现都可以转换:{"id":1,"name":"张三"}
在这里插入图片描述

回到目录…

四、5 种消息模式

官方教程https://www.rabbitmq.com/getstarted.html

🍬4.1 Simple Queue 基本消息模式

在这里插入图片描述
最简单的模型,我们上面的学习都是基于一个生产者、一个消费者的模式,发送端把消息放入队列中,接收端从队列中拿消息。

@Resource
RabbitTemplate template;
    
@Test
void publisher() { //生产者
    template.convertAndSend("amq.direct", "my-yyds", "Hello,World");
}
@Component
public class ConsumeListener {

    @RabbitListener(queues = "yyds")
    public void receiver(Message message){ //消费者
        System.out.println(new String(message.getBody()));
    }
}

回到目录…

🍬4.2 Work Queues 工作队列模式

在这里插入图片描述
接着我们来了解一下一个生产者多个消费者的情况,实际上这种模式就非常适合多个工人等待新的任务到来的场景,我们的任务有很多个,一个一个丢进消息队列,而此时工人有很多个,那么我们就可以将这些任务分配个各个工人,让他们各自负责一些任务,并且做的快的工人还可以做完成一些(能者多劳)。

①我们只需要创建两个监听器即可:

@Component
public class ConsumeListener {

    @RabbitListener(queues = "yyds")
    public void receiver1(String message) {
        System.out.println("一号消息队列监听器: " + message);
    }

    @RabbitListener(queues = "yyds")
    public void receiver2(String message) {
        System.out.println("二号消息队列监听器: " + message);
    }
}

重启后,我们可以发现yyds队列下连接了两个消费者:Prefetch count = 250(预获取数量,一次性获取消息的最大数量)
在这里插入图片描述

  • 当我们实时向队列中发送消息时,会自动进行轮询分发:
    在这里插入图片描述
  • 当我们提前向队列中囤积消息时,会发现启动后都由二号监听器处理:
    在这里插入图片描述

这是由消费者的 Prefetch count 决定的,默认每个消费者的预获取数量是250,这个参数是针对同一批消息。所以实时发消息会认为每批只有一个消息,会进行轮询;而队列中囤积的消息则属于同一批消息,只有达到250条才会轮询。

②在配置类中添加 containerFactory 的设置:

这里我们需要在 RabbitConfiguration 配置类中定义一个自定义的 ListenerContainerFactory,可以在这里设定消费者 Channel 的 PrefetchCount 的大小:

@Resource
private CachingConnectionFactory connectionFactory;

@Bean(name = "listenerContainer")
public SimpleRabbitListenerContainerFactory listenerContainer(){
    SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    factory.setConnectionFactory(connectionFactory);
    factory.setPrefetchCount(1);   //将PrefetchCount设定为1表示一次只能取一个
    return factory;
}

接着我们在监听器这边指定即可:

@Component
public class ConsumeListener {
    @RabbitListener(queues = "yyds", containerFactory = "listenerContainer")
    public void receiver1(String message) throws InterruptedException {
        Thread.sleep(500); //让每个消息处理时间长一些,结果会更清晰
        System.out.println("一号消息队列监听器: " + message);
    }

    @RabbitListener(queues = "yyds", containerFactory = "listenerContainer")
    public void receiver2(String message) throws InterruptedException {
        Thread.sleep(500);
        System.out.println("二号消息队列监听器: " + message);
    }
}

现在我们再次启动服务器,可以看到 PrefetchCount 被限定为1了:
在这里插入图片描述

再次重复上述的实现,可以看到消息不会被同一个消费者给全部抢走了:
在这里插入图片描述

③通过 concurrency 参数直接指定消费者的数量:

当然除了去定义两个相同的监听器之外,我们也可以直接在注解中定义,比如我们现在需要10个同样的消费者:

@Component
public class ConsumeListener {
    @RabbitListener(queues = "yyds",  containerFactory = "listenerContainer", concurrency = "10")
    public void receiver(String data){
        System.out.println(data);
    }
}

可以看到在管理页面中出现了10个消费者:
在这里插入图片描述

回到目录…

🍬4.3 Publish/Subscribe 发布订阅模式

在这里插入图片描述
我们来看一下发布订阅模式,比如我们在阿里云买了云服务器,但是最近快到期了,那么就会给你的手机、邮箱发送消息,告诉你需要去续费了,但是手机短信和邮件发送并不一定是同一个业务提供的,但是现在我们又希望能够都去执行,所以就可以用到发布订阅模式,简而言之就是,发布一次,消费多个。

实现这种模式其实也非常简单,但是如果使用我们之前的直连交换机,肯定是不行的,我们这里需要用到另一种类型的交换机,叫做 fanout(扇出)类型,这时一种广播类型,消息会被广播到所有与此交换机绑定的消息队列中。

这里我们使用默认的 fanout 类型的交换机:
在这里插入图片描述

①在配置类中定义扇出交换机,并绑定两个队列:

@Configuration
public class RabbitConfiguration {

    @Bean("fanoutExchange")
    public Exchange exchange(){
        //注意这里是fanoutExchange
        return ExchangeBuilder.fanoutExchange("amq.fanout").build();
    }

    @Bean("yydsQueue1")
    public Queue queue(){
        return QueueBuilder.nonDurable("yyds1").build();
    }

    @Bean("yydsQueue2")
    public Queue queue2(){
        return QueueBuilder.nonDurable("yyds2").build();
    }

    @Bean("binding")
    public Binding binding(@Qualifier("fanoutExchange") Exchange exchange,
                           @Qualifier("yydsQueue1") Queue queue){
        return BindingBuilder
                .bind(queue)
                .to(exchange)
                .with("yyds1")
                .noargs();
    }

    @Bean("binding2")
    public Binding binding2(@Qualifier("fanoutExchange") Exchange exchange,
                            @Qualifier("yydsQueue2") Queue queue){
        return BindingBuilder
                .bind(queue)
                .to(exchange)
                .with("yyds2")
                .noargs();
    }
}

②接着我们搞两个监听器,监听一下这两个队列:

@Component
public class ConsumeListener {

    @RabbitListener(queues = "yyds1")
    public void receiver(String data){
        System.out.println("一号消息队列监听器 " + data);
    }

    @RabbitListener(queues = "yyds2")
    public void receiver2(String data){
        System.out.println("二号消息队列监听器 " + data);
    }
}

重启后,我们可以发现默认的 amq.fanout 交换机已经绑定了两个队列:
在这里插入图片描述
此时,我们向该交换机中发布一条消息:
在这里插入图片描述
可以发现,该交换机以广播的形式分发给它绑定的两个消息队列:
在这里插入图片描述

回到目录…

🍬4.4 Routing 路由模式

在这里插入图片描述

  • 路由模式实际上我们一开始就已经实现了,我们可以在绑定时指定想要的 routingKey 只有生产者发送时指定了对应的 routingKey 才能到达对应的队列。
  • 当然除了我们之前的一次绑定之外,同一个消息队列可以多次绑定到交换机,并且使用不同的 routingKey,这样只要满足其中一个都可以被发送到此消息队列中。

①我们就在配置类中,将同一个交换机和同一个队列进行不同 routingKey 的绑定:

@Configuration
public class RabbitConfiguration {

    @Bean("directExchange")
    public Exchange exchange(){
        return ExchangeBuilder.directExchange("amq.direct").build();
    }

    @Bean("yydsQueue")
    public Queue queue(){
        return QueueBuilder.nonDurable("yyds").build();
    }

    @Bean("binding")   //使用yyds1绑定
    public Binding binding(@Qualifier("directExchange") Exchange exchange,
                           @Qualifier("yydsQueue") Queue queue){
        return BindingBuilder
                .bind(queue)
                .to(exchange)
                .with("yyds1") //routingKey=yyds1
                .noargs();
    }

    @Bean("binding2")   //使用yyds2绑定
    public Binding binding2(@Qualifier("directExchange") Exchange exchange,
                           @Qualifier("yydsQueue") Queue queue){
        return BindingBuilder
                .bind(queue)
                .to(exchange)
                .with("yyds2") //routingKey=yyds2
                .noargs();
    }
}

②我们去监听唯一的消息队列 yyds:

@Component
public class ConsumeListener {
    @RabbitListener(queues = "yyds")
    public void receiver(String data){
        System.out.println("yyds消息队列监听器 " + data);
    }
}

启动后,可以发现 amq.direct 交换机绑定了两个路由:实际上指向同一个消息队列 yyds
在这里插入图片描述

测试一下,无论我们使用 yyds1yyds2 哪个 routingKey,都可以成功路由到 yyds 消息队列中:
在这里插入图片描述
在这里插入图片描述

回到目录…

🍬4.5 Topics 主题模式

在这里插入图片描述

  1. 实际上就是一种模糊匹配的模式,我们可以将 routingKey 以模糊匹配的方式去进行转发。
    • * 表示任意的一个单词,如 *.orange.* 可以表示为 a.orange.b、c.orange.c
    • # 表示0个或多个单词,如 lazy.# 可以表示为 lazy、lazy.a、lazy.a.b
  2. 应用一amq.topic 交换机实现模糊路由转发。
  3. 应用二amq.rabbitmq.trace 交换机实现消息追踪。

应用一:amq.topic 交换机实现模糊路由转发
在这里插入图片描述
在配置类中定义主题交换机,并且以模糊匹配的方式绑定一个队列

@Configuration
public class RabbitConfiguration {

    @Bean("topicExchange")  //这里使用预置的Topic类型交换机
    public Exchange exchange(){
        return ExchangeBuilder.topicExchange("amq.topic").build();
    }

    @Bean("yydsQueue")
    public Queue queue(){
        return QueueBuilder.nonDurable("yyds").build();
    }

    @Bean("binding")
    public Binding binding2(@Qualifier("topicExchange") Exchange exchange,
                           @Qualifier("yydsQueue") Queue queue){
        return BindingBuilder
                .bind(queue)
                .to(exchange)
                .with("*.test.*")
                .noargs();
    }
}

启动项目,绑定成功:
在这里插入图片描述
可以看到只要是满足通配符条件的都可以成功转发到对应的消息队列:
在这里插入图片描述
在这里插入图片描述

回到目录…

应用二:amq.rabbitmq.trace 交换机实现消息追踪
在这里插入图片描述
可以看到它也是 topic 类型的,它是一个内部交换机,用于帮助我们记录和追踪生产者和消费者使用消息队列的交换机。

①首先,我们需要在控制台将虚拟主机 /test 的追踪功能开启:

rabbitmqctl trace_on -p /test

②创建一个 trace 消息队列用于接收记录:
在这里插入图片描述

③我们给 amq.rabbitmq.trace 交换机绑定上刚刚的队列: 因为该交换机是内部的,所以只能在 Web 管理页面中绑定
在这里插入图片描述
在这里插入图片描述
由于发送到此交换机上的 routingKey 为 publish.交换机名称deliver.队列名称,分别对应生产者投递到交换机的消息,和消费者从队列上获取的消息,因此这里使用 # 通配符进行绑定。

④现在我们来测试一下,往 yyds 队列中发送消息: 会发现 trace 队列中多了2条信息。
在这里插入图片描述

通过追踪,我们可以很明确地得知消息发送的交换机、routingKey、用户等信息,包括信息本身:
在这里插入图片描述
同样的,消费者在取出数据时也有记录:我们可以明确消费者的地址、端口、具体操作的队列以及取出的消息信息等。
在这里插入图片描述

回到目录…

五、headers 交换机

实际上,RabbitMQ 默认的七种交换机一共分为4类: directfanouttopicheaders,当然前3种类型我们已经在之前学习过了。

NameTypeFeatures描述
(AMQP default)directD所有虚拟主机都会自带的一个默认交换机,并且此交换机不可删除。
此交换机默认绑定到所有的消息队列,不能解绑,并且通过队列名称进行路由。
amq.directdirectD是一个普通直连交换机,持久化的。
该交换机是具有绑定关系的,默认没有任何绑定,需要手动绑定消息队列。
amq.fanoutfanoutD是一个扇出类型的交换机,一种广播类型,消息会被广播到所有与此交换机绑定的消息队列中。
应用于发布订阅模式。
amq.headersheadersD它不通过RoutingKey进行分发消息,而时通过消息中内容的headers属性进行匹配。
amq.matchheadersD和 amq.headers 一样,目前不清楚区别在哪儿。
amq.rabbitmq.tracetopicD I是一个topic类型的内部交换机,可以实现消息追踪,帮助我们记录和追踪生产者和消费者使用消息队列的情况,应用于主题模式。
amq.topictopicD是一个topic类型的交换机,可以实现模糊路由转发,应用于主题模式。

那么,我们现在学习一下 headers 类型的交换机:amq.headers

它不通过 RoutingKey 进行分发消息,而是根据头部信息来决定的,在我们发送的消息中是可以携带一些头部信息的(类似于HTTP),我们可以根据这些头部信息来决定路由到哪一个消息队列中。

@Configuration
public class RabbitConfiguration {

    @Bean("headerExchange")  //注意这里返回的是HeadersExchange
    public HeadersExchange exchange(){
        return ExchangeBuilder
                .headersExchange("amq.headers")  //RabbitMQ为我们预置了两个,这里用第一个就行
                .build();
    }

    @Bean("yydsQueue")
    public Queue queue(){
        return QueueBuilder.nonDurable("yyds").build();
    }

    @Bean("binding")
    public Binding binding2(@Qualifier("headerExchange") HeadersExchange exchange,  //这里和上面一样的类型
                            @Qualifier("yydsQueue") Queue queue){
        return BindingBuilder
                .bind(queue)
                .to(exchange)
                .where("test").matches("hello"); //设置消息的头部信息中包含test=hello, 才能转发给yyds消息队列
                //.whereAny("a", "b").exist();  // 这个是只要存在任意一个指定的头部Key就行
                //.whereAll("a", "b").exist();  // 这个是必须存在所有指定的的头部Key        
                //.whereAny(Collections.singletonMap("test", "hello")).match();  传入Map也行,批量指定键值对
    }
}

启动后,发现 amq.headers 交换机成功绑定了 yyds 消息队列:
在这里插入图片描述

我们尝试向 amq.headers 交换机中发布消息:
在这里插入图片描述

结果发现,消息可以成功发送到消息队列,这就是使用头部信息进行路由。
在这里插入图片描述

回到目录…

六、集群搭建

🍡6.1 多服务器集群

①我们首先在两个服务器上开启 rabbitmq 服务,保证都能正常访问接下来我们以 rabbit@ebe207194e57 作为主服务器,第二个服务器作为从节点,搭建一主一从的集群。

②从服务器的准备工作:

 我们可以在管理页面看到主服务器的名称,之后我们从服务器需要通过这个名称连接主服务器。
在这里插入图片描述
 a. 需要修改本地的 hosts:为了让服务器能解析到主服务器的IP地址。

vim /etc/hosts
# 添加主服务器地址 -> 名称
1.15.76.95 ebe207194e57

 b. 修改相同的 cookie 值:

# 赋予写权限
chmod 777 /var/lib/rabbitmq/.erlang.cookie
# 编辑cookie值和主服务器的值相同
vim /var/lib/rabbitmq/.erlang.cookie
# 改回只读权限
chmod 400 /var/lib/rabbitmq/.erlang.cookie

③连接主服务器: 搭建集群

rabbitmqctl stop_app
rabbitmqctl join_cluster rabbit@ebe207194e57
rabbitmqctl start_app

此时,虽然已经搭建好集群了,但是从服务器还没有拷贝功能。目前它只能显示主服务器的信息,但主服务器宕机时,它是无法查看信息的。

④添加一条政策: 赋予从节点镜像拷贝的功能。
在这里插入图片描述

实际上添加完成后,主从都有这条政策了,此时集群就彻底搭建完成了。
在这里插入图片描述

⑤高可用主节点掉线重启后,发现从节点变成了主节点,原本的主节点却变成了从节点。
在这里插入图片描述

回到目录…

🍡6.2 Docker 部署集群

我们之前学习 不同环境下安装 RabbitMQ 时,学会了在 Docker 中部署 RabbitMQ 服务器。那么,我们也可以在 Docker 中搭建 RabbitMQ 集群。

①创建并启动服务器节点

a. 主节点:

docker run -d \
--hostname rabbit1 \
--name myrabbit1 \
--restart always \
-p 5672:5672 -p 15672:15672 \
-e RABBITMQ_ERLANG_COOKIE='rabbitcookie' \
rabbitmq:management

b. 从节点:

docker run -d \
--hostname rabbit2 \
--name myrabbit2 \
--restart always \
-p 5673:5672 -p 15673:15672 \
--link myrabbit1:rabbit1 \
-e RABBITMQ_ERLANG_COOKIE='rabbitcookie' \
rabbitmq:management

注意

  • 给每个 rabbitmq 服务器设置 hostname 主机名称,连接集群时会用到。
  • 多个容器之间使用 -link 连接,使它们之间可见。
  • 给每个 rabbitmq 服务器设置相同的 RABBITMQ_ERLANG_COOKIE ,因为 RabbitMQ 是用 Erlang 实现的,Erlang Cookie 相当于不同节点之间相互通讯的秘钥,Erlang 节点通过交换 Erlang Cookie 获得认证。

c. 创建新用户:为了远程登录管理页面,两个服务器都要创建新用户。

rabbitmqctl add_user admin 123456

# 修改用户角色为管理员
rabbitmqctl set_user_tags admin administrator

②搭建集群:需要进入 rabbit2 从服务器中操作。

# 停止RabbitMQ进程
rabbitmqctl stop_app

# mq2加入mq1集群
rabbitmqctl join_cluster rabbit@rabbit1

# 再次启动mq进程
rabbitmqctl start_app

此时,我们登录两个服务器的管理页面,可以看到它们的集群中有两个节点:
在这里插入图片描述

尝试在 rabbit1 中创建队列,可以看到如下信息,rabbit2 也是这样的信息,说明从节点只是显示了主节点的信息,而并没有复制信息。
在这里插入图片描述

③添加一条政策: 赋予从节点镜像拷贝的功能。
在这里插入图片描述
添加完成后,我们发现主从服务器都有了这条政策:
在这里插入图片描述

而信息也发生了变化,可以看到 Node + 1,yyds 存储到了两个服务器节点中了。
在这里插入图片描述

进入 yyds 可以看到集群的详细信息:
在这里插入图片描述

④高可用

当主节点 rabbit1 宕机时:
在这里插入图片描述
当主节点重启后,发现从节点变成了主节点,原本的主节点却变成了从节点,实现了高可用
在这里插入图片描述

回到目录…

🍡6.3 SpringBoot 集群配置

spring:
  rabbitmq:
    addresses: 1.15.76.95:5672, 1.15.76.95:5673
    username: admin
    password: 123456
    virtual-host: /test

回到目录…


总结:
提示:这里对文章进行总结:
本文是对RabbitMQ的学习,首先我们介绍了消息队列的业务场景,然后学习了Java操作rabbitmq的方式,并且详细学习了RabbitMQ的五种消息模式和七种默认的交换机,最后也学习了搭建集群的方法。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只咸鱼。。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值