RabbitMQ的各种消息处理模式详解

简介

文章持续更新,且遇切积。
文章以上产到gitee:https://gitee.com/XuLiZhao/cloud-basic

RabbitMQ相关概念

  • Broker:接收和分发消息的应用,RabbitMQ Server就是 Message Broker

  • Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网 络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多 个vhost,每个用户在自己的 vhost 创建 exchange/queue 等

  • Connection:publisher/consumer 和 broker 之间的 TCP 连接

  • Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCPConnection 的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线 程,通常每个thread创建单独的 channel 进行通讯,AMQP method 包含了channel id 帮助客户端和 message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销。

  • Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到

    queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)

  • Queue:消息最终被送到这里等待 consumer 取走

  • Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据。

RabbitMQ的工作模式

简单模式、work queues、Publish/Subscribe 发布与订阅模式、Routing 路由模式、Topics 主题模式、RP远程调用模式

快速入门

maven依赖

spring环境下使用
<!--rabbitmq java 客户端依赖-->
    <dependency>
      <groupId>com.rabbitmq</groupId>
      <artifactId>amqp-client</artifactId>
      <version>5.9.0</version>
    </dependency>
springboot环境下使用
 <!--AMQP依赖,包含RabbitMQ-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

生产者

public void testSendMessage() throws IOException, TimeoutException {
            // 1.建立连接
            ConnectionFactory factory = new ConnectionFactory();
            // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
            factory.setHost("192.168.187.131"); //ip:默认值为localhost
            factory.setPort(5672); //端口,默认值5672
            factory.setVirtualHost("/"); //虚拟机,默认值 /
            factory.setUsername("xihai"); //用户名 默认值为安装时设置的值guest
            factory.setPassword("123456"); //密码,默认值guest
            // 1.2.建立连接
            Connection connection = factory.newConnection();

            // 2.创建通道Channel
            Channel channel = connection.createChannel();

            // 3.创建队列
            /**
             * Description:
             * 参数:String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
             *  1.queue:队列名称,如果没有则会创建,有则不会
             *  2.durable:是否持久化,当为true时会写入到数据库中,mq重启后还会存在
             *  3.exclusive:是否独占,只有一个消费者监听次队列,当Connection关闭时,是否删除队列
             *  4.autoDelete:是否自动删除。当没有Consumer时,自动删除队列
             *  5.arguments:参数
             **/
            String queueName = "simple.queue";
            channel.queueDeclare(queueName, false, false, false, null);

            // 4.发送消息
            /**
             * Description:
             * 参数:(String exchange, String routingKey, BasicProperties props, byte[] body)
             *  1.exchange:交换机名称。简单模式下使用默认的 ""
             *  2.routingKey:路由名称
             *  3.props:配置信息
             *  4.body:字节数据,真实发送数据
             **/
            String message = "hello, rabbitmq!";
            channel.basicPublish("", queueName, null, message.getBytes());
            System.out.println("发送消息成功:【" + message + "】");

            // 5.关闭通道和连接
            channel.close();
            connection.close();

}

消费者

public static void main(String[] args) throws IOException, TimeoutException {
        // 1.建立连接
        ConnectionFactory factory = new ConnectionFactory();
        // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
        factory.setHost("192.168.187.131");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("xihai");
        factory.setPassword("123456");
        // 1.2.建立连接
        Connection connection = factory.newConnection();

        // 2.创建通道Channel
        Channel channel = connection.createChannel();

        // 3.创建队列
        String queueName = "simple.queue";
        channel.queueDeclare(queueName, false, false, false, null);

        // 4.接收消息
        /**
         * Description:
         * 参数:(String queue, boolean autoAck, Consumer callback)
         *  1.queue:队列
         *  2.autoAck:是否自动确认
         *  3.callback:回调对象
        **/
        channel.basicConsume(queueName, true, new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                // 5.处理消息。回调方法,收到消息后会自动调用此方法
                /**
                 * Description:
                 * [consumerTag, envelope, properties, body]
                 * 1.consumerTag:标识
                 * 2.envelope:获取一些信息,路由key
                 * 3.properties:配置信息
                 * 4.body:数据
                **/
                System.out.println("consumerTag:"+consumerTag);
                System.out.println("Exchange:"+envelope.getExchange());
                System.out.println("RoutingKey:"+envelope.getRoutingKey());
                System.out.println("properties:"+properties);
                String message = new String(body); //将字节数据转换为字符串
                System.out.println("接收到消息:【" + message + "】");
            }
        });
        //主线程,与上面回调线程异步
        System.out.println("等待接收消息。。。。");
    }

整合SpringBoot

环境配置

依赖就是上面那个依赖。

#配置文件
spring:
  rabbitmq:
    host: 192.168.187.131 # rabbitMQ的ip地址
    port: 5672 # 端口
    username: xihai
    password: 123456
    virtual-host: /

简单模式

生产者和消费者实现一对一的通信。在这里插入图片描述

消息发送

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSendMessage2SimpleQueue() {
        String queueName = "simple.queue";
        String message = "hello, spring amqp!";
        rabbitTemplate.convertAndSend(queueName, message);
    }
}

这里存在一个问题,由于之前我使用上面spring的代码已经创建一个队列了,所以这里的消息发送才可以运行,但是当这个simple.queue的队列不存在时,这个发送代码是不会帮你创建队列的。

消息接收

#创建一个类将它定义到spring容器中,使用@RabbitListener监听需要监听的队列
@Component
public class SpringRabbitListener {

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

工作队列 Work Queue

一个生产者对应多个消费者,但是消息只能使用一次,阅后即焚。在这里插入图片描述

生产者

//没20毫秒发送一条消息,发送50次    
@Test
    public void testSendMessage2WorkQueue() throws InterruptedException {
        String queueName = "simple.queue";
        String message = "hello, message__";
        for (int i = 1; i <= 50; i++) {
            rabbitTemplate.convertAndSend(queueName, message + i);
            Thread.sleep(20);
        }
    }

消费者

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


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

消息预取机制:当接收队列中的消息时,消费者默认是按顺序每次每人取一条消息的,所以展现出来的特征就是:多个消费者平均分配消息,消息次序固定。

添加如下配置(与port处于同一列)

listener:
      simple:
        prefetch: 1 #每次只能获取一条消息,获取完成后才能获取下一条

Publish/Subscribe 发布与订阅模式

生产者对应多个消费者,通过交换机,同一消息可以被不同消费者接收。
在这里插入图片描述
生产者

    @Test
    public void testSendFanoutExchange() {
        // 交换机名称
        String exchangeName = "xihai.fanout";
        // 消息
        String message = "hello, every one!";
        // 发送消息
        rabbitTemplate.convertAndSend(exchangeName, "", message);
    }

消费者配置类

消费者的配置与之前有些不同,因为在发布和订阅模式下使用到了交换机机制,需要到配置类中进行配置

@Configuration
public class FanoutConfig {
    // 声明交换机
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("itcast.fanout");
    }

    // 声明队列
    @Bean
    public Queue fanoutQueue1(){
        return new Queue("fanout.queue1");
    }

    // 将队列和交换机进行绑定
    @Bean
    public Binding fanoutBinding1(Queue fanoutQueue1, FanoutExchange fanoutExchange){
        return BindingBuilder
                .bind(fanoutQueue1)
                .to(fanoutExchange);
    }
}
//建议配置多个队列绑定同一交换机以便观察效果

消费者主体

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

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

创建队列的多种方式

//通过对象创建
@Bean
    public org.springframework.amqp.core.Queue SimpleQueue() {
        return QueueBuilder.nonDurable("test_queue").build();
        //return new Queue("test_queue");
    }

//通过接收消息时创建
@RabbitListener(queuesToDeclare = @Queue("simple.queue"))
     public void listenSimpleQueue(String msg) {
         ...
     }

//3.queue和exchange绑定使自动创建
@RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue1"),
            exchange = @Exchange(name = "xihai.direct", type = ExchangeTypes.DIRECT),
            key = {"red", "blue"}
    ))
    public void listenDirectQueue1(String msg){
        System.out.println("消费者接收到direct.queue1的消息:【" + msg + "】");
    }

路由模式

当生产者发送消息时设置一个routingKey值,消费者会在创建完成后与路由建立练习并设置上他们之间通信的key值(可以设置多个),生产者发送消息到达路由后,路由根据它与队列绑定时指定的key进行查询,发现key值匹配即向该队列发送信息。
在这里插入图片描述
生产者

//第二个参数为routingKey
@Test
    public void testSendDirectExchange() {
        // 交换机名称
        String exchangeName = "xihai.direct";
        // 消息
        String message = "hello, xihai!";
        // 发送消息
        rabbitTemplate.convertAndSend(exchangeName, "xihai", message);
    }

消费者

//bindings属性中绑定队列,交换机,key。ExchangeTypes.DIRECT为默认值,表示交换机类型为direct。这里的key你可以把它理解为BoundingKey。
	@RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue1"),
            exchange = @Exchange(name = "xihai.direct", type = ExchangeTypes.DIRECT),
            key = {"xihai", "lizhao"}
    ))
    public void listenDirectQueue1(String msg){
        System.out.println("消费者接收到direct.queue1的消息:【" + msg + "】");
    }

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

Topics 主题模式

主题模式与路由模式类似,不同的是routingKey必须是多个单词组成的列表,并且以.分隔。而当队列与交换机绑定时可以使用通配符。#:表示0个或多个单词。*:指代一个单词

生产者

    @Test
    public void testSendTopicExchange() {
        // 交换机名称
        String exchangeName = "xihai.topic";
        // 消息
        String message = "今天天气不错,我的心情好极了!";
        // 发送消息
        rabbitTemplate.convertAndSend(exchangeName, "china.weather", message);
    }

消费者

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

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

拓展

消息转换器

查看RabbitMQ消息传递时默认的消息格式

生产者

@Test
    public void testSendObjectQueue(){
        Map<String, Object> msg = new HashMap<>();
        msg.put("name","汐海");
        msg.put("age",21);
        rabbitTemplate.convertAndSend("object.queue",msg);
    }
}

消费者

@RabbitListener(queues = "object.queue")
    public void listenObjectQueue(Map<String,Object> msg){
        System.out.println("接收到object.queue的消息:" + msg);
    }

请添加图片描述

使用Json的序列化方式代替上述java的序列化方式。

//引入json依赖
<dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>

生产者配置类中声明Json消息转换器

@Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }

请添加图片描述

接收时也要进行如上配置,引依赖并配置。

准备更新RabbitMQ高级特性,前面一直使用的是yml文件的配置,在下一篇中我好多中方式都会涉及。具体的也列举不出来,到时候会给源码,里面都有详细注释,但也需要一定基础才能看懂。最后开个小头,就准备去开下一篇博客了。

配置类

@Configuration
public class RabbitConfig {

 @Bean
 public CachingConnectionFactory connectionFactory(){
     CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
     connectionFactory.setHost("192.168.187.131");
     connectionFactory.setPort(5672);
     connectionFactory.setUsername("xihai");
     connectionFactory.setPassword("123456");
     connectionFactory.setVirtualHost("/");
     return connectionFactory;
 }
 
 public RabbitAdmin rabbitAdmin(){
     RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory());
     rabbitAdmin.setAutoStartup(true);
     return rabbitAdmin;
 }
}

默认spring容器自动加载了这些类对象。我们在spring配置类中添加相关配置参数后,就会创建CachingConnectionFactory的对象,只是其中的一些参数不是默认了。

自动装配位置如下,各位感兴趣也可以阅读它的源码
请添加图片描述进入springboot的自动装配类的这个位置,我们可以看到这里有很多AMQP的配置:

RabbitProperties:属性配置类,映射配置文件 prefix = “spring.rabbitmq”,开头的属性
RabbitAutoConfiguration :自动装配类,里面配置了CachingConnectionFactory和RabbitTemplate两个类和AmqpAdmin接口,而RabbitAdmin是AmqpAdmin接口的实现类

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值