rabbitmq(一):基本概念,channel,virtualHost, Broker,3种exchange,原生实现及springboot整合

本文详细介绍了RabbitMQ的基本概念,包括Exchange的Direct、Fanout和Topic三种类型,以及Channel、虚拟主机(VirtualHost)和Broker的角色。通过原生和SpringBoot整合的方式展示了如何实现消息的发送和消费。重点阐述了Exchange的路由策略和解耦特性,以及SpringBoot中配置消费者的公平分发和手动确认机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

RabbitMQ基本概念

1-1:基本架构图

在这里插入图片描述

图来自于 https://www.cnblogs.com/xiaozhang666/p/13866121.html

  • rabbitmq版本:RabbitMQ 3.9.7

  • maven 版本:

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

1-2:Channel

why?

Some applications need multiple logical connections to the broker. However, it is undesirable to keep many TCP connections open at the same time because doing so consumes system resources and makes it more difficult to configure firewalls. AMQP 0-9-1 connections are multiplexed with channels that can be thought of as “lightweight connections that share a single TCP connection”.

本段话借鉴于官网

意思是说,有一些应用需要和中间件broker建立多个逻辑连接,但是,同时保持多个TCP连接很显然是不理想的,因为这样会消耗很多系统资源,并且防火墙firewall的配置也会显得更加困难,AMQP连接是复用channels的,channels可以被认作是一种共享单个TCP连接的轻量级connection。

白话的意思就是,首先有一个TCP connection起来,接着同时,会启用一个channel,一个connection里面可以有多个channel,connection消失,channel也跟着消失,channel之间是不相互影响的,每一个channel都有一个自己的id。

信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内地虚拟链接,AMQP命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说,建立和销毁TCP都是非常昂贵的开销,所以引入了信道的概念,以复用一条TCP连接。

本段话借鉴于https://www.cnblogs.com/xiaozhang666/p/13866121.html

总结,为什么有channel? 两个字 复用

1-3:Exchange

看很多blog都是直接说,exchange有几种怎么怎么样。很少看见为什么存在exchange,而不是producer直接去操作Queue。个人认为从架构上看,主要还是解耦,producer无需关心消息发送到哪个队列里面,exchange会有自己的策略去转发这些消息,就像是网络中的交换机。producer面向exchange,consumer面向Queue。

那么exchange是如何知道来了一个消息该去往哪个/哪些Queue呢?引入两个概念,routingKeybindingKey,简单可以这么理解,bindingKey代表,exchange和哪些Queue进行了关联,producer向exchange发送消息的时候,一般会带上routingKey,将routingKey和bindingKey进行匹配,从而决定将消息发送到哪个Queue里面。

1-3-1:DirectExchange(原生实现)

exchange、queue通过bindingKey进行绑定,producer发送的message 用routingkey进行标记,通过routingKey和BindingKey做匹配的方式,把message送到指定的queue中,从而被consumer进行消费。

示意图:这个图可能没有那么准确,但是可以说明一定的问题。

在这里插入图片描述

  • producer.java
package directExchange;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.nio.charset.StandardCharsets;

/**
 * @author xx
 * @date 2021/10/2 16:05
 */
public class Producer {
    public static void main(String[] args) throws Exception {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("172.20.10.2");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("rabbitmq");
        connectionFactory.setPassword("rabbitmq");

        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        String exchangeName = "directExchange";
        String routingKey = "direct";
        String message = "From director Exchange Message";
        channel.basicPublish(exchangeName, routingKey, null, message.getBytes(StandardCharsets.UTF_8));
    }
}
  • consumer.java
package directExchange;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * @author xx
 * @date 2021/10/2 18:56
 */
public class DirectConsumer {
    public static void main(String[] args) throws Exception {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("172.20.10.2");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("rabbitmq");
        connectionFactory.setPassword("rabbitmq");

        // 创建连接
        Connection connection = connectionFactory.newConnection();
        // 来一个信道
        Channel channel = connection.createChannel();
        String exchangeType = "direct";
        String exchangeName = "directExchange";
        String bindingKey = "direct";
        String queueName = "directQueue";
        // 声明一个exchange交换机
        channel.exchangeDeclare(exchangeName, exchangeType);
        // 声明一个队列
        channel.queueDeclare(queueName, false, false, false, null);
        // 队列和交换机以及bindingKey进行绑定
        channel.queueBind(queueName, exchangeName, bindingKey);

        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body)
                    throws IOException {
                String message = new String(body, StandardCharsets.UTF_8);
                System.out.println("消费消息:" + message);
            }
        };

        channel.basicConsume(queueName, true, consumer);
    }
}

代码其实不用纠结,原生的代码更能体现思想,比如谁和谁绑定在一起,一个信道里面有几样东西才能完成整个的流程。需要注意的是,要先启动consumer端,再启动producer端,才能看见效果。

在这里插入图片描述

1-3-2:Fanout Exchange(原生实现)

个人感觉fanoutExchange和directExchange的区别在于,fanoutExchange不受routingKey和bindingKey所限制,即使配置了也会被忽略,会直接发送到和exchange绑定的所有queue中。而directExchange会进行匹配,转发到指定的queue中,是精准匹配,不支持模糊匹配。

producer.java

package fanoutExchange;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.util.UUID;

/**
 * @author xx
 * @date 2021/10/2 20:41
 */
public class FanoutProducer {

    public static void main(String[] args) throws Exception {
        //1 创建ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("172.20.10.2");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("rabbitmq");
        connectionFactory.setPassword("rabbitmq");
        //2 创建Connection
        Connection connection = connectionFactory.newConnection();
        //3 创建Channel
        Channel channel = connection.createChannel();
        //4 声明
        String exchangeName = "fanoutExchange";
        //5 发送
        for (int i = 0; i < 5; i++) {
            String msg = "Test Fanout Exchange Message" + UUID.randomUUID().toString();
            channel.basicPublish(exchangeName, "producer", null, msg.getBytes());
        }
        channel.close();
        connection.close();
    }
}

consumer.java

package fanoutExchange;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * @author xx
 * @date 2021/10/2 20:48
 */
public class FanoutConsumer {
    public static void main(String[] args) throws Exception {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("172.20.10.2");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("rabbitmq");
        connectionFactory.setPassword("rabbitmq");

        // 创建连接
        Connection connection = connectionFactory.newConnection();
        // 来一个信道
        Channel channel = connection.createChannel();
        String exchangeType = "fanout";
        String exchangeName = "fanoutExchange";
        String bindingKey = "fanout";
        String queueName = "fanoutQueue";
        // 声明一个exchange交换机
        channel.exchangeDeclare(exchangeName, exchangeType);
        // 声明一个队列
        channel.queueDeclare(queueName, false, false, false, null);
        // 队列和交换机以及bindingKey进行绑定
        channel.queueBind(queueName, exchangeName, bindingKey);

        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body)
                    throws IOException {
                String message = new String(body, StandardCharsets.UTF_8);
                System.out.println("消费消息:" + message);
            }
        };

        channel.basicConsume(queueName, true, consumer);
    }
}

在这里插入图片描述

1-3-3:TopicExchange(原生实现)

Topic Exchange 和 Direct Exchange 类似,也需要通过 RoutingKey 来路由消息,区别在于Direct Exchange 对 RoutingKey 是精确匹配,而 Topic Exchange 支持模糊匹配。分别支持*#通配符,*表示匹配一个单词,#则表示匹配没有或者多个单词。

producer.java

package topicExchange;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.util.UUID;

/**
 * @author xx
 * @date 2021/10/2 21:19
 */
public class TopicProducer {
    public static void main(String[] args) throws Exception {
        //1 创建ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("172.20.10.2");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("rabbitmq");
        connectionFactory.setPassword("rabbitmq");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        String exchangeName = "topicExchange";
        String routingKey1 = "user.insert";
        String routingKey2 = "user.update";
        String routingKey3 = "user.update.a";
        String msg1 = "Test Topic Exchange Message" + routingKey1;
        String msg2 = "Test Topic Exchange Message" + routingKey2;
        String msg3 = "Test Topic Exchange Message" + routingKey3;
        channel.basicPublish(exchangeName, routingKey1, null, msg1.getBytes());
        channel.basicPublish(exchangeName, routingKey2, null, msg2.getBytes());
        channel.basicPublish(exchangeName, routingKey3, null, msg3.getBytes());
        channel.close();
        connection.close();
    }
}

consumer.java

package topicExchange;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * @author xx
 * @date 2021/10/2 21:19
 */
public class TopicConsumer {

    public static void main(String[] args) throws Exception {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("172.20.10.2");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("rabbitmq");
        connectionFactory.setPassword("rabbitmq");

        // 创建连接
        Connection connection = connectionFactory.newConnection();
        // 来一个信道
        Channel channel = connection.createChannel();
        String exchangeType = "topic";
        String exchangeName = "topicExchange";
        String bindingKey = "user.*";
        String queueName = "topicQueue";
        // 声明一个exchange交换机
        channel.exchangeDeclare(exchangeName, exchangeType);
        // 声明一个队列
        channel.queueDeclare(queueName, false, false, false, null);
        // 队列和交换机以及bindingKey进行绑定
        channel.queueBind(queueName, exchangeName, bindingKey);

        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body)
                    throws IOException {
                String message = new String(body, StandardCharsets.UTF_8);
                System.out.println("消费消息:" + message);
            }
        };

        channel.basicConsume(queueName, true, consumer);
    }
}

在这里插入图片描述

1-4: virtualHost

RabbitMQ is multi-tenant system: connections, exchanges, queues, bindings, user permissions, policies and some other things belong to virtual hosts, logical groups of entities

rabbitmq是一个多租户的系统,connection,exchange,queues,bindings,user permission,policies 和其他东西都属于virtual hosts,也就是说,最小粒度就是virtual host,virtual host就像是一个最小粒度的权限系统,用户无法直接拥有exchange,queue的权限,只能拥有访问virtual host的权限。并且virtual host之间是隔离的,没有关系的。但是有例外,看官网的一段话,user tag是例外。

A user doesn’t have global permissions, only permissions in one or more virtual hosts. User tags can be considered global permissions but they are an exception to the rule.

这里给出一份user tag的说明。来源于网上,只做记录,无从考证。

RabbitMQ中的用户角色
在RabbitMQ中的用户角色主要分为五类:
超级管理员(administrator)、监控者(monitor)、决策制定者(policymaker)、普通管理者(management)和其他(none)。

每个角色对应的相应权限如下:

none:

不能登录管理控制台(启用management plugin的情况下,以下相同)

management:

用户可以通过AMQP做的任何事外加:
列出自己可以通过AMQP登入的virtual hosts
查看自己的virtual hosts中的queues, exchanges 和 bindings
查看和关闭自己的channels 和 connections
查看有关自己的virtual hosts的“全局”的统计信息,包含其他用户在这些virtual hosts中的活动

policymaker:

management的权限外加:
查看、创建和删除自己的virtual hosts所属的policies和parameters

monitoring:

management的权限外加:
列出所有virtual hosts,包括他们不能登录的virtual hosts
查看其他用户的connections和channels
查看节点级别的数据如clustering和memory使用情况
查看真正的关于所有virtual hosts的全局的统计信息

administrator :

policymaker和monitoring的权限外加:
创建和删除virtual hosts
查看、创建和删除users
查看创建和删除permissions
关闭其他用户的connections

我这里对test设置了权限,我们通过代码看看test能不能访问到/这个virtual host

在这里插入图片描述

Caused by: com.rabbitmq.client.ShutdownSignalException: connection error; protocol method: #method<connection.close>(reply-code=530, reply-text=NOT_ALLOWED - access to vhost '/' refused for user 'test', class-id=10, method-id=40)
	at com.rabbitmq.utility.ValueOrException.getValue(ValueOrException.java:66)
	at com.rabbitmq.utility.BlockingValueOrException.uninterruptibleGetValue(BlockingValueOrException.java:36)
	at com.rabbitmq.client.impl.AMQChannel$BlockingRpcContinuation.getReply(AMQChannel.java:502)
	at com.rabbitmq.client.impl.AMQChannel.privateRpc(AMQChannel.java:293)
	at com.rabbitmq.client.impl.AMQChannel.exnWrappingRpc(AMQChannel.java:141)
	... 8 more

可以看出,被拒绝了,我只给了test,/test的权限,因为test用户没有这个权限,那么virtual Host之间的隔离,就可以得以验证,至于其他的复杂访问控制怎么搞,用到的时候再弄。

1-5: Broker

broker是中间人的意思,我们可以把broker看做是一个rabbitmq实例,为什么要有broker其实就是为什么要有中间件的存在。就回到了老话题,异步,解耦,削锋?

2-1: springboot 整合rabbitmq 实现三种exchange

环境:

  • RabbitMQ 3.9.7
  • java 1.8
  • springboot 2.5.5

2-1-1: dependency 依赖

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

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

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

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

        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>

        <!--rabbitmq-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
            <version>2.5.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>

    </dependencies>

2-1-2: application.yml

server:
  port: 8021
spring:
  #给项目来个名字
  application:
    name: rabbitmq-provider
  #配置rabbitMq 服务器
  rabbitmq:
    #    host: 172.2200.10.2
    host: 127.0.0.1
    port: 5672
    username: rabbitmq
    password: rabbitmq
    #虚拟host 可以不设置,使用server默认host
    virtual-host: /
    # 用来配置发布者异步确认,尚硅谷视频中说,queue持久+消息持久(rabbitTemplate 自动持久化消息)+生产者确认 可以实现不丢失消息
    publisher-confirm-type: correlated
  redis:
    #    host: 172.20.10.2
    host: 127.0.0.1
    port: 5070

2-1-3: RabbitmqConfig

package com.example.springbootrabbitmq.configuration;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author xx
 * @date 2021/10/5 16:24
 */
@Configuration
@Slf4j
public class RabbitmqConfig {
    @Autowired
    private CachingConnectionFactory connectionFactory;
    // 自动装配消息监听器所在的容器工厂配置类实例
    @Autowired
    private SimpleRabbitListenerContainerFactoryConfigurer factoryConfigurer;

//    @Bean
//    public MessageConverter jsonMessageConverter() {
//        return new Jackson2JsonMessageConverter();
//    }
		
    // 这个配置可以忽略,是用来测试发送发确认用的
    @Bean
    public RabbitTemplate rabbitTemplate() {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            if (ack) {
                log.info("发送成功");
            } else {
                correlationData.getReturned().getMessage().getMessageProperties().getMessageId();
                log.info("发送失败");
            }
        });
        return rabbitTemplate;
    }

    /**
     * 针对不同的消费者,可以进行不同的容器配置,来实现多个消费者应用不同的配置。
     */
    @Bean(name = "singleListenerContainer")
    public SimpleRabbitListenerContainerFactory listenerContainer() {
        //定义消息监听器所在的容器工厂
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        //设置容器工厂所用的实例
        factory.setConnectionFactory(connectionFactory);
        //设置消息在传输中的格式,在这里采用JSON的格式进行传输
        factory.setMessageConverter(new Jackson2JsonMessageConverter());
//        //设置并发消费者实例的初始数量。在这里为1个
//        factory.setConcurrentConsumers(1);
//        //设置并发消费者实例的最大数量。在这里为1个
//        factory.setMaxConcurrentConsumers(1);
//        //设置并发消费者实例中每个实例拉取的消息数量-在这里为1个
//        factory.setPrefetchCount(1);
        // 关闭自动应答
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        // 设置不公平分发,更改为每次读取1条消息,在消费者未回执确认之前,不在进行下一条消息的投送,而不是默认的轮询;
        // 也就是说,我处理完了,我再接受下一次的投递,属于消费者端的控制
        factory.setPrefetchCount(1);
        return factory;
    }
}

2-1-4:DirectExchange

2-1-4-1:DirectRabbitConfig.java
    //队列 起名:springDirectQueue
    @Bean
    public Queue springDirectQueue() {
        // durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,不然你电脑关机的时候队列就会消失。
        // exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
        // autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。

        //一般设置一下队列的持久化就好,其余两个就是默认false
        return new Queue("springDirectQueue", true);
    }

    @Bean
    public Queue springDirectQueue2() {
        return new Queue("springDirectQueue2", true);
    }

    //Direct交换机 起名:springDirectExchange
    @Bean
    public DirectExchange TestDirectExchange() {
        return new DirectExchange("springDirectExchange", true, false);
    }

    //用两个队列,将队列和交换机绑定, 并设置用于匹配键:springDirectRouting1, springDirectRouting2,用来实验routingKey的作用
    @Bean
    public Binding bindingDirectQueue1() {
        return BindingBuilder.bind(springDirectQueue()).to(TestDirectExchange()).with("springDirectRouting1");
    }

    @Bean
    public Binding bindingDirectQueue2() {
        return BindingBuilder.bind(springDirectQueue2()).to(TestDirectExchange()).with("springDirectRouting2");
    }
2-1-4-2:DirectProducerController.java
package com.example.springbootrabbitmq.controller;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;

/**
 * @author xx
 * @date 2021/10/5 16:10
 */
@RestController
@RequestMapping("/directProducer")
@Slf4j
public class DirectProducerController {
    @Resource
    private RabbitTemplate rabbitTemplate;


    @GetMapping("/sendMessage")
    public void sendMessage(String msg) {
        Map<String, String> msgMap = new HashMap<>();
        msgMap.put("message", msg);
        String messageJson = JSONObject.toJSONString(msgMap);
        Message message = MessageBuilder
                .withBody(messageJson.getBytes())
                .setContentType(MessageProperties.CONTENT_TYPE_JSON)
                .setContentEncoding("utf-8")
                .setMessageId(UUID.randomUUID() + "")
                .build();
        log.info("生产者发送:" + message);
        rabbitTemplate.convertAndSend("springDirectExchange", "springDirectRouting1", message);
    }
}
2-1-4-3:DirectConsumerController.java(其实不用写成controller)
package com.example.springbootrabbitmq.controller;

import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;

/**
 * @author xx
 * @date 2021/10/5 16:18
 */
@RestController
@RequestMapping("/directConsumer")
@Slf4j
public class DirectConsumerController {

    /*******************验证同一个队列的消息,默认采用轮询的方法 start ********************/
    @RabbitListener(queues = "springDirectQueue", containerFactory = "singleListenerContainer")
    public void processMessageQueue(Message message, Channel channel) throws Exception{
        String messageString = new String(message.getBody(), StandardCharsets.UTF_8);
        String messageId = message.getMessageProperties().getMessageId();

        log.info("springDirectQueue_consumer1消费者接受:" + messageString);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }

    @RabbitListener(queues = "springDirectQueue", containerFactory = "singleListenerContainer")
    public void processMessageQueue2(Message message, Channel channel) throws Exception{
        String messageString = new String(message.getBody(), StandardCharsets.UTF_8);
        String messageId = message.getMessageProperties().getMessageId();

        log.info("springDirectQueue_consumer2消费者接受:" + messageString);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }

    /*******************验证同一个队列的消息,默认采用轮询的方法 end ********************/

    @RabbitListener(queues = "springDirectQueue2", containerFactory = "singleListenerContainer")
    public void processMessageQueue3(Message message, Channel channel) throws Exception{
        String messageString = new String(message.getBody(), StandardCharsets.UTF_8);
        String messageId = message.getMessageProperties().getMessageId();

        log.info("springDirectQueue2_consumer1消费者接受:" + messageString);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
}
2-1-4-4: postman测试
Request: http://localhost:8021/directProducer/sendMessage?msg=测试directExchange(试2次)

----output-----
2021-10-11 19:22:17.344  INFO 9710 --- [nio-8021-exec-6] c.e.s.c.DirectProducerController         : 生产者发送:{"message":"测试directExchange"}
2021-10-11 19:22:17.349  INFO 9710 --- [nectionFactory2] c.e.s.configuration.RabbitmqConfig       : 发送成功
2021-10-11 19:22:17.349  INFO 9710 --- [ntContainer#2-1] c.e.s.c.DirectConsumerController         : springDirectQueue_consumer1消费者接受:{"message":"测试directExchange"}
2021-10-11 19:28:59.636  INFO 9710 --- [nio-8021-exec-9] c.e.s.c.DirectProducerController         : 生产者发送:{"message":"测试directExchange"}
2021-10-11 19:28:59.640  INFO 9710 --- [ntContainer#3-1] c.e.s.c.DirectConsumerController         : springDirectQueue_consumer2消费者接受:{"message":"测试directExchange"}
2021-10-11 19:28:59.640  INFO 9710 --- [nectionFactory3] c.e.s.configuration.RabbitmqConfig       : 发送成功
2021-10-11 19:30:56.420  INFO 9710 --- [nio-8021-exec-2] c.e.s.c.DirectProducerController         : 生产者发送:{"message":"测试directExchange"}
2021-10-11 19:30:56.425  INFO 9710 --- [ntContainer#2-1] c.e.s.c.DirectConsumerController         : springDirectQueue_consumer1消费者接受:{"message":"测试directExchange"}
2021-10-11 19:30:56.426  INFO 9710 --- [nectionFactory4] c.e.s.configuration.RabbitmqConfig       : 发送成功

通过routingKey匹配到springDirectQueue队列上,这个队列有两个消费者,如果没有设置factory.setPrefetchCount(1);,默认是轮询的方法,每一个消费者会依次对队列进行消费,你一条我一条。这里打印的发送成功是因为,发送方对消息进行了异步确认。不懂异步确认的,可以尝试搜索一下。

2-1-5: FanoutExchange

2-1-5-1:FanoutExchangeConfig.java

一个fanout交换机和两个队列进行绑定

@Bean
public Queue testFanoutQueue1() {
    return new Queue("springFanoutQueue1", true);
}

@Bean
public Queue testFanoutQueue2() {
    return new Queue("springFanoutQueue2", true);
}

@Bean
public FanoutExchange testFanoutExchange() {
    return new FanoutExchange("springFanoutExchange", true, false);
}

@Bean
public Binding bindingFanout1() {
    return BindingBuilder.bind(testFanoutQueue1()).to(testFanoutExchange());
}

@Bean
public Binding bindingFanout2() {
    return BindingBuilder.bind(testFanoutQueue2()).to(testFanoutExchange());
}
2-1-5-2: FanoutProducerController.java
package com.example.springbootrabbitmq.controller;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @author xx
 * @date 2021/10/6 15:53
 */
@RestController
@Slf4j
@RequestMapping("/fanoutProducer")
public class FanoutProducerController {
    @Resource
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/sendMessage")
    public void sendMessage(String msg) {
        Map<String, String> msgMap = new HashMap<>();
        msgMap.put("message", msg);
        String messageJson = JSONObject.toJSONString(msgMap);
        Message message = MessageBuilder
                .withBody(messageJson.getBytes())
                .setContentType(MessageProperties.CONTENT_TYPE_JSON)
                .setContentEncoding("utf-8")
                .setMessageId(UUID.randomUUID() + "")
                .build();
        log.info("生产者发送:" + new String(message.getBody(), StandardCharsets.UTF_8));
        rabbitTemplate.convertAndSend("springFanoutExchange", "springDirectRouting", message);
    }
}
2-1-5-3: FanoutConsumerController.java
package com.example.springbootrabbitmq.controller;

import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.web.bind.annotation.RestController;

import java.nio.charset.StandardCharsets;

/**
 * @author xx
 * @date 2021/10/6 15:55
 */
@RestController
@Slf4j
public class FanoutConsumerController {

    @RabbitListener(queues = "springFanoutQueue1", containerFactory = "singleListenerContainer")
    public void processMessage2(Message message, Channel channel) throws Exception {
        String messageString = new String(message.getBody(), StandardCharsets.UTF_8);
        String messageId = message.getMessageProperties().getMessageId();

        log.info("consumer2消费者接受:" + messageString);
        // multiple 意思是,要不要进行多个确认还是对当前消息进行确认。
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }

    @RabbitListener(queues = "springFanoutQueue1", containerFactory = "singleListenerContainer")
    public void processMessage3(Message message, Channel channel) throws Exception {
        String messageString = new String(message.getBody(), StandardCharsets.UTF_8);
        String messageId = message.getMessageProperties().getMessageId();

        log.info("consumer3消费者接受:" + messageString);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }


    @RabbitListener(queues = "springFanoutQueue2", containerFactory = "singleListenerContainer")
    public void processMessage(Message message, Channel channel) throws Exception {
        String messageString = new String(message.getBody(), StandardCharsets.UTF_8);
        String messageId = message.getMessageProperties().getMessageId();

        log.info("consumer1消费者接受:" + messageString);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
}
2-1-5-4: postman测试
request: http://localhost:8021/fanoutProducer/sendMessage?msg=测试fanoutExchange  (两次)

2021-10-11 19:47:13.023  INFO 10132 --- [nio-8021-exec-1] c.e.s.c.FanoutProducerController         : 生产者发送:{"message":"测试fanoutExchange"}
2021-10-11 19:47:13.037  INFO 10132 --- [nectionFactory1] c.e.s.configuration.RabbitmqConfig       : 发送成功
2021-10-11 19:47:13.075  INFO 10132 --- [ntContainer#5-1] c.e.s.c.FanoutConsumerController         : consumer2消费者接受:{"message":"测试fanoutExchange"}
2021-10-11 19:47:13.075  INFO 10132 --- [ntContainer#7-1] c.e.s.c.FanoutConsumerController         : consumer1消费者接受:{"message":"测试fanoutExchange"}
2021-10-11 19:47:18.426  INFO 10132 --- [nio-8021-exec-3] c.e.s.c.FanoutProducerController         : 生产者发送:{"message":"测试fanoutExchange"}
2021-10-11 19:47:18.431  INFO 10132 --- [ntContainer#6-1] c.e.s.c.FanoutConsumerController         : consumer3消费者接受:{"message":"测试fanoutExchange"}
2021-10-11 19:47:18.431  INFO 10132 --- [ntContainer#7-1] c.e.s.c.FanoutConsumerController         : consumer1消费者接受:{"message":"测试fanoutExchange"}
2021-10-11 19:47:18.432  INFO 10132 --- [nectionFactory1] c.e.s.configuration.RabbitmqConfig       : 发送成功

一个exchagne绑定两个queue,springFanoutQueue1 配置两个消费者,springFanoutQueue2 配置一个消费者。fanoutExchange 会向所有的队列进行发送消息,忽略了message的routingKey,并且,一个queue中的两个消费者,并不会同时收到消息。在rabbitmq中,我并没有找到方法,能使消费者同时收到一个queue中的消息。似乎,只能通过各种queue来实现消费者同时接受到消息。待考证。。。

2-1-6: TopicExchange

2-1-6-1: TopicExchangeConfig
    public static final String TOPIC_QUEUE1_ROUTING_KEY = "user.insert";
    public static final String TOPIC_QUEUE2_ROUTING_KEY = "user.update";
    public static final String TOPIC_QUEUE3_ROUTING_KEY = "user.*";

    @Bean
    public Queue testTopicQueue1() {
        return new Queue("springTopicQueue1", true);
    }

    @Bean
    public Queue testTopicQueue2() {
        return new Queue("springTopicQueue2", true);
    }

    @Bean
    public Queue testTopicQueue3() {
        return new Queue("springTopicQueue3", true);
    }


    @Bean
    public TopicExchange testTopicExchange() {
        return new TopicExchange("springTopicExchange", true, false);
    }

    @Bean
    public Binding bindingTopic1() {
        return BindingBuilder.bind(testTopicQueue1()).to(testTopicExchange()).with(TOPIC_QUEUE1_ROUTING_KEY);
    }

    @Bean
    public Binding bindingTopic2() {
        return BindingBuilder.bind(testTopicQueue2()).to(testTopicExchange()).with(TOPIC_QUEUE2_ROUTING_KEY);
    }

    @Bean
    public Binding bindingTopic3() {
        return BindingBuilder.bind(testTopicQueue3()).to(testTopicExchange()).with(TOPIC_QUEUE3_ROUTING_KEY);
    }

3个队列绑定了3个queue,分别用了3个routingKey,其中有一个模糊匹配。

2-1-6-2: TopicProducerController.java
package com.example.springbootrabbitmq.controller;

import com.alibaba.fastjson.JSONObject;
import com.example.springbootrabbitmq.configuration.DirectRabbitConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@RestController
@Slf4j
@RequestMapping("/topicExchange")
public class TopicProducerController {
    @Resource
    private RabbitTemplate rabbitTemplate;


    @GetMapping("/sendMessage")
    public void sendMessage(String msg, String routingKey) {
        Map<String, String> msgMap = new HashMap<>();
        msgMap.put("message", msg);
        String messageJson = JSONObject.toJSONString(msgMap);
        Message message = MessageBuilder
                .withBody(messageJson.getBytes())
                .setContentType(MessageProperties.CONTENT_TYPE_JSON)
                .setContentEncoding("utf-8")
                .setMessageId(UUID.randomUUID() + "")
                .build();
        log.info("topic生产者发送:" + new String(message.getBody(), StandardCharsets.UTF_8));
        rabbitTemplate.convertAndSend("springTopicExchange", routingKey, message);
    }
}
2-1-6-3:TopicConsumerController.java
package com.example.springbootrabbitmq.controller;

import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.web.bind.annotation.RestController;

import java.nio.charset.StandardCharsets;

@RestController
@Slf4j
public class TopicConsumerController {

//    public static final String TOPIC_QUEUE1_ROUTING_KEY = "user.insert";
//    public static final String TOPIC_QUEUE2_ROUTING_KEY = "user.update";
//    public static final String TOPIC_QUEUE3_ROUTING_KEY = "user.*";

    @RabbitListener(queues = "springTopicQueue1", containerFactory = "singleListenerContainer")
    public void processMessage1(Message message, Channel channel) throws Exception {
        String messageString = new String(message.getBody(), StandardCharsets.UTF_8);
        String messageId = message.getMessageProperties().getMessageId();

        log.info("user.insert消费者接受:" + messageString);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }

    @RabbitListener(queues = "springTopicQueue2", containerFactory = "singleListenerContainer")
    public void processMessage2(Message message, Channel channel) throws Exception {
        String messageString = new String(message.getBody(), StandardCharsets.UTF_8);
        String messageId = message.getMessageProperties().getMessageId();

        log.info("user.update消费者接受:" + messageString);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }

    @RabbitListener(queues = "springTopicQueue3", containerFactory = "singleListenerContainer")
    public void processMessage3(Message message, Channel channel) throws Exception {
        String messageString = new String(message.getBody(), StandardCharsets.UTF_8);
        String messageId = message.getMessageProperties().getMessageId();

        log.info("user.*消费者接受:" + messageString);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
}
2-1-6-4:postman测试
request: http://localhost:8021/topicExchange/sendMessage?msg=测试fanoutExchange&routingKey=user.update


----output----
2021-10-11 20:02:23.518  INFO 10339 --- [nio-8021-exec-2] c.e.s.c.TopicProducerController          : topic生产者发送:{"message":"测试fanoutExchange"}
2021-10-11 20:02:23.532  INFO 10339 --- [nectionFactory1] c.e.s.configuration.RabbitmqConfig       : 发送成功
2021-10-11 20:02:23.564  INFO 10339 --- [ntContainer#8-1] c.e.s.c.TopicConsumerController          : user.update消费者接受:{"message":"测试fanoutExchange"}
2021-10-11 20:02:23.564  INFO 10339 --- [ntContainer#9-1] c.e.s.c.TopicConsumerController          : user.*消费者接受:{"message":"测试fanoutExchange"}

根据模糊*和具体匹配,消息发送到了对应的consumer中。

总结

主要是了解一下rabbitmq中的三种交换机,以及原生和springboot中的基本使用。原生的方法,意思不一定到位,因为当时认识还不够,但是springboot的整合是自己好好写demo,测试 出结果的。可以复现,没啥问题,如果当中有问题,还请读者,提出,一起进步!支持原创,给我点个赞!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

河海哥yyds

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

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

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

打赏作者

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

抵扣说明:

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

余额充值