顺序消息
消息有序指的是可以按照消息的发送顺序来消费(FIFO)。RocketMq可以严格的保证消息有序,可以分为区分有序或者全局有序。
顺序消费的原理解析,在默认的情况下消息发送会采取Round Robin轮询方式把消息发送到不同的queue(分区队列);而消费消息的时候从多个queue上拉取消息,这种情况发送和消息式不能保证顺序。但是如果控制发送的顺序消息只依次发送到同一个queue中,消费的时候只从这个queue上拉取,则就保证了顺序。当发送和消费参与的queue只有一个,则是全局有序;如果多个queue参与,则为份去有序,即相对每个queue,消息都是有序的。
下面用订单进行份去有序的示例。一个订单的顺序流程是:创建、付款、推送、完成。订单号相同的消息会被先后发送到同一个队列中,消费时,同一个OrderId获取到的肯定时同一个队列。
创建生成订单类
/**
* 订单的步骤
*/
private static class OrderStep {
private long orderId;
private String desc;
public long getOrderId() {
return orderId;
}
public void setOrderId(long orderId) {
this.orderId = orderId;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "OrderStep{" +
"orderId=" + orderId +
", desc='" + desc + '\'' +
'}';
}
}
/**
* 生成模拟订单数据
*/
private List<OrderStep> buildOrders() {
List<OrderStep> orderList = new ArrayList<OrderStep>();
OrderStep orderDemo = new OrderStep();
orderDemo.setOrderId(15103111039L);
orderDemo.setDesc("创建");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(15103111065L);
orderDemo.setDesc("创建");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(15103111039L);
orderDemo.setDesc("付款");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(15103117235L);
orderDemo.setDesc("创建");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(15103111065L);
orderDemo.setDesc("付款");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(15103117235L);
orderDemo.setDesc("付款");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(15103111065L);
orderDemo.setDesc("完成");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(15103111039L);
orderDemo.setDesc("推送");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(15103117235L);
orderDemo.setDesc("完成");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(15103111039L);
orderDemo.setDesc("完成");
orderList.add(orderDemo);
return orderList;
}
}
生产者消费者
public class Producer {
public static void main(String[] args) throws Exception {
//1.创建消息生产者producer,并制定生产者组名
DefaultMQProducer producer = new DefaultMQProducer("group1");
//2.指定Nameserver地址
producer.setNamesrvAddr("一号服务器公网IP:9876;二号服务器公网IP:9876");
//3.启动producer
producer.start();
//构建消息集合
List<OrderStep> orderSteps = OrderStep.buildOrders();
//发送消息
for (int i = 0; i < orderSteps.size(); i++) {
String body = orderSteps.get(i) + "";
Message message = new Message("OrderTopic", "Order", "i" + i, body.getBytes());
/**
* 参数一:消息对象
* 参数二:消息队列的选择器
* 参数三:选择队列的业务标识(订单ID)
*/
SendResult sendResult = producer.send(message, new MessageQueueSelector() {
/**
*
* @param mqs:队列集合
* @param msg:消息对象
* @param arg:业务标识的参数
* @return
*/
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
long orderId = (long) arg;
long index = orderId % mqs.size();
return mqs.get((int) index);
}
}, orderSteps.get(i).getOrderId());
System.out.println("发送结果:" + sendResult);
}
producer.shutdown();
}
}
消费者
public class Consumer {
public static void main(String[] args) throws MQClientException {
//1.创建消费者Consumer,制定消费者组名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
//2.指定Nameserver地址
consumer.setNamesrvAddr("一号服务器公网IP:9876;二号服务器公网IP:9876");
//3.订阅主题Topic和Tag
consumer.subscribe("OrderTopic", "*");
//4.注册消息监听器
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
for (MessageExt msg : msgs) {
System.out.println("线程名称:【" + Thread.currentThread().getName() + "】:" + new String(msg.getBody()));
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
//5.启动消费者
consumer.start();
System.out.println("消费者启动");
}
}
消费结果
消费者启动
线程名称:【ConsumeMessageThread_1】:OrderStep{orderId=1065, desc='创建'}
线程名称:【ConsumeMessageThread_1】:OrderStep{orderId=1065, desc='付款'}
线程名称:【ConsumeMessageThread_1】:OrderStep{orderId=1065, desc='完成'}
线程名称:【ConsumeMessageThread_2】:OrderStep{orderId=7235, desc='创建'}
线程名称:【ConsumeMessageThread_2】:OrderStep{orderId=7235, desc='付款'}
线程名称:【ConsumeMessageThread_2】:OrderStep{orderId=7235, desc='完成'}
线程名称:【ConsumeMessageThread_3】:OrderStep{orderId=1039, desc='创建'}
线程名称:【ConsumeMessageThread_3】:OrderStep{orderId=1039, desc='付款'}
线程名称:【ConsumeMessageThread_3】:OrderStep{orderId=1039, desc='推送'}
线程名称:【ConsumeMessageThread_3】:OrderStep{orderId=1039, desc='完成'}
从上面我们可以看到实现了分区有序,即一个线程只完成唯一标识的订单消息
延时消息
比如电商里,提交了一个订单就可以发一个延时消息,1h后去检查这个订单的状态,如果还是未付款就取消订单释放库存。
使用限制
现在RocketMq并不支持任意时间的延迟,需要设置几个固定的延时等级从1s到2h分别对应着等级1-18
// org/apache/rocketmq/store/config/MessageStoreConfig.java
private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";
延时消费
与之前不同的时,多了msg.setDelayTimeLevel(2)来控制延时
public class Producer {
public static void main(String[] args) throws InterruptedException, RemotingException, MQClientException, MQBrokerException {
//1.创建消息生产者producer,并制定生产者组名
DefaultMQProducer producer = new DefaultMQProducer("group1");
//2.指定Nameserver地址
producer.setNamesrvAddr("一号服务器公网IP:9876;二号服务器公网IP:9876");
//3.启动producer
producer.start();
for (int i = 0; i < 10; i++) {
//4.创建消息对象,指定主题Topic、Tag和消息体
/**
* 参数一:消息主题Topic
* 参数二:消息Tag
* 参数三:消息内容
*/
Message msg = new Message("DelayTopic", "Tag1", ("Hello World" + i).getBytes());
//设定延迟时间
msg.setDelayTimeLevel(2);
//5.发送消息
SendResult result = producer.send(msg);
//发送状态
SendStatus status = result.getSendStatus();
System.out.println("发送结果:" + result);
//线程睡1秒
TimeUnit.SECONDS.sleep(1);
}
//6.关闭生产者producer
producer.shutdown();
}
}
消费者
public class Consumer {
public static void main(String[] args) throws Exception {
//1.创建消费者Consumer,制定消费者组名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
//2.指定Nameserver地址
consumer.setNamesrvAddr("一号服务器公网IP:9876;二号服务器公网IP:9876");
//3.订阅主题Topic和Tag
consumer.subscribe("DelayTopic", "*");
//4.设置回调函数,处理消息
consumer.registerMessageListener(new MessageListenerConcurrently() {
//接受消息内容
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
System.out.println("消息ID:【" + msg.getMsgId() + "】,延迟时间:" + (System.currentTimeMillis() - msg.getStoreTimestamp()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//5.启动消费者consumer
consumer.start();
System.out.println("消费者启动");
}
}
延时与非延时比较
延时
消费者启动
消息ID:【C0A80325321C00B4AAC2373564AA0003】,延迟时间:27884
消息ID:【C0A80325321C00B4AAC2373570C60006】,延迟时间:24777
消息ID:【C0A80325321C00B4AAC2373558250000】,延迟时间:31027
消息ID:【C0A80325321C00B4AAC2373578E30008】,延迟时间:22699
消息ID:【C0A80325321C00B4AAC2373568B20004】,延迟时间:29139
消息ID:【C0A80325321C00B4AAC237355C720001】,延迟时间:32241
消息ID:【C0A80325321C00B4AAC237357CF40009】,延迟时间:23954
消息ID:【C0A80325321C00B4AAC23735609E0002】,延迟时间:31204
消息ID:【C0A80325321C00B4AAC2373574D50007】,延迟时间:26058
消息ID:【C0A80325321C00B4AAC237356CB90005】,延迟时间:28134
非延时
消息ID:【C0A8032527E800B4AAC2373811BC0000】,延迟时间:1126
消息ID:【C0A8032527E800B4AAC2373815F20001】,延迟时间:1218
消息ID:【C0A8032527E800B4AAC237381A800002】,延迟时间:1231
消息ID:【C0A8032527E800B4AAC237381F700003】,延迟时间:1132
消息ID:【C0A8032527E800B4AAC2373824620004】,延迟时间:1242
消息ID:【C0A8032527E800B4AAC2373829070005】,延迟时间:1133
消息ID:【C0A8032527E800B4AAC237382D160006】,延迟时间:1133
消息ID:【C0A8032527E800B4AAC2373831240007】,延迟时间:1127
消息ID:【C0A8032527E800B4AAC23738352F0008】,延迟时间:1132
消息ID:【C0A8032527E800B4AAC23738393B0009】,延迟时间:1127
考虑到网络延迟,机器卡顿等一些因素,消息可能不是即时送达,但是延时与非延时的延时时间有很大区别
批量消息
如果每次只发送不超过4MB的消息,则很容易使用批处理,样例如下
生产者
消息ID:【C0A8032527E800B4AAC2373811BC0000】,延迟时间:1126
消息ID:【C0A8032527E800B4AAC2373815F20001】,延迟时间:1218
消息ID:【C0A8032527E800B4AAC237381A800002】,延迟时间:1231
消息ID:【C0A8032527E800B4AAC237381F700003】,延迟时间:1132
消息ID:【C0A8032527E800B4AAC2373824620004】,延迟时间:1242
消息ID:【C0A8032527E800B4AAC2373829070005】,延迟时间:1133
消息ID:【C0A8032527E800B4AAC237382D160006】,延迟时间:1133
消息ID:【C0A8032527E800B4AAC2373831240007】,延迟时间:1127
消息ID:【C0A8032527E800B4AAC23738352F0008】,延迟时间:1132
消息ID:【C0A8032527E800B4AAC23738393B0009】,延迟时间:1127
发送结果
消息ID:【C0A8032527E800B4AAC2373811BC0000】,延迟时间:1126
消息ID:【C0A8032527E800B4AAC2373815F20001】,延迟时间:1218
消息ID:【C0A8032527E800B4AAC237381A800002】,延迟时间:1231
消息ID:【C0A8032527E800B4AAC237381F700003】,延迟时间:1132
消息ID:【C0A8032527E800B4AAC2373824620004】,延迟时间:1242
消息ID:【C0A8032527E800B4AAC2373829070005】,延迟时间:1133
消息ID:【C0A8032527E800B4AAC237382D160006】,延迟时间:1133
消息ID:【C0A8032527E800B4AAC2373831240007】,延迟时间:1127
消息ID:【C0A8032527E800B4AAC23738352F0008】,延迟时间:1132
消息ID:【C0A8032527E800B4AAC23738393B0009】,延迟时间:1127
消息分割
如果消息的总长度可能大于4MB,这时候最好把消息进行分割
消息ID:【C0A8032527E800B4AAC2373811BC0000】,延迟时间:1126
消息ID:【C0A8032527E800B4AAC2373815F20001】,延迟时间:1218
消息ID:【C0A8032527E800B4AAC237381A800002】,延迟时间:1231
消息ID:【C0A8032527E800B4AAC237381F700003】,延迟时间:1132
消息ID:【C0A8032527E800B4AAC2373824620004】,延迟时间:1242
消息ID:【C0A8032527E800B4AAC2373829070005】,延迟时间:1133
消息ID:【C0A8032527E800B4AAC237382D160006】,延迟时间:1133
消息ID:【C0A8032527E800B4AAC2373831240007】,延迟时间:1127
消息ID:【C0A8032527E800B4AAC23738352F0008】,延迟时间:1132
消息ID:【C0A8032527E800B4AAC23738393B0009】,延迟时间:1127