Motan_ 高可用(HA)

本贴最后更新于 1702 天前,其中的信息可能已经东海扬尘

Motan 系列文章


HA 即高可用,也可以理解为容错策略。在 Motan 中,HA 作用于 Client 端,其含义是当 RPC 调用失败时,采取的策略。目前有以下两种策略:

  • Failover:失效转移(默认)

配置方式如下:

<motan:protocol ... haStrategy="failover"/>

其含义是,当 RPC 调用失败时,自动重试其他服务器。

  • Failfast:快速失败

配置方式如下:

<motan:protocol ... haStrategy="failfast"/>

其含义是,只发起一次调用,失败立即报错。

0 工程结构及继承关系

下图展示了 HA 源码在工程中的位置:

MotanHA1.png

下面来看一下 HA 的体系结构:

MotanHA2.png

这里最核心的是 call() 方法。这个方法在 HaStrategy 接口定义,由具体实现类实现,用于完成 HA 策略逻辑。

1 HA 策略的选取及设置

Motan 会在 Cluster 初始化阶段设置 HA 策略。HA 策略可通过上述配置来定义,如果不配置,默认使用 Failover,即失效转移策略。

在实现上,HA 采用插件化开发(即 SPI),每个 HA 的具体实现都是一个 SPI,其实现类都标注了 @SpiMeta 注解,在 setHaStrategy 时,根据具体的 HA 名称即可找到具体的实现。以 Failover 为例:

@SpiMeta(name = "failover")
public class FailoverHaStrategy<T> extends AbstractHaStrategy<T> {
    
}

然后在 ClusterSupport 中通过 SPI 获取 HA 的具体实现,并 setHaStrategy。

private void prepareCluster() {
    String clusterName = url.getParameter(URLParamType.cluster.getName(), URLParamType.cluster.getValue());
    String loadbalanceName = url.getParameter(URLParamType.loadbalance.getName(), URLParamType.loadbalance.getValue());
    // 获取HA策略,优选获取用户配置的,如果没有配置,取URLParamType配置的默认值,即 failover
    String haStrategyName = url.getParameter(URLParamType.haStrategy.getName(), URLParamType.haStrategy.getValue());

    cluster = ExtensionLoader.getExtensionLoader(Cluster.class).getExtension(clusterName);
    LoadBalance<T> loadBalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(loadbalanceName);
    // 通过SPI的方式获取具体HA实现
    HaStrategy<T> ha = ExtensionLoader.getExtensionLoader(HaStrategy.class).getExtension(haStrategyName);
    ha.setUrl(url);
    cluster.setLoadBalance(loadBalance); // setLoadBalance
    cluster.setHaStrategy(ha);
    cluster.setUrl(url);
}

2 调用时序

配置好 HA 以后,再来看一下调用时序。以下时序图说明了一个 RPC 请求的调用时序,主要关注 HaStrategy 的调用时机。

MotanReferer 调用时序图.jpg

至此,本文已说明 HA 的设置以及 HA 的调用时机,下面来介绍一下两种 HA 的具体实现。

3 HA 的实现

下面介绍一下两种 HA 的具体源码实现:

  • Failover:失效转移
  • Failfast:快速失败

3.1 Failover

Failover 是默认的 HA 策略。

@SpiMeta(name = "failover")
public class FailoverHaStrategy<T> extends AbstractHaStrategy<T> {
    
    // 用ThreadLocal存储每次调用中 可用的集群备选节点
    protected ThreadLocal<List<Referer<T>>> referersHolder = new ThreadLocal<List<Referer<T>>>() {
        @Override
        protected java.util.List<com.weibo.api.motan.rpc.Referer<T>> initialValue() {
            return new ArrayList<Referer<T>>();
        }
    };

    @Override
    public Response call(Request request, LoadBalance<T> loadBalance) {

        List<Referer<T>> referers = selectReferers(request, loadBalance);
        if (referers.isEmpty()) {
            throw new MotanServiceException(String.format("FailoverHaStrategy No referers for request:%s, loadbalance:%s", request,
                    loadBalance));
        }
        URL refUrl = referers.get(0).getUrl();
        // 获取重试次数配置,默认等于0,即不重试
        int tryCount =
                refUrl.getMethodParameter(request.getMethodName(), request.getParamtersDesc(), URLParamType.retries.getName(),
                        URLParamType.retries.getIntValue());
        // 如果有问题,则设置为不重试
        if (tryCount < 0) {
            tryCount = 0;
        }

        for (int i = 0; i <= tryCount; i++) {
            // 失效转移,当tryCount大于0时,会挨个尝试referers中的节点。
            Referer<T> refer = referers.get(i % referers.size());
            try {
                // 设置重试次数
                request.setRetries(i);
                return refer.call(request);
            } catch (RuntimeException e) {
                // 对于业务异常,直接抛出
                if (ExceptionUtil.isBizException(e)) {
                    throw e;
                // 当重试次数未达到tryCount时,继续下一次循环,否则抛出异常
                } else if (i >= tryCount) {
                    throw e;
                }
                LoggerUtil.warn(String.format("FailoverHaStrategy Call false for request:%s error=%s", request, e.getMessage()));
            }
        }

        throw new MotanFrameworkException("FailoverHaStrategy.call should not come here!");
    }
    
    // 根据LoadBalance策略选取备选节点,并存在ThreadLocal中
    protected List<Referer<T>> selectReferers(Request request, LoadBalance<T> loadBalance) {
        List<Referer<T>> referers = referersHolder.get();
        referers.clear();
        loadBalance.selectToHolder(request, referers);
        return referers;
    }

}

由上述代码可知,失效转移机制和 retries 这个参数有关,当这个参数为 0 时,只会调用一次,如果失败就直接抛出异常了,当这个参数大于 0 时,会挨个尝试 LoadBalance 策略返回的可用集群节点。

3.2 Failfast

Failfast 会在 RPC 调用失败时直接抛出异常,其实现很简单:

@SpiMeta(name = "failfast")
public class FailfastHaStrategy<T> extends AbstractHaStrategy<T> {

    @Override
    public Response call(Request request, LoadBalance<T> loadBalance) {
        Referer<T> refer = loadBalance.select(request);
        return refer.call(request);
    }
}

由于快速失败策略不用重试,所以调用 LoadBalance 的 select 方法选取一个节点即可(Failover 因为需要满足失效转移,所以需要多个备选节点,referersHolder 就是获取多个节点的方法),如果节点调用失败,则抛出 RuntimeException。

参考

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...