1. 顺序消息原理图

2. 什么是顺序消息?

  消费消息的顺序要求同发送消息的顺序一致,在RocketMQ中,主要指的是局部顺序,即一类消息为满足顺序性,必须Producer单线程顺序发送,并且发送给到同一队列,这样Consumer就可以按照Producer发送的顺序去消费消息。

  2.1 普通顺序消息

  正常情况下可以保证完全的顺序消息,但是一旦发生通信异常,Broker重启,由于队列总数发生变化,哈希取模后定位的队列会变化,产生短暂的消息顺序不一致。

  如果业务能够容忍在集群异常(如某个Broker宕机或者重启)下,消息短暂乱序,使用普通顺序方式比较合适。

  2.2 严格顺序消息

  无论正常异常都保证顺序,但是牺牲了分布式Failover特性,即Broker集群中只要有一台机器不可用,则整个集群都不可用,服务可用性大大降低。

  如果服务器部署方式为同步双写模式,此缺陷可通过备机自动切换为主避免,不过仍然会存在短暂时间服务不可用。

  目前已知的应用只有数据库binlog同步强依赖严格顺序消息。其他应用绝大部分都可以容忍短暂乱序,推荐使用普通顺序消息。

3. 顺序消息缺陷 

  1. 发送顺序消息无法利用集群FailOver 特性
  2.  消费顺序消息的幵行度依赖亍队列数量
  3.  队列热点问题,个别队列由亍哈希丌均导致消息过多,消费速度跟丌上,产生消息堆积问题
  4.  遇到消息失败的消息,无法跳过,当前队列消费暂停

5. 顺序消息的使用方式

  跟普通消息相比,顺序消息的使用需要在producer的send()方法中添加MessageQueueSelector接口的实现类,并重写select选择使用的队列,因为顺序消息局部顺序,需要将所有消息指定发送到同一队列中。

  Producer实现如下:

package com.wangx.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 OrderProducer { public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
DefaultMQProducer producer = new DefaultMQProducer("my-order-producer-group"); //2. 设置namesrvAddr,集群环境多个nameserver用;分割 producer.setNamesrvAddr("47.105.149.61:9876;47.105.145.123:9876");
//3. 启动
producer.start(); for (int i = 0; i < 4; i++) {
Message message = new Message("OrderTopic", "tabA", ("Hello World " + i).getBytes());
/**send() 方法中的MessageQueueSelector.select()方法用于指定消息队列,
* send() 的最后一个参数为队列下表,一个topic默认初始化四个队列
* select()方法中的第一个参数为队列list,里面存放队列,第二个为send()方法传入的message
* 第三个参数为send()方法传入的队列下标
*
*/
SendResult result = producer.send(message, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
Integer qID = (Integer) o;
MessageQueue queue = list.get(qID);
return queue;
}
},1);
System.out.println(result);
}
producer.shutdown();
}
}

  与普通消息相比,Consumer的使用只需要在注册监听时使用MessageListenerOrderly对象,并重写consumeMessage即可,需要注意的是consumeMessage的返回值与普通消息不同,具体实现如下:

package com.wangx.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.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt; import java.util.List; public class OrderConsumer { public static void main(String[] args) throws MQClientException {
//实例化一个consumer组
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("my-order-consumer-group");
//设置setNamesrvAddr,同生产者
consumer.setNamesrvAddr("47.105.149.61:9876;47.105.145.123:9876"); //设置消息读取方式,这里设置的是队尾开始读取
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); //设置订阅主题,第二个参数为过滤tabs的条件,可以写为tabA|tabB过滤Tab,*表示接受所有
consumer.subscribe("OrderTopic", "*"); //注册消息监听
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
try{
MessageExt ext = list.get(0);
int quID = ext.getQueueId();
String body = new String(ext.getBody(),"UTF-8");
System.out.println("quID: " + quID + "接收到消息:" + body);
}catch (Exception e) {
return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
consumer.start();
}
}

  启动consumer,在启动producer,控制台打印如下:

quID: 1接收到消息:Hello World 0
quID: 1接收到消息:Hello World 1
quID: 1接收到消息:Hello World 2
quID: 1接收到消息:Hello World 3

  可以看到所有消息都在队列1中,且都是按照发送消息的顺序进行消费的。

  那么当向多个消息队列中分别顺序发送多条消息时是什么情况呢?

  分别下下标为1,2,3的三个队列中发送四条消息,代码如下:

package com.wangx.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 OrderProducer { public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
DefaultMQProducer producer = new DefaultMQProducer("my-order-producer-group"); //2. 设置namesrvAddr,集群环境多个nameserver用;分割 producer.setNamesrvAddr("47.105.149.61:9876;47.105.145.123:9876");
//3. 启动
producer.start(); for (int i = 0; i < 4; i++) {
Message message = new Message("OrderTopic", "tabA", ("Hello World " + i).getBytes());
/**send() 方法中的MessageQueueSelector.select()方法用于指定消息队列,
* send() 的最后一个参数为队列下表,一个topic默认初始化四个队列
* select()方法中的第一个参数为队列list,里面存放队列,第二个为send()方法传入的message
* 第三个参数为send()方法传入的队列下标
*
*/
SendResult result = producer.send(message, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
Integer qID = (Integer) o;
MessageQueue queue = list.get(qID);
return queue;
}
},1);
System.out.println(result);
}
for (int i = 0; i < 4; i++) {
Message message = new Message("OrderTopic", "tabA", ("Hello World " + i).getBytes());
/**send() 方法中的MessageQueueSelector.select()方法用于指定消息队列,
* send() 的最后一个参数为队列下表,一个topic默认初始化四个队列
* select()方法中的第一个参数为队列list,里面存放队列,第二个为send()方法传入的message
* 第三个参数为send()方法传入的队列下标
*
*/
SendResult result = producer.send(message, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
Integer qID = (Integer) o;
MessageQueue queue = list.get(qID);
return queue;
}
},2);
System.out.println(result);
}
for (int i = 0; i < 4; i++) {
Message message = new Message("OrderTopic", "tabA", ("Hello World " + i).getBytes());
/**send() 方法中的MessageQueueSelector.select()方法用于指定消息队列,
* send() 的最后一个参数为队列下表,一个topic默认初始化四个队列
* select()方法中的第一个参数为队列list,里面存放队列,第二个为send()方法传入的message
* 第三个参数为send()方法传入的队列下标
*
*/
SendResult result = producer.send(message, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
Integer qID = (Integer) o;
MessageQueue queue = list.get(qID);
return queue;
}
},3);
System.out.println(result);
}
producer.shutdown();
}
}

  消费者不变,控制台打印如下:

quID: 1接收到消息:Hello World 0
quID: 1接收到消息:Hello World 1
quID: 1接收到消息:Hello World 2
quID: 1接收到消息:Hello World 3
quID: 2接收到消息:Hello World 0
quID: 2接收到消息:Hello World 1
quID: 2接收到消息:Hello World 2
quID: 2接收到消息:Hello World 3
quID: 3接收到消息:Hello World 0
quID: 3接收到消息:Hello World 1
quID: 3接收到消息:Hello World 2
quID: 3接收到消息:Hello World 3

  这里很明显的看到,每个队列中所有消息均是按照顺序消费的,这里可能消息队列并不一定为1,2,3这样的方式顺序消费(因为每个队列对应单个线程,多个线程在跑时并不一定能够保证队列的顺序),但是针对单个队列中的消息,确实顺序的,这就是rockemq局部顺序的概念的实现。

  

RocketMQ学习笔记(9)----RocketMQ的Producer 顺序消息的更多相关文章

  1. 【rocketmq学习笔记】rocketmq入门学习

    基本介绍 rocketmq是阿里巴巴团队使用java语言开发的一款基于发布订阅模型的分布式消息队列中间件,是一款低延迟,高可用,拥有海量消息堆积能力和灵活拓展性的消息队列. 特点 可以实现集群无单点故 ...

  2. RocketMQ学习笔记(13)----RocketMQ的Consumer消息重试

    1. 概念 Producer端重试: 生产者端的消息失败,也就是Producer往MQ上发消息没有发送成功,比如网络抖动导致生产者发送消息到MQ失败. 这种消息失败重试我们可以手动设置发送失败重试的次 ...

  3. RocketMQ学习笔记(4)----RocketMQ搭建双Master集群

    前面已经学习了RockeMQ的四种集群方式,接下来就来搭建一个双Master(2m)的集群环境. 1. 双Master服务器环境 序号 ip 用户名 密码 角色 模式 (1) 47.105.145.1 ...

  4. RocketMQ学习笔记(16)----RocketMQ搭建双主双从(异步复制)集群

    1. 修改RocketMQ默认启动端口 由于只有两台机器,部署双主双从需要四个节点,所以只能修改rocketmq的默认启动端口,从官网下载rocketmq的source文件,解压后使用idea打开,全 ...

  5. RocketMQ学习笔记(15)----RocketMQ的消息模式

    在前面学习ActiveMQ时,看到ActiveMQ可以是队列消息模式,也可以是订阅发布模式. 同样,在RocketMQ中,也存在两种消息模式,即是集群消费模式和广播消费模式. 1. 集群消费模式 跟A ...

  6. RocketMQ学习笔记(14)----RocketMQ的去重策略

    1. Exactly Only Once (1). 发送消息阶段,不允许发送重复的消息 (2). 消费消息阶段,不允许消费重复的消息. 只有以上两个条件都满足情况下,才能认为消息是“Exactly O ...

  7. RocketMQ学习笔记(10)----RocketMQ的Producer 事务消息使用

    1. 事务消息原理图 RocketMQ除了支持普通消息,顺序消息之外,还支持了事务消息. 1. 什么是分布式事务? 分布式事务就是指事务的参与者.支持事务的服务器.资源服务器以及事务管理器分别位于不同 ...

  8. ActiveMQ学习笔记(5)——使用Spring JMS收发消息

      摘要 ActiveMQ学习笔记(四)http://my.oschina.net/xiaoxishan/blog/380446 中记录了如何使用原生的方式从ActiveMQ中收发消息.可以看出,每次 ...

  9. RabbitMQ学习笔记五:RabbitMQ之优先级消息队列

    RabbitMQ优先级队列注意点: 1.只有当消费者不足,不能及时进行消费的情况下,优先级队列才会生效 2.RabbitMQ3.5以后才支持优先级队列 代码在博客:RabbitMQ学习笔记三:Java ...

随机推荐

  1. (转)Django学习之 第一章:Django介绍

    Django: Python编程Web框架 如果你上djangoproject.com你会发现对Django的如下解释: “Django is a high-level Python Web fram ...

  2. Node.js获取本机IP

    function getIPAdress() { var interfaces = require('os').networkInterfaces(); for (var devName in int ...

  3. 如何将App从一个账号迁移到另一个账号?

    App迁移(App transfer):将App从一个开发者账号迁移至另一个开发者账号.此文演示了整个迁移过程,为了方便解释,在此过程中,将App转出的开发者账号我们下文将会称之为A账号,接收杭州Ap ...

  4. “双十二”年终盛典,Guitar Pro邀您一起倾情共舞

    躲过了双十一,躲不过双十二,隐约昨天还是双十一,马上双十二又叕来了,弱弱的问一句“你们的手长粗了来了吗?”在这即将结束的年终盛典里,各商家又将如“双十一”般纷纷使出浑身解数,作为吉他最佳拍档的编曲软件 ...

  5. CF894E Ralph and Mushrooms_强连通分量_记忆化搜索_缩点

    Code: #include<cstdio> #include<stack> #include<cstring> using namespace std; cons ...

  6. 路飞学城Python-Day19

    [23.绑定方法与非绑定方法介绍] 再类的内部的定义的函数分为两大类: 1.绑定方法: 绑定到对象的方法:直接用def做的函数属性,类内部定义的函数,如果没有绑定装饰器,就是给对象使用的函数,绑定给谁 ...

  7. android handler传递数据

    起因:在android使用get请求获取验证码时需要重开一个线程,这就造成了我无法获取到从服务器后台返回的数据 解决方法:创建全局变量,将返回的数据解析后返回给handler,再在handler中将数 ...

  8. 计算机网络Intro

    1. 计算机网络体系结构 1.1 简介 定义 计算机网络的各层 + 其协议的集合 作用 定义该计算机网络的所能完成的功能 1.2 结构介绍 计算机网络体系结构分为3种:OSI体系结构.TCP / IP ...

  9. [luogu2501 HAOI2006] 数字序列 (递推LIS)

    题目描述 现在我们有一个长度为n的整数序列A.但是它太不好看了,于是我们希望把它变成一个单调严格上升的序列.但是不希望改变过多的数,也不希望改变的幅度太大. 输入输出格式 输入格式: 第一行包含一个数 ...

  10. 开发微信小程序必须要知道的事

    为什么是小程序? 为什么我们会开发小程序呢?或许是因为工作需要,或许是源于自己的追求(来自名利的欲望),但我要说--这是一种缘分,很美好的缘分,很多年后还值得庆幸的缘分 小程序目前可以分为三个阶段 一 ...