SpringBoot - 消息服务AMQP(RabbitMQ)的整合

SpringBoot - 消息服务AMQP(RabbitMQ)的整合

1. 基本概念介绍

1.1 什么是消息队列?

  • 消息队列(Message Queue)是一种进程间或者线程间的异步通信方式。
  • 使用消息队列,消息生产者在产生消息后,会将消息保存在消息队列中,直到消息消费者来取走它,即消息的发送者和接收者不需要同时与消息队列交互。
  • 使用消息队列可以有效实现服务的解耦,并提高系统的可靠性以及可扩展性。
  • 目前,开源的消息队列服务非常多,如 Apache ActiveMQRabbitMQ 等,这些产品也就是常说的消息中间件。

1.2 什么是 AMQP?

  • AMQPAdvanced Message Queuing Protocol,高级消息队列协议)是一个线路层的协议规范,而不是 APl 规范(例如 JMS)。
  • 由于 AMQP 是一个线路层协议规范,因此它天然就是跨平台的,就像 SMTPHTTP 等协议一样,只要开发者按照规范的格式发送数据,任何平台都可以通过 AMQP 进行消息交互。
  • 像目前流行的 StormMQRabbitMQ 等都实现了 AMQP

1.3 什么是 RabbitMQ?

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

2. 整合Spring Boot

Spring Boot为AMQP提供了自动化配置依赖spring-boot-stater-amqp,因此首先创建Spring

Boot项目并添加该依赖,代码如下:

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

项目创建成功后,在application.properties中配置RabbitMQ的基本连接信息,代码如下:

spring.rabbitmq.host=120.55.61.170
spring.rabbitmq.port=5672
spring.rabbitmq.username=suohe
spring.rabbitmq.password=123

接下来进行RabbitMQ配置,在RabbitMQ中,所有的消息生产者提交的消息都会交由Exchange进行再分配,Exchange会根据不同的策略将消息分发到不同的Queue中。RabbitMQ中一共提供了4种不同的Exchange策略,分别是Direct、Fanout、 Topic 以及Header,这4种不同的策略中,前3种的使用频率较高,第4种的使用频率较低,下面分别对这4种不同的ExchangeType 予以介绍。

(1) Direct

简单来说,Direct 交换机就是完全根据Key进行投递。

DirectExchange的路由策略是将消息队列绑定到一个DirectExchange上,当一条消息到达DirectExchange时会被转发到与该条消息routing key相同的Queue上,例如消息队列名为“hello-queue",则routingkey为“hello-queue”的消息会被该消息队列接收。DirectExchange 的配
置如下:

@Configuration
public class RabbitDirectConfig {

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

    // 提供一个 DirectExchange
    @Bean
    DirectExchange directExchange() {
        // 三个参数分别是名字、重启后是否依然有效、长期未使用时是否删除
        return new DirectExchange("suohe-direct", true, false);
    }

    // 创建一个Binding对象,将Exchange和Queue绑定在一起
    @Bean
    Binding binding() {
        return BindingBuilder.bind(queue())
                .to(directExchange()).with("direct");
    }
}

代码解释:

  • 首先提供一个消息队列Queue,然后创建一个DirectExchange 对象,三个参数分别是名字、重启后是否依然有效以及长期未用时是否删除。
  • 创建一个 Binding对象,将Exchange和Queue绑定在一起。
  • DirectExchange 和Binding两个Bean的配置可以省略掉,即如果使用DirectExchange,只配置一个Queue的实例即可。

接下来配置一个消费者,代码如下:

@Component
public class DirectReceiver {

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

最后创建一个 Controller,并注入一个 RabbitTemplate 对象来进行消息发送:

@RestController
public class HelloController {

    @Autowired
    RabbitTemplate rabbitTemplate;

    @GetMapping("/hello")
    public void hello() {
        rabbitTemplate.convertAndSend("hello-queue", "hello direct!");
    }
}

项目启动,访问 http://localhost:8080/hello 地址发送一条消息,可以看到控制台这边成功接收到消息并打印出来。
在这里插入图片描述

(2) Fanout

简单来说,Fanout交换机不需要任何 Key,它采取广播的模式,一个消息进来时,投递到与该交换机绑定的所有队列。

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

@Configuration
public class RabbitFanoutConfig {
    public final static String FANOUTNAME = "suohe-fanout";
 
    // 提供一个 FanoutExchange
    @Bean
    FanoutExchange fanoutExchange() {
        // 三个参数分别是名字、重启后是否依然有效、长期未使用时是否删除
        return new FanoutExchange(FANOUTNAME, true, false);
    }
 
    // 提供两个消息队列 Queue
    @Bean
    Queue queueOne() {
        return new Queue("queue-one");
    }
    @Bean
    Queue queueTwo() {
        return new Queue("queue-two");
    }
 
    // 将这两个 Queue 都绑定到 FanoutExchange 上
    @Bean
    Binding bindingOne() {
        return BindingBuilder.bind(queueOne()).to(fanoutExchange());
    }
    @Bean
    Binding bindingTwo() {
        return BindingBuilder.bind(queueTwo()).to(fanoutExchange());
    }
}

在这里首先创建FanoutExchange, 参数的含义与创建DirectExchange 参数的含义一致,然后创建两个Queue,再将这两个Queue都绑定到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);
    }
}

最后创建一个 Controller,并注入一个 RabbitTemplate 对象来进行消息发送:

@RestController
public class HelloController {
 
    @Autowired
    RabbitTemplate rabbitTemplate;
 
    @GetMapping("/hello")
    public void hello() {
        rabbitTemplate.convertAndSend(RabbitFanoutConfig.FANOUTNAME, null, "hello fanout!");
    }
}

项目启动,访问 http://localhost:8080/hello 地址发送一条消息,可以看到所有和这个 FanoutExchange 绑定的 Queue都收到了消息。
在这里插入图片描述

(3) Topic

简单来说 Topic 交换机会对 Key 进行模式匹配后进行投递(可以使用符号 # 匹配一个或多个词,符号 * 匹配正好一个词)

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

@Configuration
public class RabbitTopicConfig {
    public final static String TOPICNAME = "suohe-topic";
 
    // 提供一个 TopicExchange
    @Bean
    TopicExchange topicExchange() {
        // 三个参数分别是名字、重启后是否依然有效、长期未使用时是否删除
        return new TopicExchange(TOPICNAME, true, false);
    }
 
    // 提供三个消息队列 Queue
    @Bean
    Queue xiaomi() {
        return new Queue("xiaomi");
    }
    @Bean
    Queue huawei() {
        return new Queue("huawei");
    }
    @Bean
    Queue phone() {
        return new Queue("phone");
    }
 
    // 将三个 Queue 都绑定到 TopicExchange 上
    @Bean
    Binding xiaomiBinding() {
        // 凡是 routingkey 以 "xiaomi" 开头的消息,都被路由到 xiaomi 这个 Queue 上
        return BindingBuilder.bind(xiaomi()).to(topicExchange())
                .with("xiaomi.#");
    }
    @Bean
    Binding huaweiBinding() {
        // 凡是 routingkey 以 "huawei" 开头的消息,都被路由到 huiwei 这个 Queue 上
        return BindingBuilder.bind(huawei()).to(topicExchange())
                .with("huawei.#");
    }
    @Bean
    Binding phoneBinding() {
        // 凡是 routingkey 中包含 "phone" 的消息,都被路由到 phone 这个 Queue 上
        return BindingBuilder.bind(phone()).to(topicExchange())
                .with("#.phone.#");
    }
}

代码解释:

  • 首先创建TopicExchange,参数和前面的一致。然后创建三个Queue,第一个Queue用来存储和“xiaomi” 有关的消息,第二个Queue用来存储和“huawei” 有关的消息,第三个Queue用来存储和“phone”有关的消息。
  • 将三个Queue分别绑定到TopicExchange 上,第一个Binding 中的“xiaomi.#” 表示消息的routingkey凡是以“xiaomi” 开头的,都将被路由到名称为“xiaomi" 的Queue 上;第二个Binding中的“huawei.#" 表示消息的routingkey 凡是以“huawei” 开头的,都将被路由到名称为“huawei" 的Queue上;第三个Binding中的“#.phone.#” 则表示消息的routingkey 中凡是包含“phone”的,都将被路由到名称为“phone” 的Queue上。

接下来针对三个Queue创建三个消费者,代码如下:

@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);
    }
}

最后创建一个 Controller,并注入一个 RabbitTemplate 对象来进行消息发送:

@RestController
public class HelloController {
 
    @Autowired
    RabbitTemplate rabbitTemplate;
 
    @GetMapping("/hello")
    public void hello() {
        rabbitTemplate.convertAndSend(RabbitTopicConfig.TOPICNAME, "xiaomi.news", "(1)小米新闻..");
        rabbitTemplate.convertAndSend(RabbitTopicConfig.TOPICNAME, "xiaomi.phone", "(2)小米手机..");
        rabbitTemplate.convertAndSend(RabbitTopicConfig.TOPICNAME, "phone.news", "(3)手机新闻..");
    }
}

项目启动,访问 http://localhost:8080/hello 地址发送 3 条消息,可以看到:
在这里插入图片描述

  • 第一条消息被路由到名为“xiaomi”的 Queue 上。
  • 第二条消息被路由到名为“xiaomi”以及名为“phone”的 Queue 上。
  • 第三条消息被路由到名为“phone”的 Queue 上。

(4) Header

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

注意: 除了下面用到的 whereAnywhere 这两个匹配方法,还有个 whereAll 方法,该方法表示消息的所有的 Header 都要匹配。

@Configuration
public class RabbitHeaderConfig {
    public final static String HEADERNAME = "suohe-header";
 
    // 提供一个 HeadersExchange
    @Bean
    HeadersExchange headersExchange() {
        // 三个参数分别是名字、重启后是否依然有效、长期未使用时是否删除
        return new HeadersExchange(HEADERNAME, true, false);
    }
 
    // 提供两个消息队列 Queue
    @Bean
    Queue queueName() {
        return new Queue("name-queue");
    }
    @Bean
    Queue queueAge() {
        return new Queue("age-queue");
    }
 
    // 将两个 Queue 都绑定到 HeadersExchange 上
    @Bean
    Binding bindingName() {
        Map<String, Object> map = new HashMap<>();
        map.put("name", "hangge");
        return BindingBuilder.bind(queueName())
                .to(headersExchange())
                .whereAny(map) // 消息只要有一个Header匹配上map中的key/value,便路由到这个Queue上
                .match();
    }
    @Bean
    Binding bindingAge() {
        return BindingBuilder.bind(queueAge())
                .to(headersExchange())
                .where("age") //只要消息Header中包含age(无论值多少),便路由到这个Queue上
                .exists();
    }
}

接下来创建两个消息消费者。注意:这里的参数用 byte 数组接收:

@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));
    }
}

最后创建一个 Controller,并注入一个 RabbitTemplate 对象发送两条消息:

@RestController
public class HelloController {
 
    @Autowired
    RabbitTemplate rabbitTemplate;
 
    @GetMapping("/hello")
    public void hello() {
        Message nameMsg = MessageBuilder
                .withBody("hello header! name-queue".getBytes())
                .setHeader("name", "hangge").build();
        rabbitTemplate.send(RabbitHeaderConfig.HEADERNAME, null, nameMsg);
 
        Message ageMsg = MessageBuilder
                .withBody("hello header! age-queue".getBytes())
                .setHeader("age", "99").build();
        rabbitTemplate.send(RabbitHeaderConfig.HEADERNAME, null, ageMsg);
    }
}

项目启动,访问 http://localhost:8080/hello 地址发送 2 条消息,可以看到不同 header 的消息被不同的 Queue 中:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值