RabbitMQ--集成Springboot--01--工作模式

RabbitMQ–集成Springboot–01–工作模式


代码位置

https://gitee.com/DanShenGuiZu/learnDemo/tree/master/rabbitMq-learn/rabbitMq-01

1、前置条件

1.1、新建一个虚拟分区

因为我需要MQ单独拿来测试使用,所以我新建一个分区

在这里插入图片描述

在这里插入图片描述

1.2、创建分区的用户

在这里插入图片描述

1.3、给用户授权

在这里插入图片描述
在这里插入图片描述

1.4、创建springboot

在这里插入图片描述

1.4.1、依赖

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


1.4.2、配置

server:
  port: 8080
spring:
  #给项目来个名字
  application:
    name: rabbitmq-learn
  #配置rabbitMq 服务器
  rabbitmq:
    host: 192.168.187.171
    port: 5672
    username: root
    password: root
    #虚拟host 可以不设置,使用server默认host
    virtual-host: test



2、模式–简单模式

在这里插入图片描述

  1. 消息发送规则
    1. 生产者 发送消息 给交换机
    2. 交换机 通过 路由键 转发 给队列
  2. 简单模式 使用默认交换机,所以发送消息的时候,不需要配置交换机
  3. 简单模式 路由键的名称就是队列的名称,所以发送消息的时候,只要配置队列名称就行。

2.1、代码

@Configuration
public class Simple_Config {
    
    static String queue_name = "code_simple_queue1";
    
    // 声明队列
    // @param1 队列名称
    // @param2 是否持久化 持久化:RabbitMQ服务器重启,队列还存在;反之,不存在。
    // 持久化的队列中的消息会存盘,不会随着服务器的重启会消失
    // @param3 排他性 是否独占一个队列(一般不会)
    // @param4 是否自动删除 随着最后一个消费者消费消息完毕后,是否自动删除这个队列
    // @param5 携带一些附加信息 供其它消费者获取
    @Bean
    public Queue simpleQueue() {
        return new Queue(queue_name, true, false, false, null);
    }
}

@Component
public class Simple_Consumer {

    // MQ中code_simple_queue1队列必须存在,否则就会报错
    // 默认队列是持久化,非独占式的
    @RabbitListener(queues = { "code_simple_queue1" })
    public void receive(Message message, Channel channel) throws Exception {
        // 消息id
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        
        System.out.println("Simple_Consumer:" + new String(message.getBody()));
    }
    
}
@RestController
@RequestMapping("/simple")
public class Simple_Producer {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @RequestMapping("/sendMsg")
    public String sendMsgA() {
        for (int i = 0; i < 5; i++) {
            String msg = "simple_queue " + i;
            //发送消息
            rabbitTemplate.convertAndSend(null, "code_simple_queue1", msg);
        }
        return "ok";
    }
    
}

2.2、代码结构和测试

http://127.0.0.1:8080/simple/sendMsg

在这里插入图片描述

3、模式–工作队列

在这里插入图片描述

  1. 工作队列模式 也叫 竞争消费者模式
    1. 多个消费端消费同一个队列中的消息
    2. 队列采用轮询的方式将消息是平均发送给消费者,此处的资源是竞争关系
  2. 与简单模式相比 多了一个(或者多个)消费端
  3. 它解决了当消息队列的消息过多的情况,单消费者消费速率有限,导致的消息堆积的问题。
  4. 工作队列模式中的队列是和默认的交换机 AMQP default 进行绑定(和简单模式一样)
  5. 保证同一个消息只能被一个消费者消费掉
    1. 可以设置一个开关,syncronize,保证一条消息只能被一个消费者使用
  6. 用于场景
    1. 红包场景
    2. 大型项目中的资源调度
  7. 有2这种模式
    1. 轮询分发
      1. 消息平均分配。不管谁忙,都不会多给消息,总是你一个我一个
    2. 公平分发
      1. 能者多劳。谁消费得快,谁就消费得多。
      2. 必须关闭自动应答 ACK,改成手动应答,并且设置 channel.basicQos(1),表示消费者一次只处理一条消息

3.1、轮询分发

  1. 消息平均分配。
  2. 不管谁忙,都不会多给消息,总是你一个我一个

3.1.1、代码

 
@Configuration
public class Work_Config {
    
    static String queue_name1 = "code_work_queue1";

    // 声明队列
    // @param1 队列名称
    // @param2 是否持久化 持久化:RabbitMQ服务器重启,队列还存在;反之,不存在。
    // 持久化的队列中的消息会存盘,不会随着服务器的重启会消失
    // @param3 排他性 是否独占一个队列(一般不会)
    // @param4 是否自动删除 随着最后一个消费者消费消息完毕后,是否自动删除这个队列
    // @param5 携带一些附加信息 供其它消费者获取
    @Bean
    public Queue workQueue1() {
        return new Queue(queue_name1, true, false, false, null);
    }

}
/**
 *   2个消费者
 */
@Component
public class Work_Consumer {
    
    @RabbitListener(queues = { "code_work_queue1" })
    public void receive(Message message, Channel channel) throws Exception {
        // 模拟任务耗时 5s
        TimeUnit.SECONDS.sleep(5);
        // 消息id
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        
        System.out.println("Work_Consumer1:" + new String(message.getBody()));
    }
    
    @RabbitListener(queues = { "code_work_queue1" })
    public void receive2(Message message, Channel channel) throws Exception {
        // 消息id
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        
        System.out.println("Work_Consumer2:" + new String(message.getBody()));
    }
    
}
 
@RestController
@RequestMapping("/work")
public class Work_Producer {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @RequestMapping("/sendMsg")
    public String sendMsg() {
        for (int i = 0; i < 10; i++) {
            String msg = "work_queue " + i;
            rabbitTemplate.convertAndSend("", "code_work_queue1", msg);
        }
        return "ok";
    }
    
}

3.1.2、代码结构和测试

http://127.0.0.1:8080/work/sendMsg

在这里插入图片描述

在这里插入图片描述

3.2、公平分发

  1. 能者多劳。谁消费得快,谁就消费得多,就上面而言,Consumer2应该消费最多
  2. 实现步骤
    1. 消费者 必须 关闭自动应答 ACK,改成手动应答
    2. 消费者 设置 channel.basicQos(1),表示消费者一次只处理一条消息

3.2.1、代码改动点

在这里插入图片描述

    # 手动ACK
    listener:
      simple:
        #手动应答
        acknowledge-mode: manual
        # 最小消费者数量
        concurrency: 1
        # 最多消费者数量
        max-concurrency: 10
        # 是否支持重试
        retry:
          enabled: true
        # 每次只处理一个消息
        prefetch: 1

        //手动ACk
        channel.basicAck(deliveryTag, false);

3.2.2、代码结构和测试

在这里插入图片描述

4、模式–发布/订阅模式(fanout)

  1. 交换机类型为 fanout
  2. 相对于Work queues模式多了一个交换机,此处的资源是共享的
  3. 会把所有发送到该交换机的消息路由到所有与该交换机绑定的队列中,无视 BindingKey
  4. 一个生产者,多个消费者
  5. 每个消费者都绑定一个自己的队列
  6. 生产者没有将消息直接发送给队列,而是发送给交换机(Exchange)
  7. 每个队列都需要绑定到交换机上
  8. 生产者发送的消息,经过交换机到达队列,可实现一个消息被多个消费者消费

在这里插入图片描述

4.1、代码

@Configuration
public class Fanout_Config {
    
    /**
     * 创建2个队列 :fanout_queue_1 fanout_queue_2 将2个队列都绑定在交换机 fanout_exchange 上
     * 因为是扇型交换机, 路由键无需配置,配置也不起作用
     */
    
    // 声明队列
    @Bean
    public Queue fanoutQueue1() {
        return new Queue("fanout_queue_1");
    }
    
    @Bean
    public Queue fanoutQueue2() {
        return new Queue("fanout_queue_2");
    }
    
    // 声明交换机
    @Bean
    public FanoutExchange exchange() {
        return new FanoutExchange("fanout_exchange");
    }
    
    // 声明交换机与队列之间的关系
    @Bean
    public Binding bindingFanoutQueue1(Queue fanoutQueue1, FanoutExchange exchange) {
        return BindingBuilder.bind(fanoutQueue1).to(exchange);
    }
    
    @Bean
    public Binding bindingFanoutQueue2(Queue fanoutQueue2, FanoutExchange exchange) {
        return BindingBuilder.bind(fanoutQueue2).to(exchange);
    }
}
@Component
public class Fanout_Consumer {

    @RabbitListener(queues = { "fanout_queue_1" })
    public void receive(Message message, Channel channel) throws Exception {

        // 消息id
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        // 手动ACk
        channel.basicAck(deliveryTag, false);

        System.out.println("Fanout_Consumer1:" + new String(message.getBody()));
    }

    @RabbitListener(queues = { "fanout_queue_2" })
    public void receive2(Message message, Channel channel) throws Exception {

        // 消息id
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        // 手动ACk
        channel.basicAck(deliveryTag, false);

        System.out.println("Fanout_Consumer2:" + new String(message.getBody()));
    }
    
}
 
@RestController
@RequestMapping("/fanout")
public class Fanout_Producer {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @RequestMapping("/sendMsg")
    public String sendMsg() {
        for (int i = 0; i < 5; i++) {
            String msg = "fanout_queue " + i;
            // 消息发给交换机,不需要路由键
            rabbitTemplate.convertAndSend("fanout_exchange", "", msg);
        }
        return "ok";
    }
    
}

4.2、代码结构和测试

http://127.0.0.1:8080/fanout/sendMsg

在这里插入图片描述

5、模式—路由模式(Direct)

  1. Direct 模式在 Fanout 模式之上做了一个路由键 RoutingKey,对发送给交换机 Exchange 的消息进行筛选
  2. 每个消费者监听自己的队列,并且设置带统配符的 routingkey
  3. 生产者将消息发给broker,由交换机根据 routingkey 来转发消息到指定的队列
  4. 路由模式 = 发布/订阅模式(fanout) + 指定 RoutingKey

在这里插入图片描述

5.1、代码

@Configuration
public class Direct_Config {
    
    // 声明队列
    // @param1 队列名称
    // @param2 是否持久化 持久化:RabbitMQ服务器重启,队列还存在;反之,不存在。
    // 持久化的队列中的消息会存盘,不会随着服务器的重启会消失
    // @param3 排他性 是否独占一个队列(一般不会),true:只能被当前创建的连接使用,而且当连接关闭后队列即被删除
    // @param4 是否自动删除 随着最后一个消费者消费消息完毕后,是否自动删除这个队列
    // @param5 携带一些附加信息 供其它消费者获取
    
    // 声明队列
    @Bean
    public Queue directQueue1() {
        return new Queue("direct_queue1", true, false, false, null);
        
    }
    
    @Bean
    public Queue directQueue2() {
        return new Queue("direct_queue2");
    }
    
    @Bean
    public Queue directQueue3() {
        return new Queue("direct_queue3");
    }
    
    // 声明交换机
    @Bean
    public DirectExchange directExchange() {
        return new DirectExchange("direct_exchange");
    }
    
    // 声明交换机与队列之间的关系

    // 路由键为 warning 的消息,路由到direct_queue1
    @Bean
    public Binding bindingDirectQueue1(Queue directQueue1, DirectExchange exchange) {
        return BindingBuilder.bind(directQueue1).to(exchange).with("warning");
    }
    // 路由键为 warning,info的消息,路由到direct_queue2
    @Bean
    public Binding bindingDirectQueue2_1(Queue directQueue2, DirectExchange exchange) {
        return BindingBuilder.bind(directQueue2).to(exchange).with("info");
        
    }
    @Bean
    public Binding bindingDirectQueue2_2(Queue directQueue2, DirectExchange exchange) {
        return BindingBuilder.bind(directQueue2).to(exchange).with("warning");
    }

    // 路由键为debug,info的消息,路由到direct_queue3
    @Bean
    public Binding bindingDirectQueue3_1(Queue directQueue3, DirectExchange exchange) {
        return BindingBuilder.bind(directQueue3).to(exchange).with("debug");
    }
    @Bean
    public Binding bindingDirectQueue3_2(Queue directQueue3, DirectExchange exchange) {
        return BindingBuilder.bind(directQueue3).to(exchange).with("info");
    }
}

@Component

public class Direct_Consumer {
    
    @RabbitListener(queues = { "direct_queue1" })
    public void receive(Message message, Channel channel) throws Exception {
        // 消息id
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        // 手动ACk
        channel.basicAck(deliveryTag, false);
        
        System.out.println("direct_queue1队列消息:" + new String(message.getBody()));
    }
    
    @RabbitListener(queues = { "direct_queue2" })
    public void receive2(Message message, Channel channel) throws Exception {
        // 消息id
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        // 手动ACk
        channel.basicAck(deliveryTag, false);
        
        System.out.println("direct_queue2队列消息:" + new String(message.getBody()));
    }
    
    @RabbitListener(queues = { "direct_queue3" })
    public void receive3(Message message, Channel channel) throws Exception {
        // 消息id
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        // 手动ACk
        channel.basicAck(deliveryTag, false);
        
        System.out.println("direct_queue3队列消息:" + new String(message.getBody()));
    }
}
 
@RestController
@RequestMapping("/direct")
public class Direct_Producer {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @RequestMapping("/sendMsgA")
    public String sendMsgA() {
        
        String msg = "direct_queue  warning";
        rabbitTemplate.convertAndSend("direct_exchange", "warning", msg);
        
        return "ok";
    }
    
    @RequestMapping("/sendMsgB")
    public String sendMsgB() {
        
        String msg = "direct_queue  info";
        rabbitTemplate.convertAndSend("direct_exchange", "info", msg);
        
        return "ok";
    }
    
    @RequestMapping("/sendMsgC")
    public String sendMsgC() {
        
        String msg = "direct_queue  debug";
        rabbitTemplate.convertAndSend("direct_exchange", "debug", msg);
        
        return "ok";
    }
}

5.2、代码结构和测试

http://127.0.0.1:8080/direct/sendMsgA
http://127.0.0.1:8080/direct/sendMsgB
http://127.0.0.1:8080/direct/sendMsgC

在这里插入图片描述

6、模式–主题模式(Topic)

  1. topic类型的交换机 实际上是 direct类型的交换机的一种
    1. 都将消息路由到 BindingKey 和 RoutingKey 相匹配的队列中
    2. topic 在匹配规则上进行了扩展。使用了通配符
  2. 工作流程
    1. 消息生产者生产消息,把消息交给交换机 exchange
    2. 交换机 exchange 根据 key 的规则模糊匹配到对应的队列,由队列的监听消费者接收消息消费
  3. 和路由模式区别
    1. 绑定键 BindingKey 中带有模糊匹配
    2. 路由键 RoutingKey 由多个单词构成

6.1、Topic 模型

  1. 路由键为 com.rabbitmq.client 的消息,会同时路由到 Q1、Q2、Q3
  2. 路由键为 com.hidden.client 的消息,会路由到 Q2、Q3
  3. 路由键为 com.hidden.demo 的消息,会路由到 Q3
  4. 路由键为 java.util.concurrent 的消息,会被丢弃或者返回给生产者,因为,它没有匹配任何路由键

在这里插入图片描述

6.1、代码

@Configuration
public class Topic_Config {
    
    // 声明队列
    @Bean
    public Queue topicQueue1() {
        return new Queue("topic_queue1");
    }
    
    @Bean
    public Queue topicQueue2() {
        return new Queue("topic_queue2");
    }
    
    @Bean
    public Queue topicQueue3() {
        return new Queue("topic_queue3");
    }
    
    // 声明交换机
    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange("topic_exchange");
    }
    
    // 声明交换机与队列之间的关系
    // *:匹配一个词
    // #:匹配一个或多个词
    
    @Bean
    public Binding topicBinding1(Queue topicQueue1, TopicExchange exchange) {
        return BindingBuilder.bind(topicQueue1).to(exchange).with("*.rabbitmq.*");
    }
    
    @Bean
    public Binding topicBinding2(Queue topicQueue2, TopicExchange exchange) {
        return BindingBuilder.bind(topicQueue2).to(exchange).with("*.*.client");
    }
    
    @Bean
    public Binding topicBinding3(Queue topicQueue3, TopicExchange exchange) {
        return BindingBuilder.bind(topicQueue3).to(exchange).with("com.#");
    }
}


@Component
public class Topic_Consumer {
    
    @RabbitListener(queues = { "topic_queue1" })
    public void receive(Message message, Channel channel) throws Exception {
        // 消息id
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        // 手动ACk
        channel.basicAck(deliveryTag, false);
        
        System.out.println("topic_queue1队列消息:" + new String(message.getBody()));
    }
    
    @RabbitListener(queues = { "topic_queue2" })
    public void receive2(Message message, Channel channel) throws Exception {
        // 消息id
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        // 手动ACk
        channel.basicAck(deliveryTag, false);
        
        System.out.println("topic_queue2队列消息:" + new String(message.getBody()));
    }
    
    @RabbitListener(queues = { "topic_queue3" })
    public void receive3(Message message, Channel channel) throws Exception {
        // 消息id
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        // 手动ACk
        channel.basicAck(deliveryTag, false);
        
        System.out.println("topic_queue3队列消息:" + new String(message.getBody()));
    }
    
}
@RestController
@RequestMapping("/topic")
public class Topic_Producer {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @RequestMapping("/sendMsgA")
    public String sendMsgA() {
        String msg = "topic_queue   (com.rabbitmq.client)";
        
        rabbitTemplate.convertAndSend("topic_exchange", "com.rabbitmq.client", msg);
        return "ok";
    }
    
    @RequestMapping("/sendMsgB")
    public String sendMsgB() {
        String msg = "topic_queue   (com.hidden.client)";
        rabbitTemplate.convertAndSend("topic_exchange", "com.hidden.client", msg);
        return "ok";
        
    }
    
    @RequestMapping("/sendMsgC")
    public String sendMsgC() {
        String msg = "topic_queue   (com.hidden.demo)";
        rabbitTemplate.convertAndSend("topic_exchange", "com.hidden.demo", msg);
        return "ok";
        
    }
    
}

6.2、代码结构和测试

http://127.0.0.1:8080/topic/sendMsgA
http://127.0.0.1:8080/topic/sendMsgB
http://127.0.0.1:8080/topic/sendMsgC

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值