最近在项目中遇到了这样一个业务需求。要求订单每次生成之后根据订单状态及URL地址进行回调处理。如不成功或出现异常则过10分钟再次回调,最多三次;我们开始是使用Redis延迟队列进行处理,订单生成时候进行回调,不成功则放入Redis延迟队列,然后使用分布式调度系统在其他服务取出队列消息消费处理,不成功则再次放入队列,最多三次;最后压测发现性能有点低,于是考虑使用roketmq的延迟消息发送队列。
现在好消息是springboot整合roketmq比较简单了;只需maven引入相关依赖即可
org.apache.rocketmq
rocketmq-spring-boot-starter
2.0.3
rocketmq:
name-server: 127.0.0.1:9876 #自己服务使用的roketmq nameserver地址
producer:
group: my-group
retry-times-when-send-failed: 2
retry-times-when-send-async-failed: 2
@RunWith(SpringJUnit4ClassRunner.class)
@Slf4j
@SpringBootTest(classes = 自己的服务Application.class)
public class RocketMqTest {
@Resource
private RocketMQTemplate rocketMQTemplate;
@Test
public void testRocketMq1() {
NotifyVo notifyVo = new NotifyVo();
notifyVo.setOrderNumber("3ea9da3a5037449fa5acf6dd18658fc8");
notifyVo.setIndex(1);
notifyVo.setPaymentNumber("1152160156201185280");
rocketMQTemplate.asyncSend("test_topic", MessageBuilder.withPayload(notifyVo).build(), new SendCallback() {
@Override
public void onSuccess(SendResult var1) {
log.info("async onSucess SendResult :{}", var1);
}
@Override
public void onException(Throwable var1) {
log.info("async onException Throwable :{}", var1);
}
}, 300000, 2);
log.info("发送成功...")
}
}
@Service
@Slf4j
@RocketMQMessageListener(topic = “test_topic”, consumerGroup = WALLET_ORDER_CONSUMER)
public class NotifyEventConsumer implements RocketMQListener {
@Resource
private RocketMQTemplate rocketMQTemplate;//消息处理失败再次发送消息
@Override
public void onMessage(NotifyVo message) {
log.info("------- NotifyEventConsumer received:" + JSON.toJSONString(message));
if (message.getIndex() < 3) {
notify(message);//消息的具体处理逻辑
}
}
}
简单来说,同步发送就是指 producer 发送消息后,会在接收到 broker 响应后才继续发下一条消息的通信方式。由于这种同步发送的方式确保了消息的可靠性,同时也能及时得到消息发送的结果,故而适合一些发送比较重要的消息场景,比如说重要的通知邮件、营销短信等等。在实际应用中,这种同步发送的方式还是用得比较多的。同步发送需等待sendResult;
public SendResult syncSend(String destination, Message> message, long timeout) { return this.syncSend(destination, message, timeout, 0); }
异步发送是指 producer 发出一条消息后,不需要等待 broker 响应,就接着发送下一条消息的通信方式。需要注意的是,不等待 broker 响应,并不意味着 broker 不响应,而是通过回调接口来接收 broker 的响应。所以要记住一点,异步发送同样可以对消息的响应结果进行处理。
由于异步发送不需要等待 broker 的响应,故在一些比较注重 RT(响应时间)的场景就会比较适用。比如,在一些视频上传的场景,我们知道视频上传之后需要进行转码,如果使用同步发送的方式来通知启动转码服务,那么就需要等待转码完成才能发回转码结果的响应,由于转码时间往往较长,很容易造成响应超时。此时,如果使用的是异步发送通知转码服务,那么就可以等转码完成后,再通过回调接口来接收转码结果的响应了。
public void asyncSend(String destination, Message> message, SendCallback sendCallback, long timeout, int delayLevel) {
if (!Objects.isNull(message) && !Objects.isNull(message.getPayload())) {
try {
org.apache.rocketmq.common.message.Message rocketMsg = RocketMQUtil.convertToRocketMessage(this.objectMapper, this.charset, destination, message);
if (delayLevel > 0) {
rocketMsg.setDelayTimeLevel(delayLevel);
}
this.producer.send(rocketMsg, sendCallback, timeout);
} catch (Exception var8) {
log.info("asyncSend failed. destination:{}, message:{} ", destination, message);
throw new MessagingException(var8.getMessage(), var8);
}
} else {
log.error("asyncSend failed. destination:{}, message is null ", destination);
throw new IllegalArgumentException("`message` and `message.payload` cannot be null");
}
}
producer 只负责发送消息,不等待 broker 发回响应结果,而且也没有回调函数触发,这也就意味着 producer 只发送请求不等待响应结果。
由于单向发送只是简单地发送消息,不需要等待响应,也没有回调接口触发,故发送消息所耗费的时间非常短,同时也意味着消息不可靠。所以这种单向发送比较适用于那些耗时要求非常短,但对可靠性要求并不高的场景,比如说日志收集。
public void sendOneWay(String destination, Object payload) {
Message> message = this.doConvert(payload, (Map)null, (MessagePostProcessor)null);
this.sendOneWay(destination, message);
}
同步发送异步发送都可以发送延迟消息,一个延时消息被发出到消费成功经历以下几个过程:
1.设置消息的延时级别delayLevel
2.producer发送消息
3.broker收到消息在准备将消息写入存储的时候,判断是延时消息则更改Message的topic为延时消息队列的topic,也就是将消息投递到延时消息队列
4.有定时任务从延时队列中读取消息,拿到消息后判断是否达到延时时间,如果到了则修改topic为原始topic。并将消息投递到原 始topic的队列
5consumer像消费其他消息一样从broker拉取消息进行消费
RocketMQ 支持发送延迟消息,但不支持任意时间的延迟消息的设置,仅支持内置预设值的延迟时间间隔的延迟消息。分为18个级别,0级不延时。
预设值的延迟时间间隔为:1s、 5s、 10s、 30s、 1m、 2m、 3m、 4m、 5m、 6m、 7m、 8m、 9m、 10m、 20m、 30m、 1h、 2h
在消息创建的时候,调用 消息的setDelayTimeLevel(int level) 方法设置延迟时间。当然,rocketMQTemplate发送延迟消息可以直接设置消息级别,如上代码异步发送asyncSend,broker在接收到延迟消息的时候会把对应延迟级别的消息先存储到对应的延迟队列中,等延迟消息时间到达时,会把消息重新存储到对应的topic的queue里面。