6-2 Simple case integration of basic concepts of message queue

Article Directory

1 Basic concept of message queue

producerGroup:生产者组
producer:生产者
consumerGroup:消费者组
consumer:消费者
nameServer(邮局):协调者,broker注册信息,发送者和接收者通过它获取broker信息
broker(邮递员):负责消息的接收、存储、显示
topic(地区):消息主题类型,发送接收都需要创建topic
messageQueue(邮件):消息队列,一个topic可以有多个messageQueue
message(邮件内容):具体消息

2 The test class implements message sending and receiving

Simple demonstration in order service

2.1 Add jar dependency

        <!--添加rocketmq的依赖-->
        <dependency>
            <groupId>org.apache.rockemq</groupId>
            <artifactId>roketmq-spring-boot-starter</artifactId>
            <version>2.0.2</version>
        </dependency>

2.2 Create a message sending class RocketMQSendTest in /src/test/java, and verify the result of console message after startup

package cn.hzp;

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;

/**
 * - 1 创建消息生产者DefaultMQProducer,设置组名produceGroup
 * - 2 为生产者指定nameserver地址192.168.149.11:9876
 * - 3 启动生产者start
 * - 4 创建消息对像Message:主题myTopic、标签myTag、消息体String.getBytes();
 * - 5 发送消息send
 * - 6 关闭生产者shutdown
 */
public class RocketMQSendTest {
    public static void main(String[] args) {
        try {
            DefaultMQProducer produceGroup = new DefaultMQProducer("produceGroup");
            produceGroup.setNamesrvAddr("192.168.149.11:9876");
            produceGroup.start();
            Message message = new Message("myTopic", "myTag", "消息体".getBytes());
            SendResult sendResult = produceGroup.send(message, 10000);
            System.out.println(sendResult);
            produceGroup.shutdown();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

2.3 Create the message receiving class RocketMQReceiveTest in /src/test/java, start to see the output, and then start the sending class to see the output

package cn.hzp;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

/**
 * - 1 创建消费者DefaultMQPushConsumer,组名consumerGroup
 * - 2 指定nameserver地址192.168.149.11:9876
 * - 3 指定消费者订阅subscribe的主题myTopic和标签*
 * - 4 设置回调函数registerMessageListener,通过MessageListenerConcurrently对象编写处理消息方法,返回消息状态ConsumeConcurrentlyStatus.CONSUME_SUCCESS
 * - 5 启动消费者start
 */
public class RocketMQReceiveTest {
    public static void main(String[] args) {
        try {
            DefaultMQPushConsumer consumerGroup = new DefaultMQPushConsumer("consumerGroup");
            consumerGroup.setNamesrvAddr("192.168.149.11:9876");
            consumerGroup.subscribe("myTopic", "*");
            consumerGroup.registerMessageListener((MessageListenerConcurrently) (list, consumeConcurrentlyContext) -> {
                System.out.println("消息列表为:" + list);
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            });
            consumerGroup.start();
            System.out.println("消费者启动");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

3 Simple business simulation

The order service sends a message when the order is placed successfully, and the product service monitors the message and sends a SMS notification

3.1 order to generate message end code

3.1.1 Order service pom file introduces 2 mq dependencies

rocketmq-spring-boot-starter2.0.2和rocketmq-client4.4.0

        <!--添加rocketmq的依赖-->
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-client</artifactId>
            <version>4.4.0</version>
        </dependency>

3.1.2 Add configuration to the yml file of the order service

rocketmq:
  # rocketmq服务地址
  name-server: 192.168.149.11:9876
  # 生产者组
  producer:
    group: shop-order

3.1.3 Send mq message in order methodrocketMQTemplate.convertAndSend("orderTopic", order);

3.1.4 After starting the service and accessing the browser, http://localhost:8091/order/product/1check the message with the subject of orderTopic in the mq console

3.2 producer consumption message end code

3.2.1 The product service pom file introduces 2 mq dependencies, the same as 3.1.1

3.2.2 Add configuration of product service yml file

rocketmq:
  # rocketmq服务地址
  name-server: 192.168.149.11:9876

3.2.3 Product service adds receiving class cn.hzp.mq.SmsService

package cn.hzp.mq;

import cn.hzp.domain.Order;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Service;

/**
 * - 需要实现RocketMQListener<消息内容>,消息内容为Order
 * 	- 监听注解@RocketMQMessageListener,
 * 		- 指定消费者组名consumerGroup
 * 		- 消费主题topic
 * 		- consumeMode消费模式,ORDERLY顺序,CONCURRENTLY默认同步即没顺序
 * 		- messageModel消息模式,
 * 			广播BRODCASTING,一个消息被多个消费者多次消费
 * 			默认集群CLUSTERING,一个消息只能被一个消费者消费
 */
@Slf4j
@Service
@RocketMQMessageListener(
        consumerGroup = "productGroup",
        topic = "orderTopic",
        consumeMode = ConsumeMode.CONCURRENTLY,
        messageModel = MessageModel.CLUSTERING
)
public class SmsService implements RocketMQListener<Order> {
    @Override
    public void onMessage(Order order) {
        log.info("接收消息:{},接下来发送短信", order);
    }
}

3.2.4 Restart product service, browser request http://localhost:8091/order/product/1, view information output of idea

4 mq message type demo

Common message (asynchronous, synchronous, one-way), sequential message, transaction message

4.1 Test common messages

Including reliable synchronization, reliable asynchronous, one-way transmission

  • Reliable synchronization: The sender waits for the receiver to send the second message after receiving the notification of the message confirmation, such as the registration SMS notification
  • Reliable and asynchronous: The sender just sends the message and processes the receiver's response result asynchronously through the callback interface, which is used for a long link such as consumer transcoding after video upload
  • One-way sending: send only, such as log collection

4.1.1 The pom file of the order service introduces test dependencies

        <!--测试依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>

4.1.2 Under src/test/java, create a new test class cn.hzp.RocketMQMessageTypeTest

package cn.hzp;

import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = OrderApplication.class)
@Slf4j
public class RocketMQMessageTypeTest {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    
    @Test
    public void ordinaryMessage() throws Exception {
        // 同步消息,第一个参数为topic:tag,tag可以为空,直接写topic;
        SendResult sendResult = rocketMQTemplate.syncSend("typeTopicSync:tag1", "这是同步消息", 10000);
        log.info("同步消息发送结果为:{}", sendResult);

        // 异步消息
        rocketMQTemplate.asyncSend("typeTopicAync", "这是异步消息", new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                log.info("消息发送成功,{}", sendResult);
            }

            @Override
            public void onException(Throwable throwable) {
                log.info("消息发送异常,{}", throwable.getMessage());
            }
        });
        log.info("异步消息准备要发送了");
        Thread.sleep(30000);

        // 单向消息
        rocketMQTemplate.sendOneWay("typeTopicOneWay", "这是单向消息");
    }
}

4.1.3 Start the test and view the message information of the mq web console

4.2 Sequence messages, add test methods to the RocketMQMessageTypeTest class

A bunch of messages of the same topic will be sent to a messageQueue, and the sending method can be added with the orderly suffix, such as a one-way sequential message

    @Test
    public void orderMessage() {
        // 单向顺序消息,hashkey为分配到哪个队列的key
        for (int i = 0; i < 10; i++) {
            rocketMQTemplate.sendOneWayOrderly("typeTopicOneWay", "这是单向消息", "xxx");
        }
    }

Test, check the maximum position information of the queue in the status under the topic tab of the mq web console

4.3 Transaction message

Implementation process:

  • 1 The sender (order order service) sends a semi-transactional message (order information order) to the mq server, and the mq server responds that the semi-transactional message is sent successfully
  • 2 The sender (order order service) executes a local transaction (order operation), the execution result tells the mq server, and the mq server commits or rolls the message according to the result
  • 3 If the mq server does not receive the execution result, it will check the local transaction status, and mq will send the message after receiving the commit.

4.3.1 Order service sends semi-transactional message, add test method in class OrderController

    /**
     * 测试mq的事务消息
     * - 1 向mq服务端发送半事务消息,mq服务端回应消息接收情况
     * - 2 执行本地事务下单操作,结果通知mq服务端,成功commit失败rollback
     * - 3 mq服务端没有收到通知,回查本地事务,成功commit失败rollback
     */
    @RequestMapping("/order/testMQ")
    public TransactionSendResult testRocketMQ(){
        Order order = Order.builder()
                .pid(1)
                .uid(1).uname("测试用户")
                .number(1)
                .build();
        // 演示,半事务消息,这里发送半事务消息,可以放在单独service操作
        UUID uuid = UUID.randomUUID();
        TransactionSendResult transactionSendResult = rocketMQTemplate.sendMessageInTransaction(
                "txProducerGroup",
                "topicTransaction:tagOrder",
                MessageBuilder.withPayload(order).setHeader("txId", uuid).build(),
                order);
        log.info("发送事务消息,结果为{}", transactionSendResult);
        return transactionSendResult;
    }

4.3.2 The domain service adds the local transaction log txLog, which is convenient for the mq server to review the execution results of the local order operation transaction

@Data
@Entity(name = "shop_txlog")
public class TxLog {
    @Id
    private String txId;
    private Date date;
}

4.3.3 Order service adds transaction log persistence layer cn.hzp.dao.TxLogDao under directory src/main/java

public interface TxLogDao extends JpaRepository<TxLog, String> {
}

4.3.4 In orderService and impl are added to save transaction logs when placing orders, which is convenient for the mq server to check back

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveMQTest(String txId, Order order) {
        TxLog txLog = new TxLog();
        txLog.setTxId(txId);
        txLog.setDate(new Date());
        TxLog txLogResult = txLogDao.save(txLog);
        log.info("保存日志信息,便于mq回查事务结果,{}", txLogResult);
        Order orderResult = orderDao.save(order);
        log.info("保存订单信息,{}", orderResult);
    }

4.3.5 Create a new class cn.hzp.mq.MQTranListener in the src/main/java directory to implement local transactions (orders) and message review

package cn.hzp.mq;

import cn.hzp.dao.TxLogDao;
import cn.hzp.domain.Order;
import cn.hzp.domain.TxLog;
import cn.hzp.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RocketMQTransactionListener(txProducerGroup = "txProducerGroup")
public class MQTranListener implements RocketMQLocalTransactionListener {
    @Autowired
    private OrderService orderService;
    @Autowired
    private TxLogDao txLogDao;

    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object arg) {
        try {
            log.info("执行本地事务:进行下单操作");
            String txId = (String) message.getHeaders().get("txId");
            Order order = (Order) arg;
            orderService.saveMQTest(txId, order);
            return RocketMQLocalTransactionState.COMMIT;
        } catch (Exception e) {
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }

    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
        log.info("mq服务端执行消息回查");
        String txId = (String) message.getHeaders().get("txId");
        TxLog txLog = txLogDao.findById(txId).get();
        if (txLog != null) {
            log.info("回查结果,下单成功");
            return RocketMQLocalTransactionState.COMMIT;
        } else {
            log.info("回查结果,下单失败");
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }
}

4.3.6 Start the order service,

  • Browser request to http://localhost:8091/order/testMQtest local transaction
  • Interrupt the break point before the return of the executeLocalTransaction method, then stop the service and restart, and test back and check

Guess you like

Origin blog.csdn.net/weixin_45544465/article/details/106003654