Loading

spring boot 集成 rabbitmq 指南

先决条件

  1. rabbitmq server 安装参考
  2. 一个添加了 web 依赖的 spring boot 项目 我的版本是 2.5.2

添加 maven 依赖

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

配置 application.yml

spring:
  rabbitmq:
    host: [此处填写 rabbitmq 服务地址(ip/domain),不包括[]]
    port: [此处填写 rabbitmq 服务端口(默认是 5672),不包括[]]
    username: [此处填写用户名,不包括[]]
    password: [此处填写密码,不包括[]]

添加 config bean

这里面只需要配置 MessageConverter,其他的可以不用配置,它的作用是:

  1. 发布消息时,将 java bean 序列化为 rabbitmq 的消息
  2. 消费消息时,将 rabbitmq 消息反序列化为 java bean

如果你只用简单类型发布和接收消息,就大可不必配置这个了

package com.xx.config;

import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {

    /**
     * MessageConverter用于将Java对象转换为RabbitMQ的消息。
     *
     * 默认情况下,Spring Boot使用SimpleMessageConverter,只能发送String和byte[]类型的消息,不太方便。
     * 使用Jackson2JsonMessageConverter,我们就可以发送JavaBean对象,由Spring Boot自动序列化为JSON并以文本消息传递。
     *
     *
     * convertAndSend 可以发送 java bean,接收方也可也自动反序列化
     * https://www.liaoxuefeng.com/wiki/1252599548343744/1282385960239138
     * http://www.ityouknow.com/springboot/2016/11/30/spring-boot-rabbitMQ.html
     *
     * @return
     */
    @Bean
    MessageConverter createMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }

配置队列

  1. 配置 exchange
  2. 配置 queue
  3. 配置 binding

配置好之后,会自动创建 exchangequeuebinding,不需要手动在 rabbitmq web management ui 中手动操作了。

这块配置代码如下:

Constant 类根据自己情况,要不要看你自己

package com.xx.constant;

public final class Constant {

    /**
     * rabbitmq
     */
    public static final class MQ {

        /**
         * XX 消息采用 Direct Exchange 模式
         */
        public static final class XX {

            private static final String namePrefix = "company-module-";
            private static final String routingKeyPrefix = "company_module_";
            private static final String create = "create";
            private static final String update = "update";

            public static final String exchange = namePrefix + "events";

            public static final class Queue {
                public static final String create = namePrefix + XX.create;
                public static final String update = namePrefix + XX.update;
            }

            public static final class RoutingKey {
                public static final String create = routingKeyPrefix + XX.create;
                public static final String update = routingKeyPrefix + XX.update;
            }
        }
    }
}

package com.xx.message.receiver.xx;

import com.xx.constant.Constant;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQXXConfig {

    @Bean
    public DirectExchange xxExchange() {
        return new DirectExchange(Constant.MQ.XX.exchange, true, false);
    }

    @Bean
    public Queue xxCreateQueue() {
        return new Queue(Constant.MQ.XX.Queue.create, true, false, false);
    }

    @Bean
    public Queue xxUpdateQueue() {
        return new Queue(Constant.MQ.XX.Queue.update, true, false, false);
    }

    @Bean
    public Binding xxCreateBinding() {
        return BindingBuilder
                .bind(xxCreateQueue())
                .to(xxExchange())
                .with(Constant.MQ.XX.RoutingKey.create);
    }

    @Bean
    public Binding xxUpdateBinding() {
        return BindingBuilder
                .bind(xxUpdateQueue())
                .to(xxExchange())
                .with(Constant.MQ.XX.RoutingKey.update);
    }
}

编写 messageReceiver

这里为了测试,把 createupdate 的消息接收器写了两个,实际一个就可以了

package com.xx.message.receiver.xx;

import com.xx.constant.Constant;
import com.xx.exception.ServiceException;
import com.xx.vo.xx.XXVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 * XX 消息接收器
 */
@Slf4j
@Component
public class RabbitMQXXMessageReceiver {

    @RabbitListener(queues = Constant.MQ.XX.Queue.create)
    public void onCreate1Message(XXVo message) {
        log.info("mq:xx-create1 received <{}>", message);

//        throw new ServiceException("报错模拟");
    }

    @RabbitListener(queues = Constant.MQ.XX.Queue.create)
    public void onCreate2Message(XXVo message) {
        log.info("mq:xx-create2 received <{}>", message);

//        throw new ServiceException("报错模拟");
    }

    @RabbitListener(queues = Constant.MQ.XX.Queue.update)
    public void onUpdate1Message(XXVo message) {
        log.info("mq:xx-update1 received <{}>", message);
    }

    @RabbitListener(queues = Constant.MQ.XX.Queue.update)
    public void onUpdate2Message(XXVo message) {
        log.info("mq:xx-update2 received <{}>", message);
    }
}

发布消息

你可以用自己喜欢的方式发送,我这里用 RestController

package com.xx.controller;

import com.xx.constant.Constant;
import com.xx.vo.xx.XXVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;

@Slf4j
@RestController()
@RequestMapping("mq")
public class MQController {

    private final RabbitTemplate rabbitTemplate;

    public MQController(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
    }

    @GetMapping("create")
    public ResponseEntity<String> create(){
        final XXVo message = new XXVo()
                .setName("myesn");

        rabbitTemplate.convertAndSend(
                Constant.MQ.XX.exchange,
                Constant.MQ.XX.RoutingKey.create,
                message);
//        log.info("pub: send create message");

        return ResponseEntity.ok("create pub");
    }

    @GetMapping("update")
    public ResponseEntity<String> update(){
        final XXVo message = new XXVo()
                .setName("myesn");

        rabbitTemplate.convertAndSend(
                Constant.MQ.XX.exchange,
                Constant.MQ.XX.RoutingKey.update,
                message);
//        log.info("pub: send update message");

        return ResponseEntity.ok("update pub");
    }

测试

现在可以通过 http://ip:port/mq/createhttp://ip:port/mq/update 来测试消息的发布与消费

总结

示例中只使用了 direct exchange 模式,并且最终是 point to point 的通道类型(channel)。

在消息系统标准协议中,一个队列只能被一个消费者消费,如果想一个消息被多个消费者同时消费(发布消息后,所有消费者都能收到),那么就需要多个 queue 了。

测试总结(归纳文章要点):

  1. 自动创建 exchange、queue、binding,不需要手动在 web 上创建
  2. exchange、queue 持久化,rabbitmq 重启或无客户端时都不会自动删除 exchange、queue
  3. 消费者消费过程中报错,消息不会丢失,会自动切换到其他监听此队列的消费者(如果没有消息就一直被持久化),如果网站做了负载均衡,它也会自动让其他实例消费,如果负载均衡中所有节点中的消费者都报错,它也会一直循环在各个节点中消费消息,消息不会丢失,只有消费者成功消费消息(没有任何抛错),它才会从队列中移除此消息,点赞👍

文章参考

  1. RabbitMQ中的Exchange Types
  2. RabbitMQ Exchange类型详解
  3. RabbitMQ各个参数含义

我自己也整理了一份 xmind 放在 github,但 repo 目前是 private 的,就不开放了。

posted @ 2021-08-08 23:14  myEsn2E9  阅读(141)  评论(0编辑  收藏  举报