[Java middleware] RocketMQ

RocketMQ

1. Overview of MQ

Message Queue is a middleware that provides message queuing services. A software system that provides an API for the entire process of message production, storage, and consumption.

The role of MQ

  • Current limiting and peak clipping: When users send excessive requests, the requests are temporarily stored for later processing. If you do not use MQ temporary storage to directly request to the business system, it is easy to cause the system to crash.
  • Asynchronous decoupling: If the upstream system and downstream system are called synchronously, the throughput and concurrency of the system will be greatly reduced. The MQ layer implements asynchronous calls between two systems
  • Data collection: Distributed systems will generate massive data streams, such as business logs, monitoring data, and user behavior. Collect and summarize these data streams for big data analysis.

MQ products for mainstream applications

  • Kafka: Scala/Java language development. It is characterized by high throughput, but it will lose data. It is commonly used in real-time computing and log collection in the field of big data. Do not follow any MQ protocol, use self-developed protocol.
  • RocketMQ: Java language development. After several years of Alibaba Double Eleven tests, the performance and stability are very high, and the functions are comprehensive. Do not follow any MQ protocol, use self-developed protocol. The open source version is not as good as the cloud version (Ali commercial version)

MQ Common Protocols

  • JMS : Java Messaging Service. The technical specification of MOM (Message Orientated Middleware) on the Java platform. He facilitates the message exchange of Java applications and provides a standard interface to simplify development. Typical implementation of ActiveMQ

  • STOMP : Streaming Text Oriented Message Protocol. is a simple text protocol for MOM. STOMP provides an interoperable connection format that allows clients to interact with any STOMP message broker. Typical implementation of ActiveMQ

  • AMQP : Advanced Message Queuing Protocol. An application layer standard that provides unified messaging services and is an open standard for application layer protocols. RabbitMQ is a typical implementation

  • MQTT : Message Queueing Telemetry Transport. An instant messaging protocol (binary protocol) developed by IBM, mainly used for communication between servers and low-power IoT devices

2. Basic concepts

Topic: Indicates a collection of a type of message (which can be understood as the type of message). Each message can only belong to one topic, which is the basic unit of RocketMQ for message subscription. A producer can send multiple Topic messages at the same time, but a consumer can only receive one Topic message

Tag (Tag): used to quickly filter messages

3. Linux deploys RocketMQ service

1. Download the compiled binary compressed package on the official website, version 5.0.0, and upload it to Linux

2. Decompress

3. Configure environment variables ROCKETMQ_HOME and NAMESRV_ADDR

insert image description here

4. Configure runserver.sh in the bin directory, and modify the memory parameters of the JVM according to the actual situation

5. Configure runbroker.sh in the bin directory, and modify the memory parameters of the JVM according to the actual situation

6. Execute the nohup command to run the RocketMQ service in the background (the nameserver must be started first, and the broker needs to be registered on the nameserver)

# 启动nameserver
nohup bin/mqnamesrv &	

# 启动broker
nohup bin/mqbroker -c [confFile] & # -c可指定加载的配置文件,默认为conf/broker.conf

# 查看日志rocketmq是否成功启动
tail nohup.out	

# 查看进程
jps		

# 停止broker
sh bin/mqshutdown broker

# 停止namesrv
sh bin/mqshutdown namesrv

7. Execute the command test (the test sample provided by rocketmq, the producer will send a thousand messages)

bin/tools.sh org.apache.rocketmq.example.quickstart.Producer

8. Execute the command test (the test sample provided by rocketmq, the consumer will accept a thousand messages)

bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer

4. RocketMQ API

Producer sends messages synchronously

public void test_SyncProducer() throws MQClientException {
    
    
    DefaultMQProducer producer = new DefaultMQProducer("producer_group_name");
    //设置注册服务的ip地址的端口
    producer.setNamesrvAddr(RocketMQConstant.NAME_SRV_ADDR);
    //启动生产者
    producer.start();


    for(int i=0; i<3; i++){
    
    
        try {
    
    
            // 封装消息,设置topic,tag(用于消息快速过滤),消息数据
            Message message = new Message(
                "TopicTest",
                "TagA",
                "ID04287777",
                ("Hello, RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
            //同步发送消息并获取发送结果,producer从broker获取发送结果
            SendResult sendResult = producer.send(message);

            System.out.println(sendResult);

            Thread.sleep(1500);
        } catch (Exception e) {
    
    
            throw new RuntimeException(e);
        }
    }


    producer.shutdown();
}

Producers send messages asynchronously

public void test_AsyncProducer() throws Exception{
    
    
    DefaultMQProducer producer = new DefaultMQProducer(RocketMQConstant.PRODUCER_GROUP_NAME);
    producer.setNamesrvAddr(RocketMQConstant.NAME_SRV_ADDR);
    producer.start();
    producer.setRetryTimesWhenSendAsyncFailed(0);

    int messageCount = 10;

    final CountDownLatch countDownLatch = new CountDownLatch(messageCount);

    for(int i=0; i<messageCount; i++){
    
    
        final int index = i;
        // 封装消息,设置topic,tag(用于消息快速过滤),消息数据
        Message message = new Message(
            "TopicTest",
            "TagA",
            "ID04287777",
            ("Hello, RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));

        // 异步发送消息,若broker有响应会调用SendCallback中的方法
        producer.send(message, new SendCallback() {
    
    
            public void onSuccess(SendResult sendResult) {
    
    
                countDownLatch.countDown();
                System.out.println("    Send Message "+ index +" OK: "+sendResult);
            }

            public void onException(Throwable throwable) {
    
    
                countDownLatch.countDown();
                System.out.println("    Send Message "+ index +" Exception: "+throwable);
            }
        });

        //单向发送
        producer.sendOneway(message);

        System.out.println("Message "+index+" send done");
    }
    //在100条消息发送完后关闭
    countDownLatch.await(5, TimeUnit.SECONDS);
    producer.shutdown();
}

Producer sends messages in one direction

public void test_OneWayProducer() throws Exception{
    
    
    DefaultMQProducer producer = new DefaultMQProducer(RocketMQConstant.PRODUCER_GROUP_NAME);
    producer.setNamesrvAddr(RocketMQConstant.NAME_SRV_ADDR);
    producer.start();
    producer.setRetryTimesWhenSendAsyncFailed(0);

    int messageCount = 10;

    final CountDownLatch countDownLatch = new CountDownLatch(messageCount);

    for(int i=0; i<messageCount; i++){
    
    
        final int index = i;
        // 封装消息,设置topic,tag(用于消息快速过滤),消息数据
        Message message = new Message(
            "TopicTest",
            "TagA",
            "ID04287777",
            ("Hello, RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));

        //单向发送
        producer.sendOneway(message);

        System.out.println("Message "+index+" send done");
    }
    //在100条消息发送完后关闭
    countDownLatch.await(5, TimeUnit.SECONDS);
    producer.shutdown();
}

consumer push model

public static void test_PushConsumer() throws Exception{
    
    
    DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer_group_name");

    consumer.setNamesrvAddr(RocketMQConstant.NAME_SRV_ADDR);
    consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
    //消费者订阅的消息topic和tag(subExpression,*表示任意)
    consumer.subscribe("TopicTest", "*");
    consumer.registerMessageListener(new MessageListenerConcurrently() {
    
    
        public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
    
    
            System.out.println("Receive New Message : "+list);
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        }
    });

    consumer.start();

    System.out.println("Consumer Start...");
}

consumer pull model

Different from the push mode consumer, the pull mode needs to manually manage the mapping relationship between MessageQueue and offset. However, the latest underlying source code of LitePullConsumer has realized the management of mq and offset, which is more convenient.

//拉模式消费者
public static void test_LitePullConsumer() throws Exception{
    
    
    DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer(RocketMQConstant.CONSUMER_GROUP_NAME);
    litePullConsumer.setNamesrvAddr(RocketMQConstant.NAME_SRV_ADDR);
    litePullConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
    litePullConsumer.subscribe("TopicTest", "*");
    litePullConsumer.start();

    try {
    
    
        while(true){
    
    
            List<MessageExt> messageExts = litePullConsumer.poll();
            System.out.printf("%s%n", messageExts);
        }
    }finally {
    
    
        litePullConsumer.shutdown();
    }
}

RocketMQ transfers objects, and the class to which the object belongs needs to implement the serialization interface, and convert the object into a byte array and store it in the message body.

sequential message

Ensure local ordering of messages (ordering of several messages, not necessarily ordering of all messages) to prevent being affected by network transmission.

Realization principle

The producer sends a group of ordered messages to the same MessageQueue at a time (depending on the characteristics of the queue to ensure local order). Consumers will consume the next MessageQueue message after consuming a MessageQueue message.

public class OrderProducer {
    
    
    public static void main(String[] args) {
    
    
        DefaultMQProducer producer = new DefaultMQProducer(WanfengConstant.PRODUCER_GROUP_NAME);
        try {
    
    
            producer.setNamesrvAddr(WanfengConstant.NAMESRV_ADDR);
            producer.start();
            for(int i=0; i<5; i++){
    
    
                //用于指定顺序的id
                int orderId = i;

                for(int j=0; j<5; j++){
    
    
                    Message message = new Message(
                            WanfengConstant.ORDER_TOPIC,
                            "order_"+orderId,
                            "KEY"+orderId,
                            ("order_"+orderId+" step "+j).getBytes(RemotingHelper.DEFAULT_CHARSET)
                    );
                    //实现消息队列选择器对象,使同一个orderId的消息发送到同一个消息队列
                    SendResult sendResult = producer.send(
                            message,
                            new MessageQueueSelector() {
    
    
                                @Override
                                public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
    
    
                                    Integer id = (Integer) arg;
                                    int index = id % mqs.size();
                                    return mqs.get(index);
                                }
                            },
                            orderId
                    );
                    System.out.printf("%s%n", sendResult);
                }
            }

        }catch(Exception e){
    
    
            e.printStackTrace();
            producer.shutdown();
        }

    }
}
public class OrderConsumer {
    
    
    public static void main(String[] args) {
    
    
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(WanfengConstant.CONSUMER_GROUP_NAME);
        consumer.setNamesrvAddr(WanfengConstant.NAMESRV_ADDR);
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
        try {
    
    
            consumer.subscribe(WanfengConstant.ORDER_TOPIC, "*");
            //实现顺序消息监听者接口
            consumer.registerMessageListener(new MessageListenerOrderly() {
    
    
                @Override
                public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
    
    
                    context.setAutoCommit(true);
                    for(MessageExt messageExt : msgs){
    
    
                        System.out.println("Receive Message: " + new String(messageExt.getBody()));
                    }
                    return ConsumeOrderlyStatus.SUCCESS;
                }
            });
            consumer.start();
            System.out.println("Consumer Start...");
        } catch (Exception e) {
    
    
            e.printStackTrace();
            consumer.shutdown();
        }
    }
}

broadcast message

The message sent by the producer is pushed to all consumers of the group

Implementation principle: set the consumer to the MessageModel in broadcast mode.

public class BroadcastConsumer {
    
    
    public static void main(String[] args) {
    
    
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(WanfengConstant.CONSUMER_GROUP_NAME);
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
        //设定消息模式为广播
        consumer.setMessageModel(MessageModel.BROADCASTING);
        try {
    
    
            consumer.subscribe(WanfengConstant.ARCHIVE_TOPIC, "*");
            consumer.registerMessageListener(new MessageListenerConcurrently() {
    
    
                @Override
                public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
    
    
                    msgs.forEach(messageExt -> {
    
    
                        Archive archive = (Archive) WanfengObjectUtil.bytesToObject(messageExt.getBody());
                        System.out.println("Receive Message : "+archive.getId());
                    });
                    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                }
            });
            consumer.start();
            System.out.println("Broadcast Consumer Start...");
        }catch (Exception e){
    
    
            e.printStackTrace();
            consumer.shutdown();
        }
    }
}

If the specified MessageModel is CLUSTERING, the message sent by the producer will randomly specify the consumer for consumption.

delayed message

As the name implies, when the message is sent to the broker, it is delayed for a specified time before being sent to the consumer. Often used for timing sending

filter messages

Filtering messages is implemented by tag, and the filter tag can be specified on the consumer side.

//消费者订阅tag1或tag2的消息
consumer.subscribe("TopicTest", "tag1 || tag2");

In RocketMQ, after the consumer specifies the filter condition, it is pushed up to the Broker, and tag filtering is performed in the Broker to reduce network IO, but it also increases the busyness of the Broker.

transactional message

insert image description here

public class TransactionProducer {
    
    
    public static void main(String[] args) {
    
    
        TransactionMQProducer producer = new TransactionMQProducer(WanfengConstant.PRODUCER_GROUP_NAME);
        TransactionListener transactionListener = new TransactionListener() {
    
    
            @Override
            public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
    
    
                System.out.println("[WANFENG-INFO] TransactionProducer.executeLocalTransaction(): 执行成功...");

                String tags = msg.getTags();
                if (StringUtils.contains(tags, "TagA")) {
    
    
                    //消息提交(发送出去)
                    return LocalTransactionState.COMMIT_MESSAGE;
                } else if (StringUtils.contains(tags, "TagB")) {
    
    
                    //消息回滚(丢掉消息)
                    return LocalTransactionState.ROLLBACK_MESSAGE;
                } else {
    
    
                    return LocalTransactionState.UNKNOW;
                }
            }

            @Override
            public LocalTransactionState checkLocalTransaction(MessageExt msg) {
    
    
                System.out.println("[WANFENG-INFO] TransactionProducer.checkLocalTransaction(): 执行成功...");
                String tags = msg.getTags();
                if (StringUtils.contains(tags, "TagC")) {
    
    
                    return LocalTransactionState.COMMIT_MESSAGE;
                } else {
    
    
                    return LocalTransactionState.UNKNOW;
                }
            }
        };
        ExecutorService executorService = new ThreadPoolExecutor(
                2,
                5,
                100, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3)
        );
        producer.setExecutorService(executorService);
        producer.setTransactionListener(transactionListener);
        try {
    
    
            producer.start();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }

        String[] tags = new String[]{
    
    "TagA", "TagB", "TagC"};
        CountDownLatch countDownLatch = new CountDownLatch(9);
        for (int i = 0; i < 9; i++) {
    
    
            try {
    
    
                Message message = new Message("TopicTest", tags[i % tags.length], "Key" + i, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
                SendResult sendResult = producer.sendMessageInTransaction(message, null);
                System.out.println(sendResult);
                Thread.sleep(1000);
                countDownLatch.countDown();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }

        try {
    
    
            countDownLatch.await();
        } catch (InterruptedException e) {
    
    
            throw new RuntimeException(e);
        } finally {
    
    
            try {
    
    
                Thread.sleep(100000);
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
            producer.shutdown();
        }


    }
}

ACL permission control

ACL controls the user's access to topic resources

Introduce the dependency package of acl in the pom dependency

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-acl</artifactId>
    <version>5.0.0</version>
</dependency>

In the conf/broker.conf file on the server side, add configuration and enable acl

aclEnable=true

In the conf/plain_acl.yml file on the server side, configure specific permission rules (hot loading, no need to restart mq)

accounts:
  - accessKey: RocketMQ #用户名
    secretKey: 12345678 #密码
    whiteRemoteAddress:   #访问地址白名单
    admin: false	#是否为管理员(管理员可以访问所有Topic)
    defaultTopicPerm: DENY #默认Topic访问权限
    defaultGroupPerm: SUB  #默认组权限
    topicPerms:		#Topic对应的权限,若这里找不到则采用defaultTopicPerm
      - topicA=DENY 	
      - topicB=PUB|SUB
      - topicC=SUB
    groupPerms:
      # the group should convert to retry topic
      - groupA=DENY
      - groupB=PUB|SUB
      - groupC=SUB

RPCHook (acl user information) needs to be added when creating a producer object

public class AclProducer {
    
    

    private static final String ACL_ACCESS_KEY = "RocketMQ";

    private static final String ACL_SECRET_KEY = "12345678";

    /**
     * 通过用户名和密码获取RPCHook
     * @return
     */
    public static RPCHook getAclRPCHook(){
    
    
        return new AclClientRPCHook(new SessionCredentials(ACL_ACCESS_KEY, ACL_SECRET_KEY));
    }

    public static void main(String[] args) throws MQClientException, InterruptedException {
    
    
        //创建生产者时加入用户信息,即RPCHook
        DefaultMQProducer producer = new DefaultMQProducer(WanfengConstant.PRODUCER_GROUP_NAME, getAclRPCHook());
        producer.setNamesrvAddr(WanfengConstant.NAMESRV_ADDR);
        producer.start();

        for (int i = 0; i < 20; i++) {
    
    
            try {
    
    
                Message message = new Message(
                        "TopicTest",
                        WanfengConstant.TAGS_NAME,
                        ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /*消息体转换成二进制数组*/
                );
                SendResult sendResult = producer.send(message);
                System.out.printf("%s%n", sendResult);
            } catch (Exception e) {
    
    
                e.printStackTrace();
                Thread.sleep(1000);
            }
        }
    }
}

message track

Producer, Consumer, and Broker process information about messages

The realization principle of the message track is that MQ puts the message track in the Topic of RMQ_SYS_TRACE_TOPIC

Open the message track in the Broker configuration file

traceTopicEnable=true

When creating a producer, specify the enableMsgTrace parameter as true to enable message traces. You can also specify the customizedTraceTopic parameter to customize the topic of the message trace.

public class TraceProducer {
    
    
    public static void main(String[] args) throws MQClientException {
    
    
        //指定enableMsgTrace参数为true,开启消息轨迹
        DefaultMQProducer producer = new DefaultMQProducer(WanfengConstant.PRODUCER_GROUP_NAME, true);
        producer.setNamesrvAddr(WanfengConstant.NAMESRV_ADDR);
        producer.start();

        for (int i = 0; i < 20; i++) {
    
    
            try {
    
    
                Message message = new Message(
                        "TopicTest",
                        WanfengConstant.TAGS_NAME,
                        ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /*消息体转换成二进制数组*/
                );
                SendResult sendResult = producer.send(message);
                System.out.printf("%s%n", sendResult);
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

Guess you like

Origin blog.csdn.net/Dae_Lzh/article/details/131942720