SpringBoot整合RabbitMQ

AMQP简介

AMQP(Advanced Message Queuing Protocol,高级消息队列协议)是一个线路层的协议规范,而不是API规范(例如JMS)。

由于AMQP是一个线路层协议规范,因此它天然就是跨平台的,就像SMTP、HTTP等协议一样,只要开发者按照规范的格式发送数据,任何平台都可以通过AMQP进行消息交互。

像目前流行的StormMQ、RabbitMQ等都实现了AMQP。

和JMS一样,使用AMQP也是使用AMQP的某个实现,本案例以RabbitMQ为例介绍AMQP的使用。

 

RabbitMQ简介

RabbitMQ是一个实现了AMQP的开源消息中间件,使用高性能的Erlang编写。RabbitMQ具有可靠性、支持多种协议、高可用、支持消息集群以及多语言客户端等特点,在分布式系统中存储转发消息,具有不错的性能表现。

 

添加依赖:

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

 

配置RabbitMQ的基本连接信息:

spring:
  rabbitmq:
    host: 172.19.25.170
    port: 5672
    username: xxx
    password: 123

 

RabbitMQ配置:

在RabbitMQ中,所有的消息生产者提交的消息都会交由Exchange进行再分配,Exchange会根据不同的策略将消息分发到不同的Queue中。

RabbitMQ中一共提供了4种不同的Exchange策略,分别是Direct、Fanout、Topic以及Header,这4种不同的策略中,前3种的使用频率较高,第4种的使用频率较低。

 

(1)Direct

DirectExchange的路由策略是将消息队列绑定到一个DirectExchange上,当一条消息到达DirectExchange时会被转发到与该条消息routing key相同的Queue上

DirectExchange的配置:

// DirectExchange和Binding两个Bean的配置可以省略掉,即如果使用DirectExchange,只配置一个Queue的实例即可
@Configuration
public class RabbitDirectConfig {

    public final static String DIRECTNAME = "sang-direct";

    @Bean
    Queue queue() {
        // 首先提供一个消息队列Queue
        return new Queue("hello-queue");
    }

    @Bean
    DirectExchange directExchange() {
        // 然后创建一个DirectExchange对象,三个参数分别是名字、重启后是否依然有效以及长期未用时是否删除
        return new DirectExchange(DIRECTNAME, true, false);
    }

    @Bean
    Binding binding() {
        return BindingBuilder.bind(queue()).to(directExchange()).with("direct");
    }

}

接下来配置一个消费者:

@Slf4j
@Component
public class DirectReceiver {

    // 通过@RabbitListener注解指定一个方法是一个消息消费方法,方法参数就是所接收到的消息
    @RabbitListener(queues = "hello-queue")
    public void handler1(String msg) {
        log.info("DirectReceiver:" + msg);
    }

}

  

(2)Fanout

FanoutExchange的数据交换策略是把所有到达FanoutExchange的消息转发给所有与它绑定的Queue,在这种策略中,routingkey将不起任何作用

@Configuration
public class RabbitFanoutConfig {

    public final static String FANOUTNAME = "sang-fanout";

    @Bean
    FanoutExchange fanoutExchange() {
        // 首先创建FanoutExchange,参数的含义与创建DirectExchange参数的含义一致
        return new FanoutExchange(FANOUTNAME, true, false);
    }

    // 然后创建两个Queue,再将这两个Queue都绑定到FanoutExchange上
    @Bean
    Queue queueOne() {
        return new Queue("queue-one");
    }

    @Bean
    Queue queueTwo() {
        return new Queue("queue-two");
    }

    @Bean
    Binding bindingOne() {
        return BindingBuilder.bind(queueOne()).to(fanoutExchange());
    }

    @Bean
    Binding bindingTwo() {
        return BindingBuilder.bind(queueTwo()).to(fanoutExchange());
    }

}

@Component
public class FanoutReceiver {

    @RabbitListener(queues = "queue-one")
    public void handler1(String message) {
        System.out.println("FanoutReceiver:handler1:" + message);
    }

    @RabbitListener(queues = "queue-two")
    public void handler2(String message) {
        System.out.println("FanoutReceiver:handler2:" + message);
    }

}

  

(3)Topic

TopicExchange是比较复杂也比较灵活的一种路由策略,在TopicExchange中,Queue通过routingkey绑定到TopicExchange上,当消息到达TopicExchange后,TopicExchange根据消息的routingkey将消息路由到一个或者多个Queue上。

@Configuration
public class RabbitHeaderConfig {

    public final static String HEADERNAME = "sang-header";

    @Bean
    HeadersExchange headersExchange() {
        return new HeadersExchange(HEADERNAME, true, false);
    }

    @Bean
    Queue queueName() {
        return new Queue("name-queue");
    }

    @Bean
    Queue queueAge() {
        return new Queue("age-queue");
    }

    @Bean
    Binding bindingName() {
        Map<String, Object> map = new HashMap<>();
        map.put("name", "sang");
        /*
         * whereAny表示消息的Header中只要有一个Header匹配上map中的key/value,就把该消息路由到名为“name-queue”的Queue上
         * 这里也可以使用whereAll方法,表示消息的所有Header都要匹配
         * whereAny和whereAll实际上对应了一个名为x-match的属性
         */
        return BindingBuilder.bind(queueName()).to(headersExchange()).whereAny(map).match();
    }

    @Bean
    Binding bindingAge() {
        // bindingAge中的配置则表示只要消息的Header中包含age,无论age的值是多少,都将消息路由到名为“age-queue”的Queue上
        return BindingBuilder.bind(queueAge()).to(headersExchange()).where("age").exists();
    }
}


@Component
public class HeaderReceiver {

    @RabbitListener(queues = "name-queue")
    public void handler1(byte[] msg) {
        System.out.println("HeaderReceiver:name:" + new String(msg, 0, msg.length));
    }

    @RabbitListener(queues = "age-queue")
    public void handler2(byte[] msg) {
        System.out.println("HeaderReceiver:age:" + new String(msg, 0, msg.length));
    }

}

  

(4)Header

HeadersExchange是一种使用较少的路由策略,HeadersExchange会根据消息的Header将消息路由到不同的Queue上,这种策略也和routingkey无关

@Configuration
public class RabbitTopicConfig {

    public final static String TOPICNAME = "sang-topic";

    @Bean
    TopicExchange topicExchange() {
        // 首先创建TopicExchange,参数和前面的一致
        return new TopicExchange(TOPICNAME, true, false);
    }

    // 然后创建三个Queue,第一个Queue用来存储和“xiaomi”有关的消息,第二个Queue用来存储和“huawei”有关的消息,第三个Queue用来存储和“phone”有关的消息。
    @Bean
    Queue xiaomi() {
        return new Queue("xiaomi");
    }

    @Bean
    Queue huawei() {
        return new Queue("huawei");
    }

    @Bean
    Queue phone() {
        return new Queue("phone");
    }

    // 将三个Queue分别绑定到TopicExchange上,第一个Binding中的“xiaomi.#”表示消息的routingkey凡是以“xiaomi”开头的,都将被路由到名称为“xiaomi”的Queue上
    @Bean
    Binding xiaomiBinding() {
        return BindingBuilder.bind(xiaomi()).to(topicExchange()).with("xiaomi.#");
    }

    // 第二个Binding中的“huawei.#”表示消息的routingkey凡是以“huawei”开头的,都将被路由到名称为“huawei”的Queue上
    @Bean
    Binding huaweiBinding() {
        return BindingBuilder.bind(huawei()).to(topicExchange()).with("huawei.#");
    }

    // 第三个Binding中的“#.phone.#”则表示消息的routingkey中凡是包含“phone”的,都将被路由到名称为“phone”的Queue上
    @Bean
    Binding phoneBinding() {
        return BindingBuilder.bind(phone()).to(topicExchange()).with("#.phone.#");
    }
}

@Component
public class TopicReceiver {

    @RabbitListener(queues = "phone")
    public void handler1(String message) {
        System.out.println("PhoneReceiver:" + message);
    }

    @RabbitListener(queues = "xiaomi")
    public void handler2(String message) {
        System.out.println("XiaoMiReceiver:" + message);
    }

    @RabbitListener(queues = "huawei")
    public void handler3(String message) {
        System.out.println("HuaWeiReceiver:" + message);
    }

}

  

测试:

@Slf4j
@Api(tags = "RabbitMqSendController")
@RequestMapping("/mq")
@RestController
public class RabbitMqSendController {

    @Autowired
    RabbitTemplate rabbitTemplate;

    @GetMapping(value = "/directTest")
    public void directTest(String message) {
        log.info("directTest:" + message);
        rabbitTemplate.convertAndSend("hello-queue", message);
    }

    @GetMapping(value = "/fanoutTest")
    public void fanoutTest(String message) {
        log.info("fanoutTest:" + message);
        rabbitTemplate.convertAndSend(RabbitFanoutConfig.FANOUTNAME, "", message);
    }

    @GetMapping(value = "/topicTest")
    public void topicTest() {
        rabbitTemplate.convertAndSend(RabbitTopicConfig.TOPICNAME, "xiaomi.news", "小米新闻..");
        rabbitTemplate.convertAndSend(RabbitTopicConfig.TOPICNAME, "huawei.news", "华为新闻..");
        rabbitTemplate.convertAndSend(RabbitTopicConfig.TOPICNAME, "xiaomi.phone", "小米手机..");
        rabbitTemplate.convertAndSend(RabbitTopicConfig.TOPICNAME, "huawei.phone", "华为手机.. ");
        rabbitTemplate.convertAndSend(RabbitTopicConfig.TOPICNAME, "phone.news", "手机新闻..");
    }

    @GetMapping(value = "/headerTest")
    public void headerTest() {
        Message nameMsg = MessageBuilder.withBody("hello header! name-queue".getBytes()).setHeader("name", "sang").build();
        Message ageMsg = MessageBuilder.withBody("hello header! age-queue".getBytes()).setHeader("age", "99").build();
        rabbitTemplate.send(RabbitHeaderConfig.HEADERNAME, null, ageMsg);
        rabbitTemplate.send(RabbitHeaderConfig.HEADERNAME, null, nameMsg);
    }

}

  

 

posted @ 2022-07-01 12:07  草木物语  阅读(88)  评论(0编辑  收藏  举报