消息队列 ~ RocketMQ ~ 从入门到入坑。

消息队列 ~ RocketMQ ~ 从入门到入坑。


文章目录


下载。

https://www.apache.org/dyn/closer.cgi?path=rocketmq/4.7.1/rocketmq-all-4.7.1-bin-release.zip



使用。

[geek@192 tools_my]$ unzip rocketmq-all-4.7.1-bin-release.zip
[geek@192 tools_my]$ cd rocketmq-all-4.7.1-bin-release
[geek@192 rocketmq-all-4.7.1-bin-release]$ ls
benchmark bin conf lib LICENSE NOTICE README.md

Quick Start
This quick start guide is a detailed instruction of setting up RocketMQ messaging system on your local machine to send and receive messages.

More Details:

English:https://github.com/apache/rocketmq/tree/master/docs/en
中文:https://github.com/apache/rocketmq/tree/master/docs/cn
ON THIS PAGE
PREREQUISITE
DOWNLOAD & BUILD FROM RELEASE
LINUX
START NAME SERVER
START BROKER
SEND & RECEIVE MESSAGES
SHUTDOWN SERVERS
WINDOWS
ADD ENVIRONMENT VARIABLES
START NAME SERVER
START BROKER
SEND & RECEIVE MESSAGES
SEND MESSAGES
RECEIVE MESSAGES
SHUTDOWN SERVERS
Prerequisite
The following softwares are assumed installed:

64bit OS, Linux/Unix/Mac is recommended;(Windows user see guide below)
64bit JDK 1.8+;
Maven 3.2.x;
Git;
4g+ free disk for Broker server
Download & Build from Release
Click here to download the 4.7.1 source release. Also you could download a binary release from here.

Now execute the following commands to unpack 4.7.1 source release and build the binary artifact.

unzip rocketmq-all-4.7.1-source-release.zip
cd rocketmq-all-4.7.1/
mvn -Prelease-all -DskipTests clean install -U
cd distribution/target/rocketmq-4.7.1/rocketmq-4.7.1

Linux
Start Name Server

nohup sh bin/mqnamesrv &
tail -f ~/logs/rocketmqlogs/namesrv.log
The Name Server boot success…

[geek@192 rocketmq-all-4.7.1-bin-release]$ nohup sh b
benchmark/ bin/       
[geek@192 rocketmq-all-4.7.1-bin-release]$ nohup sh bin/mqnamesrv &
[1] 71709
[geek@192 rocketmq-all-4.7.1-bin-release]$ nohup: ignoring input and appending output to ‘nohup.out’

[geek@192 rocketmq-all-4.7.1-bin-release]$ tail -f ~/logs/rocketmqlogs/namesrv.log
2020-09-25 12:33:39 INFO main - tls.client.keyPassword = null
2020-09-25 12:33:39 INFO main - tls.client.certPath = null
2020-09-25 12:33:39 INFO main - tls.client.authServer = false
2020-09-25 12:33:39 INFO main - tls.client.trustCertPath = null
2020-09-25 12:33:40 INFO main - Using OpenSSL provider
2020-09-25 12:33:41 INFO main - SSLContext created for server
2020-09-25 12:33:41 INFO main - Try to start service thread:FileWatchService started:false lastThread:null
2020-09-25 12:33:41 INFO NettyEventExecutor - NettyEventExecutor service started
2020-09-25 12:33:41 INFO main - The Name Server boot success. serializeType=JSON
2020-09-25 12:33:41 INFO FileWatchService - FileWatchService service started
2020-09-25 12:34:41 INFO NSScheduledThread1 - --------------------------------------------------------
2020-09-25 12:34:41 INFO NSScheduledThread1 - configTable SIZE: 0

Start Broker

nohup sh bin/mqbroker -c conf/broker.conf -n localhost:9876 &

sudo vim conf/broker.conf 中添加 brokerIP1=192.168.142.161

nohup sh bin/mqbroker -n localhost:9876 &
tail -f ~/logs/rocketmqlogs/broker.log
The broker[%s, 172.30.30.233:10911] boot success…

  • 查看 java 进程发现 Broker 没有启动成功。
[geek@192 rocketmq-all-4.7.1-bin-release]$ jps
35556 QuorumPeerMain
71716 NamesrvStartup
71774 Jps

默认 jvm 内存 8G,太大,改小。

JAVA_OPT=“${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m”

  • $ sudo vim bin/runbroker.sh
#JAVA_OPT="${JAVA_OPT} -server -Xms8g -Xmx8g -Xmn4g"
JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m"
JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0"
JAVA_OPT="${JAVA_OPT} -verbose:gc -Xloggc:${GC_LOG_DIR}/rmq_broker_gc_%p_%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy"
JAVA_OPT="${JAVA_OPT} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m"
JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow"
JAVA_OPT="${JAVA_OPT} -XX:+AlwaysPreTouch"
JAVA_OPT="${JAVA_OPT} -XX:MaxDirectMemorySize=15g"
JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages -XX:-UseBiasedLocking"
JAVA_OPT="${JAVA_OPT} -Djava.ext.dirs=${JAVA_HOME}/jre/lib/ext:${BASE_DIR}/lib:${JAVA_HOME}/lib/ext"
#JAVA_OPT="${JAVA_OPT} -Xdebug -Xrunjdwp:transport=dt_socket,address=9555,server=y,suspend=n"
JAVA_OPT="${JAVA_OPT} ${JAVA_OPT_EXT}"
JAVA_OPT="${JAVA_OPT} -cp ${CLASSPATH}"
  • $ sudo vim bin/runserver.sh
#JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
JAVA_OPT="${JAVA_OPT} -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8  -XX:-UseParNewGC"
JAVA_OPT="${JAVA_OPT} -verbose:gc -Xloggc:${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log -XX:+PrintGCDetails"
JAVA_OPT="${JAVA_OPT} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m"
JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow"
JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages"
JAVA_OPT="${JAVA_OPT} -Djava.ext.dirs=${JAVA_HOME}/jre/lib/ext:${BASE_DIR}/lib:${JAVA_HOME}/lib/ext"
#JAVA_OPT="${JAVA_OPT} -Xdebug -Xrunjdwp:transport=dt_socket,address=9555,server=y,suspend=n"
JAVA_OPT="${JAVA_OPT} ${JAVA_OPT_EXT}"
JAVA_OPT="${JAVA_OPT} -cp ${CLASSPATH}"

$JAVA ${JAVA_OPT} $@

  • 成功启动。
[geek@192 rocketmq-all-4.7.1-bin-release]$ nohup sh bin/mqbroker -n localhost:9876 &
[2] 71914
[geek@192 rocketmq-all-4.7.1-bin-release]$ nohup: ignoring input and appending output to ‘nohup.out’

[geek@192 rocketmq-all-4.7.1-bin-release]$ jps
71922 BrokerStartup
35556 QuorumPeerMain
71716 NamesrvStartup
71981 Jps



shutdown。

Shutdown Servers

sh bin/mqshutdown broker
The mqbroker(36695) is running…
Send shutdown request to mqbroker(36695) OK

sh bin/mqshutdown namesrv
The mqnamesrv(36664) is running…
Send shutdown request to mqnamesrv(36664) OK



测试发送消息。

Send & Receive Messages

Before sending/receiving messages, we need to tell clients the location of name servers. RocketMQ provides multiple ways to achieve this. For simplicity, we use environment variable NAMESRV_ADDR

先设置一个临时环境变量。

export NAMESRV_ADDR=localhost:9876
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Producer
SendResult [sendStatus=SEND_OK, msgId= …

sh bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer
ConsumeMessageThread_%d Receive New Messages: [MessageExt…



集群。

各角色介绍。

Producer ~ 消息的发送者。eg. 发信者。
Consumer ~ 消息接收者。eg. 收信者。
Broker ~ 暂存和传输消息。eg. 邮局。
NameServer ~ 管理 Broker。eg. 各个邮局的管理机构。
Topic ~ 区分消息的种类。一个发送者可以发送消息给一个或者多个 Topic;一个消息的接收者可以订阅一个或者多个 Topic 消息。
Message Queue ~ 相当于是 Topic 的分区,用于并行发送和接收消息。

集群模式。
单 Master模式。

这种方式风险较大,一旦 Broker 重启或者宕机时,会导致整个服务不可用。不建议线上环境使用,可以用于本地测试。



多 Master 模式。

一个集群无 Slave,全是 Master,例如 2 个 Master 或者 3 个 Master,这种模式的优缺点如下:

优点。
配置简单,单个 Master 宕机或重启维护对应用无影响,在磁盘配置为 RAID10时,即使机器宕机不可恢复情况下,由于 RAID10 磁盘非常可靠,消息也不会丢(异步刷盘丢失少量消息,同步刷盘一条不丢),性能最高。
缺点。
单台机器宕机期间,这台机器上未被消费的消息在机器恢复之前不可订阅,消息实时性会受到影响。



多 Master 多 Slave 模式(异步)。

每个 Master 配置一个 Slave,有多对 Master-Slave,HA 采用异步复制方式,主备有短暂消息延迟(毫秒级),这种模式的优缺点。

优点。
即使磁盘损坏,消息丢失的非常少,且消息实时性不会受影响,同时 Master 宕机后,消费者仍然可以从 Slave 消费,而且此过程对应用透明,不需要人工干预,性能同多 Master 模式几乎一样。
缺点。
Master 宕机,磁盘损坏情况下会丢失少量消息。



多 Master 多 Slave 模式(同步)。

每个 Master 配置一个 Slave,有多对 Master-Slave,HA 采用同步双写方式,即只有主备都写成功,才向应用返回成功,这种模式的优缺点如下。

优点。
数据与服务都无单点故障,Master 宕机情况下,消息无延迟,服务可用性与数据可用性都非常高。
缺点。
性能比异步复制模式略低(大约低 10% 左右),发送单个消息的 RT 会略高,且目前版本在主节点宕机后,备机不能自动切换为主机。



双主双从集群搭建。

在这里插入图片描述



集群特点。

NameServer 是一个几乎无状态节点,可集群部署,节点之间无任何信息同步。

Broker 部署相对复杂,Broker 分为 Master 与 Slave,一个 Master 可以对应多个 Slave,但是一个 Slave 只能对应一个 Master,Master 与 Slave 的对应关系通过指定相同的 BrokerName,不同的 BrokerId 来定义,BrokerId 为 0 表示 Master,非 0 表示 Slave。

Master 也可以部署多个。每个 Broker 与 NameServer 集群中的所有节点建立长连接,定时注册 Topic 信息到所有 NameServer。

Producer 与 NameServer 集群中的其中一个节点(随机选择)建立长连接,定期从 NameServer 取 Topic 路由信息,并向提供 Topic 服务的 Master 建立长连接,且定时向 Master 发送心跳。Producer 完全无状态,可集群部署。

Consumer 与 NameServer 集群中的其中一个节点(随机选择)建立长连接,定期从 NameServer 取 Topic 路由信息,并向提供 Topic 服务的 Master、Slave 建立长连接,且定时向 Master、Slave 发送心跳。Consumer 既可以从 Master 订阅消息,也可以从 Slave 订阅消息,订阅规则由 Broker 配置决定。

集群主要是 Broker 的集群搭建。

Broker Name 区分组。

Broker id 0 ~ 主节点。

Broker id 非 0 ~ 从节点。

在这里插入图片描述



集群工作流程。
  • 启动 NameServer,NameServer 起来后监听端口,等待 Broker、Producer、Consumer 连上来,相当于一个路由控制中心。NameServer 是无状态的。

  • Broker 启动,跟所有的 NameServer 保持长连接,定时发送心跳包(每一个 Name Server,所以Name Server 间不需要同步)。心跳包中包含当前 Broker 信息(IP + 端口等)以及存储所有 Topic 信息。注册成功后,NameServer 集群中就有 Topic 跟 Broker 的映射关系。

  • 收发消息前,先创建 Topic,创建 Topic 时需要指定该 Topic 要存储在哪些 Broker 上,也可以在发送消息时自动创建 Topic。

  • Producer 发送消息,启动时先跟 NameServer 集群中的其中一台建立长连接,并从 NameServer 中获取当前发送的 Topic 存在哪些 Broker 上,轮询从队列列表中选择一个队列,然后与队列所在的 Broker 建立长连接从而向 Broker 发消息。

  • Consumer 跟 Producer 类似,跟其中一台 NameServer 建立长连接,获取当前订阅 Topic 存在哪些 Broker上,然后直接跟 Broker 建立连接通道,开始消费消息。



配置文件。
[geek@192 rocketmq-all-4.7.1-bin-release]$ ls conf/2m-2s-async/
broker-a.properties  broker-a-s.properties  broker-b.properties  broker-b-s.properties



mqadmin。

https://github.com/apache/rocketmq/blob/master/docs/cn/operation.md

[geek@192 rocketmq-all-4.7.1-bin-release]$ ls bin/
cachedog.sh       mqadmin       mqbroker.numanode0  mqnamesrv       os.sh      runbroker.cmd     runserver.sh      tools.cmd
cleancache.sh     mqadmin.cmd   mqbroker.numanode1  mqnamesrv.cmd   play.cmd   runbroker.sh      runserver.sh.bak  tools.sh
cleancache.v1.sh  mqbroker      mqbroker.numanode2  mqshutdown      play.sh    runbroker.sh.bak  setcache.sh
dledger           mqbroker.cmd  mqbroker.numanode3  mqshutdown.cmd  README.md  runserver.cmd     startfsrv.sh

mqadmin 管理工具。

注意:

执行命令方法:./mqadmin {command} {args}
几乎所有命令都需要配置 -n 表示 NameServer 地址,格式为 ip:port
几乎所有命令都可以通过 -h 获取帮助
如果既有 Broker 地址(-b)配置项又有 clusterName(-c)配置项,则优先以 Broker 地址执行命令,如果不配置 Broker 地址,则对集群中所有主机执行命令,只支持一个 Broker 地址。-b 格式为 ip:port,port 默认是 10911。
在 tools 下可以看到很多命令,但并不是所有命令都能使用,只有在 MQAdminStartup 中初始化的命令才能使用,你也可以修改这个类,增加或自定义命令。
由于版本更新问题,少部分命令可能未及时更新,遇到错误请直接阅读相关命令源码。



图形管理工具。

https://github.com/apache/rocketmq-externals/tree/master/rocketmq-console

https://github.com/apache/rocketmq-externals



Java。

  • pom.xml。
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-client</artifactId>
            <version>4.4.0</version>
        </dependency>
package com.geek.rocketmq.helloworld;

import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;

import java.io.UnsupportedEncodingException;
import java.util.concurrent.TimeUnit;

/**
 * 发送同步消息。
 */
public class SyncProducer {

    public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException, UnsupportedEncodingException {

        // 创建消息生产者 Producer,并指定生产者组名。
        //Instantiate with a producer group name.
        DefaultMQProducer defaultMQProducer = new
                DefaultMQProducer("group_geek");// please_rename_unique_group_name
        // Specify name server addresses.
        defaultMQProducer.setNamesrvAddr("192.168.142.161:9876");
        //Launch the instance.
        defaultMQProducer.start();
        for (int i = 0; i < 10; i++) {
            //Create a message instance, specifying topic, tag and message body.
            // 创建消息对象,指定主题 Topic、Tag 和消息体。
            /**
             * 参数 1 ~ 消息主题 Topic。
             * 参数 2 ~ tag。
             * 参数 3 ~ 消息内容。
             */
            Message msg = new Message("TopicTest"/* Topic */,
                    "TagA"/* Tag */,
                    ("Hello RocketMQ " +
                            i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
            );
            //Call send message to deliver message to one of brokers.
            // 发送消息。
            SendResult sendResult = defaultMQProducer.send(msg);
            System.out.printf("%s%n", sendResult);

            TimeUnit.SECONDS.sleep(1);


            MessageQueue messageQueue = sendResult.getMessageQueue();
            String msgId = sendResult.getMsgId();
            String offsetMsgId = sendResult.getOffsetMsgId();
            long queueOffset = sendResult.getQueueOffset();
            String regionId = sendResult.getRegionId();
            SendStatus sendStatus = sendResult.getSendStatus();
            String transactionId = sendResult.getTransactionId();

            System.out.println("sendResult = " + sendResult);
            System.out.println("messageQueue = " + messageQueue);
            System.out.println("msgId = " + msgId);
            System.out.println("offsetMsgId = " + offsetMsgId);
            System.out.println("queueOffset = " + queueOffset);
            System.out.println("regionId = " + regionId);
            System.out.println("sendStatus = " + sendStatus);
            System.out.println("transactionId = " + transactionId);

            // SendResult [sendStatus=SEND_OK, msgId=C0A800684D2818B4AAC20E89A68A0000, offsetMsgId=C0A88EA100002A9F000000000006B620, messageQueue=MessageQueue [topic=TopicTest, brokerName=broker-a, queueId=3], queueOffset=525]
            //sendResult = SendResult [sendStatus=SEND_OK, msgId=C0A800684D2818B4AAC20E89A68A0000, offsetMsgId=C0A88EA100002A9F000000000006B620, messageQueue=MessageQueue [topic=TopicTest, brokerName=broker-a, queueId=3], queueOffset=525]
            //messageQueue = MessageQueue [topic=TopicTest, brokerName=broker-a, queueId=3]
            //msgId = C0A800684D2818B4AAC20E89A68A0000
            //offsetMsgId = C0A88EA100002A9F000000000006B620
            //queueOffset = 525
            //regionId = DefaultRegion
            //sendStatus = SEND_OK
            //transactionId = null
        }
        //Shut down once the producer instance is not longer in use.
        defaultMQProducer.shutdown();
    }

}

package com.geek.rocketmq.helloworld;

import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * 发送同步消息。
 */
public class AsyncProducer {

    public static void main(String[] args) throws MQClientException, InterruptedException {
        //Instantiate with a producer group name.
        DefaultMQProducer producer = new DefaultMQProducer("group_geek");// please_rename_unique_group_name
        // Specify name server addresses.
        producer.setNamesrvAddr("192.168.142.161:9876");
        //Launch the instance.
        producer.start();
        producer.setRetryTimesWhenSendAsyncFailed(0);

        int messageCount = 100;
        final CountDownLatch countDownLatch = new CountDownLatch(messageCount);
        for (int i = 0; i < messageCount; i++) {
            try {
                final int index = i;
                Message msg = new Message("TopicTest"/*"Jodie_topic_1023"*/,
                        "TagA",
                        "OrderID188",
                        "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
                producer.send(msg, new SendCallback() {
                    /**
                     * 发送成功回调函数。
                     * @param sendResult
                     */
                    @Override
                    public void onSuccess(SendResult sendResult) {
                        countDownLatch.countDown();
                        System.out.printf("%-10d OK %s %n", index, sendResult.getMsgId());
                    }

                    /**
                     * 发送失败回调函数。
                     * @param e
                     */
                    @Override
                    public void onException(Throwable e) {
                        countDownLatch.countDown();
                        System.out.printf("%-10d Exception %s %n", index, e);
                        e.printStackTrace();
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        countDownLatch.await(5, TimeUnit.SECONDS);
        producer.shutdown();
    }

}

package com.geek.rocketmq.helloworld;

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

/**
 * 发送单向消息。
 */
public class OnewayProducer {

    public static void main(String[] args) throws Exception {
        //Instantiate with a producer group name.
        DefaultMQProducer producer = new DefaultMQProducer("group_geek");// please_rename_unique_group_name
        // Specify name server addresses.
        producer.setNamesrvAddr("192.168.142.161:9876");
        //Launch the instance.
        producer.start();
        for (int i = 0; i < 10; i++) {
            //Create a message instance, specifying topic, tag and message body.
            Message msg = new Message("TopicTest" /* Topic */,
                    "TagA" /* Tag */,
                    ("Hello RocketMQ " +
                            i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
            );
            //Call send message to deliver message to one of brokers.
            producer.sendOneway(msg);
        }
        //Wait for sending to complete
        Thread.sleep(5000);
        producer.shutdown();
    }

}

  • Consumer。
package com.geek.rocketmq.helloworld;

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.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

public class Consumer {

    public static void main(String[] args) throws MQClientException {
        // 创建消费者 Consumer,指定消费者组名。
        // Instantiate with specified consumer group name.
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group_geek");// "please_rename_unique_group_name"

        // Specify name server addresses.
        consumer.setNamesrvAddr("192.168.142.161:9876");// 默认 localhost。

        // Subscribe one more more topics to consume.
        consumer.subscribe("TopicTest", "*");
        // Register callback to execute on arrival of messages fetched from brokers.
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            /**
             * 消费消息。
             * @param msgs
             * @param context
             * @return 消费结果。
             * ConsumeConcurrentlyStatus.RECONSUME_LATER
             * ConsumeConcurrentlyStatus.CONSUME_SUCCESS
             */
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                            ConsumeConcurrentlyContext context) {
                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
                for (MessageExt msg : msgs) {
                    System.out.println("msg = " + new String(msg.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        //Launch the consumer instance.
        consumer.start();

        System.out.printf("Consumer Started.%n");
    }

}



消息消费。

启动 2 个消费者,测试,默认是负载均衡模式。

    // 消费模式。负载均衡(默认) / 广播。
    // MessageModel.CLUSTERING
    // MessageModel.BROADCASTING
    consumer.setMessageModel(MessageModel.BROADCASTING);
//        consumer.setMessageModel(MessageModel.CLUSTERING);
负载均衡模式。


广播模式。
package com.geek.rocketmq.helloworld;

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.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;

import java.util.List;

public class Consumer {
    public static void main(String[] args) throws MQClientException {
        // 创建消费者 Consumer,指定消费者组名。
        // Instantiate with specified consumer group name.
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group_geek");// "please_rename_unique_group_name"

        // Specify name server addresses.
        consumer.setNamesrvAddr("192.168.142.161:9876");// 默认 localhost。

        // Subscribe one more more topics to consume.
        consumer.subscribe("TopicTest", "*");

        // 消费模式。负载均衡(默认) / 广播。
        // MessageModel.CLUSTERING
        // MessageModel.BROADCASTING
        consumer.setMessageModel(MessageModel.BROADCASTING);
//        consumer.setMessageModel(MessageModel.CLUSTERING);

        // Register callback to execute on arrival of messages fetched from brokers.
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            /**
             * 消费消息。
             * @param msgs
             * @param context
             * @return 消费结果。
             * ConsumeConcurrentlyStatus.RECONSUME_LATER
             * ConsumeConcurrentlyStatus.CONSUME_SUCCESS
             */
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                            ConsumeConcurrentlyContext context) {
                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
                for (MessageExt msg : msgs) {
                    System.out.println("msg = " + new String(msg.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        //Launch the consumer instance.
        consumer.start();

        System.out.printf("Consumer Started.%n");
    }

}



顺序消息。

  • 全局消息顺序。

  • 局部消息顺序。

在这里插入图片描述

package com.geek.rocketmq.order;

import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.MessageQueueSelector;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.remoting.exception.RemotingException;

import java.util.List;

public class Producer {

    public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
        //Instantiate with a producer group name.
        DefaultMQProducer defaultMQProducer = new
                DefaultMQProducer("group_geek");// please_rename_unique_group_name
        // Specify name server addresses.
        defaultMQProducer.setNamesrvAddr("192.168.142.161:9876");
        //Launch the instance.
        defaultMQProducer.start();

        // 构建消息集合。
        List<OrderStep> orderStepList = OrderStep.buildOrders();
        // 发送消息。
        for (int i = 0; i < orderStepList.size(); i++) {
            String body = orderStepList.get(i).toString();
            Message message = new Message("orderTopic", "order", i + "", body.getBytes());
            /**
             * 参数 1 ~ 消息对象。
             * 参数 2 ~ 消息队列选择器。
             * 参数 3 ~ 选择队列的业务标识(订单 id)。
             */
            SendResult sendResult = defaultMQProducer.send(message, new MessageQueueSelector() {
                /**
                 *
                 * @param mqs   队列集合。
                 * @param msg   消息对象。
                 * @param args  选择队列的业务标识(订单 id)。~ orderStepList.get(i).getOrderId()。(send(); 方法的参数 3)。
                 * @return
                 */
                @Override
                public MessageQueue select(List<MessageQueue> mqs, Message msg, Object args) {
                    Long orderId = (Long) args;// 订单 id。
                    long index = orderId % mqs.size();
                    // 根据订单 id 取模的值确定用哪一个队列。同一个订单 id 取模后 index 一样,进入同一个队列。
                    return mqs.get((int) index);
                }
            }, orderStepList.get(i).getOrderId());

            System.out.println(" ~ ~ ~ ~ ~ ~ ~ 发送结果。~ ~ ~ ~ ~ ~ ~");
            System.out.println("sendResult = " + sendResult);
        }
        defaultMQProducer.shutdown();
    }

}

package com.geek.rocketmq.order;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

public class Consumer {

    public static void main(String[] args) throws MQClientException {
        // 创建消费者 Consumer,指定消费者组名。
        // Instantiate with specified consumer group name.
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group_geek");// "please_rename_unique_group_name"

        // Specify name server addresses.
        consumer.setNamesrvAddr("192.168.142.161:9876");// 默认 localhost。

        // Subscribe one more more topics to consume.
        consumer.subscribe("orderTopic", "*");

        // 注册消息监听器。
        consumer.registerMessageListener(new MessageListenerOrderly() {
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
                for (MessageExt messageExt : list) {
                    System.out.println(Thread.currentThread().getName() + " ~ ~ ~ ~ ~ ~ ~ 消费消息。~ ~ ~ ~ ~ ~ ~ " + new String(messageExt.getBody()));
//                    System.out.println("messageExt = " + messageExt);
                }
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });

        // 启动消费者。
        consumer.start();
        System.out.println("消费者启动。");
    }

    /*
消费者启动。
ConsumeMessageThread_2 ~ ~ ~ ~ ~ ~ ~ 消费消息。~ ~ ~ ~ ~ ~ ~ OrderStep(orderId=3, desc=创建)
ConsumeMessageThread_1 ~ ~ ~ ~ ~ ~ ~ 消费消息。~ ~ ~ ~ ~ ~ ~ OrderStep(orderId=2, desc=创建)
ConsumeMessageThread_3 ~ ~ ~ ~ ~ ~ ~ 消费消息。~ ~ ~ ~ ~ ~ ~ OrderStep(orderId=1, desc=创建)
ConsumeMessageThread_1 ~ ~ ~ ~ ~ ~ ~ 消费消息。~ ~ ~ ~ ~ ~ ~ OrderStep(orderId=2, desc=付款)
ConsumeMessageThread_2 ~ ~ ~ ~ ~ ~ ~ 消费消息。~ ~ ~ ~ ~ ~ ~ OrderStep(orderId=3, desc=付款)
ConsumeMessageThread_1 ~ ~ ~ ~ ~ ~ ~ 消费消息。~ ~ ~ ~ ~ ~ ~ OrderStep(orderId=2, desc=推送)
ConsumeMessageThread_3 ~ ~ ~ ~ ~ ~ ~ 消费消息。~ ~ ~ ~ ~ ~ ~ OrderStep(orderId=1, desc=付款)
ConsumeMessageThread_2 ~ ~ ~ ~ ~ ~ ~ 消费消息。~ ~ ~ ~ ~ ~ ~ OrderStep(orderId=3, desc=完成)
ConsumeMessageThread_3 ~ ~ ~ ~ ~ ~ ~ 消费消息。~ ~ ~ ~ ~ ~ ~ OrderStep(orderId=1, desc=推送)
ConsumeMessageThread_3 ~ ~ ~ ~ ~ ~ ~ 消费消息。~ ~ ~ ~ ~ ~ ~ OrderStep(orderId=1, desc=完成)
     */

}

package com.geek.rocketmq.order;

import lombok.Data;

import java.util.ArrayList;
import java.util.List;

@Data
public class OrderStep {

    private Long orderId;
    private String desc;

    public static List<OrderStep> buildOrders() {
        List<OrderStep> list = new ArrayList<>();

        OrderStep orderStep = new OrderStep();
        orderStep.setOrderId(1039L);
        orderStep.setDesc("创建");
        list.add(orderStep);

        orderStep = new OrderStep();
        orderStep.setOrderId(1039L);
        orderStep.setDesc("付款");
        list.add(orderStep);

        orderStep = new OrderStep();
        orderStep.setOrderId(1039L);
        orderStep.setDesc("推送");
        list.add(orderStep);

        orderStep = new OrderStep();
        orderStep.setOrderId(1039L);
        orderStep.setDesc("完成");
        list.add(orderStep);

        orderStep = new OrderStep();
        orderStep.setOrderId(1065L);
        orderStep.setDesc("创建");
        list.add(orderStep);

        orderStep = new OrderStep();
        orderStep.setOrderId(1065L);
        orderStep.setDesc("付款");
        list.add(orderStep);

        orderStep = new OrderStep();
        orderStep.setOrderId(1065L);
        orderStep.setDesc("推送");
        list.add(orderStep);

        orderStep = new OrderStep();
        orderStep.setOrderId(7235L);
        orderStep.setDesc("创建");
        list.add(orderStep);

        orderStep = new OrderStep();
        orderStep.setOrderId(7235L);
        orderStep.setDesc("付款");
        list.add(orderStep);

        orderStep = new OrderStep();
        orderStep.setOrderId(7235L);
        orderStep.setDesc("完成");
        list.add(orderStep);

        return list;
    }

}



延时消息。

package com.geek.rocketmq.delay;

import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;

import java.io.UnsupportedEncodingException;
import java.util.concurrent.TimeUnit;

public class Producer {

    public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException, UnsupportedEncodingException {

        // 创建消息生产者 Producer,并指定生产者组名。
        //Instantiate with a producer group name.
        DefaultMQProducer defaultMQProducer = new
                DefaultMQProducer("group_geek");// please_rename_unique_group_name
        // Specify name server addresses.
        defaultMQProducer.setNamesrvAddr("192.168.142.161:9876");
        //Launch the instance.
        defaultMQProducer.start();
        for (int i = 0; i < 10; i++) {
            //Create a message instance, specifying topic, tag and message body.
            // 创建消息对象,指定主题 Topic、Tag 和消息体。
            /**
             * 参数 1 ~ 消息主题 Topic。
             * 参数 2 ~ tag。
             * 参数 3 ~ 消息内容。
             */
            Message msg = new Message("TopicDelay"/* Topic */,
                    "TagA"/* Tag */,
                    ("Hello RocketMQ " +
                            i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
            );

            // 延时发送。
            msg.setDelayTimeLevel(2);

            //Call send message to deliver message to one of brokers.
            // 发送消息。
            SendResult sendResult = defaultMQProducer.send(msg);
            System.out.printf("%s%n", sendResult);

            TimeUnit.SECONDS.sleep(1);
        }

        defaultMQProducer.shutdown();
    }

}

package com.geek.rocketmq.delay;

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.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

public class Consumer {
    public static void main(String[] args) throws MQClientException {
        // 创建消费者 Consumer,指定消费者组名。
        // Instantiate with specified consumer group name.
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group_geek");// "please_rename_unique_group_name"

        // Specify name server addresses.
        consumer.setNamesrvAddr("192.168.142.161:9876");// 默认 localhost。

        // Subscribe one more more topics to consume.
        consumer.subscribe("TopicDelay", "*");

        // Register callback to execute on arrival of messages fetched from brokers.
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            /**
             * 消费消息。
             * @param msgs
             * @param context
             * @return 消费结果。
             * ConsumeConcurrentlyStatus.RECONSUME_LATER
             * ConsumeConcurrentlyStatus.CONSUME_SUCCESS
             */
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                            ConsumeConcurrentlyContext context) {
//                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
                for (MessageExt msg : msgs) {
                    System.out.println("消息 id ~ ~ ~ " + msg.getMsgId() + "。延迟时间 ~ " + (System.currentTimeMillis() - msg.getBornTimestamp() + "。延迟级别 ~ " + msg.getDelayTimeLevel()));
//                    System.out.println("msg = " + new String(msg.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        //Launch the consumer instance.
        consumer.start();

        System.out.printf("Consumer Started.%n");
    }

}


/*
Consumer Started.
消息 id ~ ~ ~ C0A80068301418B4AAC20F58B96E0000。延迟时间 ~ 5260。延迟级别 ~ 2
消息 id ~ ~ ~ C0A80068301418B4AAC20F58BD640001。延迟时间 ~ 4626。延迟级别 ~ 2
消息 id ~ ~ ~ C0A80068301418B4AAC20F58C1500002。延迟时间 ~ 4637。延迟级别 ~ 2
消息 id ~ ~ ~ C0A80068301418B4AAC20F58C5400003。延迟时间 ~ 4621。延迟级别 ~ 2
消息 id ~ ~ ~ C0A80068301418B4AAC20F58C92C0004。延迟时间 ~ 4621。延迟级别 ~ 2
消息 id ~ ~ ~ C0A80068301418B4AAC20F58CD1C0005。延迟时间 ~ 4622。延迟级别 ~ 2
消息 id ~ ~ ~ C0A80068301418B4AAC20F58D1090006。延迟时间 ~ 4631。延迟级别 ~ 2
消息 id ~ ~ ~ C0A80068301418B4AAC20F58D4F40007。延迟时间 ~ 4622。延迟级别 ~ 2
消息 id ~ ~ ~ C0A80068301418B4AAC20F58D8E00008。延迟时间 ~ 4635。延迟级别 ~ 2
消息 id ~ ~ ~ C0A80068301418B4AAC20F58DCD00009。延迟时间 ~ 4624。延迟级别 ~ 2
 */



批量消息。

package com.geek.rocketmq.batch;

import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 批量发送同步消息。
 */
public class Producer {

    public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException, UnsupportedEncodingException {

        // 创建消息生产者 Producer,并指定生产者组名。
        //Instantiate with a producer group name.
        DefaultMQProducer defaultMQProducer = new
                DefaultMQProducer("group_geek");// please_rename_unique_group_name
        // Specify name server addresses.
        defaultMQProducer.setNamesrvAddr("192.168.142.161:9876");
        //Launch the instance.
        defaultMQProducer.start();

        List<Message> messageList = new ArrayList<>();

        //Create a message instance, specifying topic, tag and message body.
        // 创建消息对象,指定主题 Topic、Tag 和消息体。
        /**
         * 参数 1 ~ 消息主题 Topic。
         * 参数 2 ~ tag。
         * 参数 3 ~ 消息内容。
         */
        Message msg1 = new Message("TopicBatch"/* Topic */,
                "TagA"/* Tag */,
                ("Hello RocketMQ " +
                        1).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
        );
        Message msg2 = new Message("TopicBatch"/* Topic */,
                "TagA"/* Tag */,
                ("Hello RocketMQ " +
                        2).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
        );
        Message msg3 = new Message("TopicBatch"/* Topic */,
                "TagA"/* Tag */,
                ("Hello RocketMQ " +
                        3).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
        );

        messageList.add(msg1);
        messageList.add(msg2);
        messageList.add(msg3);

        //Call send message to deliver message to one of brokers.
        // 发送消息。
        SendResult sendResult = defaultMQProducer.send(messageList);
        System.out.printf("%s%n", sendResult);

        TimeUnit.SECONDS.sleep(1);

        //Shut down once the producer instance is not longer in use.
        defaultMQProducer.shutdown();
    }

}

package com.geek.rocketmq.batch;

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.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;

import java.util.List;

public class Consumer {
    public static void main(String[] args) throws MQClientException {
        // 创建消费者 Consumer,指定消费者组名。
        // Instantiate with specified consumer group name.
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group_geek");// "please_rename_unique_group_name"

        // Specify name server addresses.
        consumer.setNamesrvAddr("192.168.142.161:9876");// 默认 localhost。

        // Subscribe one more more topics to consume.
        consumer.subscribe("TopicBatch", "*");

        // 消费模式。负载均衡(默认) / 广播。
        // MessageModel.CLUSTERING
        // MessageModel.BROADCASTING
        consumer.setMessageModel(MessageModel.BROADCASTING);
//        consumer.setMessageModel(MessageModel.CLUSTERING);

        // Register callback to execute on arrival of messages fetched from brokers.
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            /**
             * 消费消息。
             * @param msgs
             * @param context
             * @return 消费结果。
             * ConsumeConcurrentlyStatus.RECONSUME_LATER
             * ConsumeConcurrentlyStatus.CONSUME_SUCCESS
             */
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                            ConsumeConcurrentlyContext context) {
                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
                for (MessageExt msg : msgs) {
                    System.out.println("msg = " + new String(msg.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        //Launch the consumer instance.
        consumer.start();

        System.out.printf("Consumer Started.%n");
    }

}



批量消息单次不能超过 1 M。
package com.geek.rocketmq.batch;

import org.apache.rocketmq.common.message.Message;

import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class ListSplitter implements Iterator<List<Message>> {

    private final int SIZE_LIMIT = 1000 * 1000;
    private final List<Message> messages;
    private int currIndex;

    public ListSplitter(List<Message> messages) {
        this.messages = messages;
    }

    @Override
    public boolean hasNext() {
        return currIndex < messages.size();
    }

    @Override
    public List<Message> next() {
        int nextIndex = currIndex;
        int totalSize = 0;
        for (; nextIndex < messages.size(); nextIndex++) {
            Message message = messages.get(nextIndex);
            int tmpSize = message.getTopic().length() + message.getBody().length;
            Map<String, String> properties = message.getProperties();
            for (Map.Entry<String, String> entry : properties.entrySet()) {
                tmpSize += entry.getKey().length() + entry.getValue().length();
            }
            tmpSize = tmpSize + 20; //for log overhead
            if (tmpSize > SIZE_LIMIT) {
                //it is unexpected that single message exceeds the SIZE_LIMIT
                //here just let it go, otherwise it will block the splitting process
                if (nextIndex - currIndex == 0) {
                    //if the next sublist has no element, add this one and then break, otherwise just break
                    nextIndex++;
                }
                break;
            }
            if (tmpSize + totalSize > SIZE_LIMIT) {
                break;
            } else {
                totalSize += tmpSize;
            }

        }
        List<Message> subList = messages.subList(currIndex, nextIndex);
        currIndex = nextIndex;
        return subList;
    }

}

package com.geek.rocketmq.batch;

import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

/**
 * 批量发送消息。
 * 单次消息不能超过 1MB。
 */
public class BatchProducer {

    public static void main(String[] args) throws UnsupportedEncodingException, MQClientException {


        // 创建消息生产者 Producer,并指定生产者组名。
        //Instantiate with a producer group name.
        DefaultMQProducer defaultMQProducer = new
                DefaultMQProducer("group_geek");// please_rename_unique_group_name
        // Specify name server addresses.
        defaultMQProducer.setNamesrvAddr("192.168.142.161:9876");
        //Launch the instance.
        defaultMQProducer.start();

        List<Message> messageList = new ArrayList<>();

        //Create a message instance, specifying topic, tag and message body.
        // 创建消息对象,指定主题 Topic、Tag 和消息体。
        /**
         * 参数 1 ~ 消息主题 Topic。
         * 参数 2 ~ tag。
         * 参数 3 ~ 消息内容。
         */
        Message msg1 = new Message("TopicBatch"/* Topic */,
                "TagA"/* Tag */,
                ("Hello RocketMQ " +
                        1).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
        );
        Message msg2 = new Message("TopicBatch"/* Topic */,
                "TagA"/* Tag */,
                ("Hello RocketMQ " +
                        2).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
        );
        Message msg3 = new Message("TopicBatch"/* Topic */,
                "TagA"/* Tag */,
                ("Hello RocketMQ " +
                        3).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
        );

        messageList.add(msg1);
        messageList.add(msg2);
        messageList.add(msg3);

        //then you could split the large list into small ones:
        ListSplitter splitter = new ListSplitter(messageList);
        while (splitter.hasNext()) {
            try {
                List<Message> listItem = splitter.next();
                defaultMQProducer.send(listItem);
            } catch (Exception e) {
                e.printStackTrace();
                //handle the error
            }
        }
    }

}



消息过滤。

在大多数情况下,TAG 是一个简单而有用的设计,其可以用来选择您想要的消息。
eg.

DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_EXAMPLE");
consumer.subscribe("TOPIC", "TAGA || TAGB || TAGC");

使用者将收到包含TAGA或TAGB或TAGC的消息。但是限制是,一条消息只能有一个标签,这可能不适用于复杂的情况。在这种情况下,您可以使用SQL表达式来过滤出消息。

原理。

SQL 功能可以通过您在发送消息时放入的属性进行一些计算。在 RocketMQ 定义的语法下,您可以实现一些有趣的逻辑。这是一个例子:

 - - - - - - -
|   message  |
| - - - - - -| a> 5 AND b ='abc'
|   a = 10   | --------------------> gotten
|   b = 'abc'|
|   c = true |
 - - - - - - -
 - - - - - - -
|   message  |
| - - - - - -| a> 5 AND b ='abc'
|   a = 1    | --------------------> missed
|   b = 'abc'|
|   c = false|
 - - - - - - -


sql 语法。

RocketMQ 只定义了一些基本语法来支持这个特性。你也可以很容易地扩展它。

数值比较,eg. >,>=,<,<=,BETWEEN,=
字符比较,eg. =,<>,IN
IS NULL 或者 IS NOT NULL
逻辑符号 AND,OR,NOT

常量支持类型为:

数值,eg. 123,3.1415
字符,eg. ‘abc’,必须用单引号包裹起来
NULL,特殊的常量
布尔值,TRUE 或 FALSE

只有使用 push 模式的消费者才能用使用 SQL92 标准的 sql 语句,接口如下:

public void subscribe(final String topic, final MessageSelector messageSelector)

  • 消息生产者。

发送消息时,你能通过 putUserProperty 来设置消息的属性。

DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
producer.start();

Message msg = new Message("TopicTest",
    tag,
    ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)
);
// Set some properties.
msg.putUserProperty("a", String.valueOf(i));

SendResult sendResult = producer.send(msg);
   
producer.shutdown();
  • 消息消费者。

通过 sql92 用 MessageSelector.bySql 来使用 sql 筛选消息。

DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4");

// only subsribe messages have property a, also a >=0 and a <= 3
consumer.subscribe("TopicTest", MessageSelector.bySql("a between 0 and 3");

consumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
});
consumer.start();


通过 tag 区分。
package com.geek.rocketmq.filter.tag;

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.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

public class Consumer {

    public static void main(String[] args) throws MQClientException {
        // 创建消费者 Consumer,指定消费者组名。
        // Instantiate with specified consumer group name.
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group_geek");// "please_rename_unique_group_name"

        // Specify name server addresses.
        consumer.setNamesrvAddr("192.168.142.161:9876");// 默认 localhost。

        // Subscribe one more more topics to consume.
        consumer.subscribe("TopicFilterTag", "TagA");
        // Register callback to execute on arrival of messages fetched from brokers.
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            /**
             * 消费消息。
             * @param msgs
             * @param context
             * @return 消费结果。
             * ConsumeConcurrentlyStatus.RECONSUME_LATER
             * ConsumeConcurrentlyStatus.CONSUME_SUCCESS
             */
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                            ConsumeConcurrentlyContext context) {
//                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
                for (MessageExt msg : msgs) {
                    System.out.println("msg = " + new String(msg.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        //Launch the consumer instance.
        consumer.start();

        System.out.printf("Consumer Started.%n");
    }

}

consumer.subscribe(“TopicFilterTag”, “TagA || TagB”);
consumer.subscribe(“TopicFilterTag”, “*”);

package com.geek.rocketmq.filter.tag;

import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;

import java.io.UnsupportedEncodingException;
import java.util.concurrent.TimeUnit;

public class Producer {

    public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException, UnsupportedEncodingException {

        // 创建消息生产者 Producer,并指定生产者组名。
        //Instantiate with a producer group name.
        DefaultMQProducer defaultMQProducer = new
                DefaultMQProducer("group_geek");
        // Specify name server addresses.
        defaultMQProducer.setNamesrvAddr("192.168.142.161:9876");
        //Launch the instance.
        defaultMQProducer.start();
        for (int i = 0; i < 3; i++) {
            //Create a message instance, specifying topic, tag and message body.
            // 创建消息对象,指定主题 Topic、Tag 和消息体。
            /**
             * 参数 1 ~ 消息主题 Topic。
             * 参数 2 ~ tag。
             * 参数 3 ~ 消息内容。
             */
            Message msg = new Message("TopicFilterTag"/* Topic */,
                    "TagA"/* Tag */,
                    ("Hello RocketMQ " +
                            i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
            );
            //Call send message to deliver message to one of brokers.
            // 发送消息。
            SendResult sendResult = defaultMQProducer.send(msg);
            System.out.printf("%s%n", sendResult);

            TimeUnit.SECONDS.sleep(1);
        }
        //Shut down once the producer instance is not longer in use.
        defaultMQProducer.shutdown();
    }

}



通过 sql 过滤。
package com.geek.rocketmq.filter.sql;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.MessageSelector;
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.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

public class Consumer {

    public static void main(String[] args) throws MQClientException {
        // 创建消费者 Consumer,指定消费者组名。
        // Instantiate with specified consumer group name.
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group_geek");// "please_rename_unique_group_name"

        // Specify name server addresses.
        consumer.setNamesrvAddr("192.168.142.161:9876");// 默认 localhost。

        // Subscribe one more more topics to consume.
        consumer.subscribe("TopicFilterSQL", MessageSelector.bySql("i > 5"));
        // Register callback to execute on arrival of messages fetched from brokers.
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            /**
             * 消费消息。
             * @param msgs
             * @param context
             * @return 消费结果。
             * ConsumeConcurrentlyStatus.RECONSUME_LATER
             * ConsumeConcurrentlyStatus.CONSUME_SUCCESS
             */
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                            ConsumeConcurrentlyContext context) {
//                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
                for (MessageExt msg : msgs) {
                    System.out.println("msg = " + new String(msg.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        //Launch the consumer instance.
        consumer.start();

        System.out.printf("Consumer Started.%n");
    }

}

package com.geek.rocketmq.filter.sql;

import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;

import java.io.UnsupportedEncodingException;
import java.util.concurrent.TimeUnit;

public class Producer {

    public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException, UnsupportedEncodingException {

        // 创建消息生产者 Producer,并指定生产者组名。
        //Instantiate with a producer group name.
        DefaultMQProducer defaultMQProducer = new
                DefaultMQProducer("group_geek");
        // Specify name server addresses.
        defaultMQProducer.setNamesrvAddr("192.168.142.161:9876");
        //Launch the instance.
        defaultMQProducer.start();
        for (int i = 0; i < 10; i++) {
            //Create a message instance, specifying topic, tag and message body.
            // 创建消息对象,指定主题 Topic、Tag 和消息体。
            /**
             * 参数 1 ~ 消息主题 Topic。
             * 参数 2 ~ tag。
             * 参数 3 ~ 消息内容。
             */
            Message msg = new Message("TopicFilterSQL"/* Topic */,
                    "TagA"/* Tag */,
                    ("Hello RocketMQ " +
                            i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
            );

            msg.putUserProperty("i", String.valueOf(i));

            //Call send message to deliver message to one of brokers.
            // 发送消息。
            SendResult sendResult = defaultMQProducer.send(msg);
            System.out.printf("%s%n", sendResult);

            TimeUnit.SECONDS.sleep(1);
        }
        //Shut down once the producer instance is not longer in use.
        defaultMQProducer.shutdown();
    }

}

Exception in thread “main” org.apache.rocketmq.client.exception.MQClientException: CODE: 1 DESC: The broker does not support consumer to filter message by SQL92

修改 [geek@192 rocketmq-all-4.7.1-bin-release]$ sudo vim conf/broker.conf,添加

enablePropertyFilter=true

重启。

[geek@192 rocketmq-all-4.7.1-bin-release]$ sh bin/mqshutdown broker
[geek@192 rocketmq-all-4.7.1-bin-release]$ sh bin/mqshutdown namesrv
[geek@192 rocketmq-all-4.7.1-bin-release]$ nohup sh bin/mqnamesrv &
[geek@192 rocketmq-all-4.7.1-bin-release]$ nohup sh bin/mqbroker -c conf/broker.conf -n localhost:9876 &


事务消息。

package com.geek.rocketmq.transaction;

import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;

import java.io.UnsupportedEncodingException;
import java.util.concurrent.TimeUnit;

public class Producer {

    public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException, UnsupportedEncodingException {

        // 创建消息生产者 Producer,并指定生产者组名。
        //Instantiate with a producer group name.
        // 事务消息生产者。
        TransactionMQProducer transactionMQProducer = new
                TransactionMQProducer("group_geek");// please_rename_unique_group_name
        // Specify name server addresses.
        transactionMQProducer.setNamesrvAddr("192.168.142.161:9876");

        // 事务监听器。
        transactionMQProducer.setTransactionListener(new TransactionListener() {
            /**
             * 执行本地事务。
             * @param message
             * @param o
             * @return
             */
            @Override
            public LocalTransactionState executeLocalTransaction(Message message, Object o) {
                if (StringUtils.equals("TagA", message.getTags())) {
                    return LocalTransactionState.COMMIT_MESSAGE;
                } else if (StringUtils.equals("TagB", message.getTags())) {
                    return LocalTransactionState.ROLLBACK_MESSAGE;
                } else if (StringUtils.equals("TagC", message.getTags())) {
                    return LocalTransactionState.UNKNOW;// 不做处理。之后进行回查,↓ 方法。commit。
                }
                return LocalTransactionState.UNKNOW;
            }
            /**
             * mq 消息事务状态回查。
             * @param messageExt
             * @return
             */
            @Override
            public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
                System.out.println("消息的 Tag ~ " + messageExt.getTags());
                return LocalTransactionState.COMMIT_MESSAGE;
            }
        });

        //Launch the instance.
        transactionMQProducer.start();

        String[] tags = new String[]{"TagA", "TagB", "TagC"};

        for (int i = 0; i < 3; i++) {
            //Create a message instance, specifying topic, tag and message body.
            // 创建消息对象,指定主题 Topic、Tag 和消息体。
            /**
             * 参数 1 ~ 消息主题 Topic。
             * 参数 2 ~ tag。
             * 参数 3 ~ 消息内容。
             */
            Message msg = new Message("TopicTransaction"/* Topic */,
                    tags[i]/* Tag */,
                    ("Hello RocketMQ " +
                            i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
            );
            //Call send message to deliver message to one of brokers.
            // 发送消息。
            SendResult sendResult = transactionMQProducer.sendMessageInTransaction(msg, null);
            System.out.printf("%s%n", sendResult);

            TimeUnit.SECONDS.sleep(1);
        }
        //Shut down once the producer instance is not longer in use.
//        transactionMQProducer.shutdown();
    }

}

package com.geek.rocketmq.transaction;

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.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

public class Consumer {

    public static void main(String[] args) throws MQClientException {
        // 创建消费者 Consumer,指定消费者组名。
        // Instantiate with specified consumer group name.
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group_geek");// "please_rename_unique_group_name"

        // Specify name server addresses.
        consumer.setNamesrvAddr("192.168.142.161:9876");// 默认 localhost。

        // Subscribe one more more topics to consume.
        consumer.subscribe("TopicTransaction", "*");
        // Register callback to execute on arrival of messages fetched from brokers.
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            /**
             * 消费消息。
             * @param msgs
             * @param context
             * @return 消费结果。
             * ConsumeConcurrentlyStatus.RECONSUME_LATER
             * ConsumeConcurrentlyStatus.CONSUME_SUCCESS
             */
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                            ConsumeConcurrentlyContext context) {
                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
                for (MessageExt msg : msgs) {
                    System.out.println("msg = " + new String(msg.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        //Launch the consumer instance.
        consumer.start();

        System.out.printf("Consumer Started.%n");
    }

}



Spring Boot 集成 RocketMQ。

        <!-- https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-spring-boot-starter -->
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.0.4</version>
        </dependency>
  • application.properties。
rocketmq.name-server=192.168.142.161:9876
rocketmq.producer.group=group_geek

package com.geek.rocketmqspringboot;

import lombok.extern.slf4j.Slf4j;
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
@Slf4j
public class RocketmqSpringbootApplicationTests {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Test
    public void contextLoads() {
        // void send(D var1, Message<?> var2) throws MessagingException;
        // void send(Message<?> var1) throws MessagingException;
//        rocketMQTemplate.send()

        rocketMQTemplate.convertAndSend("springboot-rocketmq", "hello springboot rocketmq.");
        log.info("消息发送成功。");
    }

}

  • 消费者。
rocketmq.name-server=192.168.142.161:9876
rocketmq.consumer.group=group_geek
package com.geek.rocketmqspringbootconsumer.listener;

import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

@RocketMQMessageListener(topic = "springboot-rocketmq", consumerGroup = "${rocketmq.consumer.group}")
@Component
public class Consumer implements RocketMQListener<String> {

    @Override
    public void onMessage(String s) {
        System.out.println("s = " + s);
    }

}

package com.geek.rocketmqspringbootconsumer;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author geek
 */
@SpringBootApplication
@Slf4j
public class RocketmqSpringbootConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(RocketmqSpringbootConsumerApplication.class, args);

        log.info("消费者启动成功。");

    }

}



Dubbo ~ Zookeeper 集群。

修改 3 个配置文件。

# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just 
# example sakes.
dataDir=/home/geek/geek/tools_my/zookeeper-cluster/zookeeper-1/data
# the port at which the clients will connect
clientPort=2181
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just 
# example sakes.
dataDir=/home/geek/geek/tools_my/zookeeper-cluster/zookeeper-2/data
# the port at which the clients will connect
clientPort=2182
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just 
# example sakes.
dataDir=/home/geek/geek/tools_my/zookeeper-cluster/zookeeper-3/data
# the port at which the clients will connect
clientPort=2183
[geek@192 zookeeper-cluster]$ sudo vim zookeeper-1/apache-zookeeper-3.6.2-bin/conf/zoo.cfg 
[geek@192 zookeeper-cluster]$ sudo vim zookeeper-2/apache-zookeeper-3.6.2-bin/conf/zoo.cfg 
[geek@192 zookeeper-cluster]$ sudo vim zookeeper-3/apache-zookeeper-3.6.2-bin/conf/zoo.cfg 

# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial 
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between 
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just 
# example sakes.
dataDir=/home/geek/geek/tools_my/zookeeper-cluster/zookeeper-3/data
# the port at which the clients will connect
clientPort=2183
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the 
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1

## Metrics Providers
#
# https://prometheus.io Metrics Exporter
#metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider
#metricsProvider.httpPort=7000
#metricsProvider.exportJvmInfo=true
  • 配置集群。

在每个 zookeeper 的 data 目录下分别创建 myid 文件,内容分别是 1 2 3,记录每个服务器的 id。

在每个 zookeeper 的 zoo.cfg 配置客户端访问端口(clientPort)和集群服务器 IP 列表。

[geek@192 zookeeper-cluster]$ sudo vim zookeeper-1/apache-zookeeper-3.6.2-bin/conf/zoo.cfg 
[sudo] password for geek: 
[geek@192 zookeeper-cluster]$ sudo vim zookeeper-2/apache-zookeeper-3.6.2-bin/conf/zoo.cfg 
[geek@192 zookeeper-cluster]$ sudo vim zookeeper-3/apache-zookeeper-3.6.2-bin/conf/zoo.cfg

server.1=192.168.142.161:2881:3881
server.2=192.168.142.161:2882:3882
server.3=192.168.142.161:2883:3883

server.服务器 id = 服务器 IP 地址:服务器之间通信端口:服务器之间投票选举端口。

[geek@192 zookeeper-cluster]$ echo 1 > zookeeper-1/data/myid
[geek@192 zookeeper-cluster]$ echo 2 > zookeeper-2/data/myid
[geek@192 zookeeper-cluster]$ echo 3 > zookeeper-3/data/myid
[geek@192 zookeeper-cluster]$ cat zookeeper-1/data/myid 
1
[geek@192 zookeeper-cluster]$ cat zookeeper-2/data/myid 
2
[geek@192 zookeeper-cluster]$ cat zookeeper-3/data/myid 
3

  • 启动每个 Zookeeper。
[geek@192 zookeeper-cluster]$ zookeeper-1/apache-zookeeper-3.6.2-bin/bin/zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /home/geek/geek/tools_my/zookeeper-cluster/zookeeper-1/apache-zookeeper-3.6.2-bin/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
[geek@192 zookeeper-cluster]$ zookeeper-2/apache-zookeeper-3.6.2-bin/bin/zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /home/geek/geek/tools_my/zookeeper-cluster/zookeeper-2/apache-zookeeper-3.6.2-bin/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
[geek@192 zookeeper-cluster]$ zookeeper-3/apache-zookeeper-3.6.2-bin/bin/zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /home/geek/geek/tools_my/zookeeper-cluster/zookeeper-3/apache-zookeeper-3.6.2-bin/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED

status 得知 2 是主节点。

[geek@192 zookeeper-cluster]$ zookeeper-1/apache-zookeeper-3.6.2-bin/bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /home/geek/geek/tools_my/zookeeper-cluster/zookeeper-1/apache-zookeeper-3.6.2-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: follower
[geek@192 zookeeper-cluster]$ zookeeper-2/apache-zookeeper-3.6.2-bin/bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /home/geek/geek/tools_my/zookeeper-cluster/zookeeper-2/apache-zookeeper-3.6.2-bin/bin/../conf/zoo.cfg
Client port found: 2182. Client address: localhost. Client SSL: false.
Mode: leader
[geek@192 zookeeper-cluster]$ zookeeper-3/apache-zookeeper-3.6.2-bin/bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /home/geek/geek/tools_my/zookeeper-cluster/zookeeper-3/apache-zookeeper-3.6.2-bin/bin/../conf/zoo.cfg
Client port found: 2183. Client address: localhost. Client SSL: false.
Mode: follower



RPC 服务接口。

springboot-dubbo-interface。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.geek</groupId>
    <artifactId>springboot-dubbo-interface</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-dubbo-interface</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>com.alibaba.spring.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

  • 接口。
package com.geek.springbootdubbointerface.service;

public interface IUserService {

    String sayHello(String name);

}



springboot-dubbo-provider。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.geek</groupId>
    <artifactId>springboot-dubbo-provider</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-dubbo-provider</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>com.geek</groupId>
            <artifactId>springboot-dubbo-interface</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <!-- dubbo。-->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.10</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.101tec</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.10</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-log4j12</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.alibaba.spring.boot/dubbo-spring-boot-starter -->
        <dependency>
            <groupId>com.alibaba.spring.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>log4j-to-slf4j</artifactId>
                    <groupId>org.apache.logging.log4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

  • 配置。
spring.application.name=dubbo-demo-provider
spring.dubbo.application.id=dubbo-demo-provider
spring.dubbo.application.name=dubbo-demo-provider
spring.dubbo.registry.address=zookeeper://192.168.142.161:2181;zookeeper://192.168.142.161:2182;zookeeper://192.168.142.161:2183
spring.dubbo.server=true
spring.dubbo.protocol.name=dubbo
spring.dubbo.protocol.port=20880

  • 启动类 ~ @EnableDubboConfiguration。
package com.geek.springbootdubboprovider;

import com.alibaba.dubbo.spring.boot.annotation.EnableDubboConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@EnableDubboConfiguration
@SpringBootApplication
public class SpringbootDubboProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootDubboProviderApplication.class, args);
    }

}

  • 服务实现。
package com.geek.springbootdubboprovider.service.impl;

import com.geek.springbootdubbointerface.service.IUserService;
import org.springframework.stereotype.Service;

@Service// @Component// 新版本可以不写。
@com.alibaba.dubbo.config.annotation.Service(interfaceClass = IUserService.class)
public class UserServiceImpl implements IUserService {

    @Override
    public String sayHello(String name) {
        return "hello ~ " + name;
    }

}



springboot-dubbo-consumer。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.geek</groupId>
    <artifactId>springboot-dubbo-consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-dubbo-consumer</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- api。-->
        <dependency>
            <groupId>com.geek</groupId>
            <artifactId>springboot-dubbo-interface</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <!-- dubbo。-->
        <dependency>
            <groupId>com.alibaba.spring.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>log4j-to-slf4j</artifactId>
                    <groupId>org.apache.logging.log4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--zookeeper-->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.10</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.101tec</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.9</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-log4j12</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

spring.application.name=dubbo-demo-consumer
spring.dubbo.application.id=dubbo-demo-consumer
spring.dubbo.application.name=dubbo-demo-consumer
spring.dubbo.registry.address=zookeeper://192.168.142.161:2181;zookeeper://192.168.142.161:2182;zookeeper://192.168.142.161:2183

  • 启动类。
package com.geek.springbootdubboconsumer;

import com.alibaba.dubbo.spring.boot.annotation.EnableDubboConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@EnableDubboConfiguration
@SpringBootApplication
public class SpringbootDubboConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootDubboConsumerApplication.class, args);
    }

}

  • controller。
package com.geek.springbootdubboconsumer.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.geek.springbootdubbointerface.service.IUserService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {

    @Reference
    private IUserService userService;

    @RequestMapping("/sayHello")
    public String sayHello(String name) {
        return userService.sayHello(name);
    }

}



RocketMQ 高级。

消息存储。

分布式队列因为有高可靠性的要求,所以数据要进行持久化存储。

存储介质。
关系型数据库 DB。

Apache 下开源的另外一款 MQ ~ ActiveMQ(默认采用的 KahaDB 作消息存储)可选用 JDBC 的方式来做消息持久化,通过简单的 xml 配置信息即可实现 JDBC 消息存储。由于普通关系型数据库(如 MySQL)在单表数据量达到千万级别的情况下,其 IO 读写性能往往会出现瓶颈。在可靠性方面,该种方案非常依赖 DB,如果一旦 DB 出现故障,则 MQ 的消息就无法落盘存储会导致线上故障。



文件系统。

目前业界较为常用的几款产品(RocketMQ / Kafka / RabbitMQ)均采用的是消息刷盘至所部署虚拟机/物理机的文件系统来做持久化(刷盘一般可以分为异步刷盘同步刷盘两种模式)。消息刷盘为消息存储提供了一种高效率、高可靠性和高性能的数据持久化方式。除非部署 MQ 机器本身或是本地磁盘挂了,否则一般是不会出现无法持久化的故障问题。

文件系统 > 关系型数据库 DB。


消息的存储和发送。

消息存储。

磁盘如果使用得当,磁盘的速度完全可以匹配上网络 的数据传输速度。目前的高性能磁盘,顺序写速度可以达到 600MB/s,超过了一般网卡的传输速度。但是磁盘随机写的速度只有大概 100KB/s,和顺序写的性能相差 6000 倍!因为有如此巨大的速度差别,好的消息队列系统会比普通的消息队列系统速度快多个数量级。RocketMQ 的消息用顺序写,保证了消息存储的速度。

Linux 操作系统分为用户态内核态,文件操作、网络操作需要涉及这两种形态的切换,免不了进行数据复制。

一台服务器把本机磁盘文件的内容发送到客户端,一般分为两个步骤:

  • read;读取本地文件内容;
  • write;将读取的内容通过网络发送出去。

这两个看似简单的操作,实际进行了4 次数据复制,分别是:

  1. 从磁盘复制数据到内核态内存;
  2. 从内核态内存复制到用户态内存;
  3. 然后从用户态内存复制到网络驱动的内核态内存;
  4. 最后是从网络驱动的内核态内存复制到网卡中进行传输。

在这里插入图片描述
通过使用 mmap 的方式,可以省去向用户态的内存复制,提高速度。这种机制在 Java 中是通过 MappedByteBuffer 实现的。RocketMQ 充分利用了上述特性,也就是所谓的零拷贝技术,提高消息存盘和网络发送的速度。(不用经过用户态)。

这里需要注意的是,采用 MappedByteBuffer 这种内存映射的方式有几个限制,其中之一是一次只能映射 1.5~2G 的文件至用户态的虚拟内存,这也是为何 RocketMQ 默认设置单个 CommitLog 日志数据文件为 1G 的原因了。

[geek@192 2m-2s-sync]$ ls /home/geek/store/consumequeue/orderTopic/1/00000000000000000000 -l
-rw-rw-r--. 1 geek geek 6000000 Dec  2 20:46 /home/geek/store/consumequeue/orderTopic/1/00000000000000000000



消息存储结构。

RocketMQ 消息的存储是由 ConsumeQueue 和 CommitLog 配合完成 的,消息真正的物理存储文件是 CommitLog,ConsumeQueue 是消息的逻辑队列,类似数据库的索引文件,存储的是指向物理存储的地址。每 个 Topic下的每个 Message Queue 都有一个对应的 ConsumeQueue 文件。

CommitLog ~ 存储消息的元数据。
ConsumerQueue ~ 存储消息在 CommitLog 的索引。
IndexFile ~ 为消息查询提供了一种通过 key 或时间区间来查询消息的方法,不影响发送与消费消息的主流程。

在这里插入图片描述



刷盘机制。

在这里插入图片描述

RocketMQ 的消息是存储到磁盘上的,这样既能保证断电后恢复,又可以让存储的消息量超出内存的限制。RocketMQ 为了提高性能,会尽可能地保证磁盘的顺序写。消息在通过 Producer 写入 RocketMQ 的时候,有两种写磁盘方式,分布式同步刷盘和异步刷盘。

同步刷盘。

在返回写成功状态时,消息已经被写入磁盘。具体流程是,消息写入内存的 PAGECACHE 后,立刻通知刷盘线程刷盘,然后等待刷盘完成,刷盘线程执行完成后唤醒等待的线程,返回消息写成功的状态。



异步刷盘。

在返回写成功状态时,消息可能只是被写入了内存的 PAGECACHE,写操作的返回快,吞吐量大。当内存里的消息量积累到一定程度时,统一触发写磁盘动作,快速写入。



配置。

同步刷盘还是异步刷盘,都是通过 Broker 配置文件里的 flushDiskType 参数设置的,这个参数被配置成 SYNC_FLUSH、ASYNC_FLUSH 中的 一个。



RocketMQ 高可用。

在这里插入图片描述

RocketMQ 分布式集群是通过 Master 和 Slave 的配合达到高可用性的。

Master 和 Slave 的区别:在 Broker 的配置文件中,参数 brokerId 的值为 0 表明这个 Broker 是 Master,大于 0 表明这个 Broker 是 Slave,同时 brokerRole 参数也会说明这个 Broker 是 Master 还是 Slave。

Master 角色的 Broker 支持读和写,Slave 角色的 Broker 仅支持读,也就是 Producer 只能和 Master 角色的 Broker 连接写入消息;Consumer 可以连接 Master 角色的 Broker,也可以连接 Slave 角色的 Broker 来读取消息。



消息消费高可用。

在 Consumer 的配置文件中,并不需要设置是从 Master 读还是从 Slave 读,当 Master 不可用或者繁忙的时候,Consumer 会被自动切换到从 Slave 读。有了自动切换 Consumer 这种机制,当一个 Master 角色的机器出现故障后,Consumer 仍然可以从 Slave 读取消息,不影响 Consumer 程序。这就达到了消费端的高可用性。



消息发送高可用。

在创建 Topic 的时候,把 Topic 的多个 Message Queue 创建在多个 Broker 组上(相同 Broker 名称,不同 brokerId 的机器组成一个 Broker 组),这样当一个 Broker 组的 Master 不可用后,其他组的 Master 仍然可用,Producer 仍然可以发送消息。 RocketMQ 目前还不支持把 Slave 自动转成 Master,如果机器资源不足,需要把 Slave 转成 Master,则要手动停止 Slave 角色的Broker,更改配置文件,用新的配置文件启动 Broker。



消息主从复制。

如果一个 Broker 组有 Master 和 Slave,消息需要从 Master 复制到 Slave 上,有同步和异步两种复制方式。

同步复制。

同步复制方式是等 Master 和 Slave均写成功后才反馈给客户端写成功状态。

在同步复制方式下,如果 Master 出故障, Slave 上有全部的备份数据,容易恢复,但是同步复制会增大数据写入延迟,降低系统吞吐量。



异步复制。

异步复制方式是只要 Master 写成功 即可反馈给客户端写成功状态。
在异步复制方式下,系统拥有较低的延迟和较高的吞吐量,但是如果 Master 出了故障,有些数据因为没有被写入 Slave,有可能会丢失。



配置。

同步复制和异步复制是通过 Broker 配置文件里的 brokerRole 参数进行设置的,这个参数可以被设置成 ASYNC_MASTER、 SYNC_MASTER、SLAVE 三个值中的一个。

实际应用中要结合业务场景,合理设置刷盘方式和主从复制方式, 尤其是 SYNC_FLUSH 方式,由于频繁地触发磁盘写动作,会明显降低性能。通常情况下,应该把 Master 和 Save 配置成 ASYNC_FLUSH 的刷盘方式,主从之间配置成 SYNC_MASTER 的复制方式,这样即使有一台机器出故障,仍然能保证数据不丢,是个不错的选择。



负载均衡。

Producer 负载均衡。

Producer 端,每个实例在发消息的时候,默认会轮询所有的 message queue 发送,以达到让消息平均落在不同的 queue 上。而由于 queue 可以散落在不同的 broker,所以消息就发送到不同的 broker 下。



Consumer 负载均衡。

在集群消费模式下,每条消息只需要投递到订阅这个 topic 的 Consumer Group 下的一个实例即可。

RocketMQ 采用主动拉取的方式拉取并消费消息,在拉取的时候需要明确指定拉取哪一条 message queue。

而每当实例的数量有变更,都会触发一次所有实例的负载均衡,这时候会按照 queue 的数量和实例的数量平均分配 queue 给每个实例。



消息重试。

顺序消息的重试。

对于顺序消息,当消费者消费消息失败后,消息队列 RocketMQ 会自动不断进行消息重试(每次间隔时间为 1 秒),这时,应用会出现消息消费被阻塞的情况。因此,在使用顺序消息时,务必保证应用能
够及时监控并处理消费失败的情况,避免阻塞现象的发生。



无序消息的重试。

对于无序消息(普通、定时、延时、事务消息),当消费者消费消息失败时,可以通过设置返回状态达到消息重试的结果。

无序消息的重试只针对集群消费方式生效;广播方式不提供失败重试特性,即消费失败后,失败消息不再重试,继续消费新的消息。

重试次数。

消息队列 RocketMQ 默认允许每条消息最多重试 16 次,每次重试的间隔时间如下:

第几次重试与上一次重试的时间间隔
110 秒
230 秒
31 分钟
42 分钟
53 分钟
64 分钟
75 分钟
86 分钟
97 分钟
108 分钟
119 分钟
1210 分钟
1320 分钟
1430 分钟
151 小时
162 小时

如果消息重试 16 次后仍然失败,消息将不再投递。如果严格按照上述重试时间间隔计算,某条消息在一直消费失败的前提下,将会在接下来的 4 小时 46 分钟之内进行 16 次重试,超过这个时间范围消息
将不再重试投递。

注意:一条消息无论重试多少次,这些重试消息的 Message ID 不会改变。



配置方式。
消费失败后,重试配置方式。

集群消费方式下,消息消费失败后期望消息重试,需要在消息监听器接口的实现中明确进行配置(三种方式任选一种)。

  • 返回 Action.ReconsumeLater(推荐)。

  • 返回 Null。

  • 抛出异常。


public class MQListenerImpl implements MessageListener {

    @Override
    public Action consume(Message message, ConsumeContext context) {
        // 处理消息。
        doConsumeMessage(message);
        // 方式 1:返回 Action.ReconsumeLater,消息将重试。
        return Action.ReconsumeLater;
        // 方式 2:返回 null,消息将重试。
        return null;
        // 方式 3:直接抛出异常,消息将重试。
        throw new RuntimeException("Consumer Message exception");
    }

}



消费失败后,不重试配置方式。

集群消费方式下,消息失败后期望消息不重试,需要捕获消费逻辑中可能抛出的异常,最终返回 Action.CommitMessage,此后这条消息将不会再重试。

public class MQListenerImpl implements MessageListener {

    public Action consume(Message message, ConsumeContext context) {
        try {
            doConsumeMessage(message);
        } catch (Throwable e) {
            // 捕获消费逻辑中的所有异常,并返回 Action.CommitMessage;
            return Action.CommitMessage;
        }
        // 消息处理正常,直接返回 Action.CommitMessage;
        return Action.CommitMessage;
    }

}



自定义消息最大重试次数。

消息队列 RocketMQ 允许 Consumer 启动的时候设置最大重试次数,重试时间间隔将按照如下策略:
最大重试次数小于等于 16 次,则重试时间间隔同上表描述。
最大重试次数大于 16 次,超过 16 次的重试时间间隔均为每次 2 小时。

注意。

消息最大重试次数的设置对相同 Group ID 下的所有 Consumer 实例有效。
如果只对相同 Group ID 下两个 Consumer 实例中的其中一个设置了 MaxReconsumeTimes,那么该配置对两个 Consumer 实例均生效。
配置采用覆盖的方式生效,即最后启动的 Consumer 实例会覆盖之前的启动实例的配置。



获取消息重试次数。



死信队列。

当一条消息初次消费失败,消息队列 RocketMQ 会自动进行消息重试;达到最大重试次数后,若消费依然失败,则表明消费者在正常情况下无法正确地消费该消息,此时,消息队列 RocketMQ 不会立刻
将消息丢弃,而是将其发送到该消费者对应的特殊队列中。

在消息队列 RocketMQ 中,这种正常情况下无法被消费的消息称为死信消息(Dead-Letter Message),存储死信消息的特殊队列称为死信队列(Dead-Letter Queue)。

  • 死信特征。

不会再被消费者正常消费。

有效期与正常消息相同,均为 3 天,3 天后会被自动删除。因此,请在死信消息产生后的 3 天内及时处理。

死信队列具有以下特性。

一个死信队列对应一个 Group ID, 而不是对应单个消费者实例。
如果一个 Group ID 未产生死信消息,消息队列 RocketMQ 不会为其创建相应的死信队列。

一个死信队列包含了对应 Group ID 产生的所有死信消息,不论该消息属于哪个 Topic。



消费幂等。

消息队列 RocketMQ 消费者在接收到消息以后,有必要根据业务上的唯一 Key 对消息做幂等处理的必要性。

消费幂等的必要性。
发送时消息重复。

当一条消息已被成功发送到服务端并完成持久化,此时出现了网络闪断或者客户端宕机,导致服务端对客户端应答失败。 如果此时生产者意识到消息发送失败并尝试再次发送消息,消费者后续会收到两条内容相同并且 Message ID 也相同的消息。



投递时消息重复。

消息消费的场景下,消息已投递到消费者并完成业务处理,当客户端给服务端反馈应答的时候网络闪断。 为了保证消息至少被消费一次,消息队列 RocketMQ 的服务端将在网络恢复后再次尝试投
递之前已被处理过的消息,消费者后续会收到两条内容相同并且 Message ID 也相同的消息。



负载均衡时消息重复(包括但不限于网络抖动、Broker 重启以及订阅方应用重启)。

当消息队列 RocketMQ 的 Broker 或客户端重启、扩容或缩容时,会触发 Rebalance,此时消费者可能会收到重复消息。



处理方式。

因为 Message ID 有可能出现冲突(重复)的情况,所以真正安全的幂等处理,不建议以 Message ID 作为处理依据。 最好的方式是以业务唯一标识作为幂等处理的关键依据,而业务的唯一标识可以通过消
息 Key 进行设置。

Message message = new Message();
message.setKey("ORDERID_100");
SendResult sendResult = producer.send(message);

订阅方收到消息时可以根据消息的 Key 进行幂等处理。

consumer.subscribe("ons_test", "*", new MessageListener() {
public Action consume(Message message, ConsumeContext context) {
String key = message.getKey()
// 根据业务唯一标识的 key 做幂等处理。
}
});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lyfGeek

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值