1.相关概念
在分布式模型-CAP理论中,A代表了可用性,即访问同一个服务总能请求到非错的结果。在微服务环境下,为了保证服务的高可用,服务通常会以集群的形式出现,当某个服务调用出现异常、网络抖动、服务暂不可用需要能自动容错,或者只想本地测试、服务降级,需要Mock返回结果。Cluter层有几大核心接口Cluster、Directory、Router、 LoadBalance,通过集群模式进行远程调用的过程如下所示,主要可分为几大步骤:
- 1.生成Invoker对象,不同的Cluster实现会生成不同类型的ClusterInvoker对象并返回;
- 2.获得可调用的服务列表,首先做前置校验,检查远端服务是否已被销毁,然后通过Direcotory#list方法获取所有可用的服务列表。接着使用Router接口处理该服务列表,根据路由规则过滤一部分服务,最终返回可用的服务列表;
- 3.做负载均衡,在获取服务列表后还需要通过不同的负载均衡策略选出一个服务节点,用作目标调用节点,Dubbo框架会根据用户的配置,调用ExtensionLoader获取不同的负载均衡策略的扩展点实现,然后调用子类实现的doInvoke方法,子类会根据具体的负载均衡策略选出一个可调用的服务;
- 做RPC调用,保存每次调用的Invoker到RPC上下文中,并实现RPC调用。然后处理调用结果,对于调用出现异常、成功、失败等情况,每种容错策略会有不同的处理方式。
2.容错机制实现
2.1 框架容错机制
Dubbo容错机制能增强整个应用的健壮性,用户可以通过不同的配置项来选择不同的容错机制,每种容错机制又有不同的配置项。如下所示就是不同的容错策略。
- Failover: 当出现失败时,会重试其他服务器。用户可以通过设置服务消费端引用配置参数"retries=2"设置重试次数, 这是Dubbo默认容错机制,会对请求做负载均衡。通常使用在读操作或幂等的写操作上,但重试节会导致接口的延迟增大,并对下游的服务节点产生请求压力,加重负载。如果不想重试直接设置为-1,而不是1。
- Failfast: 快速失败,当请求失败后,快速返回异常结果,不做任何重试。该容错机制会对请求做负载均衡,通常使用在非幂等接口的调用上。该调用受网络抖动比较大。
- Failsafe: 当出现异常的时候,直接忽略异常。会对请求做负载均衡,不关心调用是否成功,并且不希望抛异常影响正常的业务流程,如某些不重要的日志同步,出现异常也无所谓。
- Forking: 同时调用多个相同的服务,只要其中一个返回,则立即返回结果。用户可以配置forks=“最大并行调用参数"来确定最大并行调用的服务数量。通常使用在对实时性要求极高的调用上,但也会浪费很多资源。
- Failback: 请求失败后,会自动记录在失败列表中,并由一个线程池定时重试,适用于一些异步或最终一致性的请求。
- Broadcast: 广播调用所有的服务,任意一个节点报错则报错,请求不做负载均衡。
- Mock: 提供调用失败时,返回预定义的响应结果。或直接强制返回预先定义的结果。
- Available: 遍历所有的服务节点列表,找到第一个可用的节点,直接请求并返回。如果没有可用的服务节点则抛出异常。
- Mergeable: 自动把多个节点请求得到的结果进行合并。
2.2 Cluster层主要实现
在微服务环境中,可能多个节点同时提供同一个服务。当上层调用Invoker时,实际存在多个Invoker,只需要通过Cluster层,即可完成整个调用的容错逻辑。这个实现逻辑包括了获取服务列表、链路路由、负载均衡等,使用了Directory、Router、LoadBalance等接口实现。容错的接口有两种,Cluster与ClusterInvoker。其中Cluster接口有多种不同的实现,每种实现中都有join方法,在方法中都会新建一个对应的ClusterInvoker实现。在每个远程调用初始化生成的时候,会将Invoker做一层包装,最后经过代理后的FailoverInvoker作为返回,这里默认实现使用的是代码如下所示。
public class ReferenceConfig<T> extends ReferenceConfigBase<T> {
//默认使用FailoverCluster实现
private static final Cluster CLUSTER =
ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension();
private T createProxy(Map<String, String> map) {
...
//如果只有一个可用节点,则不用走集群逻辑
if (urls.size() == 1) {
invoker = REF_PROTOCOL.refer(interfacseClass, urls.get(0));
} else {
...
if (registryURL != null) {
//增加URL参数
URL u = registryURL.addParameterIfAbsent(CLUSTER_KEY, ZoneAwareCluster.NAME);
//返回FailoverInvoker
invoker = CLUSTER.join(new StaticDirectory(u, invokers));
} else {
//注册的URL是直连的
invoker = CLUSTER.join(new StaticDirectory(invokers));
}
...
}
}
}
FailoverCluster作为Cluster接口的一种实现,继承了AbstractCluster类,FailoverCluster中创建了一个新的FailoverClusterInvoker实现并返回。FailoverClusterInvoker继承的接口是Invoker,Cluster层的一些主要类结构如下所示。
Cluster接口上,默认实现是FailoverCluster,其中的还实现了AbstractCluster模版类,在执行join方法之前先要组装前置拦截器执行链,在Invoke调用的时候,会在执行前中后以及异常的情况下去执行对应的拦截器方法。
@SPI(FailoverCluster.NAME)
public interface Cluster {
@Adaptive
<T> Invoker<T> join(Directory<T> directory) throws RpcException;
}
public abstract class AbstractCluster implements Cluster {
//组装拦截器执行链路
private <T> Invoker<T> buildClusterInterceptors(
AbstractClusterInvoker<T> clusterInvoker, String key) {
AbstractClusterInvoker<T> last = clusterInvoker;
//加载拦截器扩展实现
List<ClusterInterceptor> interceptors =
ExtensionLoader.getExtensionLoader(ClusterInterceptor.class)
.getActivateExtension(clusterInvoker.getUrl(), key);
if (!interceptors.isEmpty()) {
//组装拦截器执行链路
for (int i = interceptors.size() - 1; i >= 0; i--) {
final ClusterInterceptor interceptor = interceptors.get(i);
final AbstractClusterInvoker<T> next = last;
last = new InterceptorInvokerNode<>(clusterInvoker,
interceptor, next);
}
}
return last;
}
@Override
public Result invoke(Invocation invocation) throws RpcException {
Result asyncResult;
try {
//执行拦截器前置方法
interceptor.before(next, invocation);
asyncResult = interceptor.intercept(next, invocation);
} catch (Exception e) {
if (interceptor instanceof ClusterInterceptor.Listener) {
ClusterInterceptor.Listener listener =
(ClusterInterceptor.Listener) interceptor;
listener.onError(e, clusterInvoker, invocation);
}
throw e;
} finally {
//执行拦截器后置方法
interceptor.after(next, invocation);
}
return asyncResult.whenCompleteWithContext((r, t) -> {
if (interceptor instanceof ClusterInterceptor.Listener) {
ClusterInterceptor.Listener listener =
(ClusterInterceptor.Listener) interceptor;
if (t == null) {
//触发监听器对象的onMessage
listener.onMessage(r, clusterInvoker, invocation);
} else {
//触发监听器对象的onError
listener.onError(t, clusterInvoker, invocation);
}
}
});
}
...
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
//组装调用拦截器,并返回执行器链路节点
return buildClusterInterceptors(doJoin(directory),
directory.getUrl().getParameter(REFERENCE_INTERCEPTOR_KEY));
}
protected abstract <T> AbstractClusterInvoker<T> doJoin(Directory<T> directory) throws RpcException;
}
可以看到ConsumerContextClusterInterceptor类实现了Cluster拦截器接口与Cluster监听器接口,在before/after/onMessage/onError时都触发,并将其中的调用信息放入RpcContext调用上下文中,RpcContext包括了调用URL、方法名、方法参数类型数组、方法参数数组、本地地址、远方地址等调用信息。
public class ConsumerContextClusterInterceptor implements ClusterInterceptor,
ClusterInterceptor.Listener {
...
@Override
public void before(AbstractClusterInvoker<?> invoker, Invocation invocation) {
//获取每条线程中的RpcContext实例对象,使用InternalThreadLocal
RpcContext context = RpcContext.getContext();
context.setInvocation(invocation).setLocalAddress(NetUtils.getLocalHost(), 0);
if (invocation instanceof RpcInvocation) {
((RpcInvocation) invocation).setInvoker(invoker);
}
RpcContext.removeServerContext();
}
@Override
public void after(AbstractClusterInvoker<?> clusterInvoker, Invocation invocation) {
RpcContext.removeContext(true);
}
@Override
public void onMessage(Result appResponse, AbstractClusterInvoker<?> invoker, Invocation invocation) {
RpcContext.getServerContext().setObjectAttachments(appResponse.getObjectAttachments());
}
....
}
AbstractClusterInvokerl类继承ClusterInvoker接口实现了invoke方法,主要逻辑是先调用Directory#list方法获取Invoker列表,初始化负载均衡对象,将Invoker列表和负载均衡实现作为子类实现doInvoker方法参数进行调用。
public abstract class AbstractClusterInvoker<T> implements Invoker<T> {
protected Invoker<T> select(LoadBalance loadbalance, Invocation invocation,
List<Invoker<T>> invokers,
List<Invoker<T>> selected) throws RpcException {
....
Invoker<T> invoker = doSelect(loadbalance, invocation, invokers, selected);
....
return invoker;
}
private Invoker<T> doSelect(LoadBalance loadbalance, Invocation invocation,
List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
//如果服务列表只有一个元素,就返回第一个,不去走下面的负载均衡的select方法
if (invokers.size() == 1) {
return invokers.get(0);
}
//通过负载均衡实现select挑选的一个Invoker
Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);
//判断invoker是否有效
if ((selected != null && selected.contains(invoker))
|| (!invoker.isAvailable() && getUrl() != null && availablecheck)) {
try {
//重新选择
Invoker<T> rInvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
//重新选择Invoker对象不为空
if (rInvoker != null) {
invoker = rInvoker;
} else {
//重新选择的Invoker对象为空,则调用列表中的下一个Invoker
int index = invokers.indexOf(invoker);
try {
invoker = invokers.get((index + 1) % invokers.size());
} catch (Exception e) {
...
}
}
} catch (Throwable t) {
...
}
}
return invoker;
}
@Override
public Result invoke(final Invocation invocation) throws RpcException {
...
//获取invoker列表
List<Invoker<T>> invokers = list(invocation);
//初始化负载均衡
LoadBalance loadbalance = initLoadBalance(invokers, invocation);
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
//调用子类的invoke方法
return doInvoke(invocation, invokers, loadbalance);
}
protected abstract Result doInvoke(Invocation invocation, List<Invoker<T>> invokers,
LoadBalance loadbalance) throws RpcException;
}
- Failover Cluster:失败自动切换,当出现失败,重试其它服务器 。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。
- Failfast Cluster:快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
- Failsafe Cluster:失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。 Failback Cluster:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。 Forking Cluster:并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。
- Broadcast Cluster:广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
2.3 不同的容错策略实现
2.3.1 Failover策略
Cluster的默认实现是FailoverCluster,首先校验AbstractClusterInvoker传入的Invoker列表是否为空,并从调用URL中获取对应的retries重试次数,初始化一些集合和对象用于保存调用过程中出现的异常、记录调用了哪些节点。
public class FailoverClusterInvoker<T> extends AbstractClusterInvoker<T> {
...
@Override
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
List<Invoker<T>> copyInvokers = invokers;
//校验invoker列表
checkInvokers(copyInvokers, invocation);
String methodName = RpcUtils.getMethodName(invocation);
//获取url中的参数
int len = getUrl().getMethodParameter(methodName, RETRIES_KEY, DEFAULT_RETRIES) + 1;
RpcException le = null;
List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyInvokers.size());
Set<String> providers = new HashSet<String>(len);
for (int i = 0; i < len; i++) {
if (i > 0) {
checkWhetherDestroyed();
copyInvokers = list(invocation);
checkInvokers(copyInvokers, invocation);
}
Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);
invoked.add(invoker);
RpcContext.getContext().setInvokers((List) invoked);
try {
Result result = invoker.invoke(invocation);
return result;
} catch (RpcException e) {
...
} catch (Throwable e) {
...
} finally {
providers.add(invoker.getUrl().getAddress());
}
}
}
}
2.3.2 Failfast策略
Failfast会在失败后直接抛出异常并返回,首先校验AbstractClusterInvoker传入的Invoker列表是否为空,调用select方法做负载均衡,得到要调用的节点,最后进行远程调用。
public class FailfastClusterInvoker<T> extends AbstractClusterInvoker<T> {
@Override
public Result doInvoke(Invocation invocation,
List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
checkInvokers(invokers, invocation);
Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
try {
return invoker.invoke(invocation);
} catch (Throwable e) {
...
}
}
}
2.3.3 Failsafe策略
Failsafe调用时,会使用try-catch将select和invoke过程中抛出的异常捕获,返回一个空的结果集。
public class FailsafeClusterInvoker<T> extends AbstractClusterInvoker<T> {
...
@Override
public Result doInvoke(Invocation invocation,
List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
try {
checkInvokers(invokers, invocation);
Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
return invoker.invoke(invocation);
} catch (Throwable e) {
return AsyncRpcResult.newDefaultAsyncResult(null, null, invocation); // ignore
}
}
}
2.3.4 Failback策略
Failbakck是调用失败后,会定期重试。实现原理是定义了一个ConcurrentHashMap,专门用来保存失败的调用,并定义了一个定时线程池,默认为5秒把所有失败的调用拿出来,重试一次,如果调用重试成功,则从ConcurrentHashMap中移除,在调用异常时也会被捕获到而不会报错,返回空结果。
public class FailbackClusterInvoker<T> extends AbstractClusterInvoker<T> {
...
private void addFailed(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, Invoker<T> lastInvoker) {
if (failTimer == null) {
synchronized (this) {
if (failTimer == null) {
//初始化定时线程池
failTimer = new HashedWheelTimer(
new NamedThreadFactory("failback-cluster-timer", true),
1,
TimeUnit.SECONDS, 32, failbackTasks);
}
}
}
//默认5秒重试一次
RetryTimerTask retryTimerTask = new RetryTimerTask(loadbalance, invocation, invokers, lastInvoker, retries, RETRY_FAILED_PERIOD);
try {
failTimer.newTimeout(retryTimerTask, RETRY_FAILED_PERIOD, TimeUnit.SECONDS);
} catch (Throwable e) {
logger.error("Failback background works error,invocation->" + invocation + ", exception: " + e.getMessage());
}
}
@Override
protected Result doInvoke(Invocation invocation,
List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
Invoker<T> invoker = null;
try {
checkInvokers(invokers, invocation);
invoker = select(loadbalance, invocation, invokers, null);
return invoker.invoke(invocation);
} catch (Throwable e) {
//失败后添加定时任务
addFailed(loadbalance, invocation, invokers, invoker);
//返回空结果
return AsyncRpcResult.newDefaultAsyncResult(null, null, invocation); // ignore
}
}
}
2.3.5 Available策略
Available是找到第一个可用的服务器就直接调用,并返回结果。
public class AvailableClusterInvoker<T> extends AbstractClusterInvoker<T> {
...
@Override
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers,
LoadBalance loadbalance) throws RpcException {
//遍历invoker列表,找到第一个可用的服务节点
for (Invoker<T> invoker : invokers) {
if (invoker.isAvailable()) {
return invoker.invoke(invocation);
}
}
}
}
2.3.6 Broadcast策略
Broadcast会广播给所有的可用节点,如果其中一个节点调用发生异常,则返回异常。
public class BroadcastClusterInvoker<T> extends AbstractClusterInvoker<T> {
...
@Override
public Result doInvoke(final Invocation invocation,
List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
...
//遍历invoker列表
for (Invoker<T> invoker : invokers) {
//捕获每次调用发生的异常
try {
result = invoker.invoke(invocation);
} catch (RpcException e) {
exception = e;
logger.warn(e.getMessage(), e);
} catch (Throwable e) {
exception = new RpcException(e.getMessage(), e);
logger.warn(e.getMessage(), e);
}
}
//如果其中一个节点的调用发生了异常,则抛出异常
if (exception != null) {
throw exception;
}
return result;
}
}
2.4 Directory实现解析
整个容错过程中首先会使用Directory#list方法来获取所有的Invoker列表,目前Directory有静态和动态注册两种实现,其中静态列表是用户自己设置的Invoker列表,动态列表则根据注册中心的数据动态变化。其中Direcotry定义如下,有获取接口/获取Invoker列表/获取所有的Invoker列表方法。
public interface Directory<T> extends Node {
Class<T> getInterface();
List<Invoker<T>> list(Invocation invocation) throws RpcException;
List<Invoker<T>> getAllInvokers();
URL getConsumerUrl();
}
Directory是顶层接口,AbstractDirectory封装了通用的实现逻辑,主要有RegistryDirectory和StaticDirectory两个实现类,其中AbstractDirectory中实现四个方法: 检测Invoker是否可用,销毁所有的Invoker,获取Invoker列表,还有一个抽象的doList方法留给子类自行实现,其中list是最主要的方法用于返回所有可用的服务器节点,
public abstract class AbstractDirectory<T> implements Directory<T> {
protected RouterChain<T> routerChain;
public AbstractDirectory(URL url, RouterChain<T> routerChain) {
this.url = url.removeParameter(REFER_KEY).removeParameter(MONITOR_KEY);
this.consumerUrl = url.addParameters(StringUtils.parseQueryString(
url.getParameterAndDecoded(REFER_KEY)))
.removeParameter(MONITOR_KEY);
setRouterChain(routerChain);
}
@Override
public List<Invoker<T>> list(Invocation invocation) throws RpcException {
return doList(invocation);
}
@Override
public void destroy() {
destroyed = true;
}
protected abstract List<Invoker<T>> doList(Invocation invocation) throws RpcException;
}
Directory的默认实现是RegistryDirectory,其中有两个比较重要的逻辑,一个是框架与注册中心的订阅,并动态更新本地的Invoker列表、路由列表、配置信息等,一个是实现父类的doList方法。在订阅和动态更新主要涉及subscribe、notify、refreshInvoker三个方法。其中RegistryProtocol#doRefer时会调用RegistryDirectory#subscribe,在subscribe方法中会初始化配置监听器和注册中心监听器,当注册中心监听到变动后会发出通知事件并调用RegistryDirecoty#notify方法,在该方法中会去更新url的相关配置。
public class RegistryDirectory<T> extends AbstractDirectory<T>
implements NotifyListener {
...
//订阅
public void subscribe(URL url) {
setConsumerUrl(url);
CONSUMER_CONFIGURATION_LISTENER.addNotifyListener(this);
serviceConfigurationListener = new ReferenceConfigurationListener(this, url);
registry.subscribe(url, this);
}
//取消订阅
public void unSubscribe(URL url) {
setConsumerUrl(null);
CONSUMER_CONFIGURATION_LISTENER.removeNotifyListener(this);
serviceConfigurationListener.stop();
registry.unsubscribe(url, this);
}
@Override
public synchronized void notify(List<URL> urls) {
Map<String, List<URL>> categoryUrls = urls.stream()
.filter(Objects::nonNull)
.filter(this::isValidCategory)
.filter(this::isNotCompatibleFor26x)
.collect(Collectors.groupingBy(this::judgeCategory));
List<URL> configuratorURLs = categoryUrls.getOrDefault(CONFIGURATORS_CATEGORY, Collections.emptyList());
this.configurators = Configurator.toConfigurators(configuratorURLs).orElse(this.configurators);
List<URL> routerURLs = categoryUrls.getOrDefault(ROUTERS_CATEGORY, Collections.emptyList());
toRouters(routerURLs).ifPresent(this::addRouters);
//代表服务提供者列表的url列表
List<URL> providerURLs = categoryUrls.getOrDefault(PROVIDERS_CATEGORY, Collections.emptyList());
//加入新的服务提供节点地址
ExtensionLoader<AddressListener> addressListenerExtensionLoader =
ExtensionLoader.getExtensionLoader(AddressListener.class);
List<AddressListener> supportedListeners =
addressListenerExtensionLoader.getActivateExtension(getUrl(), (String[]) null);
if (supportedListeners != null && !supportedListeners.isEmpty()) {
for (AddressListener addressListener : supportedListeners) {
providerURLs = addressListener.notify(providerURLs, getConsumerUrl(),this);
}
}
refreshOverrideAndInvoker(providerURLs);
}
}
RegistryDirecotry#doList方法中,会去调用Router路由的相关实现类的route方法,返回Invoker列表。
public class RegistryDirectory<T> extends AbstractDirectory<T>
implements NotifyListener {
...
@Override
public List<Invoker<T>> doList(Invocation invocation) {
...
List<Invoker<T>> invokers = null;
try {
//根据路由调用链路返回invoker列表
invokers = routerChain.route(getConsumerUrl(), invocation);
} catch (Throwable t) {
...
}
return invokers == null ? Collections.emptyList() : invokers;
}
}
3.总结
本篇主要讲述了Dubbo框架在集群容错时做的一些设计实现,主要是Cluster层的主要类结构,不同的容错策略以及策略的具体实现。