Motan_ 高可用(HA)

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

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。

参考

相关帖子

欢迎来到这里!

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

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