消息可达性
现在大数据时代,很多项目都要使用消息队列,但是一直有一个问题困扰着我们,那就是怎么保证消息不丢?上网搜了一下,反正没找到讲的很清楚的文章,所以这里准备分析一个MQ的源码,然后结合自己的理解写一篇实用性比较强的文章。
那么拿什么MQ分析呢?市面上的MQ太多了,但是我们一定要分析金融级别的MQ,因为它们为金融和订单而设计的,显然它们在可达性上是最具参考价值的,无疑阿里的RocketMQ是最好的选择,了解了金融级的MQ的处理方法,其他的也就大同小异了
OK,不废话,show me the code,源码里面无秘密
那必然就要分析3个方法:
send 同步
send 异步
sendOneway 异步单向
那这3个方法肯定各有千秋,它们会在性能和可达性上左右偏移来达到不同的效果,要不然何必要3个接口呢?
重试的触发点在哪里
要重试,首先最关键的是找到触发点,其实也就两点,如果是Java
- catch异常的地方
- 判断返回值的结果
如果是c/c++/go
- 判断error值
- 判断返回值的结果
怎么重试
重试一般是在一个for循环里通过判断错误来做有限次数的重试
为了保证消息的绝对可达,重发肯定是必须的手段,那么重发有接口内重试和接口外重试,这是什么意思呢?
- 接口内重试就是接口里面的实现会去重试
- 接口外重试就是根据接口抛出的异常和方法的返回值来进行业务层面的重试
重试又分两个级别:
- 本地错误重试 本地就能产生的错误 包括系统错 Io错 业务错
- 远程错误码重试,即人为自定义错误 但是必须是broker通过io传过来的错,相当于要收到broker反馈的消息
先说结果再分析
本地错误重试:sendOneway/同步send/异步Send
远程broker自定义错误重试:同步send/异步Send
sendOneway
这个方法最简单,所以我们先分析这个方法,分析过程中也会掌握一些代码的公共方法的实现,利于后面的分析
我们先看看这个方法样子
void sendOneway(org.apache.rocketmq.common.message.Message)
从字面意思理解,oneway就是单向,感觉应该是不会重试的
我们先看看接口内重试了没有,怎么重试的,咱们来看看调用链:
DefaultMQProducer#sendOneway
DefaultMQProducerImpl#sendDefaultImpl
NettyRemotingClient#invokeOneway
...
NettyRemotingClient#invokeOneway
NettyRemotingAbstract#invokeOnewayImpl
这里面DefaultMQProducerImpl#sendDefaultImpl是个小重点
...
int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 +this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1;
for (; times < timesTotal; times++) {
...
switch (communicationMode) {
case ASYNC:
return null;
case ONEWAY:
return null;
case SYNC:
return sendResult;
}
...
What ! 我们看到有retryTimes,然后又看到for循环了啊,这不是在重试吗,那么这个重试到底是本地错重试还是远程错误码重试呢?
这里有个重点是communicationMode,这个有同步,异步和单向3种类型
我们接着看调用链的下一个个方法:
很明显调用下去肯定没有远程的返回值,所以sendOneway不可能对远程状态码重试,因为它不会收到任何值
public void invokeOneway(String addr, RemotingCommand request, long timeoutMillis) throws InterruptedException,
RemotingConnectException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
final Channel channel = this.getAndCreateChannel(addr);
if (channel != null && channel.isActive()) {
try {
if (this.rpcHook != null) {
this.rpcHook.doBeforeRequest(addr, request);
}
this.invokeOnewayImpl(channel, request, timeoutMillis);
} catch (RemotingSendRequestException e) {