过了个年懈怠了,距离上篇文章已经过去了40多天😰,好在已经调整好了状态,那么从今天开始立个flag:尽自己最大努力保证每周至少更新总结一篇博客✊小伙伴们也要一起共同加油进步哈😄。
闲话少说,通过上篇我们大致知道了消息的整个流转过程:生产者发送消息到broker,会首先持久化到CommitLog文件中,然后通过定时任务将每个消息的“索引”信息(1ms)分发给各个ConsumerQueue,消费者消费的时候先通过ConsumerQueue找到消息的索引,再通过索引去CommitLog文件中找到全量的消息进行消费。
那么这篇文章就把目光转移到生产者这边,RocketMQ官方把生产者能发送的消息类型分为4类:
普通消息(批量)
顺序消息
定时/延时消息
事务消息
再贴一下RocketMQ官方地址:消息(Message) | RocketMQ
我们一起来看看。
一、消息发送的三种方式
在介绍这些消息之前,我们先看下消息的三种发送方式。
1.1 同步消息
同步消息就是生产者发送一条消息给Broker,需要等Broker返回响应,然后才会继续发送后续的消息(这里就简单写了,相信大家都会集成RocketMQ):
// 1.同步发送消息
// 同步发送是指发送方发送一条消息后,会等待服务器返回确认信息后再进行后续操作。这种方式适用于需要可靠性保证的场景。
public void sendSyncMessage(String message){
rocketMQTemplate.syncSend(topic, MessageBuilder.withPayload(message).build());
System.out.printf("同步发送结果: %s\n", message);
}

那么这就有一个问题,如果生产者发送A消息后,由于网络原因,生产者迟迟没收到Broker的反馈咋办?这时候生产者就会进行重发消息,默认会重试3次,超过三次还是没收到的话就会抛异常,这时候我们需要捕获这个异常,然后在catch里面做一些兜底操作,比如把这条消息持久化到数据库、打印日志等。
等等!!!既然是重发,就必然存在一个问题,在重试之前,Broker很可能是已经收到了消息并持久化到了ComiitLog文件中,只是生产者没收到Broker的反馈而已,那么再进行重试的话,消息就重复发送了,那消费者也就会重复消费,如果涉及像扣款的业务,那不天塌了。
ps:所以这里就需要做好消息的防重,这个后续会专门出一篇文章介绍。
1.2 异步消息
与同步对应的就是异步消息啦,异步消息就不会等Broker的反馈来决定是否发下一条消息:
// 2.异步发送消息
// 异步发送是指发送方发送消息后,不等待服务器返回确认信息,而是通过回调接口处理返回结果。这种方式适用于对响应时间要求较高的场景。
public void sendAsyncMessage(String message){
rocketMQTemplate.asyncSend(topic, MessageBuilder.withPayload(message).build(), new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
//发送成功
System.out.printf("异步发送成功: %s\n", sendResult);
}
@Override
public void onException(Throwable throwable) {
//发送失败(兜底操作)
System.out.printf("异步发送失败: %s\n", throwable.getMessage());
}
});
}

当然,异步消息也可以设置重试次数,有个参数是RetryTimesWhenSendAsyncFailed,所以他和同步消息一样也有消息重复的问题。
1.3 单向消息
前面的不论是同步还是异步,都会关注Broker的响应,但有些场景压根就不关系这个结果,比如日志的收集,这种场景对消息的可靠性就不高,丢几条日志也没关系,所以就一直发就完事了:
// 3.单向发送消息
// 单向发送是指发送方只负责发送消息,不关心服务器的响应。该方式适用于对可靠性要求不高的场景,如日志收集。
public void sendOneWayMessage(String message){
rocketMQTemplate.sendOneWay(topic, MessageBuilder.withPayload(message).build());
System.out.println("单向消息发送成功");
}

但有个缺点就是上面提的,不保证Broker一定会收到消息(丢消息)。
以上就是消息发送的三种方式,那么来到面试题吧,问:如何保证消息一定发送成功?
答:首先发送消息的方式不能是单向的,同步和异步可以利用Broker返回的ack(类似接收到了响应)来保证,比如同步发送可以通过try-catch的方式,重试达到最大次数后catch住这条消息,做一些兜底操作,比如日志打印或持久化到数据库;异步发送的话可以利用它的回调函数,在onException里做一些兜底操作。
二、普通消息
何为普通消息?就是除去顺序、定时、事务之外的消息,就是普通消息,借用官方的话说就是,此类消息本身无特殊予以,消息之间也没有任何关联,所以这个没啥好说的。
这里得提一嘴,官方把批量消息归为了普通消息这一类,使用起来也很简单:
public void sendBatchMessages() {
List<Message> messages = new ArrayList<>();
for (int i = 0; i < 10; i++) {
String payload = "Message " + i;
Message message = new Message(topic, payload.getBytes());
messages.add(message);
}
//打包一次性发送
rocketMQTemplate.syncSend(topic, messages);
}
三、顺序消息
顾名思义,就是消费者要严格按照消息发送的顺序去消费:

比如我们常见的一个电商场景:首先我们要创建订单,才能支付订单,支付完后才能发货。
这看似很简单,但在消息队列中是有考究的,因为我们知道消费队列是一个Topic下是有多个队列的,每个队列都有消费者来并行消费这些消息。假设现在有个Topic叫Order,用户下单后创建订单的消息发送到了队列1,接着用户支付后这条支付的消息发送到了队列2,然后商家立即发货,这条发货的消息落到了队列3:

虽然从图上看消息的顺序是有先后的,但是每个消费者的消费速度是不一样的!可能消费者-2消费了支付消息,但消费者-1还没消费完创建订单的消息,订单还没创建你支付个啥?这样一来业务顺序全乱了!
那么如何解决呢?
把这些消息都发往一个队列就完事了,那么这就是顺序消息, 如果把这些消息都只发往一个队列,那就是全局顺序消息,这样并发度就不太好,一般也不会用。
如果把同一笔订单的消息发往同一个队列,而不同订单可以发往不同队列,这就是分区顺序消息,这样并发度就起来了:

那么如何实现分区呢?我们只需要拿订单号用一个sharding key的方式来分区即可,比如(伪代码):
int queueIndex = orderId % queue.size();
producer.send(msg,queueIndex);
当然,RocketMQTemplate已经封装好了,只需一行代码:
public void send(String orderId){
//默认:hash方式
//rocketMQTemplate.setMessageQueueSelector(new SelectMessageQueueByHash());
//随机方式
//rocketMQTemplate.setMessageQueueSelector(new SelectMessageQueueByRandom());
rocketMQTemplate.syncSendOrderly(topic,MessageBuilder.withPayload("msg"),orderId);
}
该方法内部就已经实现了根据OrderId选择队列的逻辑,这里就简单贴一下源码:
hash:

Random(看看就行):
四、延迟消息
这很好理解,就是生产者发送消息,但是并不想消费者马上消费,而是延迟一段时间后再消费,比较经典的场景就是订单超时关闭,一般我们下单后,半小时内没有支付,那么这笔订单就自动取消。
那么如何实现呢,我们很容易想到的一个方式是讲消息正常发给Broker,然后让消费者来判断是否需要延迟消费,如果是延迟消息,那么就等时间到了再消费。
当然,一般情况下很容易想到的往往是行不通的,还记得之前文章提到的消费点位吗?消费者每消费完一条消息,都会更新一下消息点位,比如当前消费到的点位是100,第101条消息消费后,点位+1,即101,但延迟消息时间没到就无法消息,那么就会导致不需要延迟的消息被卡住了:

所以这样是行不通的,那么RocketMQ是如何实现的呢?
RocketMQ专门搞了个Topic叫SCHEDULE_TOPIC_XXX,延迟的消息一开始不放在正常的Topic上,而是全部先投递到SCHEDULE_TOPIC_XXX中,然后有个定时任务遍历扫描消息的延迟时间到了没,到了就把该消息投递到它本身的Topic队列中,这就保证了延迟消息时间到之前,消费者不会消费到这条消息(因为它根本没有订阅SCHEDULE_TOPIC_XXX):

是不是很巧妙!!!
五、事务消息
这个涉及的东西比较多,奈何篇幅有限,事务消息之后会专门出一篇文章介绍。
End:希望对大家有所帮助,如果有纰漏或者更好的想法,请您一定不要吝啬你的赐教🙋。
&spm=1001.2101.3001.5002&articleId=146026641&d=1&t=3&u=fe9a903227bf400fb91eebc5547ecf12)
964

被折叠的 条评论
为什么被折叠?



