How can RocketMQ of "springcloud 2021 series" quickly implement the micro service message mechanism

Posted by lauxanh on Tue, 15 Feb 2022 05:06:41 +0100

Introduction to RocketMQ

For detailed explanation, you can view the following documents: rocketmq Basics

RocketMQ It is an open source distributed messaging system, based on high availability distributed cluster technology, which provides low latency and highly reliable message publishing and subscription services. At the same time, it is widely used in many fields, including asynchronous communication decoupling, enterprise solutions, financial payment, telecommunications, e-commerce, express logistics, advertising marketing, social networking, instant messaging, mobile applications, mobile games, video, Internet of things, Internet of vehicles, etc.

It has the following characteristics:

  • It can ensure strict message order
  • Provide rich message pull mode
  • Efficient subscriber horizontal scalability
  • Real time message subscription mechanism
  • 100 million message accumulation capacity

How to select a RocketMQ implementation

Spring Cloud Stream

Spring Cloud Stream Is a framework for building message based microservice applications Spring Integration Connect with Broker.

Generally speaking, Message Queuing Middleware has a Broker Server (proxy server), a message relay role, which is responsible for storing and forwarding messages

For example, in RocketMQ, the Broker is responsible for receiving and storing messages sent from the producer and preparing for the pull request of the consumer. In addition, the Broker also stores metadata related to messages, including consumer groups, consumption progress offsets, topics and queue messages

Spring Cloud Stream provides a unified abstraction of message oriented middleware and introduces the unified concepts of publish subscribe, consumer groups and partition.

There are two internal concepts: cloud Binding and spring Binding

  • Binder, a component integrated with message oriented middleware, is used to create a corresponding Binding. Each message oriented middleware has its own binder implementation

    • Kafka implements KafkaMessageChannelBinder
    • RabbitMQ implements RabbitMessageChannelBinder
    • RocketMQ implements RocketMQMessageChannelBinder
  • Binding, including Input Binding and Output Binding. Binding provides a bridge between the message oriented middleware and the Provider and Consumer provided by the application, which enables the developer to only use the Provider or Consumer of the application to produce or consume data, and shields the contact between the developer and the underlying message oriented middleware

RocketMQ basic usage

This project demonstrates how to use RocketMQ Binder to subscribe and publish Spring Cloud application messages.

Source address: https://github.com/langyastudio/langya-tech/tree/master/spring-cloud

Dependent environment

Introduce Spring Cloud Alibaba RocketMQ related dependencies

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
</dependency>

Producer producer

configuration file

server:
  port: 28081

spring:
  application:
    name: rocketmq-produce

  cloud:
    stream:
      # Corresponding BindingProperties Map
      bindings:
        output-common:
          #RocketMQ Topic
          destination: topic-common-01
          content-type: application/json
        output-tx:
          destination: topic-tx-01
          content-type: application/json

      # Spring Cloud Stream RocketMQ configuration item
      rocketmq:
        # Corresponding RocketMQBinderConfigurationProperties class
        binder:
          name-server: 192.168.123.22:9876

        # RocketMQ custom Binding configuration item, corresponding to RocketMQBindingProperties Map
        bindings:
          output-common:
            # RocketMQ Producer configuration item, corresponding to RocketMQProducerProperties class
            producer:
              #Producer grouping
              group: group-common
              #Send messages synchronously
              sync: true
              #Maximum bytes
              maxMessageSize: 8249344
              #Timeout
              sendMessageTimeout: 3000
          tx-output:
            producer:
              #group name
              group: group-tx
              #Send transaction message
              transactional: true

logging:
  level:
    com:
      alibaba:
        cloud:
          stream:
            binder:
              rocketmq: DEBUG
  • spring.cloud.stream.bindings is the Binding configuration item

    Binding can be divided into two types: Input and Output, but it cannot be reflected in the configuration item. Instead, it will be distinguished with @ Input or @Output annotation later

  • spring.cloud.stream.rocketmq.binder is the configuration item of RocketMQ Binder.

    Name server: RocketMQ Namesrv address. The name service acts as a provider for routing messages. Producers or consumers can find the corresponding Broker IP list of each topic through the name service. Multiple Namesrv instances form a cluster, but they are independent of each other without information exchange.

@Output send message

public interface OutputSource
{
    @Output("output-common")
    MessageChannel sendCommon();

    @Output("output-tx")
    MessageChannel sendTx();
}

Through the @ Output annotation, an Output Binding named Output common is declared. Note that the name should be the same as spring. Com in the configuration file cloud. stream. The bindings configuration item corresponds to the

At the same time, the return result of the @ Output annotation method is of MessageChannel type, which can be used to send messages.

SenderService message sending service

The logic of Message sending is realized by assembling the Message body and calling the OutputSource interface

@Service
public class SenderService
{
    @Autowired
    private OutputSource source;

    /**
     * send message
     *
     * @param msg Message content
     * @param tag label
     * @param delay Set the consumption delay level to x seconds
     * @return
     * @throws Exception
     */
    public <T> boolean sendObject(T msg, String tag, Integer delay) throws Exception
    {
        Message<T> message = MessageBuilder.withPayload(msg)
                .setHeader(MessageConst.PROPERTY_TAGS, tag)
                .setHeader(MessageConst.PROPERTY_DELAY_TIME_LEVEL, delay)
                .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
                .build();

        return source.sendCommon().send(message);
    }

    public <T> boolean sendTransactionalMsg(T msg, int num) throws Exception
    {
        Message<T> message  = MessageBuilder.withPayload(msg)
                .setHeader("tx-state", String.valueOf(num))
                .setHeader(MessageConst.PROPERTY_TAGS, "binder")
                .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
                .build();

        return source.sendTx().send(message);
    }
}

Controller interface definition

Provides an HTTP interface for sending messages. The code is as follows:

@RestController
@RequestMapping("/produce")
public class ProduceController
{
    @Autowired
    private SenderService senderService;

    @GetMapping("/send1")
    public boolean output1(@RequestParam("msg") String msg) throws Exception
    {
        int msgId = new SecureRandom().nextInt();
        return senderService.sendObject(new FooMsg(msgId, msg), "tagObj", 0);
    }

    @GetMapping("/send3")
    public boolean output3() throws Exception
    {
        // unknown message
        senderService.sendTransactionalMsg("transactional-msg1", 1);
        // rollback message
        senderService.sendTransactionalMsg("transactional-msg2", 2);
        // commit message
        senderService.sendTransactionalMsg("transactional-msg3", 3);

        return true;
    }
}

Application entry

Start the application. The code is as follows:

@SpringBootApplication
@EnableBinding({OutputSource.class})
public class RocketMQProduceApplication
{
    public static void main(String[] args)
    {
        SpringApplication.run(RocketMQProduceApplication.class, args);
    }
}

Use the @ EnableBinding annotation to declare that the specified interface enables the Binding function and scans its @ Input and @ Output annotations.

consumer

configuration file

server:
  # Random port, easy to start multiple consumers
  port: ${random.int[10000,19999]}

spring:
  application:
    name: rocketmq-consume

  cloud:
    # Spring Cloud Stream configuration item, corresponding to BindingServiceProperties class
    stream:
      # Binding configuration item, corresponding to BindingProperties Map
      bindings:
        input-common-1:
          # destination. RocketMQ Topic is used here
          destination: topic-common-01
          content-type: application/json
          ## Consumer grouping, naming rules: group+topic name + xx
          group: group-common-1
        input-common-2:
          destination: topic-common-01
          content-type: application/json
          consumer:
            concurrency: 20
            maxAttempts: 1
          group: group-common-2
        input-common-3:
          destination: topic-common-01
          content-type: application/json
          consumer:
            concurrency: 20
          group: group-common-3
        input-tx-1:
          destination: topic-tx-01
          content-type: text/json
          consumer:
            concurrency: 5
          group: group-tx-1

      # Spring Cloud Stream RocketMQ configuration item
      rocketmq:
        binder:
          name-server: 192.168.123.22:9876

        bindings:
          input-common-1:
            # RocketMQ Consumer configuration item
            consumer:
              # Whether to enable consumption. The default value is true
              enabled: true
              # Whether to use broadcast consumption. The default value is false. Use cluster consumption. If you want to use broadcast consumption, set the value to true
              broadcasting: false
              orderly: true
          input-common-2:
            consumer:
              orderly: false
          input-common-3:
            consumer:
              tags: tagObj

spring.cloud.stream.rocketmq.bindings

  • Enabled: whether to enable consumption. The default value is true. During daily development, if you do not want to consume in the local environment, you can turn it off by setting enabled to false

  • broadcasting: whether to use broadcast consumption. The default value is false. Cluster consumption is used.

    Clustering: in the cluster consumption mode, each Consumer instance of the same Consumer Group allocates messages equally

    Broadcast consumption: in broadcast consumption mode, each Consumer instance of the same Consumer Group receives full messages

@Input receive message

public interface InputBinding
{
    @Input("input-common-1")
    SubscribableChannel inputCommon1();

    @Input("input-common-2")
    SubscribableChannel inputCommon2();

    @Input("input-common-3")
    SubscribableChannel inputCommon3();

    @Input("input-tx-1")
    SubscribableChannel inputTx1();
}

Through the @ Input annotation, an Input binding named Input-common-1 is declared. Note that the name should be the same as spring. Com in the configuration file cloud. stream. The bindings configuration item corresponds to the.

At the same time, the return result of the @ Input annotation method is of SubscribableChannel type, which can be used to subscribe to messages for consumption.

public interface SubscribableChannel extends MessageChannel {

 boolean subscribe(MessageHandler handler); // subscribe
 boolean unsubscribe(MessageHandler handler); // Unsubscribe

}

ReceiveService receive message service

The logic of message receiving is realized by assembling StreamListener

@Service
public class ReceiveService
{
    @StreamListener("input-common-1")
    public void receiveInput1(@Payload FooMsg receiveMsg)
    {
        System.out.println("input1 receive: " + receiveMsg);
    }

    @StreamListener("input-common-2")
    public void receiveInput2(@Payload FooMsg receiveMsg)
    {
        System.out.println("input2 receive: " + receiveMsg);
    }

    @StreamListener("input-common-3")
    public void receiveInput3(@Payload FooMsg receiveMsg)
    {
        System.out.println("input3 receive: " + receiveMsg);
    }

    @StreamListener("input-tx-1")
    public void receiveTransactionalMsg(String transactionMsg)
    {
        System.out.println("input tx receive transaction msg: " + transactionMsg);
    }

}

On the method, add @ StreamListener annotation to declare the corresponding Input Binding.

Because the consumed message is of POJO type, the @ Payload annotation needs to be added and the declaration needs to be deserialized into POJO objects.

Application entry

Start the application. The code is as follows:

@SpringBootApplication
@EnableBinding({InputBinding.class})
public class RocketMQConsumerApplication
{
    public static void main(String[] args)
    {
        SpringApplication.run(RocketMQConsumerApplication.class, args);
    }
}

Use the @ EnableBinding annotation to declare that the specified interface enables the Binding function and scans its @ Input and @ Output annotations

Test the scenario of single cluster and multiple instances

  • Execute the ProducerApplication and start the instance of the producer
  • Execute RocketMQConsumerApplication and start the instance of the consumer

After that, request http://localhost:28081/produce/send1?msg= Send sms-0248 Interface three times and send three messages. At this time, the consumer print log can be seen on the IDEA console as follows:

[onMessage][input-common-2 Thread number:99 Message content: FooMsg(id=-1996543838, bar=Send SMS-0248)] 
[onMessage][input-common-3 Thread number:98 Message content: FooMsg(id=-1996543838, bar=Send SMS-0248)] 
[onMessage][input-common-1 Thread number:97 Message content: FooMsg(id=-1996543838, bar=Send SMS-0248)] 

Meet expectations. It can be seen from the log that each message is consumed only once by the same group

Timing message

Timing message refers to a message that cannot be consumed by the Consumer immediately after it is sent to the Broker. It can only be consumed after a specific time

RocketMQ does not support arbitrary time precision delay, but has solidified 18 delay levels. The following table:

Delay leveltimeDelay leveltimeDelay leveltime
11s73m139m
25s84m1410m
310s95m1520m
430s106m1630m
51m117m171h
62m128m182h

Consumption retry

However, it should be noted that only in the cluster consumption mode can messages be retried

RocketMQ provides a mechanism for consumption retry. When the message consumption fails, RocketMQ will re deliver the message to the Consumer through the consumption retry mechanism, so that the Consumer has the opportunity to re consume the message and achieve successful consumption.

Of course, RocketMQ does not resend messages to consumers for consumption indefinitely. By default, when the number of retries reaches 16 and the Consumer still fails to consume, the message will enter the dead letter queue.

Max attempts times

Because the retry interval provided by Spring Cloud Stream is realized through sleep, which will occupy the current thread and affect the consumption speed of consumers, it is not recommended here. Therefore, set the max attempts configuration item to 1 to disable the retry function provided by Spring Cloud Stream and use the retry function provided by RocketMQ.

Delay level when next consumption policy

  • -1: Do not repeat, directly into the dead letter queue
  • 0: RocketMQ Broker control retry policy
  • >0: RocketMQ Consumer control retry policy

The failure retry of each message has a certain interval. The first retry consumption starts with a delay level of 3. Therefore, the default is 16 retry consumption, which is also very understandable. After all, the highest delay level is 18.

Consumption exception handling mechanism

If the exception handling method succeeds and the exception is not thrown again, it will be deemed that the message has been consumed successfully, so the consumption retry will not be carried out.

Spring Cloud Stream provides a general consumption exception handling mechanism, which can intercept the exceptions that occur when consumers consume messages and carry out customized processing logic.

There are two ways to implement exception handling:

  • Local exception handling: specify the error channel through subscription - < destination >< group>. errors
  • Global exception handling: subscribe to global error Channel - errorChannel

When an exception occurs in the consumption message, an error message ErrorMessage will be sent to the corresponding error Channel. At the same time, all error channels are bridged to the global error channels defined by Spring Integration.

    //<destination>.<group>.errors
    //local error
    @ServiceActivator(inputChannel = "topic-common-01.group-common-3.errors")
    public void handleError(ErrorMessage errorMessage)
    {
        System.out.printf("[handleError][payload: %s] %n", ExceptionUtils.getRootCauseMessage(errorMessage.getPayload()));
        System.out.printf("[handleError][originalMessage: %s] %n", errorMessage.getOriginalMessage());
        System.out.printf("[handleError][headers: %s] %n", errorMessage.getHeaders());
    }
    // errorChannel
    // Global error
    @StreamListener(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME)
    public void globalHandleError(ErrorMessage errorMessage)
    {
        System.out.printf("[globalHandleError][payload: %s] %n",
                          ExceptionUtils.getRootCauseMessage(errorMessage.getPayload()));
        System.out.printf("[globalHandleError][originalMessage: %s] %n", errorMessage.getOriginalMessage());
        System.out.printf("[globalHandleError][headers: %s] %n", errorMessage.getHeaders());
    }

Broadcast message

In broadcast consumption mode, each Consumer instance of the same Consumer Group receives a full amount of messages.

For example, we implement IM chat based on WebSocket. When we actively send messages to users, because we don't know which application provides WebSocket users are connected to, we can broadcast consumption through RocketMQ. Each application can judge whether the current user is connected to the WebSocket service provided by itself. If so, push messages to users.

Set the broadcasting configuration item to true

Sequential message

RocketMQ provides two sequential levels:

  • Normal sequential messages: the Producer sends the associated messages to the same message queue
  • Complete strict order: Based on the normal order message, the Consumer consumes in strict order

Message order refers to that when a kind of message is consumed, it can be consumed according to the sending order. For example, an order generates three messages: order creation, order payment and order completion. It is only meaningful to consume in this order, but at the same time, orders can be consumed in parallel. RocketMQ can strictly guarantee the order of messages.

The partition order is a normal order message, and the global order is a completely strict order

  • Global Order: for a specified Topic, all messages are published and consumed in strict FIFO order. Applicable scenarios: scenarios where performance requirements are not high and all messages are released and consumed in strict accordance with FIFO principles
  • Partition order: for a specified Topic, all messages are partitioned according to the Sharding key. Messages in the same partition are published and consumed in strict FIFO order. Sharding key is a Key field used to distinguish different partitions in sequential messages. It is a completely different concept from the Key of ordinary messages. Applicable scenario: the scenario with high performance requirements, using Sharding key as the partition field, and strictly following the FIFO principle for message publishing and consumption in the same block

Partition order:

  • producer

    Add the partition key expression configuration item and set the Sharding key of the sequence message sent by the Producer

    sync: true whether to send messages synchronously. The default is false asynchronous

    server:
      port: 28081
    
    spring:
      application:
        name: rocketmq-produce
    
      cloud:
        stream:
          # Spring Cloud Stream configuration item, corresponding to BindingProperties Map
          bindings:
            output-common:
              #RocketMQ Topic
              destination: topic-common-01
              content-type: application/json
    
              # Producer configuration item, corresponding to ProducerProperties class
              producer:
                # Partition key expression. This expression is based on Spring EL and obtains the partition key from the message
                partition-key-expression: payload['id']
    
          # Spring Cloud Stream RocketMQ configuration item
          rocketmq:
            # RocketMQ Binder configuration item, corresponding to RocketMQBinderConfigurationProperties class
            binder:
              name-server: 192.168.123.22:9876
    
            # RocketMQ custom Binding configuration item, corresponding to RocketMQBindingProperties Map
            bindings:
              output-common:
                # RocketMQ Producer configuration item, corresponding to RocketMQProducerProperties class
                producer:
                  #Producer grouping
                  group: group-common
                  #Send messages synchronously
                  sync: true
    
  • consumer

    Add the order configuration item and set the order consumption message of the Consumer

    server:
      # Random port, easy to start multiple consumers
      port: ${random.int[10000,19999]}
    
    spring:
      application:
        name: rocketmq-consume
    
      cloud:
        # Spring Cloud Stream configuration item, corresponding to BindingServiceProperties class
        stream:
          # Binding configuration item, corresponding to BindingProperties Map
          bindings:
            input-common-1:
              #retry count
              max-attempts: 1
              # destination. RocketMQ Topic is used here
              destination: topic-common-01
              content-type: application/json
              ## Consumer grouping, naming rules: group+topic name + xx
              group: group-common-1
    
          # Spring Cloud Stream RocketMQ configuration item
          rocketmq:
            binder:
              name-server: 192.168.123.22:9876
    
            bindings:
              input-common-1:
                # RocketMQ Consumer configuration item
                consumer:
                  # Receive messages sequentially
                  orderly: true
    

Message filtering

RocketMQ provides two ways to filter messages for consumers:

  • Tag based filtering

    Tag: a flag set for messages, which is used to distinguish different types of messages under the same topic. Messages from the same business unit can set different labels under the same subject according to different business purposes. Tags can effectively maintain the clarity and consistency of the code and optimize the query system provided by RocketMQ. Consumers can realize different consumption logic for different sub themes according to tag to achieve better scalability

  • be based on SQL92 Filtering, example: https://www.jianshu.com/p/5b13868f4451

Tag filtering is common and needs to be set as follows:

  • producer

    Send a message with tag, such as:

    public <T> boolean sendObject(T msg, String tag) throws Exception
    {
        Message<T> message = MessageBuilder.withPayload(msg)
                .setHeader(MessageConst.PROPERTY_TAGS, tag)
                .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
                .build();
    
        return source.sendCommon().send(message);
    }
    
  • consumer

    Subscribe to tag filtered messages, such as:

    server:
      # Random port, easy to start multiple consumers
      port: ${random.int[10000,19999]}
    
    spring:
      application:
        name: rocketmq-consume
    
      cloud:
        # Spring Cloud Stream configuration item, corresponding to BindingServiceProperties class
        stream:
          # Binding configuration item, corresponding to BindingProperties Map
          bindings:
            input-common-3:
              destination: topic-common-01
              content-type: application/json
              consumer:
                concurrency: 20
              group: group-common-3
    
          # Spring Cloud Stream RocketMQ configuration item
          rocketmq:
            binder:
              name-server: 192.168.123.22:9876
    
            bindings:
              input-common-3:
                consumer:
                  # Based on Tag subscription, multiple tags are separated by 𞓜 and empty by default
                  tags: tagObj
    

Transaction message

In the distributed message queue, RocketMQ is the only one that provides complete transaction messages. On this point, we can still advocate.

Consider an extreme case. When the local database transaction has been committed, if the transaction message is not committed due to network reasons or crashes, which eventually leads to the loss of this transaction message and problems in distributed transactions.

In contrast, RocketMQ provides a transaction check back mechanism. If the application fails to commit or rollback this transaction message for a certain period of time, RocketMQ will actively check the application and ask whether the transaction message is commit or rollback, so as to realize that the state of the transaction message can be committed or rollback finally, so as to achieve the consistency of the final transaction.

configuration option

RocketMQ Binder Properties

  • spring.cloud.stream.rocketmq.binder.name-server

    RocketMQ NameServer address (namesrv addr configuration item is used in the old version). Default: 127.0.0.1:9876

  • spring.cloud.stream.rocketmq.binder.access-key

    Alicloud account AccessKey. Default: null

  • spring.cloud.stream.rocketmq.binder.secret-key

    Alibaba cloud account secret key. Default: null

  • spring.cloud.stream.rocketmq.binder.enable-msg-trace

    Whether to enable message trace function for Producer and Consumer Default: true

  • spring.cloud.stream.rocketmq.binder.customized-trace-topic

    The name of the topic stored after the message track is turned on. Default: RMQ_SYS_TRACE_TOPIC

RocketMQ Provider Properties

The following configurations are based on spring cloud. stream. rocketmq. bindings.< channelName>. producer. Configuration related to RocketMQ Producer with prefix.

  • enable

    Whether to enable Producer. Default value: true

  • group

    Producer group name. Default: empty

  • maxMessageSize

    The maximum number of bytes sent by the message. Default: 8249344

  • transactional

    Whether to send a transaction message. Default: false

  • sync

    Whether to send messages in synchronization. Default: false

  • vipChannelEnabled

    Whether to send messages on Vip Channel. Default value: true

  • sendMessageTimeout

    The timeout (in milliseconds) for sending a message. Default: 3000

  • compressMessageBodyThreshold

    Message body compression threshold (when the message body exceeds 4k, it will be compressed). Default: 4096

  • retryTimesWhenSendFailed

    In the mode of sending messages synchronously, the number of retries of message sending failure. Default: 2

  • retryTimesWhenSendAsyncFailed

    In the mode of sending messages asynchronously, the number of retries that failed to send messages. Default: 2

  • retryNextServer

    Whether to retry other broker s when message sending fails. Default: false

RocketMQ Consumer Properties

The following configurations are based on spring cloud. stream. rocketmq. bindings.< channelName>. consumer. Configuration related to RocketMQ Consumer with prefix.

  • enable

    Whether to enable Consumer. Default value: true

  • tags

    The Consumer subscribes based on TAGS, and multiple TAGS are divided by 𞓜. Default: empty

  • sql

    Consumer is based on SQL subscription. Default: empty

  • broadcasting

    Whether Consumer is a broadcast consumption mode. If you want all subscribers to receive messages, you can use broadcast mode. Default: false

  • orderly

    Whether the Consumer synchronizes the consumption message mode. Default: false

  • delayLevelWhenNextConsume

    Retry strategy for consumption failure in asynchronous consumption message mode: - 1, no repetition, directly put into dead letter queue 0,broker control retry strategy > 0, client control retry strategy default value: 0

  • suspendCurrentQueueTimeMillis

    In the synchronous consumption message mode, the time interval of re consumption after consumption failure. Default: 1000

reference resources

Rocketmq docker installation

Spring Cloud Alibaba RocketMQ

RocketMQ Example

Integration of RocketMQ and Spring Cloud Stream

Topics: Java Spring Cloud Microservices Middleware