dubbo——容错模式
一、dubbo提供6种容错模式
- failover:默认模式。调用失败时,自动切换重试,可通过retries属性设置重试次数。适用于读操作,每次读数据都相同。
- failfast:快速失败模式。只调用一次,调用失败时立即报错。适用于写操作,不能重复写。
- failsafe:安全失败模式。只调用一次,调用失败时忽略失败的调用,记录日志。
- failback:失败恢复模式。在失败后自动恢复,后台记录失败的请求,定时重发。通常用于消息通知。
- forking:并行调用多个服务器,只要有一个成功便返回。通常用于实时性要求较高的读操作,会浪费服务资源。
- broadcast:广播调用所有的提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
二、FailOverClusterInvoker——失败重试
/* org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker */ public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException { List<Invoker<T>> copyInvokers = invokers; //检查providers不能为空 checkInvokers(copyInvokers, invocation); //获取调用的方法名 String methodName = RpcUtils.getMethodName(invocation); //调用次数 1+2次重试 int len = getUrl().getMethodParameter(methodName, RETRIES_KEY, DEFAULT_RETRIES) + 1; if (len <= 0) { len = 1; } // retry loop. RpcException le = null; // last exception. List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyInvokers.size()); // invoked invokers. Set<String> providers = new HashSet<String>(len); for (int i = 0; i < len; i++) { //失败重试,成功返回 if (i > 0) { //再次检查invokers.destroyed checkWhetherDestroyed(); copyInvokers = list(invocation); // check again checkInvokers(copyInvokers, invocation); } //负载均衡策略,选取一个invoker Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked); //标记已经调用过的invoker,重试时,换其他invoker重试 invoked.add(invoker); RpcContext.getContext().setInvokers((List) invoked); try { Result result = invoker.invoke(invocation); if (le != null && logger.isWarnEnabled()) { logger.warn("Although retry the method " + methodName + " in the service " + getInterface().getName() + " was successful by the provider " + invoker.getUrl().getAddress() + ", but there have been failed providers " + providers + " (" + providers.size() + "/" + copyInvokers.size() + ") from the registry " + directory.getUrl().getAddress() + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version " + Version.getVersion() + ". Last error is: " + le.getMessage(), le); } //成功返回 return result; //catch报错,失败重试 } catch (RpcException e) { if (e.isBiz()) { // biz exception. throw e; } le = e; } catch (Throwable e) { le = new RpcException(e.getMessage(), e); } finally { providers.add(invoker.getUrl().getAddress()); } } throw new RpcException(le.getCode(), "Failed to invoke the method " + methodName + " in the service " + getInterface().getName() + ". Tried " + len + " times of the providers " + providers + " (" + providers.size() + "/" + copyInvokers.size() + ") from the registry " + directory.getUrl().getAddress() + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version " + Version.getVersion() + ". Last error is: " + le.getMessage(), le.getCause() != null ? le.getCause() : le); }
三、FailfastClusterInvoker——失败抛错
/* org.apache.dubbo.rpc.cluster.support.FailfastClusterInvoker#doInvoke */ public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException { //检查invokers不能为空 checkInvokers(invokers, invocation); //负载均衡策略,选取一个invoker Invoker<T> invoker = select(loadbalance, invocation, invokers, null); try { //调用 return invoker.invoke(invocation); } catch (Throwable e) { //调用失败直接抛错 if (e instanceof RpcException && ((RpcException) e).isBiz()) { // biz exception. throw (RpcException) e; } throw new RpcException(e instanceof RpcException ? ((RpcException) e).getCode() : 0, "Failfast invoke providers " + invoker.getUrl() + " " + loadbalance.getClass().getSimpleName() + " select from all providers " + invokers + " for service " + getInterface().getName() + " method " + invocation.getMethodName() + " on consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e.getCause() != null ? e.getCause() : e); } }
四、FaisafeClusterInvoker——失败忽略,记录日志
/* org.apache.dubbo.rpc.cluster.support.FailsafeClusterInvoker#doInvoke */ public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException { try { //检查invokers不能为空 checkInvokers(invokers, invocation); //负载均衡策略,选取一个invoker Invoker<T> invoker = select(loadbalance, invocation, invokers, null); //调用 return invoker.invoke(invocation); } catch (Throwable e) { //报错忽略 logger.error("Failsafe ignore exception: " + e.getMessage(), e); return AsyncRpcResult.newDefaultAsyncResult(null, null, invocation); // ignore } }
五、FailbackClusterInvoker——失败恢复,记录失败请求,定时重发
/* org.apache.dubbo.rpc.cluster.support.FailbackClusterInvoker#doInvoke */ protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException { Invoker<T> invoker = null; try { //检查invokers不能为空 checkInvokers(invokers, invocation); //负载均衡策略,选取一个invoker invoker = select(loadbalance, invocation, invokers, null); //调用 return invoker.invoke(invocation); } catch (Throwable e) { //失败忽略 logger.error("Failback to invoke method " + invocation.getMethodName() + ", wait for retry in background. Ignored exception: " + e.getMessage() + ", ", e); //创建定时任务TimeTask实现 addFailed(loadbalance, invocation, invokers, invoker); //返回记录失败日志 return AsyncRpcResult.newDefaultAsyncResult(null, null, invocation); // ignore } }
六、ForkingClusterInvoker——并行调用,有一个调用成功便返回
/* org.apache.dubbo.rpc.cluster.support.ForkingClusterInvoker#doInvoke */ public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException { try { //检查invokers不能为空 checkInvokers(invokers, invocation); final List<Invoker<T>> selected; final int forks = getUrl().getParameter(FORKS_KEY, DEFAULT_FORKS); final int timeout = getUrl().getParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT); if (forks <= 0 || forks >= invokers.size()) { //并发数forks大于invokers.size()时,并发调用所有invoker selected = invokers; } else { //并发数forks小于invokers.size()时,调用负载均衡策略选取的forks个invoker selected = new ArrayList<>(); for (int i = 0; i < forks; i++) { Invoker<T> invoker = select(loadbalance, invocation, invokers, selected); if (!selected.contains(invoker)) { //Avoid add the same invoker several times. selected.add(invoker); } } } RpcContext.getContext().setInvokers((List) selected); final AtomicInteger count = new AtomicInteger(); final BlockingQueue<Object> ref = new LinkedBlockingQueue<>(); for (final Invoker<T> invoker : selected) { executor.execute(() -> { try { //并发调用,报错忽略 Result result = invoker.invoke(invocation); //创建一个队列,存储返回结果 ref.offer(result); } catch (Throwable e) { int value = count.incrementAndGet(); if (value >= selected.size()) { ref.offer(e); } } }); } try { //取队首元素(结果),设置超时时间 Object ret = ref.poll(timeout, TimeUnit.MILLISECONDS); if (ret instanceof Throwable) { //正常的报错抛出错误 Throwable e = (Throwable) ret; throw new RpcException(e instanceof RpcException ? ((RpcException) e).getCode() : 0, "Failed to forking invoke provider " + selected + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e.getCause() != null ? e.getCause() : e); } //调用成功返回 return (Result) ret; } catch (InterruptedException e) { //超时抛错 throw new RpcException("Failed to forking invoke provider " + selected + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e); } } finally { // clear attachments which is binding to current thread. RpcContext.getContext().clearAttachments(); } }
七、BroadcastClusterInvoker——串行调用,所有调用成功则成功,有一台报错则抛错
/* org.apache.dubbo.rpc.cluster.support.BroadcastClusterInvoker#doInvoke */ public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException { //检查invokers不能为空 checkInvokers(invokers, invocation); RpcContext.getContext().setInvokers((List) invokers); RpcException exception = null; Result result = null; 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); } } //有一个invoker调用抛错,则抛出错误 if (exception != null) { throw exception; } return result; }