RocketMq

  • 什么是RocketMq
  • RocketMq的架构图
  • Name Server
  • Broker
  • Topic主题
  • 手动创建
  • 分为两种创建模式
  • 集群模式
  • broker模式
  • 参数
  • 主题名称
  • 写队列数量
  • 读队列数量
  • 权限
  • 源码分析
  • 自动创建(通过系统自动创建的Topic从而来创建用户自定义的Topic)
  • 源码分析
  • 生产者
  • 消费者
  • RocketMq的模型
  • RocketMq的应用场景
  • 异步解耦
  • 限流
  • 分布式事务的数据一致性
  • 消息的顺序发送和接收
  • RocketMq的消息类型
  • 发布消息
  • 普通消息
  • 同步发送
  • 原理
  • 应用场景
  • 生产者示例代码
  • 消费者示例代码
  • 异步发送
  • 原理
  • 应用场景
  • 生产者示例代码
  • 消费者示例代码
  • 单向发送
  • 原理
  • 应用场景
  • 生产者示例代码
  • 消费者示例代码
  • 三种发送方式的对比区别
  • 延时/定时消息
  • 延时消息
  • 生产者示例代码
  • 消费者示例代码
  • 定时消息
  • 生产者示例代码
  • 消费者示例代码
  • 事务消息
  • 概念介绍
  • 事务消息
  • 半事务消息
  • 消息回查
  • 分布式事务的流程
  • 事务消息发送步骤
  • 事务消息回查步骤
  • 分布式事务的消息使用规则
  • 消息状态的类别
  • 生产者生产消息的规则
  • 消费者消费消息的规则
  • 生产者示例代码
  • 消费者示例代码
  • 服务端回查的示例代码
  • 顺序消息
  • 什么是顺序消息
  • 分区顺序消息
  • 全局顺序消息
  • 生产者示例代码
  • 消费者示例代码
  • RocketMq的消息类型对比
  • 订阅消息
  • 订阅方式
  • 集群订阅
  • 广播订阅
  • 获取消息的方式
  • Push(被动)
  • push示例代码
  • push批量消费示例代码
  • Pull(主动)
  • 集群消费和广播消费的差异
  • RocketMq的Message对象
  • 构造方法
  • 形参
  • topic
  • tags
  • keys
  • flag
  • body
  • waitStoreMsgOK


什么是RocketMq

消息队列RocketMQ版在任何一个环境都是可扩展的,生产者必须是一个集群,消息服务器必须是一个集群,消费者也同样。集群级别的高可用,是消息队列RocketMQ版跟其他的消息服务器的主要区别,消息生产者发送一条消息到消息服务器,消息服务器会随机的选择一个消费者,只要这个消费者消费成功就认为是成功了。

RocketMq的架构图

信创中间件 消息队列_java

Name Server

是一个几乎无状态节点,可集群部署,在消息队列RocketMQ版中提供命名服务,更新和发现Broker服务。

Broker

消息中转角色,负责存储消息,转发消息。分为Master Broker和Slave Broker,一个Master Broker可以对应多个Slave Broker,但是一个Slave Broker只能对应一个Master Broker。Broker启动后需要完成一次将自己注册至Name Server的操作;随后每隔30s定期向Name Server上报Topic路由信息。

Topic主题

主题是消息队列RocketMQ版中消息传输和存储的顶层容器,用于标识同一类业务逻辑的消息

手动创建

手动创建即在控制面板中创建Topic

信创中间件 消息队列_消息队列_02

分为两种创建模式
集群模式

该模式下创建的Topic在该集群中,所有Broker中的Queue数量都是相同的,可多选

broker模式

该模式下创建的Topic在该集群中,每个Broker中的Queue数量可以不一样,可多选

参数
主题名称
写队列数量

即生产者生成消息的时候将消息写入多少个队列中,比如写队列数量有4个:0,1,2,3,那么生产者就会将这些信息写入0,1,2,3中

读队列数量

即消费者消费消息的时候从哪些队列中读取出来,比如读队列数量有4个:0,1,2,3,那么生产者就会将这些信息从0,1,2,3读取出来

信创中间件 消息队列_示例代码_03

Q:假设一个topic在每个broker上面都创建了数量为128的读写队列,若需要将它缩容到64个应该怎么做?

先缩写队列到64,再缩读队列到64,因为在缩写队列到64的时候要保证65-128的消息被消费完,所以读队列必须还是128,等待消息完全被消费完成后,则缩读队列到64

权限

用于设置对当前创建Topic的操作权限:2表示:只写,4表示:只读,6表示:读写。

源码分析

TopicController主要负责Topic的管理

@RequestMapping(value = "/createOrUpdate.do", method = { RequestMethod.POST})
@ResponseBody
public Object topicCreateOrUpdateRequest(@RequestBody TopicConfigInfo topicCreateOrUpdateRequest) {
    Preconditions.checkArgument(CollectionUtils.isNotEmpty(topicCreateOrUpdateRequest.getBrokerNameList()) || CollectionUtils.isNotEmpty(topicCreateOrUpdateRequest.getClusterNameList()),
                                "clusterName or brokerName can not be all blank");
    logger.info("op=look topicCreateOrUpdateRequest={}", JsonUtil.obj2String(topicCreateOrUpdateRequest));
    topicService.createOrUpdate(topicCreateOrUpdateRequest);
    return true;
}

通过TopicServiceImpl.createOrUpdate()来创建

@Override
public void createOrUpdate(TopicConfigInfo topicCreateOrUpdateRequest) {
    TopicConfig topicConfig = new TopicConfig();
    BeanUtils.copyProperties(topicCreateOrUpdateRequest, topicConfig);
    try {
        ClusterInfo clusterInfo = mqAdminExt.examineBrokerClusterInfo();
        for (String brokerName : changeToBrokerNameSet(clusterInfo.getClusterAddrTable(),
                                                       topicCreateOrUpdateRequest.getClusterNameList(), topicCreateOrUpdateRequest.getBrokerNameList())) {
            mqAdminExt.createAndUpdateTopicConfig(clusterInfo.getBrokerAddrTable().get(brokerName).selectBrokerAddr(), topicConfig);
        }
    } catch (Exception err) {
        throw Throwables.propagate(err);
    }
}

通过MQAdminExtImpl.createAndUpdateTopicConfig方法来创建

@Override
public void createAndUpdateTopicConfig(String addr, TopicConfig config)
    throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
    MQAdminInstance.threadLocalMQAdminExt().createAndUpdateTopicConfig(addr, config);
}

通过调用DefaultMQAdminExtImpl.createAndUpdateTopicConfig创建Topic

public void createAndUpdateTopicConfig(String addr, TopicConfig config) throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
    this.mqClientInstance.getMQClientAPIImpl().createTopic(addr, this.defaultMQAdminExt.getCreateTopicKey(), config, this.timeoutMillis);
}

最后通过MQClientAPIImpl.createTopic创建Topic(这里面的方法实际上是生成cmd命令再去执行这些cmd命令)

public void createTopic(String addr, String defaultTopic, TopicConfig topicConfig, long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
    CreateTopicRequestHeader requestHeader = new CreateTopicRequestHeader();
    requestHeader.setTopic(topicConfig.getTopicName());
    requestHeader.setDefaultTopic(defaultTopic);
    requestHeader.setReadQueueNums(topicConfig.getReadQueueNums());
    requestHeader.setWriteQueueNums(topicConfig.getWriteQueueNums());
    requestHeader.setPerm(topicConfig.getPerm());
    requestHeader.setTopicFilterType(topicConfig.getTopicFilterType().name());
    requestHeader.setTopicSysFlag(topicConfig.getTopicSysFlag());
    requestHeader.setOrder(topicConfig.isOrder());

    RemotingCommand request = RemotingCommand.createRequestCommand(17, requestHeader);
    RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis);

    assert response != null;

    switch(response.getCode()) {
        case 0:
            return;
        default:
            throw new MQClientException(response.getCode(), response.getRemark());
    }
自动创建(通过系统自动创建的Topic从而来创建用户自定义的Topic)

自动创建topic的时候,需要在broker配置文件中添加 autoCreateTopicEnable=true(/conf文件夹下的broker.conf配置文件)当使用自动创建topic的时候会为每个Broker创建4个Queue队列

默认情况下,topic不用手动创建,当producer进行消息发送时,会从nameserver拉取topic的路由信息,如果topic的路由信息不存在,那么会默认拉取broker启动时默认创建好名为“TBW102”的Topic,这定义在org.apache.rocketmq.common.MixAll类中

源码分析

Broker启动的时候会调用TopicConfigManager的构造方法

public static BrokerController start(BrokerController controller) {
    try {
        // broker启动调用的方法
        controller.start();
        ...
    } catch (Throwable var2) {
        ...
    }
}
public void start() throws Exception {
    // 将Broker和Topic的信息定时NameServer发送心跳
    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
        public void run() {
            try {
                // 将所有的Broker注册到NameServer中
                BrokerController.this.registerBrokerAll(true, false, BrokerController.this.brokerConfig.isForceRegister());
            } catch (Throwable var2) {
                BrokerController.log.error("registerBrokerAll Exception", var2);
            }

        }
    }, 10000L, (long)Math.max(10000, Math.min(this.brokerConfig.getRegisterNameServerPeriod(), 60000)), TimeUnit.MILLISECONDS);
}

这个方法中定义了一个逻辑判断,如果Broker开启了自动创建Topic的属性,那就把自动创建的Topic以及系统定义的Topic注册到NameServer中,如下图

public TopicConfigManager(BrokerController brokerController) {
    ...
        if (this.brokerController.getBrokerConfig().isAutoCreateTopicEnable()) {
            topic = "TBW102";
            topicConfig = new TopicConfig(topic);
            TopicValidator.addSystemTopic(topic);
            topicConfig.setReadQueueNums(this.brokerController.getBrokerConfig().getDefaultTopicQueueNums());
            topicConfig.setWriteQueueNums(this.brokerController.getBrokerConfig().getDefaultTopicQueueNums());
            int perm = 7;
            topicConfig.setPerm(perm);
            this.topicConfigTable.put(topicConfig.getTopicName(), topicConfig);
        }
    ...
}
public synchronized void registerBrokerAll(boolean checkOrderConfig, boolean oneway, boolean forceRegister) {
    // 在这一步的时候就会执行TopicConfigManager的构造方法,得到TopicConfigTable
    TopicConfigSerializeWrapper topicConfigWrapper = this.getTopicConfigManager().buildTopicConfigSerializeWrapper();
    if (!PermName.isWriteable(this.getBrokerConfig().getBrokerPermission()) || !PermName.isReadable(this.getBrokerConfig().getBrokerPermission())) {
        ...
        //topicConfigWrapper会将所有的Topic配置表设置进去
        topicConfigWrapper.setTopicConfigTable(topicConfigTable);
    }
    if (forceRegister || this.needRegister(this.brokerConfig.getBrokerClusterName(), this.getBrokerAddr(), this.brokerConfig.getBrokerName(), this.brokerConfig.getBrokerId(), this.brokerConfig.getRegisterBrokerTimeoutMills())) {
        // 这里就真正执行把所有的topic都注册到NameServer上
        this.doRegisterBrokerAll(checkOrderConfig, oneway, topicConfigWrapper);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1lTaJVb2-1662781345357)(RocketMq/image-20220908180545456.png)]

上述是将配置了自动生成参数的Topic和系统的Topic以及Broker路由信息注册到了NameServer上,那生产者如何向NameServer获取Topic的路由信息?

首先当我们实例化DefaultMQProducer对象的时候,构造方法会帮我们初始化一些参数例如 自动生成Topic的名字,默认消息队列的数量为4等等

public DefaultMQProducer(String namespace, String producerGroup, RPCHook rpcHook) {
    this.log = ClientLogger.getLog();
    this.retryResponseCodes = new CopyOnWriteArraySet(Arrays.asList(17, 14, 1, 16, 204, 205));
    this.createTopicKey = "TBW102";
    this.defaultTopicQueueNums = 4;
    this.sendMsgTimeout = 3000;
    this.compressMsgBodyOverHowmuch = 4096;
    this.retryTimesWhenSendFailed = 2;
    this.retryTimesWhenSendAsyncFailed = 2;
    this.retryAnotherBrokerWhenNotStoreOK = false;
    this.maxMessageSize = 4194304;
    this.traceDispatcher = null;
    this.namespace = namespace;
    this.producerGroup = producerGroup;
    this.defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
}

当producer调用send方法的时候,走的是sendDefaultImpl方法

private SendResult sendDefaultImpl(Message msg, CommunicationMode communicationMode, SendCallback sendCallback, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    ...
    // 尝试从NameServer中获取topic的路由信息
    TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
    ...
}

调用DefaultMqProducerImpl中的this.tryToFindTopicPublishInfo()方法

private TopicPublishInfo tryToFindTopicPublishInfo(String topic) {
    // 尝试根据主题名称获取topic的信息,如果发送一个从来未发送过的topic,那么这里获取的一定为空
    TopicPublishInfo topicPublishInfo = (TopicPublishInfo)this.topicPublishInfoTable.get(topic);
    // 如果为空或者发送消息队列的内容为空
    if (null == topicPublishInfo || !topicPublishInfo.ok()) {
        this.topicPublishInfoTable.putIfAbsent(topic, new TopicPublishInfo());
        // 在NameServer中更新Topic路由信息
        this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);
        topicPublishInfo = (TopicPublishInfo)this.topicPublishInfoTable.get(topic);
    }

    // 若该topic没有路由信息 并且 消息队列的内容为空
    if (!topicPublishInfo.isHaveTopicRouterInfo() && !topicPublishInfo.ok()) {
        // 在NameServer中更新Topic路由信息
        this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this.defaultMQProducer);
        topicPublishInfo = (TopicPublishInfo)this.topicPublishInfoTable.get(topic);
        return topicPublishInfo;
    } else {
        return topicPublishInfo;
    }
}

调用MQClientInstance的updateTopicRouteInfoFromNameServer()方法

try {
        if (!this.lockNamesrv.tryLock(3000L, TimeUnit.MILLISECONDS)) {
            
        } else {
            try {
                if (isDefault && defaultMQProducer != null) {
                    // 使用默认的TBW102 Topic获取路由信息
                    topicRouteData = this.mQClientAPIImpl.getDefaultTopicRouteInfoFromNameServer(defaultMQProducer.getCreateTopicKey(), (long)this.clientConfig.getMqClientApiTimeout());
                } else {
                    // 使用已存在的Topic获取路由信息
                    topicRouteData = this.mQClientAPIImpl.getTopicRouteInfoFromNameServer(topic, (long)this.clientConfig.getMqClientApiTimeout());
                }

                // 判断获取默认的是否存在,如果存在把当前的Topic的信息更新。也就是把TBW102 Topic的数据更新为自动创建的数据。
                TopicRouteData old = (TopicRouteData)this.topicRouteTable.get(topic);
                boolean changed = this.topicRouteDataIsChange(old, topicRouteData);
                if (!changed) {
                    changed = this.isNeedUpdateTopicRouteInfo(topic);
                } else {
                    this.log.info("the topic[{}] route info changed, old[{}] ,new[{}]", new Object[]{topic, old, topicRouteData});
                }

                // 更新本地的缓存。这样TBW102 Topic的负载和一些默认的路由信息就会被自己创建的Topic使用
                TopicRouteData cloneTopicRouteData = topicRouteData.cloneTopicRouteData();
                Iterator var8 = topicRouteData.getBrokerDatas().iterator();

                if (!this.producerTable.isEmpty()) {
                    TopicPublishInfo publishInfo = topicRouteData2TopicPublishInfo(topic, topicRouteData);
                    publishInfo.setHaveTopicRouterInfo(true);
                    it = this.producerTable.entrySet().iterator();

                    while(it.hasNext()) {
                        entry = (Entry)it.next();
                        MQProducerInner impl = (MQProducerInner)entry.getValue();
                        if (impl != null) {
                            impl.updateTopicPublishInfo(topic, publishInfo);
                        }
                    }
                }

                if (!this.consumerTable.isEmpty()) {
                    Set<MessageQueue> subscribeInfo = topicRouteData2TopicSubscribeInfo(topic, topicRouteData);
                    it = this.consumerTable.entrySet().iterator();

                    while(it.hasNext()) {
                        entry = (Entry)it.next();
                        MQConsumerInner impl = (MQConsumerInner)entry.getValue();
                        if (impl != null) {
                            impl.updateTopicSubscribeInfo(topic, subscribeInfo);
                        }
                    }
                }

                this.topicRouteTable.put(topic, cloneTopicRouteData);
            } catch (MQClientException var17) {
            } catch (RemotingException var18) {
            } finally {
                this.lockNamesrv.unlock();
            }
            return var26;
        }
    } catch (InterruptedException var20) {
        this.log.warn("updateTopicRouteInfoFromNameServer Exception", var20);
    }

    return false;
}

生产者

与Name Server集群中的其中一个节点(随机)建立长连接(Keep-alive),定期从Name Server读取Topic路由信息,并向提供Topic服务的Master Broker建立长连接,且定时向Master Broker发送心跳。

消费者

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

RocketMq的模型

信创中间件 消息队列_rocketmq_04

RocketMq的应用场景

异步解耦

限流

分布式事务的数据一致性

消息的顺序发送和接收

RocketMq的消息类型

发布消息

普通消息

消息队列RocketMQ版提供三种方式来发送普通消息:同步(Sync)发送、异步(Async)发送和单向(Oneway)发送

同步发送
原理

同步发送是指消息发送方发出一条消息后,会在收到服务端同步响应之后才发下一条消息的通讯方式。

信创中间件 消息队列_java_05

应用场景

此种方式应用场景非常广泛,例如重要通知邮件、报名短信通知、营销短信系统

生产者示例代码
import com.aliyun.openservices.ons.api.Message;
import com.aliyun.openservices.ons.api.Producer;
import com.aliyun.openservices.ons.api.SendResult;
import com.aliyun.openservices.ons.api.ONSFactory;
import com.aliyun.openservices.ons.api.PropertyKeyConst;

import java.util.Properties;

public class ProducerTest {
    public static void main(String[] args) {
        Properties properties = new Properties();
        // AccessKeyId 阿里云身份验证,在阿里云用户信息管理控制台获取。
        properties.put(PropertyKeyConst.AccessKey,"XXX");
        // AccessKeySecret 阿里云身份验证,在阿里云用户信息管理控制台获取。
        properties.put(PropertyKeyConst.SecretKey, "XXX");
        //设置发送超时时间,单位:毫秒。
        properties.setProperty(PropertyKeyConst.SendMsgTimeoutMillis, "3000");
        // 设置TCP接入域名,进入消息队列RocketMQ版控制台实例详情页面的接入点区域查看。
        properties.put(PropertyKeyConst.NAMESRV_ADDR, "XXX");
        Producer producer = ONSFactory.createProducer(properties);
        // 在发送消息前,必须调用start方法来启动Producer,只需调用一次即可。
        producer.start();

        //循环发送消息。
        for (int i = 0; i < 100; i++){
            Message msg = new Message( 
                // 普通消息所属的Topic,切勿使用普通消息的Topic来收发其他类型的消息。
                "TopicTestMQ",
                // Message Tag可理解为Gmail中的标签,对消息进行再归类,方便Consumer指定过滤条件在消息队列RocketMQ版的服务器过滤。
                // Tag的具体格式和设置方法,请参见Topic与Tag最佳实践。
                "TagA",
                // Message Body可以是任何二进制形式的数据,消息队列RocketMQ版不做任何干预。
                // 需要Producer与Consumer协商好一致的序列化和反序列化方式。
                "Hello MQ".getBytes());
            // 设置代表消息的业务关键属性,请尽可能全局唯一。
            // 以方便您在无法正常收到消息情况下,可通过消息队列RocketMQ版控制台查询消息并补发。
            // 注意:不设置也不会影响消息正常收发。
            msg.setKey("ORDERID_" + i);

            try {
                SendResult sendResult = producer.send(msg);
                // 同步发送消息,只要不抛异常就是成功。
                if (sendResult != null) {
                    System.out.println(new Date() + " Send mq message success. Topic is:" + msg.getTopic() + " msgId is: " + sendResult.getMessageId());
                }
            }
            catch (Exception e) {
                // 消息发送失败,需要进行重试处理,可重新发送这条消息或持久化这条数据进行补偿处理。
                System.out.println(new Date() + " Send mq message failed. Topic is:" + msg.getTopic());
                e.printStackTrace();
            }
        }

        // 在应用退出前,销毁Producer对象。
        // 注意:如果不销毁也没有问题。
        producer.shutdown();
    }
}
消费者示例代码

消息消费者示例代码(使用ctrl+点击跳转)

异步发送
原理

及生产者发送消息到服务端的时候,不需要等待服务端响应即可进行下一个消息的发送接收

信创中间件 消息队列_java_06

应用场景

异步发送一般用于链路耗时较长,对响应时间较为敏感的业务场景,例如,您视频上传后通知启动转码服务,转码完成后通知推送转码结果等。

生产者示例代码
import com.aliyun.openservices.ons.api.Message;
import com.aliyun.openservices.ons.api.OnExceptionContext;
import com.aliyun.openservices.ons.api.Producer;
import com.aliyun.openservices.ons.api.SendCallback;
import com.aliyun.openservices.ons.api.SendResult;
import com.aliyun.openservices.ons.api.ONSFactory;
import com.aliyun.openservices.ons.api.PropertyKeyConst;

import java.util.Properties;
import java.util.concurrent.TimeUnit;

public class ProducerTest {
    public static void main(String[] args) throws InterruptedException {
        Properties properties = new Properties();
        // AccessKeyId 阿里云身份验证,在阿里云服务器管理控制台创建。
        properties.put(PropertyKeyConst.AccessKey, "XXX");
        // AccessKeySecret 阿里云身份验证,在阿里云服务器管理控制台创建。
        properties.put(PropertyKeyConst.SecretKey, "XXX");
        //设置发送超时时间,单位毫秒。
        properties.setProperty(PropertyKeyConst.SendMsgTimeoutMillis, "3000");
        // 设置TCP接入域名,进入消息队列RocketMQ版控制台实例详情页面的接入点区域查看。
        properties.put(PropertyKeyConst.NAMESRV_ADDR, "XXX");

        Producer producer = ONSFactory.createProducer(properties);
        // 在发送消息前,必须调用start方法来启动Producer,只需调用一次即可。
        producer.start();

        Message msg = new Message(
            // 普通消息所属的Topic,切勿使用普通消息的Topic来收发其他类型的消息。
            "TopicTestMQ",
            // Message Tag,可理解为Gmail中的标签,对消息进行再归类,方便Consumer指定过滤条件在消息队列RocketMQ版的服务器过滤。
            "TagA",
            // Message Body,任何二进制形式的数据,消息队列RocketMQ版不做任何干预,需要Producer与Consumer协商好一致的序列化和反序列化方式。
            "Hello MQ".getBytes());

        // 设置代表消息的业务关键属性,请尽可能全局唯一。 以方便您在无法正常收到消息情况下,可通过消息队列RocketMQ版控制台查询消息并补发。
        // 注意:不设置也不会影响消息正常收发。
        msg.setKey("ORDERID_100");

        // 异步发送消息, 发送结果通过callback返回给客户端。
        producer.sendAsync(msg, new SendCallback() {
            @Override
            public void onSuccess(final SendResult sendResult) {
                // 消息发送成功。
                System.out.println("send message success. topic=" + sendResult.getTopic() + ", msgId=" + sendResult.getMessageId());
            }

            @Override
            public void onException(OnExceptionContext context) {
                // 消息发送失败,需要进行重试处理,可重新发送这条消息或持久化这条数据进行补偿处理。
                System.out.println("send message failed. topic=" + context.getTopic() + ", msgId=" + context.getMessageId());
            }
        });

        // 阻塞当前线程3秒,等待异步发送结果。
        TimeUnit.SECONDS.sleep(3);

        // 在应用退出前,销毁Producer对象。注意:如果不销毁也没有问题。
        producer.shutdown();
    }
}
消费者示例代码

消息消费者示例代码(使用ctrl+点击跳转)

单向发送
原理

即生产者发送消息到服务端,完全不需要服务端的响应即可进行下一个消息的发送接收,这样可能导致消息会丢失但是无法知道

信创中间件 消息队列_示例代码_07

应用场景

适用于某些耗时非常短,但对可靠性要求并不高的场景,例如日志收集。

生产者示例代码
import com.aliyun.openservices.ons.api.Message;
import com.aliyun.openservices.ons.api.Producer;
import com.aliyun.openservices.ons.api.ONSFactory;
import com.aliyun.openservices.ons.api.PropertyKeyConst;

import java.util.Properties;

public class ProducerTest {
    public static void main(String[] args) {
        Properties properties = new Properties();
        // AccessKeyId 阿里云身份验证,在阿里云用户信息管理控制台创建。
        properties.put(PropertyKeyConst.AccessKey, "XXX");
        // AccessKeySecret 阿里云身份验证,在阿里云用户信息管理控制台创建。
        properties.put(PropertyKeyConst.SecretKey, "XXX");
        //设置发送超时时间,单位:毫秒。
        properties.setProperty(PropertyKeyConst.SendMsgTimeoutMillis, "3000");
        // 设置TCP接入域名,进入消息队列RocketMQ版控制台的实例详情页面的接入点区域查看。
        properties.put(PropertyKeyConst.NAMESRV_ADDR,
                       "XXX");

        Producer producer = ONSFactory.createProducer(properties);
        // 在发送消息前,必须调用start方法来启动Producer,只需调用一次即可。
        producer.start();
        //循环发送消息。
        for (int i = 0; i < 100; i++){
            Message msg = new Message(
                // 普通消息所属的Topic,切勿使用普通消息的Topic来收发其他类型的消息。
                "TopicTestMQ",
                // Message Tag,
                // 可理解为Gmail中的标签,对消息进行再归类,方便Consumer指定过滤条件在消息队列RocketMQ版的服务器过滤。
                "TagA",
                // Message Body
                // 任何二进制形式的数据,消息队列RocketMQ版不做任何干预,需要Producer与Consumer协商好一致的序列化和反序列化方式。
                "Hello MQ".getBytes());

            // 设置代表消息的业务关键属性,请尽可能全局唯一。
            // 以方便您在无法正常收到消息情况下,可通过消息队列RocketMQ版控制台查询消息并补发。
            // 注意:不设置也不会影响消息正常收发。
            msg.setKey("ORDERID_" + i);

            // 由于在oneway方式发送消息时没有请求应答处理,如果出现消息发送失败,则会因为没有重试而导致数据丢失。若数据不可丢,建议选用可靠同步或可靠异步发送方式。
            producer.sendOneway(msg);
        }

        // 在应用退出前,销毁Producer对象。
        // 注意:如果不销毁也没有问题。
        producer.shutdown();
    }
}
消费者示例代码

消息消费者示例代码(使用ctrl+点击跳转)

三种发送方式的对比区别

发送方式

发送TPS

发送结果反馈

可靠性

同步发送



不丢失

异步发送



不丢失

单向发送

最快


可能丢失

延时/定时消息
延时消息

延时消息用于指定消息发送到消息队列RocketMQ版的服务端后,延时一段时间才被投递到客户端进行消费(例如3秒后才被消费),适用于解决一些消息生产和消费有时间窗口要求的场景,或者通过消息触发延迟任务的场景,类似于延迟队列。

生产者示例代码
import com.aliyun.openservices.ons.api.Message;
import com.aliyun.openservices.ons.api.ONSFactory;
import com.aliyun.openservices.ons.api.Producer;
import com.aliyun.openservices.ons.api.PropertyKeyConst;
import com.aliyun.openservices.ons.api.SendResult;
import java.util.Properties;

public class ProducerDelayTest {
    public static void main(String[] args) {
        Properties properties = new Properties();
        // AccessKey ID阿里云身份验证,在阿里云RAM控制台创建。
        properties.put(PropertyKeyConst.AccessKey, "XXX");
        // AccessKey Secret阿里云身份验证,在阿里云RAM控制台创建。
        properties.put(PropertyKeyConst.SecretKey, "XXX");
        // 设置TCP接入域名,进入消息队列RocketMQ版控制台实例详情页面的接入点区域查看。
        properties.put(PropertyKeyConst.NAMESRV_ADDR,
          "XXX");

        Producer producer = ONSFactory.createProducer(properties);
        // 在发送消息前,必须调用start方法来启动Producer,只需调用一次即可。
        producer.start();
        Message msg = new Message( 
                // 您在消息队列RocketMQ版控制台创建的Topic。
                "Topic",
                // Message Tag,可理解为Gmail中的标签,对消息进行再归类,方便Consumer指定过滤条件在消息队列RocketMQ版服务器过滤。
                "tag",
                // Message Body可以是任何二进制形式的数据,消息队列RocketMQ版不做任何干预,需要Producer与Consumer协商好一致的序列化和反序列化方式。
                "Hello MQ".getBytes());
        // 设置代表消息的业务关键属性,请尽可能全局唯一。
        // 以方便您在无法正常收到消息情况下,可通过控制台查询消息并补发。
        // 注意:不设置也不会影响消息正常收发。
        msg.setKey("ORDERID_100");
        try {
            
            // 延时消息,在指定延迟时间(当前时间之后)进行投递。最大可设置延迟40天投递,单位毫秒(ms)。
            // 以下示例表示消息在3秒后投递。
            long delayTime = System.currentTimeMillis() + 3000;

            // 设置消息需要被投递的时间。
            msg.setStartDeliverTime(delayTime);

            SendResult sendResult = producer.send(msg);
            // 同步发送消息,只要不抛异常就是成功。
            if (sendResult != null) {
            System.out.println(new Date() + " Send mq message success. Topic is:" + msg.getTopic() + " msgId is: " + sendResult.getMessageId());
            }
            } catch (Exception e) {
            // 消息发送失败,需要进行重试处理,可重新发送这条消息或持久化这条数据进行补偿处理。
            System.out.println(new Date() + " Send mq message failed. Topic is:" + msg.getTopic());
            e.printStackTrace();
        }
        // 在应用退出前,销毁Producer对象。
        // 注意:如果不销毁也没有问题。
        producer.shutdown();
    }
}
消费者示例代码

消息消费者示例代码(使用ctrl+点击跳转)

定时消息

延时消息用于指定消息发送到消息队列RocketMQ版的服务端后,延时一段时间才被投递到客户端进行消费(例如3秒后才被消费),适用于解决一些消息生产和消费有时间窗口要求的场景,或者通过消息触发延迟任务的场景,类似于延迟队列。

生产者示例代码
import com.aliyun.openservices.ons.api.Message;
import com.aliyun.openservices.ons.api.ONSFactory;
import com.aliyun.openservices.ons.api.Producer;
import com.aliyun.openservices.ons.api.PropertyKeyConst;
import com.aliyun.openservices.ons.api.SendResult;
import java.util.Properties;

public class ProducerDelayTest {
    public static void main(String[] args) {
        Properties properties = new Properties();
        // AccessKey ID阿里云身份验证,在阿里云RAM控制台创建。
        properties.put(PropertyKeyConst.AccessKey, "XXX");
        // AccessKey Secret阿里云身份验证,在阿里云RAM控制台创建。
        properties.put(PropertyKeyConst.SecretKey, "XXX");
        // 设置TCP接入域名,进入消息队列RocketMQ版控制台实例详情页面的接入点区域查看。
        properties.put(PropertyKeyConst.NAMESRV_ADDR,
          "XXX");

        Producer producer = ONSFactory.createProducer(properties);
        // 在发送消息前,必须调用start方法来启动Producer,只需调用一次即可。
        producer.start();
        Message msg = new Message( 
                // 您在消息队列RocketMQ版控制台创建的Topic。
                "Topic",
                // Message Tag,可理解为Gmail中的标签,对消息进行再归类,方便Consumer指定过滤条件在消息队列RocketMQ版服务器过滤。
                "tag",
                // Message Body可以是任何二进制形式的数据,消息队列RocketMQ版不做任何干预,需要Producer与Consumer协商好一致的序列化和反序列化方式。
                "Hello MQ".getBytes());
        // 设置代表消息的业务关键属性,请尽可能全局唯一。
        // 以方便您在无法正常收到消息情况下,可通过控制台查询消息并补发。
        // 注意:不设置也不会影响消息正常收发。
        msg.setKey("ORDERID_100");
        try {
            // 延时消息,在指定延迟时间(当前时间之后)进行投递。最大可设置延迟40天投递,单位毫秒(ms)。
            // 以下示例表示消息在3秒后投递。
            long delayTime = System.currentTimeMillis() + 3000;

            // 设置消息需要被投递的时间。
            msg.setStartDeliverTime(delayTime);

            SendResult sendResult = producer.send(msg);
            // 同步发送消息,只要不抛异常就是成功。
            if (sendResult != null) {
            System.out.println(new Date() + " Send mq message success. Topic is:" + msg.getTopic() + " msgId is: " + sendResult.getMessageId());
            }
            } catch (Exception e) {
            // 消息发送失败,需要进行重试处理,可重新发送这条消息或持久化这条数据进行补偿处理。
            System.out.println(new Date() + " Send mq message failed. Topic is:" + msg.getTopic());
            e.printStackTrace();
        }
        // 在应用退出前,销毁Producer对象。
        // 注意:如果不销毁也没有问题。
        producer.shutdown();
    }
}
消费者示例代码

消息消费者示例代码(使用ctrl+点击跳转)

事务消息

事务消息是rocketmq的一大特点,其他mq都没有分布式事务的特性

概念介绍
事务消息

消息队列RocketMQ版提供类似XA或Open XA的分布式事务功能,通过消息队列RocketMQ版事务消息能达到分布式事务的最终一致。

半事务消息

暂不能投递的消息,生产者已经成功地将消息发送到了消息队列RocketMQ版服务端,但是消息队列RocketMQ版服务端未收到生产者对该消息的二次确认,此时该消息被标记成“暂不能投递”状态,处于该种状态下的消息即半事务消息。

消息回查

由于网络闪断、生产者应用重启等原因,导致某条事务消息的二次确认丢失,消息队列RocketMQ版服务端通过扫描发现某条消息长期处于“半事务消息”时,需要主动向消息生产者询问该消息的最终状态(Commit或是Rollback),该询问过程即消息回查。

分布式事务的流程

信创中间件 消息队列_java_08

事务消息发送步骤
  1. 生产者将半事务消息发送至消息队列RocketMQ版服务端。
  2. 消息队列RocketMQ版服务端将消息持久化成功之后,向生产者返回Ack确认消息已经发送成功,此时消息为半事务消息。
  3. 生产者开始执行本地事务逻辑。
  4. 生产者根据本地事务执行结果向服务端提交二次确认结果(Commit或是Rollback),服务端收到确认结果后处理逻辑如下:
  • 二次确认结果为Commit:服务端将半事务消息标记为可投递,并投递给消费者。
  • 二次确认结果为Rollback:服务端将回滚事务,不会将半事务消息投递给消费者。
  1. 在断网或者是生产者应用重启的特殊情况下,若服务端未收到发送者提交的二次确认结果,或服务端收到的二次确认结果为Unknown未知状态,经过固定时间后,服务端将对消息生产者即生产者集群中任一生产者实例发起消息回查。
事务消息回查步骤
  1. 生产者收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
  2. 生产者根据检查得到的本地事务的最终状态再次提交二次确认,服务端仍按照步骤4对半事务消息进行处理。
分布式事务的消息使用规则
消息状态的类别
  • TransactionStatus.CommitTransaction:提交事务,允许消费者消费该消息。
  • TransactionStatus.RollbackTransaction:回滚事务,消息将被丢弃不允许消费。
  • TransactionStatus.Unknow:暂时无法判断状态,等待固定时间以后消息队列RocketMQ版服务端根据回查规则向生产者进行消息回查。
生产者生产消息的规则
  • 通过ONSFactory.createTransactionProducer创建事务消息的Producer时必须指定LocalTransactionChecker的实现类,处理异常情况下事务消息的回查。
  • 回查规则:本地事务执行完成后,若消息队列RocketMQ版服务端收到的本地事务返回状态为TransactionStatus.Unknow,或生产者应用退出导致本地事务未提交任何状态。则消息队列RocketMQ版服务端会向消息生产者发起事务回查,第一次回查后仍未获取到事务状态,则之后每隔一段时间会再次回查。
  • 回查间隔时间:系统默认每隔30秒发起一次定时任务,对未提交的半事务消息进行回查,共持续12小时。
  • 第一次消息回查最快时间:该参数支持自定义设置。若指定消息未达到设置的最快回查时间前,系统默认每隔30秒一次的回查任务不会检查该消息。以Java为例,以下设置表示:第一次回查的最快时间为60秒。
Message message = new Message();
message.putUserProperties(PropertyKeyConst.CheckImmunityTimeInSeconds,"60");
消费者消费消息的规则

事务消息的Group ID不能与其他类型消息的Group ID共用。与其他类型的消息不同,事务消息有回查机制,回查时消息队列RocketMQ版服务端会根据Group ID去查询生产者客户端。

生产者示例代码

发送半事务消息到服务端,及执行本地事务

package com.alibaba.webx.TryHsf.app1;

import com.aliyun.openservices.ons.api.Message;
import com.aliyun.openservices.ons.api.PropertyKeyConst;
import com.aliyun.openservices.ons.api.SendResult;
import com.aliyun.openservices.ons.api.transaction.LocalTransactionExecuter;
import com.aliyun.openservices.ons.api.transaction.TransactionProducer;
import com.aliyun.openservices.ons.api.transaction.TransactionStatus;
import java.util.Properties;
import java.util.concurrent.TimeUnit;

public class TransactionProducerClient {
    private final static Logger log = ClientLogger.getLog(); // 您需要设置自己的日志,便于排查问题。

    public static void main(String[] args) throws InterruptedException {
        final BusinessService businessService = new BusinessService(); // 本地业务。
        Properties properties = new Properties();
        // 您在消息队列RocketMQ版控制台创建的Group ID。注意:事务消息的Group ID不能与其他类型消息的Group ID共用。
        properties.put(PropertyKeyConst.GROUP_ID,"XXX");
        // AccessKey ID阿里云身份验证,在阿里云RAM控制台创建。
        properties.put(PropertyKeyConst.AccessKey,"XXX");
        // AccessKey Secret阿里云身份验证,在阿里云RAM控制台创建。
        properties.put(PropertyKeyConst.SecretKey,"XXX");
        // 设置TCP接入域名,进入消息队列RocketMQ版控制台实例详情页面的接入点区域查看。
        properties.put(PropertyKeyConst.NAMESRV_ADDR,"XXX");

        TransactionProducer producer = ONSFactory.createTransactionProducer(properties,
                                                                            new LocalTransactionCheckerImpl());
        producer.start();
        Message msg = new Message("Topic","TagA","Hello MQ transaction===".getBytes());
        try {
            SendResult sendResult = producer.send(msg, new LocalTransactionExecuter() {
                @Override
                public TransactionStatus execute(Message msg, Object arg) {
                    // 消息ID(有可能消息体一样,但消息ID不一样,当前消息属于半事务消息,所以消息ID在消息队列RocketMQ版控制台无法查询)。
                    String msgId = msg.getMsgID();
                    // 消息体内容进行crc32,也可以使用其它的如MD5。
                    long crc32Id = HashUtil.crc32Code(msg.getBody());
                    // 消息ID和crc32id主要是用来防止消息重复。
                    // 如果业务本身是幂等的,可以忽略,否则需要利用msgId或crc32Id来做幂等。
                    // 如果要求消息绝对不重复,推荐做法是对消息体使用crc32或MD5来防止重复消息。
                    Object businessServiceArgs = new Object();
                    TransactionStatus transactionStatus = TransactionStatus.Unknow;
                    try {
                        boolean isCommit =
                            businessService.execbusinessService(businessServiceArgs);
                        if (isCommit) {
                            // 本地事务已成功则提交消息。
                            transactionStatus = TransactionStatus.CommitTransaction;
                        } else {
                            // 本地事务已失败则回滚消息。
                            transactionStatus = TransactionStatus.RollbackTransaction;
                        }
                    } catch (Exception e) {
                        log.error("Message Id:{}", msgId, e);
                    }
                    System.out.println(msg.getMsgID());
                    log.warn("Message Id:{}transactionStatus:{}", msgId, transactionStatus.name());
                    return transactionStatus;
                }
            }, null);
        }
        catch (Exception e) {
            // 消息发送失败,需要进行重试处理,可重新发送这条消息或持久化这条数据进行补偿处理。
            System.out.println(new Date() + " Send mq message failed. Topic is:" + msg.getTopic());
            e.printStackTrace();
        }
        // demo example防止进程退出(实际使用不需要这样)。
        TimeUnit.MILLISECONDS.sleep(Integer.MAX_VALUE);
    }
}
消费者示例代码

消息消费者示例代码(使用ctrl+点击跳转)

服务端回查的示例代码

都是去实现LocalTransactionChecker这个接口的check方法实现回查

public class LocalTransactionCheckerImpl implements LocalTransactionChecker {
    private final static Logger log = ClientLogger.getLog();
    final  BusinessService businessService = new BusinessService();

    @Override
    public TransactionStatus check(Message msg) {
        //消息ID(有可能消息体一样,但消息ID不一样,当前消息属于半事务消息,所以消息ID在消息队列RocketMQ版控制台无法查询)。
        String msgId = msg.getMsgID();
        //消息体内容进行crc32,也可以使用其它的方法如MD5。
        long crc32Id = HashUtil.crc32Code(msg.getBody());
        //消息ID和crc32Id主要是用来防止消息重复。
        //如果业务本身是幂等的,可以忽略,否则需要利用msgId或crc32Id来做幂等。
        //如果要求消息绝对不重复,推荐做法是对消息体使用crc32或MD5来防止重复消息。
        //业务自己的参数对象,这里只是一个示例,需要您根据实际情况来处理。
        Object businessServiceArgs = new Object();
        TransactionStatus transactionStatus = TransactionStatus.Unknow;
        try {
            boolean isCommit = businessService.checkbusinessService(businessServiceArgs);
            if (isCommit) {
                //本地事务已成功则提交消息。
                transactionStatus = TransactionStatus.CommitTransaction;
            } else {
                //本地事务已失败则回滚消息。
                transactionStatus = TransactionStatus.RollbackTransaction;
            }
        } catch (Exception e) {
            log.error("Message Id:{}", msgId, e);
        }
        log.warn("Message Id:{}transactionStatus:{}", msgId, transactionStatus.name());
        return transactionStatus;
    }
}
顺序消息
什么是顺序消息

顺序消息是消息队列RocketMQ版提供的一种对消息发送和消费顺序有严格要求的消息。对于一个指定的Topic,消息严格按照先进先出(FIFO)的原则进行消息发布和消费,即先发布的消息先消费,后发布的消息后消费。顺序消息主要分为分区顺序消息全局顺序消息

分区顺序消息

对于指定的一个Topic,所有消息根据Sharding Key进行区块分区,同一个分区内的消息按照严格的先进先出(FIFO)原则进行发布和消费。同一分区内的消息保证顺序,不同分区之间的消息顺序不做要求。

  • 基本概念
  • Sharding Key:顺序消息中用来区分Topic中不同分区的关键字段,和普通消息的Key是完全不同的概念。消息队列RocketMQ版会将设置了相同Sharding Key的消息路由到同一个分区下,同一个分区内的消息将按照消息发布顺序进行消费。
  • 分区:即Topic Partition,每个Topic包含一个或多个分区,Topic中的消息会分布在这些不同的分区中。本文中的逻辑分区指的就是Topic的分区,更多信息,请参见名词解释
  • 物理分区:区别于逻辑分区,消息实际存储的单元,每个物理分区都会分配到某一台机器指定节点上。
  • 适用场景
    适用于性能要求高,以Sharding Key作为分区字段,在同一个区块中严格地按照先进先出(FIFO)原则进行消息发布和消费的场景。
  • 示例
  • 用户注册需要发送验证码,以用户ID作为Sharding Key,那么同一个用户发送的消息都会按照发布的先后顺序来消费。
  • 电商的订单创建,以订单ID作为Sharding Key,那么同一个订单相关的创建订单消息、订单支付消息、订单退款消息、订单物流消息都会按照发布的先后顺序来消费。
全局顺序消息

对于指定的一个Topic,所有消息按照严格的先入先出(FIFO)的顺序来发布和消费。

  • 适用场景
    适用于性能要求不高,所有的消息严格按照FIFO原则来发布和消费的场景。
  • 示例
    在证券处理中,以人民币兑换美元为Topic,在价格相同的情况下,先出价者优先处理,则可以按照FIFO的方式发布和消费全局顺序消息。
生产者示例代码
package com.aliyun.openservices.ons.example.order;

import com.aliyun.openservices.ons.api.Message;
import com.aliyun.openservices.ons.api.ONSFactory;
import com.aliyun.openservices.ons.api.PropertyKeyConst;
import com.aliyun.openservices.ons.api.SendResult;
import com.aliyun.openservices.ons.api.order.OrderProducer;

import java.util.Properties;


public class ProducerClient {

    public static void main(String[] args) {
        Properties properties = new Properties();
        // 您在消息队列RocketMQ版控制台创建的Group ID。
        properties.put(PropertyKeyConst.GROUP_ID,"XXX");
        // AccessKey ID阿里云身份验证,在阿里云RAM控制台创建。
        properties.put(PropertyKeyConst.AccessKey,"XXX");
        // AccessKey Secret阿里云身份验证,在阿里云RAM控制台创建。
        properties.put(PropertyKeyConst.SecretKey,"XXX");
        // 设置TCP接入域名,进入消息队列RocketMQ版控制台实例详情页面的接入点区域查看。
        properties.put(PropertyKeyConst.NAMESRV_ADDR,"XXX");
        OrderProducer producer = ONSFactory.createOrderProducer(properties);
        // 在发送消息前,必须调用start方法来启动Producer,只需调用一次即可。
        producer.start();
        for (int i = 0; i < 1000; i++) {
            String orderId = "biz_" + i % 10;
            Message msg = new Message(
                // Message所属的Topic。
                "Order_global_topic",
                // Message Tag,可理解为Gmail中的标签,对消息进行再归类,方便Consumer指定过滤条件在消息队列RocketMQ版的服务器过滤。
                "TagA",
                // Message Body,可以是任何二进制形式的数据,消息队列RocketMQ版不做任何干预,需要Producer与Consumer协商好一致的序列化和反序列化方式。
                "send order global msg".getBytes()
            );
            // 设置代表消息的业务关键属性,请尽可能全局唯一。
            // 以方便您在无法正常收到消息情况下,可通过消息队列RocketMQ版控制台查询消息并补发。
            // 注意:不设置也不会影响消息正常收发。
            msg.setKey(orderId);
            // 分区顺序消息中区分不同分区的关键字段,Sharding Key与普通消息的key是完全不同的概念。
            // 全局顺序消息,该字段可以设置为任意非空字符串。
            String shardingKey = String.valueOf(orderId);
            try {
                // 同步发送
                SendResult sendResult = producer.send(msg, shardingKey);
                // 发送消息,只要不抛异常就是成功。
                if (sendResult != null) {
                    System.out.println(new Date() + " Send mq message success. Topic is:" + msg.getTopic() + " msgId is: " + sendResult.getMessageId());
                }
            }
            catch (Exception e) {
                // 消息发送失败,需要进行重试处理,可重新发送这条消息或持久化这条数据进行补偿处理。
                System.out.println(new Date() + " Send mq message failed. Topic is:" + msg.getTopic());
                e.printStackTrace();
            }
        }
        // 在应用退出前,销毁Producer对象。
        // 注意:如果不销毁也没有问题。
        producer.shutdown();
    }

}
消费者示例代码
package com.aliyun.openservices.ons.example.order;

import com.aliyun.openservices.ons.api.Message;
import com.aliyun.openservices.ons.api.ONSFactory;
import com.aliyun.openservices.ons.api.PropertyKeyConst;
import com.aliyun.openservices.ons.api.order.ConsumeOrderContext;
import com.aliyun.openservices.ons.api.order.MessageOrderListener;
import com.aliyun.openservices.ons.api.order.OrderAction;
import com.aliyun.openservices.ons.api.order.OrderConsumer;

import java.util.Properties;


public class ConsumerClient {

    public static void main(String[] args) {
        Properties properties = new Properties();
        // 您在消息队列RocketMQ版控制台创建的Group ID。
        properties.put(PropertyKeyConst.GROUP_ID,"XXX");
        // AccessKey ID阿里云身份验证,在阿里云RAM控制台创建。
        properties.put(PropertyKeyConst.AccessKey,"XXX");
        // AccessKey Secret阿里云身份验证,在阿里云RAM控制台创建。
        properties.put(PropertyKeyConst.SecretKey,"XXX");
        // 设置TCP接入域名,进入消息队列RocketMQ版控制台实例详情页面的接入点区域查看。
        properties.put(PropertyKeyConst.NAMESRV_ADDR,"XXX");
          // 顺序消息消费失败进行重试前的等待时间,单位(毫秒),取值范围: 10毫秒~30,000毫秒。
        properties.put(PropertyKeyConst.SuspendTimeMillis,"100");
        // 消息消费失败时的最大重试次数。
        properties.put(PropertyKeyConst.MaxReconsumeTimes,"20");

        // 在订阅消息前,必须调用start方法来启动Consumer,只需调用一次即可。
        OrderConsumer consumer = ONSFactory.createOrderedConsumer(properties);

        consumer.subscribe(
                // Message所属的Topic。
                "Order_global_topic",
                // 订阅指定Topic下的Tags:
                // 1. * 表示订阅所有消息。
                // 2. TagA || TagB || TagC表示订阅TagA或TagB或TagC的消息。
                "*",
                new MessageOrderListener() {
                    /**
                     * 1. 消息消费处理失败或者处理出现异常,返回OrderAction.Suspend。
                     * 2. 消息处理成功,返回OrderAction.Success。
                     */
                    @Override
                    public OrderAction consume(Message message, ConsumeOrderContext context) {
                        System.out.println(message);
                        return OrderAction.Success;
                    }
                });

        consumer.start();
    }
}
RocketMq的消息类型对比

消息类型

是否支持可靠同步发送

是否支持可靠异步发送

是否支持单向发送

是否支持多线程发送

性能

普通消息





最高

事务消息





最高

定时和延时消息





最高

分区顺序消息






全局顺序消息





一般

订阅消息

订阅方式
集群订阅

同一个Group ID所标识的所有Consumer平均分摊消费消息。例如某个Topic有9条消息,一个Group ID有3个Consumer实例,那么在集群消费模式下每个实例平均分摊,只消费其中的3条消息。设置方式如下所示。

// 集群订阅方式设置(不设置的情况下,默认为集群订阅方式)。
properties.put(PropertyKeyConst.MessageModel, PropertyValueConst.CLUSTERING);

信创中间件 消息队列_rocketmq_09

广播订阅

同一个Group ID所标识的所有Consumer都会各自消费某条消息一次。例如某个Topic有9条消息,一个Group ID有3个Consumer实例,那么在广播消费模式下每个实例都会各自消费9条消息。设置方式如下所示。

// 广播订阅方式设置。
properties.put(PropertyKeyConst.MessageModel, PropertyValueConst.BROADCASTING);

信创中间件 消息队列_信创中间件 消息队列_10

获取消息的方式
Push(被动)

消息由消息队列RocketMQ版推送至Consumer。Push方式下,消息队列RocketMQ版还支持批量消费功能,可以将批量消息统一推送至Consumer进行消费

push示例代码
import com.aliyun.openservices.ons.api.Action;
import com.aliyun.openservices.ons.api.ConsumeContext;
import com.aliyun.openservices.ons.api.Consumer;
import com.aliyun.openservices.ons.api.Message;
import com.aliyun.openservices.ons.api.MessageListener;
import com.aliyun.openservices.ons.api.ONSFactory;
import com.aliyun.openservices.ons.api.PropertyKeyConst;

import java.util.Properties;

public class ConsumerTest {
   public static void main(String[] args) {
       Properties properties = new Properties();
        // 您在控制台创建的Group ID。
       properties.put(PropertyKeyConst.GROUP_ID, "XXX");
        // AccessKey ID阿里云身份验证,在阿里云RAM控制台创建。
       properties.put(PropertyKeyConst.AccessKey, "XXX");
        // Accesskey Secret阿里云身份验证,在阿里云服RAM控制台创建。
       properties.put(PropertyKeyConst.SecretKey, "XXX");
        // 设置TCP接入域名,进入控制台的实例详情页面的TCP协议客户端接入点区域查看。
       properties.put(PropertyKeyConst.NAMESRV_ADDR, "XXX");
          // 集群订阅方式(默认)。
          // properties.put(PropertyKeyConst.MessageModel, PropertyValueConst.CLUSTERING);
          // 广播订阅方式。
          // properties.put(PropertyKeyConst.MessageModel, PropertyValueConst.BROADCASTING);

       Consumer consumer = ONSFactory.createConsumer(properties);
       consumer.subscribe("TopicTestMQ", "TagA||TagB", new MessageListener() { //订阅多个Tag。
           public Action consume(Message message, ConsumeContext context) {
               System.out.println("Receive: " + message);
               return Action.CommitMessage;
           }
       });

        //订阅另外一个Topic,如需取消订阅该Topic,请删除该部分的订阅代码,重新启动消费端即可。
        consumer.subscribe("TopicTestMQ-Other", "*", new MessageListener() { //订阅全部Tag。
           public Action consume(Message message, ConsumeContext context) {
               System.out.println("Receive: " + message);
               return Action.CommitMessage;
           }
       });

       consumer.start();
       System.out.println("Consumer Started");
   }
}
push批量消费示例代码
import com.aliyun.openservices.ons.api.Action;
import com.aliyun.openservices.ons.api.ConsumeContext;
import com.aliyun.openservices.ons.api.Consumer;
import com.aliyun.openservices.ons.api.Message;
import com.aliyun.openservices.ons.api.MessageListener;
import com.aliyun.openservices.ons.api.ONSFactory;
import com.aliyun.openservices.ons.api.PropertyKeyConst;

import java.util.Properties;

public class ConsumerTest {
   public static void main(String[] args) {
       Properties properties = new Properties();
        // 您在控制台创建的Group ID。
       properties.put(PropertyKeyConst.GROUP_ID, "XXX");
        // AccessKey ID阿里云身份验证,在阿里云RAM控制台创建。
       properties.put(PropertyKeyConst.AccessKey, "XXX");
        // Accesskey Secret阿里云身份验证,在阿里云服RAM控制台创建。
       properties.put(PropertyKeyConst.SecretKey, "XXX");
        // 设置TCP接入域名,进入控制台的实例详情页面的TCP协议客户端接入点区域查看。
       properties.put(PropertyKeyConst.NAMESRV_ADDR, "XXX");
          // 集群订阅方式(默认)。
          // properties.put(PropertyKeyConst.MessageModel, PropertyValueConst.CLUSTERING);
          // 广播订阅方式。
          // properties.put(PropertyKeyConst.MessageModel, PropertyValueConst.BROADCASTING);

       Consumer consumer = ONSFactory.createConsumer(properties);
       consumer.subscribe("TopicTestMQ", "TagA||TagB", new MessageListener() { //订阅多个Tag。
           public Action consume(Message message, ConsumeContext context) {
               System.out.println("Receive: " + message);
               return Action.CommitMessage;
           }
       });

        //订阅另外一个Topic,如需取消订阅该Topic,请删除该部分的订阅代码,重新启动消费端即可。
        consumer.subscribe("TopicTestMQ-Other", "*", new MessageListener() { //订阅全部Tag。
           public Action consume(Message message, ConsumeContext context) {
               System.out.println("Receive: " + message);
               return Action.CommitMessage;
           }
       });

       consumer.start();
       System.out.println("Consumer Started");
   }
}
Pull(主动)

消息由Consumer主动从消息队列RocketMQ版拉取

集群消费和广播消费的差异

功能

集群消费

广播消费

TCP协议SDK



HTTP协议SDK


×

顺序消息


×

重置消费位点


×

消息重试


×

消息堆积查询、报警


×

订阅关


×

消费进度

服务端维护可靠性更高,客户端重启后,将按照上次的消费进度继续消费。支持服务端消费重试机制,详细信息,请参见消息重试。


客户端维护出现重复消费的概率稍大于集群模式,客户端每次重启都会从最新消息消费。

RocketMq的Message对象

构造方法

public Message() {
}

public Message(String topic, byte[] body) {
    this(topic, "", "", 0, body, true);
}

public Message(String topic, String tags, String keys, int flag, byte[] body, boolean waitStoreMsgOK) {
    this.topic = topic;
    this.flag = flag;
    this.body = body;
    if (tags != null && tags.length() > 0) {
        this.setTags(tags);
    }

    if (keys != null && keys.length() > 0) {
        this.setKeys(keys);
    }

    this.setWaitStoreMsgOK(waitStoreMsgOK);
}

public Message(String topic, String tags, byte[] body) {
    this(topic, tags, "", 0, body, true);
}

public Message(String topic, String tags, String keys, byte[] body) {
    this(topic, tags, keys, 0, body, true);
}

形参

topic

生产者发送给服务端的一个消息的主题标识,消费者可以通过这个标识消费消息

tags

也是相当于对消息的标识,能够让消费者过滤自己想要的tags,例如消费者只消费【用户购买的消息】user:purchase的消息

keys

所有消息的唯一标识,以方便在无法正常收到消息情况下,可通过消息队列RocketMQ版控制台查询消息并补发。用于建立索引,之后可以通过命令工具/API/或者管理平台查询key,可以为一个消息设置多个key,用空格""进行分割

flag

选填,消息的标记,完全由应用设置,RocketMQ不做任何处理,类似于memcached中flag的作用

body

消息内容,是字节数组

waitStoreMsgOK

表示发送消息后,是否需要等待消息同步刷新到磁盘上。如果broker配置为ASYNC_MASTER,那么只需要消息在master上刷新到磁盘即可;如果配置为SYNC_MASTER,那么还需要等待slave也刷新到磁盘。需要注意的是,waitStoreMsgOK默认为false,只有将设置为true的情况下,才会等待刷盘成功再返回。

信创中间件 消息队列_rocketmq_11