再回首 之 消息队列 RabbitMQ(下)

137 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情

上篇回顾

上篇给大家介绍了AMQP高级消息协议、RabbitMQ的优势、RabbitMQ组件功能(Broker、Virtual Host、Exchange、Queue、Binding)等基本概论。这些内容对于接下来的代码部分的学习起到了很好的铺垫作用。

我们知道交换机有Direct、Topic、Headers和Fanout四种消息分发类型。为了便于大家理解,我贴出了四种方式的示意图如下:

Direct模式

Direct是RabbitMQ默认的交换机模式,也是简单的模式,根据key全字匹配去寻找队列

image.png

Topic模式

Topic是RabbitMQ中使用最多的交换机模式(见图12-4),RoutingKey必须是一串字符,用符号“.”隔开,比如user.msg或者user.order.msg等

image.png

Headers模式

Headers也是根据规则匹配的,相较于Direct和Topic固定地使用RoutingKey与BindingKey的匹配规则来路由消息,Headers是根据发送的消息内容中的headers属性进行匹配的

image.png

Fanout模式

Fanout是消息广播模式,交换机不处理RoutingKey,发送到交换机的消息都会分发到所有绑定的队列上。Fanout模式不需要RoutingKey,只需要提前将交换机与队列进行绑定即可。

image.png

准备工作

step1

安装一下RabbitMQ服务,我是Docker容器中部署的,操作命令我给贴出来,小伙伴们照着步骤copy执行就可以啦!

1、docker search rabbitmq:management
2、docker pull rabbitmq:management
3、docker run -d -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:management

执行成功后可以通过访问 “http://127.0.0.1:15672” 这个地址,若成功出现如下界面则代表部署成功了,默认登录账号密码都是guest

image.png

step2

Spring Boot提供了spring-bootstarter-amqp组件对消息队列进行支持,使用非常简单,仅需要非常少的配置即可实现完整的消息队列服务。利用IDEA新建一个SpringBoot项目,具体步骤我就省略了,然后在pom文件中加入如下引用:

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

step3

在项目的application.yml文件中填写RabbitMQ相关配置信息

spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest

上代码!

简单队列模式

简单队列是RabbitMQ中最简单的工作队列模式,也叫点对点模式,即一个消息的生产者对应一个消费者,它包含一个生产者、一个消费者和一个队列。生产者向队列中发送消息,消费者从队列中获取消息并消费。

1.创建生产者

生产者用来产生消息并进行发送,需要用到RabbitTemplate类。msg类型不限定,这里也可以传入Object对象。

@Slf4j
@Component
public class Producer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 发送字符串
     */
    public void sendStr(){
        String msg ="hello world";
        rabbitTemplate.convertAndSend("rabbitmq_queue",msg);
    }
    
    /**
     * 发送对象
     */
    public void sendObj(){
        Object msg = ...;
        rabbitTemplate.convertAndSend("rabbitmq_queue",msg);
    }

}

2.创建消费者

消费者可以消费生产者发送的消息。接下来创建消费者类Consumer,并使用@RabbitListener注解来指定消息的处理方法。

@Slf4j
@Component
public class Consumer {

    /**
     * @RabbitListener Consumer消费者通过@RabbitListener注解创建侦听器端点,绑定rabbitmq_queue队列
     * @param message 待处理的消息
     */
    @RabbitHandler
    @RabbitListener(queuesToDeclare = @Queue("rabbitmq_queue"))
    public void process(String message){
        log.info("消费者消息:{}",message);
    }
    
    @RabbitHandler
    @RabbitListener(queuesToDeclare = @Queue("rabbitmq_queue"))
    public void process(Object message){
        log.info("消费者消息:{}",message);
    }

}

3.创建测试类

@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTest{

    @Autowired
    Producer producer;
    
    @Test
    public void test(){
        producer.sendStr();
        Thread.sleep(1000)
    }
}

工作队列模式

与简单队列模式的代码大致相同,生产者不变,只是有多个消费者而已,此时每条消息只会被消费一次,多个消费者循环处理消息。

1.创建生产者

创建一个生产者用来循环产生100条消息发送到队列。

@Slf4j
@Component
public class Producer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void produce(){
        for (int i = 0; i < 100; i++) {
            String msg = new Date() + " beiging";
            rabbitTemplate.convertAndSend("work_queue",msg);
        }
    }

}

2.创建消费者

创建2个消费者ConsumerA和ConsumerB

@Slf4j
@Component
public class ConsumerA {

    /**
     * @RabbitListener Consumer消费者通过@RabbitListener注解创建侦听器端点,绑定rabbitmq_queue队列
     * @param message 待处理的消息
     */
    @RabbitHandler
    @RabbitListener(queuesToDeclare = @Queue("work_queue"))
    public void process(String message) throws InterruptedException {
        Thread.sleep(1000);
        log.info("ConsumerA消费者消息:{}",message);
    }

}

@Slf4j
@Component
public class ConsumerB {

    /**
     * @RabbitListener Consumer消费者通过@RabbitListener注解创建侦听器端点,绑定rabbitmq_queue队列
     * @param message 待处理的消息
     */
    @RabbitHandler
    @RabbitListener(queuesToDeclare = @Queue("work_queue"))
    public void process(String message) throws InterruptedException {
        Thread.sleep(1000);
        log.info("ConsumerB消费者消息:{}",message);
    }

}

3.创建测试类

@Test
public void workQueueModel() throws InterruptedException {
    workProducer.produce();
    Thread.sleep(800000);
}

路由模式

之前介绍了Direct路由转发模式是“先匹配,再发送”,即在绑定时设置一个BindingKey,当消息的RoutingKey匹配队列绑定的BindingKey时,才会被交换机发送到绑定的队列中。

1.配置Direct规则

创建Direct规则配置类DirectRabbitConfig,创建对应的Exchange、Queue,并将队列绑定到交换机上。

@Configuration
public class DirectRabbitConfig {

    /**
     * 队列一
     * @return
     */
    @Bean
    public Queue directQueueQ1(){
        return new Queue("direct.Q1");
    }

    /**
     * 队列二
     * @return
     */
    @Bean
    public Queue directQueueQ2(){
        return new Queue("direct.Q2");
    }

    /**
     * 队列三
     * @return
     */
    @Bean
    public Queue directQueueQ3(){
        return new Queue("direct.Q3");
    }

    /**
     * 定义交换机Direct类型
     * @return
     */
    @Bean
    public DirectExchange myDirectExchange(){
        return new DirectExchange("directExchange");
    }

    /**
     * 队列绑定到交换机,再指定一个路由键
     * @return
     */
    @Bean
    public Binding DirectExchangeQ1(){
        return BindingBuilder.bind(directQueueQ1()).to(myDirectExchange()).with("direct.Q1");
    }

    @Bean
    public Binding DirectExchangeQ2(){
        return BindingBuilder.bind(directQueueQ2()).to(myDirectExchange()).with("direct.Q2");
    }

    @Bean
    public Binding DirectExchangeQ3(){
        return BindingBuilder.bind(directQueueQ3()).to(myDirectExchange()).with("direct.Q3");
    }
}

2.生产者

创建生产者发送消息,通过convertAndSend()方法发送消息,传入directExchange、routingKey、context三个参数。

  • directExchange为交换机名称。
  • routingKey为消息的路由键。
  • context为消息的内容。
@Slf4j
@Component
public class DirectProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void produce(String routingKey) {
        String context = "direct msg weiz";
        System.out.println("Direct Sender,routingKey: " + routingKey+",context:"+context);
        this.rabbitTemplate.convertAndSend("directExchange",routingKey, context);
    }

}

3.消费者

创建3个消费者处理程序,分别监听Q1、Q2、Q3

@Slf4j
@Component
public class DirectConsumer {

    @RabbitHandler
    @RabbitListener(queuesToDeclare = @Queue("direct.Q1"))
    public void processQ1(String message) {
        System.out.println("direct Receiver Q1: " + message);
    }

    @RabbitHandler
    @RabbitListener(queuesToDeclare = @Queue("direct.Q2"))
    public void processQ2(String message) {
        System.out.println("direct Receiver Q2: " + message);
    }

    @RabbitHandler
    @RabbitListener(queuesToDeclare = @Queue("direct.Q3"))
    public void processQ3(String message) {
        System.out.println("direct Receiver Q3: " + message);
    }

}

4.测试方法

@Test
public void test() throws InterruptedException {
    directProducer.produce("direct.Q1");
    directProducer.produce("direct.Q3");
    Thread.sleep(2000);
}

广播模式

Fanout就是熟悉的广播模式或者订阅模式,每个发送到Fanout类型交换机的消息都会分到所有绑定的队列上。 Fanout模式很像子网广播,每台子网内的主机都获得了一份复制的消息。Fanout类型转发消息是最快的。

1. 配置Fanout规则

先创建Fanout规则配置类FanoutRabbitConfig,再创建对应的Exchange、Queue,并将队列绑定到交换机上。

@Configuration
public class FanoutRabbitConfig {

    @Bean
    public Queue Q1Message(){
        return new Queue("fanout.Q1");
    }

    @Bean
    public Queue Q2Message(){
        return new Queue("fanout.Q2");
    }

    @Bean
    public Queue Q3Message(){
        return new Queue("fanout.Q3");
    }

    @Bean
    FanoutExchange fanoutExchange(){
        return new FanoutExchange("fanoutExchange");
    }

    @Bean
    Binding bindingExchangeQ1(Queue Q1Message,FanoutExchange fanoutExchange){
        return BindingBuilder.bind(Q1Message).to(fanoutExchange);
    }

    @Bean
    Binding bindingExchangeQ2(Queue Q2Message,FanoutExchange fanoutExchange){
        return BindingBuilder.bind(Q2Message).to(fanoutExchange);
    }

    @Bean
    Binding bindingExchangeQ3(Queue Q3Message,FanoutExchange fanoutExchange){
        return BindingBuilder.bind(Q3Message).to(fanoutExchange);
    }
}

2. 发送者

创建发送者

@Slf4j
@Component
public class FanoutProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void produce() {
        String context = "fanout msg weiz";
        System.out.println("Fanout Sender : " + context);
        this.rabbitTemplate.convertAndSend("fanoutExchange", "", context);
    }

}

3. 接收者

创建3个接收者分别监听Q1、Q2、Q3队列。

@Slf4j
@Component
public class FanoutConsumer {

    @RabbitHandler
    @RabbitListener(queues = "fanout.Q1")
    public void processA(String message){
        log.info("fanout receiver Q1:{}",message);
    }

    @RabbitHandler
    @RabbitListener(queues = "fanout.Q2")
    public void processB(String message){
        log.info("fanout receiver Q2:{}",message);
    }

    @RabbitHandler
    @RabbitListener(queues = "fanout.Q3")
    public void processC(String message){
        log.info("fanout receiver Q3:{}",message);
    }

}

4.测试

@Autowired
FanoutProducer fanoutProducer;

@Test
void fanout() throws InterruptedException {
    fanoutProducer.produce();
    Thread.sleep(10000);
}

发布订阅模式

Topic是RabbitMQ中灵活的一种方式,可以根据路由键绑定不同的队列。Topic类型的Exchange与Direct相比,都可以根据路由键将消息路由到不同的队列。只不过Topic类型的Exchange可以让队列在绑定路由键时使用通配符。有关通配符的规则为:

  • #:匹配一个或多个词。
  • *:只匹配一个词。

1. 配置Topic规则

先创建Topic配置规则类TopicRabbitConfig,再创建对应的Exchange、Queue,并将队列绑定到交换机上

@Configuration
public class TopicRabbitConfig {

    final static String message1 = "topic.color";
    final static String message2 = "topic.color.red";
    final static String message3 = "topic.msg.feedback";

    @Bean
    public Queue queueMessage1(){
        return new Queue(TopicRabbitConfig.message1);
    }

    @Bean
    public Queue queueMessage2(){
        return new Queue(TopicRabbitConfig.message2);
    }

    @Bean
    public Queue queueMessage3(){
        return new Queue(TopicRabbitConfig.message3);
    }

    @Bean
    TopicExchange topicExchange(){
        return new TopicExchange("topicExchange");
    }

    /**
     * 将队列和交换机绑定
     * @param queueMessage1
     * @param topicExchange
     * @return
     */
    @Bean
    Binding bindingExchangeMessage1(Queue queueMessage1,TopicExchange topicExchange){
        return BindingBuilder.bind(queueMessage1).to(topicExchange).with("topic.color.*");
    }

    @Bean
    Binding bindingExchangeMessage2(Queue queueMessage2,TopicExchange topicExchange){
        return BindingBuilder.bind(queueMessage2).to(topicExchange).with("topic.color.red");
    }

    @Bean
    Binding bindingExchangeMessage3(Queue queueMessage3,TopicExchange topicExchange){
        return BindingBuilder.bind(queueMessage3).to(topicExchange).with("topic.msg.*");
    }
}

2. 生产者

创建生产者,通过routingKey往绑定的队列发送消息

@Slf4j
@Component
public class TopicProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void produce(String routingKey) {
        String context = "topic msg weiz";
        System.out.println("Topic Sender,routingKey : " +routingKey+" context:"+ context);
        this.rabbitTemplate.convertAndSend("topicExchange", routingKey, context);
    }

}

3. 消费者

创建消费者类,然后使用@RabbitListener监听3个队列

@Slf4j
@Component
public class TopicConsumer {

    @RabbitHandler
    @RabbitListener(queues = "topic.color")
    public void processA(String message){
        log.info("topic.color---------> receiver :{}",message);
    }

    @RabbitHandler
    @RabbitListener(queues = "topic.color.red")
    public void processB(String message){
        log.info("topic.color.red----------> receiver :{}",message);
    }

    @RabbitHandler
    @RabbitListener(queues = "topic.msg.feedback")
    public void processC(String message){
        log.info("topic.msg.feedback----------> receiver :{}",message);
    }

}

4.测试

@Autowired
TopicProducer topicProducer;

@Test
void testTopic() throws InterruptedException {
    //topicProducer.produce("topic.color.green");
    topicProducer.produce("topic.color.red");
    //topicProducer.produce("topic.msg.feedback");
    Thread.sleep(1000);
}

好了,以上就是RabbitMQ几种分发模式的样例代码了,小伙伴们学废了没?