SpringBoot integrates RabbitMQ to achieve message sending and receiving

RabbitMQ is a message middleware that uses Erlang language to implement AMQP (Advanced Message Queuing Protocol, Advanced Message Queuing Protocol), which is used to store and forward messages in a distributed system. RabbitMQ is favored by more and more enterprises due to its high reliability, easy expansion, high availability and rich features.

[Example] SpringBoot integrates RabbitMQ to realize message sending and receiving.

Example requirements:

  1. Realize SpringBoot integration RabbitMQ framework.
  2. Implement RabbitMQ message acknowledgement mechanism (ACK).
  3. Implement RabbitMQ message queue delay function.
  4. Implement RabbitMQ retry mechanism.
  5. Realize sending and receiving JSON, Map format data.

Related technologies:

"RabbitMQ exchange type and message sending/receiving mode"

"RabbitMQ message acknowledgement mechanism (ACK)"

"RabbitMQ realizes message queue delay function"

"RabbitMQ Retry Mechanism"

"RabbitMQ realizes the sending and receiving of JSON and Map format data"

"SpringBoot integrates RabbitMQ to achieve message sending and receiving"

 

1. The message sender

The message sender implements message sending and message confirmation functions (exchanger confirmation, queue confirmation).

1.1 Create a project

Create the first SpringBoot project (RabbitmqProvider messaging project) and integrate the RabbitMQ framework. The project structure is as follows:

In the pom.xml configuration information file, add related dependent files:

<!-- AMQP客户端 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

Configure the RabbitMQ service in the application.yml configuration file:

spring:
  # 项目名称
  application:
    name: rabbitmq-provider
  # RabbitMQ服务配置
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    # 消息确认(ACK)
    publisher-confirm-type: correlated #确认消息已发送到交换机(Exchange)
    publisher-returns: true #确认消息已发送到队列(Queue)

1.2 Configuration class (Config layer)

In the com.pjb.config package, create a RabbitMqConfig class (RabbitMQ configuration class), the code is as follows:

package com.pjb.config;

import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * RabbitMQ配置类
 * @author pan_junbiao
 **/
@Configuration
public class RabbitMqConfig
{
    public static final String DIRECT_QUEUE = "direct_queue"; //Direct队列名称
    public static final String DIRECT_EXCHANGE = "direct_exchange"; //交换器名称
    public static final String DIRECT_ROUTING_KEY = "direct_routing_key"; //路由键

    public static final String DELAY_QUEUE = "delay_queue"; //延时队列名称
    public static final String DELAY_EXCHANGE = "delay_exchange"; //交换器名称
    public static final String DELAY_ROUTING_KEY = "delay_routing_key"; //路由键

    @Autowired
    private CachingConnectionFactory connectionFactory;

    @Bean
    public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory)
    {
        RabbitTemplate rabbitTemplate = new RabbitTemplate();
        rabbitTemplate.setConnectionFactory(connectionFactory);

        //设置Json转换器
        rabbitTemplate.setMessageConverter(jsonMessageConverter());

        //设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数
        rabbitTemplate.setMandatory(true);

        //确认消息送到交换机(Exchange)回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback()
        {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause)
            {
                System.out.println("\n确认消息送到交换机(Exchange)结果:");
                System.out.println("相关数据:" + correlationData);
                System.out.println("是否成功:" + ack);
                System.out.println("错误原因:" + cause);
            }
        });

        //确认消息送到队列(Queue)回调
        rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback()
        {
            @Override
            public void returnedMessage(ReturnedMessage returnedMessage)
            {
                System.out.println("\n确认消息送到队列(Queue)结果:");
                System.out.println("发生消息:" + returnedMessage.getMessage());
                System.out.println("回应码:" + returnedMessage.getReplyCode());
                System.out.println("回应信息:" + returnedMessage.getReplyText());
                System.out.println("交换机:" + returnedMessage.getExchange());
                System.out.println("路由键:" + returnedMessage.getRoutingKey());
            }
        });
        return rabbitTemplate;
    }

    /**
     * Json转换器
     */
    @Bean
    public Jackson2JsonMessageConverter jsonMessageConverter()
    {
        return new Jackson2JsonMessageConverter();
    }

    /**
     * Direct交换器
     */
    @Bean
    public DirectExchange directExchange()
    {
        /**
         * 创建交换器,参数说明:
         * String name:交换器名称
         * boolean durable:设置是否持久化,默认是 false。durable 设置为 true 表示持久化,反之是非持久化。
         * 持久化可以将交换器存盘,在服务器重启的时候不会丢失相关信息。
         * boolean autoDelete:设置是否自动删除,为 true 则设置队列为自动删除,
         */
        return new DirectExchange(DIRECT_EXCHANGE, true, false);
    }

    /**
     * 队列
     */
    @Bean
    public Queue directQueue()
    {
        /**
         * 创建队列,参数说明:
         * String name:队列名称。
         * boolean durable:设置是否持久化,默认是 false。durable 设置为 true 表示持久化,反之是非持久化。
         * 持久化的队列会存盘,在服务器重启的时候不会丢失相关信息。
         * boolean exclusive:设置是否排他,默认也是 false。为 true 则设置队列为排他。
         * boolean autoDelete:设置是否自动删除,为 true 则设置队列为自动删除,
         * 当没有生产者或者消费者使用此队列,该队列会自动删除。
         * Map<String, Object> arguments:设置队列的其他一些参数。
         */
        return new Queue(DIRECT_QUEUE, true, false, false, null);
    }

    /**
     * 绑定
     */
    @Bean
    Binding directBinding(DirectExchange directExchange, Queue directQueue)
    {
        //将队列和交换机绑定, 并设置用于匹配键:routingKey路由键
        return BindingBuilder.bind(directQueue).to(directExchange).with(DIRECT_ROUTING_KEY);
    }

    /******************************延时队列******************************/

    @Bean
    public CustomExchange delayExchange()
    {
        Map<String, Object> args = new HashMap<>();
        args.put("x-delayed-type", "direct");
        return new CustomExchange(DELAY_EXCHANGE, "x-delayed-message", true, false, args);
    }

    @Bean
    public Queue delayQueue()
    {
        Queue queue = new Queue(DELAY_QUEUE, true);
        return queue;
    }

    @Bean
    public Binding delaybinding(Queue delayQueue, CustomExchange delayExchange)
    {
        return BindingBuilder.bind(delayQueue).to(delayExchange).with(DELAY_ROUTING_KEY).noargs();
    }
}

1.3 Entity class (Entity layer)

In the com.pjb.entity package, create a UserInfo class (user information entity class).

package com.pjb.entity;

/**
 * 用户信息实体类
 * @author pan_junbiao
 **/
public class UserInfo
{
    private int userId; //用户编号
    private String userName; //用户姓名
    private String blogUrl; //博客地址
    private String blogRemark; //博客信息

    //省略getter与setter方法...
}

1.4 Message sending layer (Sender layer)

Create the UserSender interface (user message sending service interface) under the com.pjb.sender package.

package com.pjb.sender;

import com.pjb.entity.UserInfo;

import java.util.Map;

/**
 * 用户消息发送服务接口
 * @author pan_junbiao
 **/
public interface UserSender
{
    /**
     * 发送用户信息Json格式数据
     * @param userInfo 用户信息实体类
     */
    public void sendUserJson(UserInfo userInfo);

    /**
     * 延时发送用户信息Map格式数据
     * @param userMap 用户信息Map
     */
    public void sendDelayUserMap(Map userMap);
}

Under the com.pjb.sender.impl package, create the UserSenderImpl class (user messaging service class).

package com.pjb.sender.impl;

import com.pjb.config.RabbitMqConfig;
import com.pjb.entity.UserInfo;
import com.pjb.sender.UserSender;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

/**
 * 用户消息发送服务类
 * @author pan_junbiao
 **/
@Service
public class UserSenderImpl implements UserSender
{
    @Autowired
    RabbitTemplate rabbitTemplate;

    //时间格式
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * 发送用户信息Json格式数据
     * @param userInfo 用户信息实体类
     */
    @Override
    public void sendUserJson(UserInfo userInfo)
    {
        /**
         * 发送消息,参数说明:
         * String exchange:交换器名称。
         * String routingKey:路由键。
         * Object object:发送内容。
         */
        rabbitTemplate.convertAndSend(RabbitMqConfig.DIRECT_EXCHANGE, RabbitMqConfig.DIRECT_ROUTING_KEY, userInfo);

        System.out.println("Json格式数据消息发送成功,发送时间:" + dateFormat.format(new Date()));
    }

    /**
     * 延时发送用户信息Map格式数据
     * @param userMap 用户信息Map
     */
    @Override
    public void sendDelayUserMap(Map userMap)
    {
        rabbitTemplate.convertAndSend(RabbitMqConfig.DELAY_EXCHANGE, RabbitMqConfig.DELAY_ROUTING_KEY, userMap, new MessagePostProcessor()
        {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException
            {
                //消息延迟5秒
                message.getMessageProperties().setHeader("x-delay", 5000);
                return message;
            }
        });

        System.out.println("Map格式数据消息发送成功,发送时间:" + dateFormat.format(new Date()));
    }
}

 

2. The message receiver

The message receiving end realizes the function of message reception and message reception confirmation.

2.1 Create a project

Create a second SpringBoot project (RabbitmqConsumer message receiving project) and integrate the RabbitMQ framework. The project structure is as follows:

In the pom.xml configuration information file, add related dependent files:

<!-- AMQP客户端 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

Configure the RabbitMQ service in the application.yml configuration file:

spring:
  # 项目名称
  application:
    name: rabbitmq-consumer
  # RabbitMQ服务配置
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    listener:
      simple:
        # 重试机制
        retry:
          enabled: true #是否开启消费者重试
          max-attempts: 5 #最大重试次数
          initial-interval: 5000ms #重试间隔时间(单位毫秒)
          max-interval: 1200000ms #重试最大时间间隔(单位毫秒)
          multiplier: 2 #间隔时间乘子,间隔时间*乘子=下一次的间隔时间,最大不能超过设置的最大间隔时间

2.2 Configuration class (Config layer)

In the com.pjb.config package, create a RabbitMqConfig class (RabbitMQ configuration class), the code is as follows:

package com.pjb.config;

import com.pjb.receiver.impl.AckReceiver;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * RabbitMQ配置类
 * @author pan_junbiao
 **/
@Configuration
public class RabbitMqConfig
{
    public static final String DIRECT_QUEUE = "direct_queue"; //Direct队列名称
    public static final String DELAY_QUEUE = "delay_queue"; //延时队列名称

    /**
     * 消息接收确认处理类
     */
    @Autowired
    private AckReceiver ackReceiver;

    @Autowired
    private CachingConnectionFactory connectionFactory;

    /**
     * 客户端配置
     * 配置手动确认消息、消息接收确认
     */
    @Bean
    public SimpleMessageListenerContainer simpleMessageListenerContainer()
    {
        //消费者数量,默认10
        int DEFAULT_CONCURRENT = 10;

        //每个消费者获取最大投递数量 默认50
        int DEFAULT_PREFETCH_COUNT = 50;

        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
        container.setConcurrentConsumers(DEFAULT_CONCURRENT);
        container.setMaxConcurrentConsumers(DEFAULT_PREFETCH_COUNT);

        // RabbitMQ默认是自动确认,这里改为手动确认消息
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL);

        //添加队列,可添加多个队列
        container.addQueues(new Queue(DIRECT_QUEUE,true));
        container.addQueues(new Queue(DELAY_QUEUE,true));

        //设置消息处理类
        container.setMessageListener(ackReceiver);

        return container;
    }
}

2.3 Entity class (Entity layer)

In the com.pjb.entity package, create a UserInfo class (user information entity class).

package com.pjb.entity;

/**
 * 用户信息实体类
 * @author pan_junbiao
 **/
public class UserInfo
{
    private int userId; //用户编号
    private String userName; //用户姓名
    private String blogUrl; //博客地址
    private String blogRemark; //博客信息

    //省略getter与setter方法...
}

1.4 Message receiving layer (Receiver layer)

Under the com.pjb.receiver package, create a UserReceiver interface (user message receiving interface).

package com.pjb.receiver;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;

/**
 * 用户消息接收接口
 * @author pan_junbiao
 **/
public interface UserReceiver
{
    /**
     * 接收用户信息Json格式数据
     */
    public void receiverUserJson(Message message, Channel channel) throws Exception;

    /**
     * 延时接收用户信息Map格式数据
     */
    public void receiverDelayUserMap(Message message, Channel channel) throws Exception;
}

Under the com.pjb.receiver.impl package, create the UserReceiverImpl class (user message receiving class).

package com.pjb.receiver.impl;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pjb.entity.UserInfo;
import com.pjb.receiver.UserReceiver;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.stereotype.Service;

import java.util.Map;

/**
 * 用户消息接收类
 * @author pan_junbiao
 **/
@Service
public class UserReceiverImpl implements UserReceiver
{
    /**
     * 接收用户信息Json格式数据
     */
    @Override
    public void receiverUserJson(Message message, Channel channel) throws Exception
    {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try
        {
            //将JSON格式数据转换为实体对象
            ObjectMapper mapper = new ObjectMapper();
            UserInfo userInfo = mapper.readValue(message.getBody(), UserInfo.class);

            System.out.println("接收者收到JSON格式消息:");
            System.out.println("用户编号:" + userInfo.getUserId());
            System.out.println("用户名称:" + userInfo.getUserName());
            System.out.println("博客地址:" + userInfo.getBlogUrl());
            System.out.println("博客信息:" + userInfo.getBlogRemark());

            /**
             * 确认消息,参数说明:
             * long deliveryTag:唯一标识 ID。
             * boolean multiple:是否批处理,当该参数为 true 时,
             * 则可以一次性确认 deliveryTag 小于等于传入值的所有消息。
             */
            channel.basicAck(deliveryTag, true);

            /**
             * 否定消息,参数说明:
             * long deliveryTag:唯一标识 ID。
             * boolean multiple:是否批处理,当该参数为 true 时,
             * 则可以一次性确认 deliveryTag 小于等于传入值的所有消息。
             * boolean requeue:如果 requeue 参数设置为 true,
             * 则 RabbitMQ 会重新将这条消息存入队列,以便发送给下一个订阅的消费者;
             * 如果 requeue 参数设置为 false,则 RabbitMQ 立即会还把消息从队列中移除,
             * 而不会把它发送给新的消费者。
             */
            //channel.basicNack(deliveryTag, true, false);
        }
        catch (Exception e)
        {
            /**
             * 拒绝消息,参数说明:
             * long deliveryTag:唯一标识 ID。
             * boolean requeue:如果 requeue 参数设置为 true,
             * 则 RabbitMQ 会重新将这条消息存入队列,以便发送给下一个订阅的消费者;
             * 如果 requeue 参数设置为 false,则 RabbitMQ 立即会还把消息从队列中移除,
             * 而不会把它发送给新的消费者。
             */
            channel.basicReject(deliveryTag, false);

            e.printStackTrace();
        }
    }

    /**
     * 延时接收用户信息Map格式数据
     */
    @Override
    public void receiverDelayUserMap(Message message, Channel channel) throws Exception
    {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try
        {
            //将JSON格式数据转换为Map对象
            ObjectMapper mapper = new ObjectMapper();
            JavaType javaType = mapper.getTypeFactory().constructMapType(Map.class, String.class, Object.class);
            Map<String, Object> resultMap = mapper.readValue(message.getBody(),javaType);

            System.out.println("接收者收到Map格式消息:");
            System.out.println("用户编号:" + resultMap.get("userId"));
            System.out.println("用户名称:" + resultMap.get("userName"));
            System.out.println("博客地址:" + resultMap.get("blogUrl"));
            System.out.println("博客信息:" + resultMap.get("userRemark"));

            //确认消息
            channel.basicAck(deliveryTag, true);

            //否定消息
            //channel.basicNack(deliveryTag, true, false);
        }
        catch (Exception e)
        {
            //拒绝消息
            channel.basicReject(deliveryTag, false);

            e.printStackTrace();
        }
    }
}

Under the com.pjb.receiver.impl package, create the AckReceiver class (message reception confirmation processing class).

package com.pjb.receiver.impl;

import com.pjb.config.RabbitMqConfig;
import com.pjb.receiver.UserReceiver;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 消息接收确认处理类
 * 所有的消息,都由该类接收
 * @author pan_junbiao
 **/
@Service
public class AckReceiver implements ChannelAwareMessageListener
{
    /**
     * 用户消息接收类
     */
    @Autowired
    private UserReceiver userReceiver;

    @Override
    public void onMessage(Message message, Channel channel) throws Exception
    {
        //时间格式
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("消息接收成功,接收时间:" + dateFormat.format(new Date()) + "\n");

        //获取队列名称
        String queueName = message.getMessageProperties().getConsumerQueue();

        //接收用户信息Json格式数据
        if (queueName.equals(RabbitMqConfig.DIRECT_QUEUE))
        {
            userReceiver.receiverUserJson(message, channel);
        }

        //延时接收用户信息Map格式数据
        if (queueName.equals(RabbitMqConfig.DELAY_QUEUE))
        {
            userReceiver.receiverDelayUserMap(message, channel);
        }

        //多个队列的处理,则如上述代码,继续添加方法....
    }
}

 

3. Run the test

After completing the above two project codes, you can run the test. First start the second SpringBoot project (RabbitmqConsumer message receiving project) and let it run all the time.

3.1 Sending and receiving of message queue

In the first SpringBoot project (RabbitmqProvider messaging project), create a test class and write a method to send user information in Json format.

package com.pjb.sender;

import com.pjb.entity.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.HashMap;
import java.util.Map;

/**
 * 用户消息发送测试类
 * @author pan_junbiao
 **/
@SpringBootTest
public class UserSenderTest
{
    /**
     * 用户消息发送类
     */
    @Autowired
    private UserSender userSender;

    /**
     * 发送用户信息Json格式数据
     */
    @Test
    public void sendUserJson() throws Exception
    {
        //创建用户信息
        UserInfo userInfo = new UserInfo();
        userInfo.setUserId(1);
        userInfo.setUserName("pan_junbiao的博客");
        userInfo.setBlogUrl("https://blog.csdn.net/pan_junbiao");
        userInfo.setBlogRemark("您好,欢迎访问 pan_junbiao的博客");

        //执行发送
        userSender.sendUserJson(userInfo);

        //由于这里使用的是测试方法,当测试方法结束,RabbitMQ相关的资源也就关闭了,
        //会导致消息确认的回调出现问题,所有加段延时,该延时与业务无关
        Thread.sleep(2000);
    }
}

The execution result of the sender:

Receiving end execution result:

3.2 Delay function of message queue

Write code to test the delay function of sending user information Map format data and message queue.

/**
 * 用户消息发送类
 */
@Autowired
private UserSender userSender;

/**
 * 延时发送用户信息Map格式数据
 */
@Test
public void sendDelayUserMap() throws Exception
{
    //创建用户信息Map
    Map<String, Object> userMap = new HashMap<>();
    userMap.put("userId", "1");
    userMap.put("userName", "pan_junbiao的博客");
    userMap.put("blogUrl", "https://blog.csdn.net/pan_junbiao");
    userMap.put("userRemark", "您好,欢迎访问 pan_junbiao的博客");

    //执行发送
    userSender.sendDelayUserMap(userMap);

    //由于这里使用的是测试方法,当测试方法结束,RabbitMQ相关的资源也就关闭了,
    //会导致消息确认的回调出现问题,所有加段延时,该延时与业务无关
    Thread.sleep(2000);
}

The execution result of the sender:

Receiving end execution result:

Explanation of results:

In code 1.4, the delay of the message is set to 5 seconds. As can be seen from the above figure, the message sending time and the message receiving time are exactly 5 seconds apart, indicating that the delay function of the RabbitMQ message queue is set successfully.

 

Guess you like

Origin blog.csdn.net/pan_junbiao/article/details/113560849