Chapter 43: TopicExchange Distributed Message Consumption Based on SpringBoot & RabbitMQ

In the previous two chapters, Chapter 41: Completing DirectExchange Distributed Message Consumption Based on SpringBoot & RabbitMQ , Chapter 42: Completing DirectExchange Distributed Message Multi-Consumer Consumption Based on SpringBoot & RabbitMQ Improves the exchange type of RabbitMQmessage queues There are three types of exchanges that are commonly used in DirectExchangeour previous chapters RabbitMQ. Let's take a look at the TopicExchangetopic exchange types today.

Objectives of this chapter

The exchange of message types done based on the SpringBootplatform .RabbitMQTopicExchange

SpringBoot enterprise-level core technology learning topic

Topic Topic name Thematic description
001 Spring Boot core technology Explain some core components of SpringBoot at the enterprise level
002 Spring Boot core technology chapter source code Each article in the Spring Boot core technology brief book corresponds to the source code of the code cloud
003 Spring Cloud core technology Comprehensive explanation of the core technologies of Spring Cloud
004 Spring Cloud core technology chapter source code Each article in the Spring Cloud core technology brief book corresponds to the source code
005 QueryDSL core technology Comprehensively explain the core technology of QueryDSL and integrate SpringDataJPA based on SpringBoot
006 SpringDataJPA core technology Comprehensive explanation of the core technology of Spring Data JPA

Solve the problem

The teenager also encountered a problem before. After classifying multiple modules, the message queue cannot be automatically created. It is funny to say that I didn’t have time to look at this problem before. When I wrote this article today, I found that the reason was that the configuration class SpringBootin the module was not scanned . common. Make my head big~~~, we can automatically create a queue by XxxApplicationadding it to the startup class ! @ComponentScan(value = "com.hengyu.rabbitmq")! !

Build the project

When building the project in this chapter, the design is also carried out in a multi-module way. You can see the effect of message processing well. Because it is a multi-module project, let's create a SpringBootproject first. The pom.xmlconfiguration file dependency configuration is as follows:

<dependencies>
        <!--rabbbitMQ相关依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <!--web相关依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--lombok依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--spring boot tester-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--fast json依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.40</version>
        </dependency>
    </dependencies>

Next, let's build public RabbitMQmodules first, because our consumers and producers need RabbitMQrelevant configuration information, here we can extract them and use them for reference between modules.

rabbitmq-topic-common

Create a submodule ,rabbitmq-topic-common add a configuration file and configure the relevant dependency configuration below, as follows:resourcesapplication.ymlRabbitMQ

spring:
  #rabbitmq消息队列配置信息
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    publisher-confirms: true
Define exchange configuration information

Like the previous chapter, we independently write an enumeration type to configure the exchange information of the message queue, as follows:

/**
 * rabbitmq交换配置枚举
 * ========================
 *
 * @author 恒宇少年
 * Created with IntelliJ IDEA.
 * Date:2017/11/26
 * Time:13:56
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@Getter
public enum ExchangeEnum
{
    /**
     * 用户注册交换配置枚举
     */
    USER_REGISTER_TOPIC_EXCHANGE("register.topic.exchange")
    ;
    private String name;

    ExchangeEnum(String name) {
        this.name = name;
    }
}
Define queue configuration information

The basic information configuration of the same message queue is also configured in the form of enumeration, as shown below:

/**
 * 队列配置枚举
 * ========================
 *
 * @author 恒宇少年
 * Created with IntelliJ IDEA.
 * Date:2017/11/26
 * Time:14:05
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@Getter
public enum QueueEnum
{
    /**
     * 用户注册
     * 创建账户消息队列
     */
    USER_REGISTER_CREATE_ACCOUNT("register.account","register.#"),
    /**
     * 用户注册
     * 发送注册成功邮件消息队列
     */
    USER_REGISTER_SEND_MAIL("register.mail","register.#")
    ;
    /**
     * 队列名称
     */
    private String name;
    /**
     * 队列路由键
     */
    private String routingKey;

    QueueEnum(String name, String routingKey) {
        this.name = name;
        this.routingKey = routingKey;
    }
}

Two attributes have been added to the message queue enumeration, corresponding to , respectively 队列名称. 队列路由The type of message queue explained in this chapter TopicExchangecan configure multiple message consumers according to the path information, and the forwarding matching rule information is the routing information of the queue we defined. .

Define routing information for sending messages

When we send a message to the queue, we need to pass a routing-related configuration information, which RabbitMQwill be matched according to the message routing rule information when sending and the routing information when defining the message queue. If it can match, the consumer of the queue will be called to complete the message. The configuration of the consumption and sending message routing information is as follows:

/**
 * 消息队列topic交换路由key配置枚举
 * ========================
 *
 * @author 恒宇少年
 * Created with IntelliJ IDEA.
 * Date:2017/12/11
 * Time:21:58
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@Getter
public enum TopicEnum {
    /**
     * 用户注册topic路由key配置
     */
    USER_REGISTER("register.user")
    ;

    private String topicRouteKey;

    TopicEnum(String topicRouteKey) {
        this.topicRouteKey = topicRouteKey;
    }
}
routing special characters#

There is a special symbol when we configure it QueueEnuminternally : , when routing configuration in the message queue, it means that zero or more characters can be matched, and what we define in the enumeration can match the routing rules of the queue defined by the enumeration . Of course, if the routing is passed when sending a message: it is also a routing rule that can be matched in the same way.路由键#RabbitMQ#TopicEnumregister.userQueueEnumregister.#
register.user.accountregister.#

routing special characters*

In addition to this, there is one more commonly used special character , which means that a character can be matched when routing configuration *in the RabbitMQmessage queue . We define that if the routing key is modified to , the message can be received when the routing key is sent. But if the route at the time of sending is time , then the message cannot be matched.*QueueEnumregister.*register.userregister.user.account

message queue configuration

The configuration preparations have been completed, and then we start to configure the queue-related content. As before, we need to configure Queue, Exchange, Bindingand bind the message queue to the exchange. Let's take a look at the differences between the configuration and the previous chapters. The code is as follows:

/**
 * 用户注册消息队列配置
 * ========================
 * @author 恒宇少年
 * Created with IntelliJ IDEA.
 * Date:2017/11/26
 * Time:16:58
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@Configuration
public class UserRegisterQueueConfiguration {

    private Logger logger = LoggerFactory.getLogger(UserRegisterQueueConfiguration.class);
    /**
     * 配置用户注册主题交换
     * @return
     */
    @Bean
    public TopicExchange userTopicExchange()
    {
        TopicExchange topicExchange = new TopicExchange(ExchangeEnum.USER_REGISTER_TOPIC_EXCHANGE.getName());
        logger.info("用户注册交换实例化成功。");
        return topicExchange;
    }

    /**
     * 配置用户注册
     * 发送激活邮件消息队列
     * 并设置持久化队列
     * @return
     */
    @Bean
    public Queue sendRegisterMailQueue()
    {
        Queue queue = new Queue(QueueEnum.USER_REGISTER_SEND_MAIL.getName());
        logger.info("创建用户注册消息队列成功");
        return queue;
    }

    /**
     * 配置用户注册
     * 创建账户消息队列
     * 并设置持久化队列
     * @return
     */
    @Bean
    public Queue createAccountQueue()
    {
        Queue queue = new Queue(QueueEnum.USER_REGISTER_CREATE_ACCOUNT.getName());
        logger.info("创建用户注册账号队列成功.");
        return queue;
    }

    /**
     * 绑定用户发送注册激活邮件队列到用户注册主题交换配置
     * @return
     */
    @Bean
    public Binding sendMailBinding(TopicExchange userTopicExchange,Queue sendRegisterMailQueue)
    {
        Binding binding = BindingBuilder.bind(sendRegisterMailQueue).to(userTopicExchange).with(QueueEnum.USER_REGISTER_SEND_MAIL.getRoutingKey());
        logger.info("绑定发送邮件到注册交换成功");
        return binding;
    }

    /**
     * 绑定用户创建账户到用户注册主题交换配置
     * @return
     */
    @Bean
    public Binding createAccountBinding(TopicExchange userTopicExchange,Queue createAccountQueue)
    {
        Binding binding = BindingBuilder.bind(createAccountQueue).to(userTopicExchange).with(QueueEnum.USER_REGISTER_CREATE_ACCOUNT.getRoutingKey());
        logger.info("绑定创建账号到注册交换成功。");
        return binding;
    }
}

Let's start with the analysis above.
Step 1: First we create the TopicExchangemessage queue object, using ExchangeEnumthe type in the enumeration USER_REGISTER_TOPIC_EXCHANGEas the exchange name.

Step 2: We created the queue to send the registration email sendRegisterMailQueue, using QueueEnumthe type in the enumeration USER_REGISTER_SEND_MAILas the queue name.

Step 3: Consistent with the sending mail queue, the account information needs to be initialized after the user is created, and the createAccountQueuesubsequent logic of the message queue is to complete the work, using QueueEnumthe enumeration in the USER_REGISTER_CREATE_ACCOUNTenumeration as the name of the account creation queue.

Step 4: The exchange and queue have been created in the above steps, and now the queue is bound to the user registration exchange, so as to realize the message consumption of the registered user message queue and the sendMailBindingbound configuration information.QueueEnum.USER_REGISTER_SEND_MAILRoutingKey

createAccountBindingBinding configuration information QueueEnum.USER_REGISTER_CREATE_ACCOUNT.RoutingKey

So far we have completed rabbitmq-topic-commonall the configuration information of the module, let's start to write the user registration message consumer module.

rabbitmq-topic-consumer

Let's first create a submodule named and add a reference to the module rabbitmq-topic-consumerin the pom.xmlconfiguration file rabbitmq-topic-common, as follows:

....//
<dependencies>
        <!--公共模块依赖-->
        <dependency>
            <groupId>com.hengyu</groupId>
            <artifactId>rabbitmq-topic-common</artifactId>
            <version>${parent.version}</version>
        </dependency>
    </dependencies>
....//
consumer program entry

Next, let's create the program startup class RabbitMqTopicConsumerApplication. It should be noted here that the scan path is manually configured . The @ComponentScanstartup class code is as follows:

/**
 * 消息消费者程序启动入口
 * ========================
 *
 * @author 恒宇少年
 * Created with IntelliJ IDEA.
 * Date:2017/12/11
 * Time:21:48
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@SpringBootApplication
@ComponentScan(value = "com.hengyu.rabbitmq")
public class RabbitMqTopicConsumerApplication {

    /**
     * logback
     */
    private static Logger logger = LoggerFactory.getLogger(RabbitMqTopicConsumerApplication.class);

    /**
     * 程序入口
     * @param args
     */
    public static void main(String[] args)
    {
        SpringApplication.run(RabbitMqTopicConsumerApplication.class,args);

        logger.info("【【【【【Topic队列消息Consumer启动成功】】】】】");
    }
}

The manual configuration scan path was explained at the beginning of the article. The main purpose is to scan RabbitMQConfigurationthe information in the configuration class, so that the RabbitAdminconfiguration information can be automatically created to the serverend.

send mail consumer

Send mail messages to monitor register.mailmessage queue information, as shown below:

/**
 * 发送用户注册成功邮件消费者
 * ========================
 *
 * @author 恒宇少年
 * Created with IntelliJ IDEA.
 * Date:2017/12/11
 * Time:22:07
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@Component
@RabbitListener(queues = "register.mail")
public class SendMailConsumer
{

    /**
     * logback
     */
    Logger logger = LoggerFactory.getLogger(SendMailConsumer.class);

    /**
     * 处理消息
     * 发送用户注册成功邮件
     * @param userId 用户编号
     */
    @RabbitHandler
    public void handler(String userId)
    {

        logger.info("用户:{},注册成功,自动发送注册成功邮件.",userId);

        //... 发送注册成功邮件逻辑
    }
}

Here I just completed the monitoring of the message, and the specific business logic can be processed according to the requirements.

Create Account Consumer

Create a user account information consumer listening queue register.account, the code is as follows:

/**
 * ========================
 *
 * @author 恒宇少年
 * Created with IntelliJ IDEA.
 * Date:2017/12/11
 * Time:22:04
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@Component
@RabbitListener(queues = "register.account")
public class CreateAccountConsumer {

    /**
     * logback
     */
    Logger logger = LoggerFactory.getLogger(CreateAccountConsumer.class);

    /**
     * 处理消息
     * 创建用户账户
     * @param userId 用户编号
     */
    @RabbitHandler
    public void handler(String userId)
    {
        logger.info("用户:{},注册成功,自动创建账户信息.",userId);

        //... 创建账户逻辑
    }
}

Account creation and account initialization logic can be handlerprocessed in the method. This chapter does not do complex database processing, so there is not too much logic processing in the consumer business.

rabbitmq-topic-provider

Next is the module writing of our message provider. We still create the program entry class first and add the scan configuration @ComponentScanpath. The code is as follows:

/**
 * 消息生产者程序启动入口
 * ========================
 *
 * @author 恒宇少年
 * Created with IntelliJ IDEA.
 * Date:2017/12/11
 * Time:21:48
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@SpringBootApplication
@ComponentScan(value = "com.hengyu.rabbitmq")
public class RabbitMqTopicProviderApplication {

    /**
     * logback
     */
    private static Logger logger = LoggerFactory.getLogger(RabbitMqTopicProviderApplication.class);

    /**
     * 程序入口
     * @param args
     */
    public static void main(String[] args)
    {
        SpringApplication.run(RabbitMqTopicProviderApplication.class,args);

        logger.info("【【【【【Topic队列消息Provider启动成功】】】】】");
    }
}
Define the message sending interface

Create a QueueMessageServicequeue message sending interface and add sendmethods as follows:

/**
 * 消息队列业务
 * ========================
 *
 * @author 恒宇少年
 * Created with IntelliJ IDEA.
 * Date:2017/11/26
 * Time:14:50
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
public interface QueueMessageService
{
    /**
     * 发送消息到rabbitmq消息队列
     * @param message 消息内容
     * @param exchangeEnum 交换配置枚举
     * @param routingKey 路由key
     * @throws Exception
     */
    public void send(Object message, ExchangeEnum exchangeEnum, String routingKey) throws Exception;
}

sendThere are three parameters in the method, and the analysis is as follows:
- message: the content of the sent message, which can be of any type, of course, only java.lang.String in this chapter.
- exchangeEnum: Our custom exchange enumeration type, which is convenient for sending messages to the specified exchange.
- routingKey: The content of the routing key when sending a message. This value takes the value in the TopicEnumenumeration topicRouteKeyas the parameter value.

Let's take a look at the method implementation in the implementation class QueueMessageServiceSupportof this interface send, as follows:

/**
 * 消息队列业务逻辑实现
 * ========================
 *
 * @author 恒宇少年
 * Created with IntelliJ IDEA.
 * Date:2017/11/26
 * Time:14:52
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@Component
public class QueueMessageServiceSupport
    implements QueueMessageService
{
    /**
     * 消息队列模板
     */
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public void send(Object message, ExchangeEnum exchangeEnum, String routingKey) throws Exception {
        //发送消息到消息队列
        rabbitTemplate.convertAndSend(exchangeEnum.getName(),routingKey,message);
    }
}

We convert the object type into a string and send it to the message queue server through RabbitTemplatethe method of instance. After receiving the message, we forward the message according to the registered consumers and filter the routing rules, and realize the consumption of the message.convertAndSendJSONRabbitMQ

run the test

To facilitate testing we create an UserServiceimplementation class named as follows:

/**
 * 用户业务逻辑
 * ========================
 *
 * @author 恒宇少年
 * Created with IntelliJ IDEA.
 * Date:2017/12/11
 * Time:22:10
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@Service
public class UserService
{
    /**
     * 消息队列发送业务逻辑
     */
    @Autowired
    private QueueMessageService queueMessageService;

    /**
     * 随机创建用户
     * 随机生成用户uuid编号,发送到消息队列服务端
     * @return
     * @throws Exception
     */
    public String randomCreateUser() throws Exception
    {
        //用户编号
        String userId = UUID.randomUUID().toString();
        //发送消息到rabbitmq服务端
        queueMessageService.send(userId, ExchangeEnum.USER_REGISTER_TOPIC_EXCHANGE, TopicEnum.USER_REGISTER.getTopicRouteKey());
        return userId;
    }
}

randomCreateUserA method named Randomly Create User is added to this class, and the UUIDrandomly generated string is passed as the user's number to the user registration message queue to complete the simulated creation of the user.

Write test cases

Next, we create a RabbitMqTestertest class to complete the random user creation message sending, the test case completes a simple UserServiceinjection, and calls the randomCreateUsermethod, as shown below:

/**
 * ========================
 *
 * @author 恒宇少年
 * Created with IntelliJ IDEA.
 * Date:2017/12/11
 * Time:22:10
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RabbitMqTopicProviderApplication.class)
public class RabbitMqTester
{
    /**
     * 用户业务逻辑
     */
    @Autowired
   private UserService userService;

    /**
     * 模拟随机创建用户 & 发送消息到注册用户消息队列
     * @throws Exception
     */
    @Test
    public void testTopicMessage() throws Exception
    {
        userService.randomCreateUser();
    }
}

So far, our coding is complete, let's start the test by following the steps below:

  1. Start the rabbitmq-topic-consumermessage consumer module and check whether the console output is normal
  2. run rabbitmq-topic-providermodule test case methodtestTopicMessage
  3. View rabbitmq-topic-consumerconsole output

final effect:


2017-12-30 18:39:16.819  INFO 2781 --- [           main] c.h.r.c.RabbitMqTopicConsumerApplication : 【【【【【Topic队列消息Consumer启动成功】】】】】
2017-12-30 18:39:29.376  INFO 2781 --- [cTaskExecutor-1] c.h.r.consumer.CreateAccountConsumer     : 用户:c6ef682d-da2e-4cac-a004-c244ff4c4503,注册成功,自动创建账户信息.
2017-12-30 18:39:29.376  INFO 2781 --- [cTaskExecutor-1] c.h.rabbitmq.consumer.SendMailConsumer   : 用户:c6ef682d-da2e-4cac-a004-c244ff4c4503,注册成功,自动发送注册成功邮件.

Summarize

This chapter mainly explains how the TopicExchangeexchange type consumes queue messages, explains the commonly used special characters #and *how to match them, and solves the problem that the queue configuration information under multiple modules cannot be automatically created. Another point to note is that TopicExchangethe exchange type does not have a fixed order of message consumption! ! !

The source code of this chapter has been uploaded to the code cloud:
SpringBoot supporting source code address: https://gitee.com/hengboy/spring-boot-chapter
SpringCloud supporting source code address: https://gitee.com/hengboy/spring-cloud-chapter
SpringBoot related For the series of articles, please visit: Catalog: SpringBoot Learning Catalogue
QueryDSL-related series of articles, please visit: QueryDSL General Query Framework Learning Catalogue
SpringDataJPA-related series of articles, please visit: Catalog: SpringDataJPA Learning Catalogue , thank you for reading!
Welcome to join the QQ technical exchange group and make progress together.
QQ technical exchange group

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324711009&siteId=291194637