RocketMQ第4讲——生产者(producer)

过了个年懈怠了,距离上篇文章已经过去了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:希望对大家有所帮助,如果有纰漏或者更好的想法,请您一定不要吝啬你的赐教🙋。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

橡 皮 人

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值