RocketMQ(十一)生产案例:为什么基于rocketmq进行订单库数据同步时会消息乱序?+基于延迟消息机制优化大量订单的定时退款扫描问题+订单定时退款场景,分析rocketMQ的延迟消息代码实现

前言:

insert into order(num) value 0;
update order set num = 100,where orderId = 20;
这两条sql要是执行顺序不一致会发生啥问题?下了订单迟迟不支付,堆压一堆未支付的信息在后台线程扫描?会出现什么问题?

1.为什么基于rocketmq进行订单库数据同步时会消息乱序?

1.1.大数据团队同步订单数据库的技术方案回顾:大数据系统直接跑复杂的大sql在订单系统的数据库上出一些数据报表,是会严重影响订单系统的性能的,所以基于canal这样的中间件去监听订单数据库的binlog,就是一些增删改操作的日志,然后将这些binlog发送到mq中去;

大数据框架简单回顾
1.2.问题:订单数据库的binlog消息乱序:

例如: insert into order(num) value 0;
update order set num = 100,where orderId = 20;如果这两条语句出现乱序问题,出来的数据完全就是不一样的结果;

1.3.乱序的原因:

每个topic可以指定多个messagequeue,也就是当写入i奥西的时候,会将topic消息均匀的分布给不同的message queue的,假设insert 的binlog写入一个messagequeue中,update的binlog写入另外一个messagequeue中,接着大数据系统在获取binlog的时候,可能会部署多台机器组成一个consumer group,对于consumer group中的每台机器都会负责消费一部分messagequeue的消息,所以可能一台从consumer01获取令insert log,另一台从consume queue02中获取倒来update binlog,完全就有可能先执行的update,再执行的inset;

1.4.如何解决这个问题呢?

让同一个订单的binlog进入一个message queue中。

1.4.1考虑问题:

mysql数据的binlog一定也是有顺序的,例如先执行了insert,然后是update,当从MySQL数据库中获取binlog的时候此时也必须要按照binog的顺序来获取的,也就是说比如canal作为一个中间件从MySQL那里监听和获取binlog,那么binlog传输到canal那里监听和获取binlog,那么binlog传递到canal的时候,也必然是先后顺序的,先insert再update;接着将binlog发送给mq的时候,必须将一个订单的binlog都发送到一个message queue中去,而且发送过去的时候也必须严格按照顺序发送,只有这样才能让一个订单的binlog进入同一个messagequeue,而且还是有序的

1.5:消息处理原理分析:

一个consumer可以处理多个messagequeue消息,但是一个messagequeue只能给一个consume进行处理,所以一个订单的binlog只会有序的交给一个consumer来进行处理,这样的话一个大数据系统就可以获取到一个订单的有序binlog,然后有序的根据binlog将数据还原到自己的存储中;【消息乱序解决初稿】
在这里插入图片描述

1.6:可能引发问题:

consumer处理消息的时候,可能出现很多原因执行失败,此时返回RECONSUME_LATER状态,broker会稍后重试,那这样不又乱序了?

1.7:1.6问题解决分析:

对于有序消息的方案中,如果遇到消息处理失败的场景,就必须要返回SUSPEND_CURRENT_QUEUE_A-MOMENT这个状态,意思是先等一会,一会再处理这一整批消息,而不是直接将消息放入重试队列中,而是直接处理下一批消息先;

2:基于订单数据库同步场景,来分析rocketMQ的顺序消息机制的代码实现Demo

2.1:1.如何让一个订单的binlog进入一个messagequeue?

SendResult sendResult = producr.send(
message,
new MessageQueueSelector(){
@Overrid
public MessageQueue select(
List<Messagequeue> mqs,
Message msg,Object aeg
){
Long orderId = (Long)arg;//根据订单id选择发送queue
Long index = id % mqs.size();//用订单id对message queue数量取模
return mqs.get((int) index); //返回一个message queue
}
},
orderId//传入订单id
)

代码片段中可以看到,关键的因素就是两个,一个是发送消息的时候传入一个messageQueueSelector,在这个里面要根据订单id和MessageQueue数量去选择这个订单id的数据进入哪个Messagequeue,同时在发送消息的时候除了带上消息自己以外,还要带上订单id,如何messagequeueSelector就会根据订单id去选择一个messagequeue发送过去,就可以保证一个订单多个binlog都会进入一个messagequeue中去。

2.2:消费者如何保证按照顺序来获取一个messagequeue中的消息?

consumer.registerMessageListener(
new MessageListenerOrderly(){
@Override
public ConsumeOrderlyStatus consumeMessage(
List<MessageExt> msgs,
ConsumeOrderlyContext context){
context.setAutoCommit(true);
try{
for(MessageExt msg:msgs){
//对有序消息进行消息处理
}

return ConsumeOrderlyStatus.SUCCESS;
}catch(Exception e){
//如果消息处理有问题,返回一个状态,暂停一会再继续发送这批消息
return SUSPEND_CUPRENT_QUEUE_A_MOMENT;
}
}
}
)

MessageListenerOrderly这个Orderly的意思也就是说consumer会对每一个consume queue,都仅仅用一个线程来处理其中的消息。如果交给多线程处理的话,还是会出现乱序的;

3.基于rocketmq的数据过滤机制,提升订单数据库同步的处理效率:tags

一个数据库会包含很多张表的数据,比如说订单数据库,除了订单,还包含了很多其他的表,所以在进行数据库binlog同步的时候,很有可能是将一个数据库所有表的binlog都推送到mq中去的,所以在mq的某个人topic中,可能是混杂了订单数据库中的几个甚至十几个表的binlog数据的,不一定仅仅包含了想要推动表的binlog数据;

3.1:场景举例:

假设大数据系统仅仅关注订单数据库中的表A的binlog,并不关注其他表的binlog,那么大数据系统可能需要在获取到所有表的binlog之后,对每条binlog判断一下,是不是表A的binlog,如果不是表A的binlog直接丢弃不处理,如果是表A的binlog,再进行处理,这样的话必然就会导致大数据系统处理很多不关注的表的binlog,也会很浪费时间,降低消息的效率;【引入tag数据过滤机制】
tag数据过滤

3.2:问题解决方案:

发送消息的时候,给消息设置tag和属性,针对这个问题,可以采用rocketMQ支持的数据过滤机制,来让大数据系统仅仅关注他想要的表的binlog数据即可;

Message  msg = new Message(
"TopicOrderDbData",//订单数据库写入的topic
“tableA”,//这条数据的tag,可以是表的名称
("binlog").getBytes(RemotingHelper.DEFAULT_CHARSET)//这是一条binlog数据
)//给消息设置一些属性
msg.putUserProperty("a",10);
msg.putUserProperty("b","abc");

其实是可以给消息设置tag/属性等等多个附加信息的,在消费数据的时候根据tag和属性进行过滤,片段demo:

可以在消费的时候根据tag和属性进行过滤,例如可以通过下面的代码去指定
例如只要tag = tableA和tag = tableB的数据
consumer.subscribe(“TopicOrderDbData","tableA||tableB”)

也可以使用语法指定:例如:

consumer.subscribe(
"TopicOrderDbData"
MessageSelecor.bysql("a>5 AND b = 'abc')
)

4.基于延迟消息机制优化大量订单的定时退款扫描问题+订单定时退款场景

【订单扫描积压场景:简洁场景图】
订单扫描积压场景
问题:订单系统的后台线程必须要不停的扫描各种未支付的订单:

1.未支付的订单可能是比较多的,然后还需要不停的扫描,每个未支付的订单可能都要被扫描N多变,才会发现已经超过了30分钟没有支付了;

2.另外一个是很难去分布式并行扫描订单,业务假设订单数据量特别的多,打算部署订单扫描服务,但是问题,每台机器扫描哪些订单?怎么扫描?什么时候扫描?这都是一些麻烦的问题;

4.1:MQ的延迟消息出场,针对扫描问题

在实际的项目中,mq的延迟消息往往使用的是很多的,所谓的延迟消息,也就是说,在订单系统中创建了一个订单,可以发送一条消息到MQ中去,指定这条消息是延迟消息,例如设置等待30分钟后,才能被订单扫描服务给消费到;

【一图惊醒梦中人:可能只有如花能做到了】
在这里插入图片描述

不爽可以继续优化一下:如果订单数量很多,完全可以让订单扫描服务多部署一些机器,mq中的topic多指定一个messagequeue,这样每个订单扫描服务的机器作为一个consumer都会处理一部分订单的查询任务;

4.2:生产案例:订单定时退款场景,分析rocketMQ的延迟消息代码实现Demo:

producer生产者:

public class ScheduledMessageProducer{
public static void main(String[] args)throws Exception{
//订单系统的生产者
DefaultMQPruducer producer = new DefaultMQProducer("OrderSystemProducerGroup");
//启动生产者
producer.start();

Message message = new Message(
"CreateOrderInfoformTopic",//创建订单通知topic
orderInfoObject.getBytes()//订单信息的json串
)//设置消息为延迟消息,延迟级别为3
message.setDelayTimeLevel(3);
//发送消息
producer.send(message);
}
}

consumer消费者:

public class ScheduledMessageConsumer
public static void main(String[] args)throws Exception{
//订单消息服务的消费者
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("OrderScanServletConsumer");
//订阅订单创建通知topic
consumer.subscribe("CreateOrderInfoformTopic","*");
//注册消息监听者
consumer.registerMessageListener(new MessageListenerConcurrentiy(){
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> messages,consumeConcurrentlyContext context){
for(MessageExt message:messages){
//打印消息存储时间到消费时间的差值
.........................
}
}
})
}

rocketMq进行一个小总结《暂时更新到这里,后续还会持续更新一些企业型的权限问题和消息的链路追踪等等》:

1.灵活运用的tags来过滤数据,合理规划topic和里面的tags来过滤数据,

2.基于消息的key来定位消息是否丢失 如何从mq中查消息是否丢失?基于消息key就可以实现了,例如通过下面的方式设置一个消息的key为订单id:message.setKeys(orderId),这样这个消息就具备一个key了,接着这个消息到broker上面,会基于key构建hash索引,这个hash索引就放在indexFile索引文件中,通过mq提供的命令去根据key 查询这个消息例如 mqadmin queryMsgByKey -n 127.0.0.1:9876 -t SCANRECORD -K orderId

3.消息零丢失方案补贴 一般金融级别,银行,支付系统等等,一般假设mq机器彻底奔溃了,生产者就应该把消息写入到本地磁盘文件进去持久化,或者是写入数据库,等待mq恢复之后,再把持久化的消息继续投递到mq中去。

4.三种提高消费者的吞吐量:
第一种,部署更多的consumer机器,但是要注意,如果部署了5台consumer,topic的messagequeue就要对应增加,要不然consumer有一台是空闲的;

第二种就是增加consumer的线程数量,可以设置consumer端的参数:consumeThreadMin/consumeThreadMax,这样一台consumer机器尚消费线程多,消费速度就越快。

第三种:还可以开启消费者的批量消费功能,就是设置consumeMessageBatchMaxSize参数,默认是1,可以根据需求多设置一些,那么一次就会交给你的回调函数一批消息来处理,通过批量处理消息的方式,也可以大幅度提升消息消费的进度;

5.要不要消费历史消息 consumer支持设置从哪里开始消费消息的,两种:第一种就是从topic的第一条数据开始消费,一个是从最后一次消费国的消息之后开始消费,
对应的是: CONSUME_FROM_LAST_OFFSET CONSUME_FROM_FIRST_OFFSET
一般正常情况下都是选择从最后一次消费过的消息之后开始消费:CONSUME_FROM_FIRST_OFFSET,后续每一次重启,都是从上一次消费到的位置继续往后消费。

RocketMq暂时完结散花了,谢谢!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

咖喱ABC

无需打赏,共同进步学习!

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

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

打赏作者

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

抵扣说明:

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

余额充值