RocketMQ源码解析——Producer部分之消息发送过程的一些重试和容错逻辑(2)

前面已经说了消息发送的主逻辑这里对发送过程中的一些容错相关实现进行讲解

发送消息失败时候的重试

同步模式——不同Broker之间重试

发送消息失败时候的重试,这个重试只有在同步发送消息的逻辑中会有存在,这里的重试是重试别的Broker。具体截取部分代码,在之前分析过的消息发送主逻辑方法sendDefaultImpl

	private SendResult sendDefaultImpl(
        Message msg,
        final CommunicationMode communicationMode,
        final SendCallback sendCallback,
        final long timeout
    ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {

	/......省略代码......../
 int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1;
            int times = 0;
            //记录重试时候发送消息目标Broker名字的数组
            String[] brokersSent = new String[timesTotal];
			//进行重试次数的循环发送消息逻辑
            for (; times < timesTotal; times++) {
				/......省略代码......../
			}
		/......省略代码......../
	}

这里需要注意的是,这里的重试是只有在同步发送的逻辑中才会有这个重试,是不同的Broker之间的重试

异步模式——同一个Broker内部重试

异步模式发送消息的方式的重试,是在同一个Broker内部进行重试。这个次数是由Producer端的retryTimesWhenSendAsyncFailed参数控制的。具体的逻辑是MQClientAPIImpl的异步消息发送逻辑失败时候的逻辑中,方法名onExceptionImpl

private void onExceptionImpl(final String brokerName,
        final Message msg,
        final long timeoutMillis,
        final RemotingCommand request,
        final SendCallback sendCallback,
        final TopicPublishInfo topicPublishInfo,
        final MQClientInstance instance,
        final int timesTotal,
        final AtomicInteger curTimes,
        final Exception e,
        final SendMessageContext context,
        final boolean needRetry,
        final DefaultMQProducerImpl producer
    ) {
        int tmp = curTimes.incrementAndGet();
        //是否需要重试,并且重试次数没有达到总次数
        if (needRetry && tmp <= timesTotal) {
        /......省略代码..发送逻辑....../
        }
  }
发送结果不是正常之后的重试

前面说的发送失败是在Producer端发送消息没有发送到Broker之间发生了异常。而在消息发送给Broker之后,可能存在失败的情况,消息刷盘(主或备)超时或slave不可用(返回状态非SEND_OK)。
在Producer端有一个参数retryAnotherBrokerWhenNotStoreOK。这个参数的作用就是当发送失败之后,进行重试选择别的Broker进行发送。这个配置默认是关闭的,如果是十分重要的消息可以开启。这里截取一下这部分的代码逻辑,方法的逻辑在DefaultMQProducerImplsendDefaultImpl

	private SendResult sendDefaultImpl(
        Message msg,
        final CommunicationMode communicationMode,
        final SendCallback sendCallback,
        final long timeout
    ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {

	    /......省略代码......../
 			switch (communicationMode) {
                            case ASYNC:
                                return null;
                            case ONEWAY:
                                return null;
                            case SYNC:
                            	//发送的结果如果不是 SEND_OK ,如果开启了重试,则进行选择别的Broker进行重试
                                if (sendResult.getSendStatus() != SendStatus.SEND_OK) {
                                    if (this.defaultMQProducer.isRetryAnotherBrokerWhenNotStoreOK()) {
                                        continue;
                                    }
                                }

                                return sendResult;
                            default:
                                break;
                      }
		/......省略代码......../
	}

发送消息容错

RocketMQ的容错逻辑主要都在MQFaultStrategy中。先对其中的几个变量讲解一下

    //是否开启 故障延迟机制
    private boolean sendLatencyFaultEnable = false;
    
    //不同的最大延迟级别,每个延迟级别的index都会跟下面的不可用间隔的index对应
    private long[] latencyMax = {50L, 100L, 550L, 1000L, 2000L, 3000L, 15000L};
    
    //不可用间隔级别,
    private long[] notAvailableDuration = {0L, 0L, 30000L, 60000L, 120000L, 180000L, 600000L};
更新容错策略——updateFaultItem
    public void updateFaultItem(final String brokerName, final long currentLatency, boolean isolation) {
		//是否开启故障延迟机制
        if (this.sendLatencyFaultEnable) {
        	//计算不可用的持续时间,默认是30s
            long duration = computeNotAvailableDuration(isolation ? 30000 : currentLatency);
			//更新broker对应的不可用时间
            this.latencyFaultTolerance.updateFaultItem(brokerName, currentLatency, duration);
        }
    }

    private long computeNotAvailableDuration(final long currentLatency) {
        for (int i = latencyMax.length - 1; i >= 0; i--) {
			//根据上次请求延迟时间选择退避时间,例如,如果上次请求的latency超过550Lms,就退避3000Lms;超过1000L,就退避60000L
            if (currentLatency >= latencyMax[i])
                return this.notAvailableDuration[i];
        }
        return 0;
    }

分析一下上面的逻辑:

  1. 如果开启了故障延迟机制,则计算次吃的不可用时间,其中要注意参数isolation为true的时候才会有效,计算逻辑如下,根据此次发送的延迟时间选择对应的不可用时间
  2. 根据选择出来的不可用时间更新对应的Broker不可用信息

这个方法的在DefaultMQProducerImplupdateFaultItem中被调用,而这个方法又在各个发送消息失败的位置会被调用到。感兴趣的可以自行查看。

这里再对更新Broker的延迟信息的方法,也就是LatencyFaultToleranceImplupdateFaultItem方法。

    public void updateFaultItem(final String name, final long currentLatency, final long notAvailableDuration) {
		//根据BrokerName获取对应的故障信息
        FaultItem old = this.faultItemTable.get(name);
		//如果为空则创建
        if (null == old) {
            final FaultItem faultItem = new FaultItem(name);
			//设置当前的延迟级别
            faultItem.setCurrentLatency(currentLatency);
			//设置下次可以使用的时间
            faultItem.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);
            //保存
            old = this.faultItemTable.putIfAbsent(name, faultItem);
			//这里是避免多个线程同时设置
            if (old != null) {
                old.setCurrentLatency(currentLatency);
                old.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);
            }
        } else {
            old.setCurrentLatency(currentLatency);
            old.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);
        }
    }

这里还需要说一下检查Broker是否故障的方法isAvailable

   public boolean isAvailable(final String name) {
    	//根据brokerName获取容错配置
        final FaultItem faultItem = this.faultItemTable.get(name);
		//如果不为空,检查时间是否到达了下次可使用的时间点,startTimestamp
        if (faultItem != null) {
            return faultItem.isAvailable();
        }
        return true;
    }
选择MessageQueue过程中的故障延迟机制——selectOneMessageQueue

Producer端在发送消息的时候,会先根据Topic找到指定的TopicPublishInfo,在获取了TopicPublishInfo路由信息后,RocketMQ的客户端在默认方式下selectOneMessageQueue方法会从TopicPublishInfo中的messageQueueList中选择一个队列(MessageQueue)进行发送消息。在选择过程中故障延迟机制就在里面。

具体的容错策略均在MQFaultStrategy这个类中定义。这里有一个sendLatencyFaultEnable开关变量,如果开启,在随机递增取模的基础上,所谓的latencyFaultTolerance,是指对之前失败的,按一定的时间做退避。例如,如果上次请求的latency超过550Lms,就退避3000Lms;超过1000L,就退避60000L;如果关闭,采用随机递增取模的方式选择一个队列(MessageQueue)来发送消息,latencyFaultTolerance机制是实现消息发送高可用的核心关键所在。

直接对这个方法进行分析

public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
		//是否开启故障延迟
        if (this.sendLatencyFaultEnable) {
            try {
            	//生成选择队列用的的index,这里是用的ThreadLocal,每个线程有自己的递增id,
                int index = tpInfo.getSendWhichQueue().incrementAndGet();
                //获取topic的MessageQueue集合迭代选择
                for (int i = 0; i < tpInfo.getMessageQueueList().size(); i++) {
                	//根据MessageQueue的数量取模(随机递增取模),选择一个MessageQueue
                    int pos = Math.abs(index++) % tpInfo.getMessageQueueList().size();
                    if (pos < 0)
                        pos = 0;
                    MessageQueue mq = tpInfo.getMessageQueueList().get(pos);
                    //检查这个MessageQueue的Broker是不是可用的,是的则返回
                    if (latencyFaultTolerance.isAvailable(mq.getBrokerName()))
                        return mq;
                }
                //如果所有的都不可用,则尝试选择一个,如果没有则返回null
                final String notBestBroker = latencyFaultTolerance.pickOneAtLeast();
				//根据broker名字获取对应的QueueId
                int writeQueueNums = tpInfo.getQueueIdByBroker(notBestBroker);
				//如果id大于0
                if (writeQueueNums > 0) {
                	//选一个MessageQueue
                    final MessageQueue mq = tpInfo.selectOneMessageQueue();
                    if (notBestBroker != null) {
                        mq.setBrokerName(notBestBroker);
                        //按照队列数量取模选择一个
                        mq.setQueueId(tpInfo.getSendWhichQueue().incrementAndGet() % writeQueueNums);
                    }
                    //返回
                    return mq;
                } else {
                	//如果没有可用的broker
                    latencyFaultTolerance.remove(notBestBroker);
                }
            } catch (Exception e) {
                log.error("Error occurred when selecting message queue", e);
            }

            return tpInfo.selectOneMessageQueue();
        }
        //没有开启就随机取模,选择一个
        return tpInfo.selectOneMessageQueue(lastBrokerName);
    }

主要逻辑如下:

  1. 随机生成一个随机数,这个随机数是跟随线程的
  2. 随机选择一个消息队列,检查这个队列对应的Broker是否可用,这个可用检查逻辑就在上面说的isAvailable中,如果不可用就重复步骤1和2。直到选择出一个Broker或者遍历完
  3. 如果遍历完了都没有一个合适的,那就随机选择一个
  4. 获取选择的Broker的写队列个数,然后随机选一个
  5. 返回队列

整个Producer部分的重试和容错策略说完了

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值