dubbo 容错策略
官网:https://dubbo.apache.org/zh/docs/advanced/fault-tolerent-strategy/
容错策略
容错策略:消费端服务调用出错时的处理方式
消费端服务调用流程
消费端应用启动时,创建clusterInvoker对象(默认为failoverClusterInvoker)
# 消费端发起服务调用时,依次执行如下过程:
获取所有可用invoker列表(directory#list)
* dynamicDirectory:动态invoker列表,从注册中心获取
* staticDirectory:静态invoker列表,创建staticDirectory对象时传入的invoker列表
对所有可用列表进行路由过滤(routerChain#route)
使用负载均衡算法(loadbalance#select)在路由过滤后的invoker列表选择一个invoker,发起远程服务调用;
# 服务调用结果处理
如果调用正常,消费端收到服务端返回的结果(如果有结果返回);
如果调用异常,执行容错策略:重试(failover,默认)、忽略异常(failsafe)等;
如果设置了mock,服务调用异常时,可进行服务降级;
Cluster:容错接口
@SPI(Cluster.DEFAULT) //默认为failover
public interface Cluster {
String DEFAULT = "failover";
/**
* Merge the directory invokers to a virtual invoker.
*
* @param <T>
* @param directory
* @return cluster invoker
* @throws RpcException
*/
@Adaptive
<T> Invoker<T> join(Directory<T> directory, boolean buildFilterChain) throws RpcException;
static Cluster getCluster(ScopeModel scopeModel, String name) {
return getCluster(scopeModel, name, true);
}
static Cluster getCluster(ScopeModel scopeModel, String name, boolean wrap) {
if (StringUtils.isEmpty(name)) {
name = Cluster.DEFAULT;
}
return ScopeModelUtil.getApplicationModel(scopeModel).getExtensionLoader(Cluster.class).getExtension(name, wrap);
}
}
容错策略说明
# FailoverCluster
消费端调用失败,会进行重试(retries参数设置重试次数);
该策略适合读操作、幂等写操作,同时重试会加大下游服务器负载
<dubbo:reference retries="2" /> //接口级别容错设置
<dubbo:reference>
<dubbo:method name="findFoo" retries="2" /> //方法级别容错设置
</dubbo:reference>
# FailfastCluster
消费端调用失败,不会重试,直接返回异常;
该策略用在非幂等接口上,受网络抖动影响较大(可能网络原因导致服务调用失败)
# FailsafeCluster
消费端调用失败,忽略异常;
该策略不关心是否调用成功,也不抛出异常,适用于日志发送等场景
# FailbackCluster
消费端调用失败,会在失败记录中记录下来,由定时线程池重试;
适用于一致性(最终一致性)要求比较高的场景
# ForkingCluster
同时对多个服务发起调用,只要有一个返回结果,调用结束;
适用于对事实现性要求比较高的场景,但会浪费很多资源
# BroadcastCluster
调用所有可用服务,任何一个出错则抛出异常;
适用于服务状态更新后的广播
# AvaliableCluster
请求不做负载均衡,遍历所有可用列表,选出第一个可用服务调用,如果没有可用服务,则抛出异常
# MockCluster
服务调用失败,不抛出异常,返回默认的结果;
或者不调用远程服务,强制返回预设结果
# MergeableCluster
把不同group的服务请求结果合并
# ZoneAwareCluster:多注册中心,服务选取设置
This extension provides a strategy to decide how to distribute traffics among them:
* 1. registry marked as 'preferred=true' has the highest priority.
* 2. check the zone the current request belongs, pick the registry that has the same zone first.
* 3. Evenly balance traffic between all registries based on each registry's weight.
* 4. Pick anyone that's available.
带有prefered=true的注册中心优先使用:
* <dubbo:registry address="zookeeper://127.0.0.1:2181" preferred="true" />
优先选取注册中心为同一个zone的服务:
* <dubbo:registry address="zookeeper://127.0.0.1:2181" zone="beijing" />
可根据权重对注册中心进行负载均衡:
* <dubbo:registry id="beijing" address="zookeeper://127.0.0.1:2181" weight="100" />
* <dubbo:registry id="shanghai" address="zookeeper://127.0.0.1:2182" weight="10" />
如果注册中心没有以上配置,选取任何一个可用的服务中心拉取服务信息,进行服务调用
FailoverCluster
public class FailoverCluster extends AbstractCluster {
public final static String NAME = "failover";
@Override
public <T> AbstractClusterInvoker<T> doJoin(Directory<T> directory) throws RpcException {
return new FailoverClusterInvoker<>(directory);
} //消费端应用启动时,创建failoverClusterInvoker对象
}
消费端创建failoverClusterInvoker对象调用栈
# 创建FailoverClusterInvoker对象,默认创建该对象
at org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker.<init>(FailoverClusterInvoker.java:52)
# join操作
at org.apache.dubbo.rpc.cluster.support.FailoverCluster.doJoin(FailoverCluster.java:33)
at org.apache.dubbo.rpc.cluster.support.wrapper.AbstractCluster.join(AbstractCluster.java:58)
at org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper.join(MockClusterWrapper.java:39)
# 创建invoker
at org.apache.dubbo.registry.integration.RegistryProtocol.doCreateInvoker(RegistryProtocol.java:564)
# 获取invoker
at org.apache.dubbo.registry.integration.InterfaceCompatibleRegistryProtocol.getInvoker(InterfaceCompatibleRegistryProtocol.java:58)
# MigrationInvoker类
at org.apache.dubbo.registry.client.migration.MigrationInvoker.refreshInterfaceInvoker(MigrationInvoker.java:448)
at org.apache.dubbo.registry.client.migration.MigrationInvoker.migrateToApplicationFirstInvoker(MigrationInvoker.java:239)
# MigrationRuleHandler类
at org.apache.dubbo.registry.client.migration.MigrationRuleHandler.refreshInvoker(MigrationRuleHandler.java:73)
at org.apache.dubbo.registry.client.migration.MigrationRuleHandler.doMigrate(MigrationRuleHandler.java:57)
- locked <0x21db> (a org.apache.dubbo.registry.client.migration.MigrationRuleHandler)
at org.apache.dubbo.registry.client.migration.MigrationRuleListener.onRefer(MigrationRuleListener.java:241)
# RegistryProtocol类
at org.apache.dubbo.registry.integration.RegistryProtocol.interceptInvoker(RegistryProtocol.java:531)
at org.apache.dubbo.registry.integration.RegistryProtocol.doRefer(RegistryProtocol.java:500)
at org.apache.dubbo.registry.integration.RegistryProtocol.refer(RegistryProtocol.java:485)
# refer操作
at org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper.refer(ProtocolListenerWrapper.java:74)
at org.apache.dubbo.qos.protocol.QosProtocolWrapper.refer(QosProtocolWrapper.java:83)
at org.apache.dubbo.rpc.cluster.filter.ProtocolFilterWrapper.refer(ProtocolFilterWrapper.java:71)
at org.apache.dubbo.rpc.protocol.ProtocolSerializationWrapper.refer(ProtocolSerializationWrapper.java:52)
at org.apache.dubbo.rpc.Protocol$Adaptive.refer(Protocol$Adaptive.java:-1)
# ReferenceConfig类
at org.apache.dubbo.config.ReferenceConfig.createInvokerForRemote(ReferenceConfig.java:481)
at org.apache.dubbo.config.ReferenceConfig.createProxy(ReferenceConfig.java:386)
at org.apache.dubbo.config.ReferenceConfig.init(ReferenceConfig.java:275)
- locked <0x21dc> (a org.apache.dubbo.config.ReferenceConfig)
at org.apache.dubbo.config.ReferenceConfig.get(ReferenceConfig.java:216)
# SimpleReferenceCache类
at org.apache.dubbo.config.utils.SimpleReferenceCache.get(SimpleReferenceCache.java:110)
# DefaultModuleDeployer类
at org.apache.dubbo.config.deploy.DefaultModuleDeployer.lambda$referServices$6(DefaultModuleDeployer.java:384)
at org.apache.dubbo.config.deploy.DefaultModuleDeployer$$Lambda$880/0x0000000801039dc8.accept(Unknown Source:-1)
at java.util.concurrent.ConcurrentHashMap$ValuesView.forEach(ConcurrentHashMap.java:4780)
# DefaultModuleDeployer类
at org.apache.dubbo.config.deploy.DefaultModuleDeployer.referServices(DefaultModuleDeployer.java:364)
at org.apache.dubbo.config.deploy.DefaultModuleDeployer.start(DefaultModuleDeployer.java:151)
- locked <0x21dd> (a org.apache.dubbo.config.deploy.DefaultModuleDeployer)
# DubboDeployApplicationListener类
at org.apache.dubbo.config.spring.context.DubboDeployApplicationListener.onContextRefreshedEvent(DubboDeployApplicationListener.java:108)
at org.apache.dubbo.config.spring.context.DubboDeployApplicationListener.onApplicationEvent(DubboDeployApplicationListener.java:98)
at org.apache.dubbo.config.spring.context.DubboDeployApplicationListener.onApplicationEvent(DubboDeployApplicationListener.java:44)
# SimpleApplicationEventMulticaster类
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143)
# AbstractApplicationContext类
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:421)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:378)
at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:938)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586)
- locked <0x21de> (a java.lang.Object)
# ServletWebServerApplicationContext类
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145)
# SpringApplication类
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:730)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:412)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:302)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1301)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1290)
# 项目启动入口
at com.example.demo.DemoApplication.main(DemoApplication.java:12)
AbstractClusterInvoker
public abstract class AbstractClusterInvoker<T> implements ClusterInvoker<T> {
@Override
public Result invoke(final Invocation invocation) throws RpcException {
checkWhetherDestroyed();
// binding attachments into invocation.
// Map<String, Object> contextAttachments = RpcContext.getClientAttachment().getObjectAttachments();
// if (contextAttachments != null && contextAttachments.size() != 0) {
// ((RpcInvocation) invocation).addObjectAttachmentsIfAbsent(contextAttachments);
// }
List<Invoker<T>> invokers = list(invocation); //返回路由过滤后的invoker服务列表
LoadBalance loadbalance = initLoadBalance(invokers, invocation); //初始化负载均衡器
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
return doInvoke(invocation, invokers, loadbalance);
}
protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
return getDirectory().list(invocation);
} //getDirectory():获取所有invoker服务列表
//list(invocation):执行路由过滤,返回过滤后的列表
protected LoadBalance initLoadBalance(List<Invoker<T>> invokers, Invocation invocation) {
ApplicationModel applicationModel = ScopeModelUtil.getApplicationModel(invocation.getModuleModel());
if (CollectionUtils.isNotEmpty(invokers)) { //invokers列表不为空,从url的参数中获取使用的负载均衡算法
return applicationModel.getExtensionLoader(LoadBalance.class).getExtension(
invokers.get(0).getUrl().getMethodParameter(
RpcUtils.getMethodName(invocation), LOADBALANCE_KEY, DEFAULT_LOADBALANCE
) //默认使用随机负载均衡
);
} else {
return applicationModel.getExtensionLoader(LoadBalance.class).getExtension(DEFAULT_LOADBALANCE);
} //invokers列表为空,默认也是使用随机负载均衡算法
}
FailoverClusterInvoker:服务调用失败时进行重试
public class FailoverClusterInvoker<T> extends AbstractClusterInvoker<T> {
private static final Logger logger = LoggerFactory.getLogger(FailoverClusterInvoker.class);
public FailoverClusterInvoker(Directory<T> directory) {
super(directory);
}
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
List<Invoker<T>> copyInvokers = invokers;
checkInvokers(copyInvokers, invocation);
String methodName = RpcUtils.getMethodName(invocation);
int len = calculateInvokeTimes(methodName);
// 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++) {
//Reselect before retry to avoid a change of candidate `invokers`.
//NOTE: if `invokers` changed, then `invoked` also lose accuracy.
if (i > 0) {
checkWhetherDestroyed();
copyInvokers = list(invocation);
// check again
checkInvokers(copyInvokers, invocation);
}
Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);
invoked.add(invoker);
RpcContext.getServiceContext().setInvokers((List) invoked);
boolean success = false;
try {
Result result = invokeWithContext(invoker, 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);
}
success = true;
return result;
} catch (RpcException e) {
if (e.isBiz()) { // biz exception.
throw e;
} //如果消费端收到的异常为biz exception,不重试,直接抛出异常
le = e;
} catch (Throwable e) {
le = new RpcException(e.getMessage(), e);
} finally {
if (!success) {
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);
}
private int calculateInvokeTimes(String methodName) { //失败重试次数,失败后默认重试2次
int len = getUrl().getMethodParameter(methodName, RETRIES_KEY, DEFAULT_RETRIES) + 1;
RpcContext rpcContext = RpcContext.getClientAttachment();
Object retry = rpcContext.getObjectAttachment(RETRIES_KEY);
if (retry instanceof Number) {
len = ((Number) retry).intValue() + 1;
rpcContext.removeAttachment(RETRIES_KEY);
}
if (len <= 0) {
len = 1;
}
return len;
}
}
容错配置
@DubboReference:消费端服务调用,使用该注解配置容错策略
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
public @interface DubboReference {
/**
* Interface class, default value is void.class
*/
Class<?> interfaceClass() default void.class;
/**
* Interface class name, default value is empty string
*/
String interfaceName() default "";
String group() default ""; //服务分组
String version() default ""; //服务版本
/**
* Service target URL for direct invocation, if this is specified, then registry center takes no effect.
*/
String url() default ""; //直连服务提供端(如果配置了,则不使用注册中心)
/**
* Client transport type, default value is "netty"
*/
String client() default ""; //客户端传输方式,默认为netty
/**
* Whether to enable generic invocation, default value is false
* @deprecated Do not need specify generic value, judge by injection type and interface class
*/
@Deprecated
boolean generic() default false; //已禁用
/**
* When enable, prefer to call local service in the same JVM if it's present, default value is true
* @deprecated using scope="local" or scope="remote" instead
*/
@Deprecated
boolean injvm() default true; //已禁用
/**
* Check if service provider is available during boot up, default value is true
*/
boolean check() default true;
/**
* Whether eager initialize the reference bean when all properties are set, default value is true ( null as true)
* @see ReferenceConfigBase#shouldInit()
*/
boolean init() default true;
/**
* Whether to make connection when the client is created, the default value is false
*/
boolean lazy() default false;
/**
* Export an stub service for event dispatch, default value is false.
* <p>
* see org.apache.dubbo.rpc.Constants#STUB_EVENT_METHODS_KEY
*/
boolean stubevent() default false;
/**
* Whether to reconnect if connection is lost, if not specify, reconnect is enabled by default, and the interval
* for retry connecting is 2000 ms
* <p>
* see org.apache.dubbo.remoting.Constants#DEFAULT_RECONNECT_PERIOD
*/
String reconnect() default "";
/**
* Whether to stick to the same node in the cluster, the default value is false
* <p>
* see Constants#DEFAULT_CLUSTER_STICKY
*/
boolean sticky() default false;
/**
* How the proxy is generated, legal values include: jdk, javassist
*/
String proxy() default "";
/**
* Service stub name, use interface name + Local if not set
*/
String stub() default "";
/**
* Cluster strategy, legal values include: failover, failfast, failsafe, failback, forking
* you can use {@link org.apache.dubbo.common.constants.ClusterRules#FAIL_FAST} ……
*/
String cluster() default ClusterRules.EMPTY; //容错策略,可选值:failover, failfast, failsafe, failback, forking
/**
* Service invocation retry times
* <p>
* see Constants#DEFAULT_RETRIES
*/
int retries() default -1; //重试次数
/**
* Service mock name, use interface name + Mock if not set
*/
String mock() default ""; //服务降级接口
/**
* Service merger
*/
String merger() default ""; //分组聚合
/**
* Customized parameter key-value pair, for example: {key1, value1, key2, value2} or {"key1=value1", "key2=value2"}
*/
String[] parameters() default {}; //自定义参数,如:
//forking:@DubboReference(cluster = ClusterRules.FORKING, parameters = {"forks","3"})
//broadcast:@DubboReference(cluster = "broadcast", parameters = {"broadcast.fail.percent", "20"})
/**
* Load balance strategy, legal values include: random, roundrobin, leastactive
* you can use {@link org.apache.dubbo.common.constants.LoadbalanceRules#RANDOM} ……
*/
String loadbalance() default LoadbalanceRules.EMPTY; //负载均衡算法,默认为随机算法
/**
* Maximum connections service provider can accept, default value is 0 - connection is shared
*/
int connections() default -1;
/**
* The callback instance limit peer connection
* <p>
* see org.apache.dubbo.rpc.Constants#DEFAULT_CALLBACK_INSTANCES
*/
int callbacks() default -1;
/**
* Callback method name when connected, default value is empty string
*/
String onconnect() default "";
/**
* Callback method name when disconnected, default value is empty string
*/
String ondisconnect() default "";
/**
* Service owner, default value is empty string
*/
String owner() default "";
/**
* Service layer, default value is empty string
*/
String layer() default "";
/**
* Whether to enable async invocation, default value is false
*/
boolean async() default false;
/**
* Maximum active requests allowed, default value is 0
*/
int actives() default -1;
/**
* Whether the async request has already been sent, the default value is false
*/
boolean sent() default false;
/**
* Whether to use JSR303 validation, legal values are: true, false
*/
String validation() default "";
/**
* Timeout value for service invocation, default value is 0
*/
int timeout() default -1;
/**
* Specify cache implementation for service invocation, legal values include: lru, threadlocal, jcache
*/
String cache() default "";
/**
* Filters for service invocation
* <p>
* see Filter
*/
String[] filter() default {};
/**
* Listeners for service exporting and unexporting
* <p>
* see ExporterListener
*/
String[] listener() default {};
/**
* Application name
* @deprecated This attribute was deprecated, use bind application/module of spring ApplicationContext
*/
@Deprecated
String application() default "";
/**
* Module associated name
*/
String module() default "";
/**
* Consumer associated name
*/
String consumer() default "";
/**
* Monitor associated name
*/
String monitor() default "";
/**
* Registry associated name
*/
String[] registry() default {};
/**
* The communication protocol of Dubbo Service
*
* @return the default value is ""
* @since 2.6.6
*/
String protocol() default "";
/**
* Service tag name
*/
String tag() default "";
/**
* methods support
*/
Method[] methods() default {};
/**
* The id
* NOTE: The id attribute is ignored when using @DubboReference on @Bean method
* @return default value is empty
* @since 2.7.3
*/
String id() default "";
/**
* @return The service names that the Dubbo interface subscribed
* @see RegistryConstants#SUBSCRIBED_SERVICE_NAMES_KEY
* @since 2.7.8
* @deprecated using {@link DubboReference#providedBy()}
*/
@Deprecated
String[] services() default {};
/**
* declares which app or service this interface belongs to
* @see RegistryConstants#PROVIDED_BY
*/
String[] providedBy() default {};
/**
* the scope for referring/exporting a service, if it's local, it means searching in current JVM only.
* @see org.apache.dubbo.rpc.Constants#SCOPE_LOCAL
* @see org.apache.dubbo.rpc.Constants#SCOPE_REMOTE
*/
String scope() default "";
/**
* Weather the reference is refer asynchronously
*/
boolean referAsync() default false;
}
@Method:标注在注解上,设置方法级别的服务调用
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE}) //标注在注解上
@Inherited
public @interface Method {
String name();
int timeout() default -1;
int retries() default -1;
String merger() default "";
String loadbalance() default "";
boolean async() default false;
boolean sent() default true;
int actives() default -1;
int executes() default -1;
boolean deprecated() default false;
boolean sticky() default false;
boolean isReturn() default true;
String oninvoke() default "";
String onreturn() default "";
String onthrow() default "";
String cache() default "";
String validation() default "";
Argument[] arguments() default {};
/**
* Customized parameter key-value pair, for example: {key1, value1, key2, value2} or {"key1=value1", "key2=value2"}
*/
String[] parameters() default {};
}
ClusterRules
public interface ClusterRules {
/**
* When invoke fails, log the initial error and retry other invokers
* (retry n times, which means at most n different invokers will be invoked)
**/
String FAIL_OVER = "failover";
/**
* Execute exactly once, which means this policy will throw an exception immediately in case of an invocation error.
**/
String FAIL_FAST = "failfast";
/**
* When invoke fails, log the error message and ignore this error by returning an empty Result.
**/
String FAIL_SAFE = "failsafe";
/**
* When fails, record failure requests and schedule for retry on a regular interval.
**/
String FAIL_BACK = "failback";
/**
* Invoke a specific number of invokers concurrently, usually used for demanding real-time operations, but need to waste more service resources.
**/
String FORKING = "forking";
/**
* Call all providers by broadcast, call them one by one, and report an error if any one reports an error
**/
String BROADCAST = "broadcast";
String AVAILABLE = "available";
String MERGEABLE = "mergeable";
String EMPTY = "";
}
HelloController
@RestController
public class HelloController {
@DubboReference(cluster = ClusterRules.FORKING, parameters = {"forks","3"})
private HelloService helloService;
@DubboReference
private HelloService2 helloService2;
@RequestMapping("/hello")
public String hello(){
System.out.println(helloService.hello());
System.out.println(helloService2.hello2());
return "hello consumer 2";
}
}