InvokerInvocationHandler
在上一篇文章【Dubbo服务引用】的最后,我们介绍到通过标签<dubbo:reference>
引用一个远程服务接口之后,会得到一个代理对象,该代理对象包含了一个调用处理器InvokerInvocationHandler,它实现了InvocationHandler接口。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
public class InvokerInvocationHandler implements InvocationHandler {
/**真正实例是MockClusterInvoker*/
private final Invoker<?> invoker;
public InvokerInvocationHandler(Invoker<?> handler) {
this.invoker = handler;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
//省略部分代码......
return invoker.invoke(new RpcInvocation(method, args)).recreate();
}
}
InvocationHandler是一个JDK的接口,它由代理对象的调用处理器负责实现该接口。每一个代理对象实例都有一个关联的InvocationHandler实例,当通过代理对象调用方法时,该方法调用将被编码和转发到InvocationHandler实例的invoke()方法。所以对于Dubbo框架来说,InvokerInvocationHandler的invoke()
方法将是消费者服务发起服务远程调用的入口。在发起远程服务调用之前需要先经过Cluster层(集群容错层),下面我们就来介绍Dubbo框架的集群容错。
集群容错整体流程
下图是集群容错层的流程图:
InvokerInvocationHandler包含了一个Invoker类型变量invoker,它的实例对象是MockClusterInvoker,通过它执行invoker.invoke(new RpcInvocation(method, args))
,进入MockClusterInvoker的invoke()方法:
public class MockClusterInvoker<T> implements Invoker<T> {
/**实例对象是RegistryDirectory*/
private final Directory<T> directory;
/**实例对象是FailoverClusterInvoker*/
private final Invoker<T> invoker;
public MockClusterInvoker(Directory<T> directory, Invoker<T> invoker) {
this.directory = directory;
this.invoker = invoker;
}
@Override
public Result invoke(Invocation invocation) throws RpcException {
Result result = null;
String value = directory.getUrl().
getMethodParameter(invocation.getMethodName(),
Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();
if (value.length() == 0 ||
value.equalsIgnoreCase("false")) {//@1
//no mock
result = this.invoker.invoke(invocation);
} else if (value.startsWith("force")) {//@2
if (logger.isWarnEnabled()) {
logger.info("force-mock: " +
invocation.getMethodName() + " force-mock enabled , url : " +
directory.getUrl());
}
//force:direct mock
result = doMockInvoke(invocation, null);
} else {//@3
//fail-mock
try {
result = this.invoker.invoke(invocation);
} catch (RpcException e) {
if (e.isBiz()) {
throw e;
} else {
if (logger.isWarnEnabled()) {
logger.warn("fail-mock: " + invocation.getMethodName() +
" fail-mock enabled , url : " + directory.getUrl(), e);
}
result = doMockInvoke(invocation, e);
}
}
}
return result;
}
}
代码@1:处理没有使用Mock的情况(Dubbo通过Mock机制实现服务降级),不做其他额外操作,直接执行this.invoker.invoke(invocation)
交给FailoverClusterInvoker处理。
代码@2:处理强制Mock情况,主要逻辑在方法doMockInvoke(invocation, null)
。
代码@3:处理失败调用Mock情况,该情况下会处理捕获this.invoker.invoke(invocation)
出现的RpcException异常,如果是业务异常则直接抛出,否则会执行 doMockInvoke(invocation, e)
。
我们先来介绍正常流程,FailoverClusterInvoker继承了AbstractClusterInvoker抽象类,进入到AbstractClusterInvoker的invoke()方法。
@Override
public Result invoke(final Invocation invocation) throws RpcException {
checkWhetherDestroyed();
LoadBalance loadbalance = null;
List<Invoker<T>> invokers = list(invocation);//@1
if (invokers != null && !invokers.isEmpty()) {
//Constants.LOADBALANCE_KEY = "loadbalance"
//Constants.DEFAULT_LOADBALANCE = "random"
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).
getExtension(invokers.get(0).getUrl()
.getMethodParameter(invocation.getMethodName(),
Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));//@2
}
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
return doInvoke(invocation, invokers, loadbalance); //@3
}
代码@1:获取所有可用的Invoker集合,这里一个Invoker代表了一个实现该远程接口的服务提供者。
protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
List<Invoker<T>> invokers = directory.list(invocation);
return invokers;
}
通过directory获取Invoker集合,Directory接口有两个实现类:RegistryDirectory和StaticDirectory,RegistryDirectory继承AbstractDirectory并实现了NotifyListener,主要负责处理注册中心的事件通知,刷新本地目录缓存和Invoker缓存(前面章节已经详细介绍过)。StaticDirectory表示“静态目录”,内容不会改变,不常使用。
@Override
public List<Invoker<T>> list(Invocation invocation) throws RpcException {
if (destroyed) {
throw new RpcException("Directory already destroyed .url: " + getUrl());
}
List<Invoker<T>> invokers = doList(invocation);
List<Router> localRouters = this.routers; // local reference
if (localRouters != null && !localRouters.isEmpty()) {
for (Router router : localRouters) {
try {
if (router.getUrl() == null ||
router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) {
invokers = router.route(invokers, getConsumerUrl(), invocation);
}
} catch (Throwable t) {
logger.error("Failed to execute router: " + getUrl() + ",
cause: " + t.getMessage(), t);
}
}
}
return invokers;
}
进入AbstractDirectory的list()方法,首先执行模板方法doList()将获取Invoker集合的任务交给子类RegistryDirectory处理,在获取到Invoker集合之后,还要再执行路由操作router.route(invokers, getConsumerUrl(), invocation)
,过滤掉部分Invoker。
代码@2:获取负载均衡扩展接口LoadBalance实现类对象,默认使用RandomLoadBalance,随机负载均衡策略。
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
@Adaptive("loadbalance")
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url,
Invocation invocation) throws RpcException;
}
代码@3:调用doInvoke()模板方法,传递筛选之后的Invoker集合、调用参数Invocation和负载均衡对象,交给子类FailoverClusterInvoker(默认实现)处理。FailoverClusterInvoker是一个Invoker,同时它又是一个负责处理容错策略的类,这里涉及到一个容错扩展接口Cluster。
@SPI(FailoverCluster.NAME)
public interface Cluster {
@Adaptive
<T> Invoker<T> join(Directory<T> directory) throws RpcException;
}
在【Dubbo服务引用】文章中我们提到过,服务引用时会通过cluster调用join()方法,将多个Invoker合并成一个Invoker。默认扩展接口Cluster的实现类是FailoverCluster,称为失败转移容错策略类,该类的join()
方法通过Directory构建了FailoverClusterInvoker实例对象。
public class FailoverCluster implements Cluster {
public final static String NAME = "failover";
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return new FailoverClusterInvoker<T>(directory);
}
}
FailoverClusterInvoker的doList()方法,负责发起远程调用,并且处理调用失败后的处理情况,后面会详细介绍。
MockClusterInvoker的Invoker的实例对象是FailoverClusterInvoker,那么MockClusterInvoker又是怎么来的呢?其实就是在服务引用执行cluster.join()方法时,获取到目标扩展对象FailoverCluster,因为扩展接口存在包装类MockClusterWrapper,所以先执行了MockClusterWrapper的join()方法,在该方法里面初始化了MockClusterInvoker:
public class MockClusterWrapper implements Cluster {
private Cluster cluster;
public MockClusterWrapper(Cluster cluster) {
this.cluster = cluster;
}
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return new MockClusterInvoker<T>(directory,
this.cluster.join(directory));
}
}
Dubbo整个集群容错流程并不难,MockClusterInvoker负责处理正常流程、强制Mock流程和失败Mock流程三种情况。正常流程交给ClusterInvoker(多个实现类)执行,ClusterInvoker继承了AbstractClusterInvoker,总体的流程步骤都在AbstractClusterInvoker这个类,执行list()方法通过Directory和Router筛选Invoker,实例化负载均衡对象LoadBalance,最后交给ClusterInvoker发起远程调用并处理调用失败情况。
Dubbo整个集群容错涉及到了四个非常重要的接口:Directory、Router、Cluster和LoadBalance,下面我们分别对他们进行详细介绍。
Directory
通过上面的集群容错流程,我们知道通过Directory可以获取到所有可用的Invoker,获取Invoker集合之后再交给Router(路由)进一步路由筛选。
获取所有可用的Invoker在Directory实现类的doList()
方法,这里我们主要介绍RegistryDirectory,进入RegistryDirectory的doList()
方法:
@Override
public List<Invoker<T>> doList(Invocation invocation) {
if (forbidden) {
// 1. No service provider 2. Service providers are disabled
throw new RpcException(RpcException.FORBIDDEN_EXCEPTION,
"No provider available from registry " + getUrl().getAddress() + "
for service " + getConsumerUrl().getServiceKey() +
" on consumer " + NetUtils.getLocalHost()
+ " use dubbo version " + Version.getVersion() + ",
+ please check status of
+ providers(disabled, not registered or in blacklist).");
}
List<Invoker<T>> invokers = null;
Map<String, List<Invoker<T>>> localMethodInvokerMap
= this.methodInvokerMap;//@1 local reference
if (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) {
String methodName = RpcUtils.getMethodName(invocation);
Object[] args = RpcUtils.getArguments(invocation);
if (args != null && args.length > 0 && args[0] != null
&& (args[0] instanceof String || args[0].getClass().isEnum())) {
invokers = localMethodInvokerMap.get(methodName + "." + args[0]); // The routing can be enumerated according to the first parameter
}
if (invokers == null) {
invokers = localMethodInvokerMap.get(methodName);
}
if (invokers == null) {
invokers = localMethodInvokerMap.get(Constants.ANY_VALUE);
}
if (invokers == null) {
Iterator<List<Invoker<T>>> iterator = localMethodInvokerMap.values().iterator();
if (iterator.hasNext()) {
invokers = iterator.next();
}
}
}
return invokers == null ? new ArrayList<Invoker<T>>(0) : invokers;
}
代码@1:该方法最重要的一行代码是Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap;
,通过这行代码我们知道,RegistryDirectory是通过methodInvokerMap这个集合变量存储了服务接口和对应的Invoker信息。
// Map<methodName, Invoker> cache service method to invokers mapping.
private volatile Map<String, List<Invoker<T>>> methodInvokerMap;
那么methodInvokerMap又是什么时候被赋值的呢?答案就在RegistryDirectory的refreshInvoker(List<URL> invokerUrls)
方法里:
private void refreshInvoker(List<URL> invokerUrls) {
//省略部分代码......
Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map
Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // Change method name to map Invoker Map
this.methodInvokerMap = multiGroup ?
toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
try {
destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
} catch (Exception e) {
logger.warn("destroyUnusedInvokers error. ", e);
}
}
该方法首先执行toInvokers()
和toMethodInvokers()
,将invokerUrls转换为Invoker集合,接着赋值给methodInvokerMap,最后执行destroyUnusedInvokers()
关闭掉没有使用的Invoker。
refreshInvoker()
方法是在notify()
方法中被调用的,notify()
方法在服务引用时会被调用(执行),还有接收到注册中心事件通知后也会被调用。
Router
路由分为条件路由、文件路由、脚本路由,对应dubbo-admin中三种不同的规则配置方式,条件路由是通过Dubbo定义的语法规则编写的路由规则,文件路由则是一个包含路由规则的文件,脚本路由则是使用JDK自身的脚本引擎解析路由规则脚本。下图是路由相关类的关系图。
我们主要介绍条件路由,条件路由可以在dubbo-admin管理台的路由规则菜单进行配置。
我们来做个实验,将提供者服务部署在两台机器上:192.168.0.205和192.168.0.149。提供者服务在服务接口实现中,通过InetAddress获取本机地址以便知道消息来自哪台服务器:
@Override
public List<UserAddress> getUserAddressList(String userId) {
System.out.println("---------getUserAddressList-----------userId:"+userId);
UserAddress address1 = new UserAddress(1, "北京天安门广场",
userId, "Luke", "010-7984654", "Y");
try {
//获取ip地址
address1.setIp(InetAddress.getLocalHost().getHostAddress());
} catch (UnknownHostException e) {
e.printStackTrace();
}
return Arrays.asList(address1);
}
消费者服务将负载均衡策略设置为轮询(轮询策略让消费者依次交替调用205和149机器上的提供者服务,后面会详细介绍),同时将结果打印出来:
<dubbo:reference id="userService"
interface="com.luke.dubbo.api.service.UserService"
loadbalance="roundrobin"/>
for(int i = userIdInt; i < 1000; i++){
List<UserAddress> userAddressList = userService.getUserAddressList(i+"");
userAddressList.forEach((userAddress -> {
//答应ip地址
System.out.println("RESPONSE FROM 【"+userAddress.getIp()+"】:address:"+
userAddress.getUserAddress()+",userId:"+userAddress.getUserId());
}));
Thread.sleep(1_000);
}
启动两个提供者服务和一个消费者服务,观察消费者服务控制台,轮询调用服务提供者:
RESPONSE FROM 【192.168.0.205】:address:北京天安门广场,userId:751
RESPONSE FROM 【192.168.0.149】:address:北京天安门广场,userId:752
RESPONSE FROM 【192.168.0.205】:address:北京天安门广场,userId:753
RESPONSE FROM 【192.168.0.149】:address:北京天安门广场,userId:754
RESPONSE FROM 【192.168.0.205】:address:北京天安门广场,userId:755
RESPONSE FROM 【192.168.0.149】:address:北京天安门广场,userId:756
RESPONSE FROM 【192.168.0.205】:address:北京天安门广场,userId:757
RESPONSE FROM 【192.168.0.149】:address:北京天安门广场,userId:758
RESPONSE FROM 【192.168.0.205】:address:北京天安门广场,userId:759
在dubbo-admin配置路由规则,过滤192.168.0.205服务提供者:
再次查看消费者控制台,结果全部来自192.168.0.149服务提供者:
RESPONSE FROM 【192.168.0.149】:address:北京天安门广场,userId:17
RESPONSE FROM 【192.168.0.149】:address:北京天安门广场,userId:18
RESPONSE FROM 【192.168.0.149】:address:北京天安门广场,userId:19
RESPONSE FROM 【192.168.0.149】:address:北京天安门广场,userId:20
RESPONSE FROM 【192.168.0.149】:address:北京天安门广场,userId:21
RESPONSE FROM 【192.168.0.149】:address:北京天安门广场,userId:22
RESPONSE FROM 【192.168.0.149】:address:北京天安门广场,userId:23
RESPONSE FROM 【192.168.0.149】:address:北京天安门广场,userId:24
RESPONSE FROM 【192.168.0.149】:address:北京天安门广场,userId:25
RESPONSE FROM 【192.168.0.149】:address:北京天安门广场,userId:26
路由规则在配置之后,会改变注册中心信息,注册中心回调消费者事件通知,执行RegistryDirectory.notify()方法,路由规则事件通知主要做两件事情:
1、刷新本地路由规则缓存
例如上面的例子中增加了一条路由规则,会在notify()方法中刷新本地路由规则缓存:
// routers
if (routerUrls != null && !routerUrls.isEmpty()) {
List<Router> routers = toRouters(routerUrls);
if (routers != null) { // null - do nothing
setRouters(routers);
}
}
路由规则以"route"协议开头:
2、通过路由规则刷新本地Invoker缓存methodInvokerMap
通知方法notify()最后执行refreshInvoker(invokerUrls)
方法,在该方法里面会执行toMethodInvokers()
方法,完成对Invoker的过滤,过滤之后更新newMethodInvokerMap。
private void refreshInvoker(List<URL> invokerUrls) {
//省略部分代码......
//获取所有Invoker,赋值给methodInvokerMap
Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap);
this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
}
toMethodInvokers()方法会调用route(invokersList, null)
,交给路由过滤处理,过滤掉205机器的服务提供者。
private Map<String, List<Invoker<T>>> toMethodInvokers(Map<String, Invoker<T>> invokersMap) {
//省略部分代码......
Map<String, List<Invoker<T>>> newMethodInvokerMap = new HashMap<String, List<Invoker<T>>>();
//执行路由过滤
List<Invoker<T>> newInvokersList = route(invokersList, null);
newMethodInvokerMap.put(Constants.ANY_VALUE, newInvokersList);
return Collections.unmodifiableMap(newMethodInvokerMap);
}
private List<Invoker<T>> route(List<Invoker<T>> invokers, String method) {
Invocation invocation = new RpcInvocation(method, new Class<?>[0], new Object[0]);
List<Router> routers = getRouters();
if (routers != null) {
for (Router router : routers) {
if (router.getUrl() != null) {
invokers = router.route(invokers, getConsumerUrl(), invocation);
}
}
}
return invokers;
}
所以当我们断点调试上面的集群容错调用,你会发现执行到AbstractDirectory的list()方法时,该行代码List<Invoker<T>> invokers = doList(invocation);
获取的Invoker集合就已经是只有149服务提供者,因为doList()方法中就是从methodInvokerMap获取的Invoker集合,而methodInvokerMap在路由刷新的时候也已经跟着刷新了。
@Override
public List<Invoker<T>> list(Invocation invocation) throws RpcException {
if (destroyed) {
throw new RpcException("Directory already destroyed .url: " + getUrl());
}
List<Invoker<T>> invokers = doList(invocation);
List<Router> localRouters = this.routers; // local reference
if (localRouters != null && !localRouters.isEmpty()) {
for (Router router : localRouters) {
try {
if (router.getUrl() == null || router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) {
invokers = router.route(invokers, getConsumerUrl(), invocation);
}
} catch (Throwable t) {
logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
}
}
}
return invokers;
}
配置路由规则可以屏蔽某些提供者服务,还可以通过配置消费者黑白名单来屏蔽某些消费者服务,例如在访问控制菜单项添加配置205的消费者服务为黑名单,这样205的消费者服务远程调用时将会抛出RpcException异常。
Cluster
Cluster接口被称为容错接口(注意区分Cluster接口和Cluster层,Cluster层表示集群容错层,Cluster接口是Cluster层的一部分),Dubbo框架提供了多种集群容错策略:
Failover Cluster
失败自动切换,当出现失败,重试其它服务器 。通常用于读操作,但重试会带来更长延迟。可通过 retries="2"来设置重试次数(不含第一次)。
Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2"来设置最大并行数。
Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错 [2]。通常用于通知所有提供者更新缓存或日志等本地资源信息。
集群模式配置
按照以下示例在服务提供方和消费方配置集群模式
<dubbo:service cluster="failsafe" />
或 <dubbo:reference cluster="failsafe" />
我们重点介绍前面两种容错策略Failover Cluster和Failfast Cluster。
Failover Cluster
public class FailoverCluster implements Cluster {
public final static String NAME = "failover";
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return new FailoverClusterInvoker<T>(directory);
}
}
FailoverCluster在join()方法构造了FailoverClusterInvoker实例对象,FailoverClusterInvoker继承了AbstractClusterInvoker,AbstractClusterInvokerto在invoke()
方法最后调用模板方法doInvoke()
方法,交给子类去执行。
@Override
public Result invoke(final Invocation invocation) throws RpcException {
checkWhetherDestroyed();
LoadBalance loadbalance = null;
List<Invoker<T>> invokers = list(invocation);
if (invokers != null && !invokers.isEmpty()) {
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
.getMethodParameter(invocation.getMethodName(), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
}
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
return doInvoke(invocation, invokers, loadbalance);//默认方法,交给子类处理
}
@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);
int len = getUrl().getMethodParameter(invocation.getMethodName(),
Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;//@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++) {
//Reselect before retry to avoid a change of candidate `invokers`.
//NOTE: if `invokers` changed, then `invoked` also lose accuracy.
if (i > 0) {//@2
checkWhetherDestroyed();
copyinvokers = list(invocation);
// check again
checkInvokers(copyinvokers, invocation);
}
Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);//@3
invoked.add(invoker);
RpcContext.getContext().setInvokers((List) invoked);
try {
Result result = invoker.invoke(invocation);//@4
if (le != null && logger.isWarnEnabled()) {
logger.warn("Although retry the method " + invocation.getMethodName()
+ " 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);//@5
}
return result;
} catch (RpcException e) {//@6
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 != null ? le.getCode() : 0, "Failed to invoke the method "
+ invocation.getMethodName() + " 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 != null ? le.getMessage() : ""), le != null && le.getCause() != null ?
+ le.getCause() : le);//@7
}
代码@1:获取调用失败最大重试次数,默认是三次。
代码@2:当i>0的时候说明已经发生了调用失败,调用失败后的重试操作都需要调用list()方法重新获取Invoker集合。
代码@3:通过负载均衡器LoadBalance从Invoker集合里面选出一个Invoker。
代码@4:发起远程调用,这块会在后面的文章中详细介绍。
代码@5:le用来存储最近一次调用失败的异常信息,如果最后调用成功但le不为空,说明发生过重复调用,输出警告日志。
代码@6:捕获远程调用抛出的RpcException异常,如果是业务异常则直接抛出,否则赋值给le。
代码@7:如果执行超过最大重试次数仍然失败的话,就向外抛出调用失败异常信息。
Failfast Cluster
public class FailfastCluster implements Cluster {
public final static String NAME = "failfast";
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return new FailfastClusterInvoker<T>(directory);
}
}
FailfastCluster在join()方法则是返回FailfastClusterInvoker实例对象。
public class FailfastClusterInvoker<T> extends AbstractClusterInvoker<T> {
public FailfastClusterInvoker(Directory<T> directory) {
super(directory);
}
@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);//@1
try {
return invoker.invoke(invocation);//@2
} catch (Throwable e) {//@3
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);
}
}
}
代码@1:通过负载均衡器从Invoker集合里面选出一个Invoker。
代码@2:发起远程调用,FailfastClusterInvoker和FailoverClusterInvoker最大的区别就是前者远程调用失败后,不再执行重试操作。
代码@3:只执行一次调用,调用失败就直接向外抛出异常信息。
LoadBalance
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
@Adaptive("loadbalance")
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url,
Invocation invocation) throws RpcException;
}
Dubbo框架提供了多种负载均衡策略(默认使用随机策略):
Random LoadBalance
随机,按权重设置随机概率。
在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
RoundRobin LoadBalance
轮询,按公约后的权重设置轮询比率。
存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
LeastActive LoadBalance
最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
ConsistentHash LoadBalance
一致性 Hash,相同参数的请求总是发到同一提供者。
当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
配置方式
1、服务端服务级别:
<dubbo:service interface="..." loadbalance="roundrobin" />
2、客户端服务级别:
<dubbo:reference interface="..." loadbalance="roundrobin" />
3、服务端方法级别:
<dubbo:service interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:service>
4、客户端方法级别
<dubbo:reference interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:reference>
我们重点介绍前面两种:Random LoadBalance和RoundRobin LoadBalance。
RandomLoadBalance
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
//Invoker的数量
int length = invokers.size(); // Number of invokers
//全部Invoker合起来的权重
int totalWeight = 0; // The sum of weights
//各个Invoker权重是否一样
boolean sameWeight = true; // Every invoker has the same weight?
for (int i = 0; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);//@1
totalWeight += weight; // Sum
if (sameWeight && i > 0
&& weight != getWeight(invokers.get(i - 1), invocation)) {//@2
sameWeight = false;
}
}
if (totalWeight > 0 && !sameWeight) {//@3
// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
int offset = random.nextInt(totalWeight);//@4
// Return a invoker based on the random value.
for (int i = 0; i < length; i++) {
offset -= getWeight(invokers.get(i), invocation);//@5
if (offset < 0) {
return invokers.get(i);
}
}
}
// If all invokers have the same weight value or totalWeight=0, return evenly.
return invokers.get(random.nextInt(length));//@6
}
随机负载均衡策略是通过权重来实现的。
代码@1:通过getWeight()
方法获取Invoker所占权重,默认获取到的权重是100,后面会详细介绍该方法。
代码@2:totalWeight存储累加所有Invoker的权重值总和,sameWeight用于判断是否所有的Invoker权重都是相同的,通过将当前的Invoker权重和Invoker集合的前一个Invoker权重做比较,如果不一样,那么sameWeight赋值为false。
代码@3:当权重值大于零,且各个Invoker的权重值不完全相同时执行if逻辑,否则执行代码@6。
代码@4:通过Random获取0到totalWeight-1之间的一个值,赋值给offset。
代码@5:for循环依次获取集合里的每个Invoker,再次执行getWeight()获取Invoker的权重,接着执行offset = offset - getWeigth()
运算。如果offset为负值,那么该Invoker将被选中,否则继续下一个。我们可以举一个例子来帮助大家更好理解:
首先假设现在有三个Invoker,获取的总权重是200,其中Invoker1是100,剩余两个各50,那么Invoker1将占0-99,Invoker2将占100-149,Invoker2将占150-199,此时代码@4计算出来的随机数offset的范围就是0-199之间,如果offset算出是0-99之间的一个数,那么执行offset = offset -getWeigth()后offset的值将小于零,选中了Invoker1,如果offset是100,那么将选中Invoker2,后面以此类推。
代码@6:如果每个Invoker的权重都是一样的,那么将随机返回Invoker集合中的一个。
下面我们来详细介绍一下getWeight()
方法:
protected int getWeight(Invoker<?> invoker, Invocation invocation) {
int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(),
Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT);//@1
if (weight > 0) {
long timestamp = invoker.getUrl().
getParameter(Constants.REMOTE_TIMESTAMP_KEY, 0L);//@2
if (timestamp > 0L) {
int uptime = (int) (System.currentTimeMillis() - timestamp);//@3
int warmup = invoker.getUrl().
getParameter(Constants.WARMUP_KEY, Constants.DEFAULT_WARMUP);//@4
if (uptime > 0 && uptime < warmup) {
weight = calculateWarmupWeight(uptime, warmup, weight);//@5
}
}
}
return weight;
}
代码@1:获取默认权重,Constants.DEFAULT_WEIGHT = 100。
代码@2:获取提供者服务的启动时间。
代码@3:获取提供者服务自启动后到现在过去的时间(即运行时长),赋值给uptime。
代码@4:获取默认的唤醒时间Constants.DEFAULT_WARMUP=60000(即10分钟),赋值给warmup。
代码@5:如果uptime < warmup,即提供者服务自启动后的运行时长还不满足10分钟的时候,将执行calculateWarmupWeight()
去计算预热权重,计算的结果值在100以内,预热权重值随运行的时长增加而增大。Dubbo框架认为提供者服务启动后运行时长在10分钟以内时,不应该给予100%的权重,而是先通过一个预热阶段,不断增加它的权重,10分钟后才分配给他和其他提供者服务相同的权重。
RoundRobinLoadBalance
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
int length = invokers.size(); // Number of invokers
int maxWeight = 0; // The maximum weight
int minWeight = Integer.MAX_VALUE; // The minimum weight
final LinkedHashMap<Invoker<T>, IntegerWrapper> invokerToWeightMap = new LinkedHashMap<Invoker<T>, IntegerWrapper>();
int weightSum = 0;
for (int i = 0; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);//@1
maxWeight = Math.max(maxWeight, weight); // Choose the maximum weight
minWeight = Math.min(minWeight, weight); // Choose the minimum weight
if (weight > 0) {
invokerToWeightMap.put(invokers.get(i), new IntegerWrapper(weight));//@2
weightSum += weight;//@3
}
}
AtomicPositiveInteger sequence = sequences.get(key);//@4
if (sequence == null) {
sequences.putIfAbsent(key, new AtomicPositiveInteger());
sequence = sequences.get(key);
}
int currentSequence = sequence.getAndIncrement();
if (maxWeight > 0 && minWeight < maxWeight) {//@5
/**将请求次数对权重总和取模*/
int mod = currentSequence % weightSum;
for (int i = 0; i < maxWeight; i++) {
for (Map.Entry<Invoker<T>, IntegerWrapper> each : invokerToWeightMap.entrySet()) {
final Invoker<T> k = each.getKey();
final IntegerWrapper v = each.getValue();//取出Invoker权重
if (mod == 0 && v.getValue() > 0) {//如果mod为0且权重值大于0,则返回
return k;
}
if (v.getValue() > 0) {//权重值大于0,权重值减1,模式mod也减1。
v.decrement();
mod--;
}
}
}
}
// Round robin
return invokers.get(currentSequence % length);//@6
}
代码@1:获取Invoker权重。
代码@2:将每一个Invoker和对应的权重存储到invokerToWeightMap。
代码@3:累加每个Invoker权重,存储到weightSum。
代码@4:使用原子变量存储接口和对应的累计请求次数。
代码@5:如果各个Invoker权重并不完全相同,那么将进行权重比较计算。这部分代码稍复杂,我们同样通过举一个例子来说明,假如现在有三个提供者服务的Invoker,servers = [A, B, C],对应的权重为 weights = [2, 5, 1]。那么随着调用次数currentSequence的累加,mod的值始终是保持在0-7之间反复循环,那么选中的Invoker将如下:
mod | A计算后权重 | B计算后权重 | C计算后权重 | 选中的Invoker |
---|---|---|---|---|
0 | 2 | 5 | 1 | A |
1 | 1 | 5 | 1 | B |
2 | 1 | 4 | 1 | C |
3 | 1 | 4 | 0 | A |
4 | 0 | 4 | 0 | B |
5 | 0 | 3 | 0 | B |
6 | 0 | 2 | 0 | B |
7 | 0 | 1 | 0 | B |
所以按照三个Invoker权重的大小,在每8次请求内,A将被选中两次,B将被选中5次,C则被选中1次,可见选中概率和权重大小保持一致。
代码@6:如果每个Invoker权重都一样则直接通过取模获取对应Invoker。
可以通过dubbo-admin调节权重分配
服务降级
服务降级是指临时屏蔽某些服务,并自定义这些服务接口的返回策略。通常这些服务是系统的非关键服务,不会影响到核心业务。例如一些系统在某些时段会突然出现业务流量激增的情况(比如双11),这时候可以通过服务降级,临时屏蔽一些不重要的服务,腾出资源给关键服务,让系统平稳度过这段流量激增时期。服务降级可以在dubbo-admin管理台配置,也可以通过代码指定。
服务降级类型
- mock=force:return+null 表示消费方对该服务的方法调用都直接返回 null值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
- mock=fail:return+null 表示消费方对该服务的方法调用在失败后,再返回 null值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
源码解读服务降级
查看MockClusterInvoker的invoke()方法,一旦配置了mock,那么将不走no mock逻辑:
@Override
public Result invoke(Invocation invocation) throws RpcException {
Result result = null;
String value = directory.getUrl().getMethodParameter(invocation.getMethodName(),
Constants.MOCK_KEY,
Boolean.FALSE.toString()).trim();
if (value.length() == 0 || value.equalsIgnoreCase("false")) {
//no mock
result = this.invoker.invoke(invocation);
} else if (value.startsWith("force")) {
if (logger.isWarnEnabled()) {
logger.info("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl());
}
//force:direct mock
result = doMockInvoke(invocation, null);//@1
} else {
//fail-mock
try {
result = this.invoker.invoke(invocation);//@2
} catch (RpcException e) {//@3
if (e.isBiz()) {
throw e;
} else {
if (logger.isWarnEnabled()) {
logger.warn("fail-mock: " + invocation.getMethodName() +
" fail-mock enabled , url : " + directory.getUrl(), e);
}
result = doMockInvoke(invocation, e);
}
}
}
return result;
}
代码@1:如果是强制Mock,即屏蔽服务,直接执行doMockInvoke(invocation, null)方法。
代码@2:如果是失败Mock,那么仍然会执行一次正常调用。
代码@3:捕获RpcException异常,如果出现业务异常那么直接向外抛出,否则输出警告日志,接着执行doMockInvoke(invocation, null)
方法。
进入doMockInvoke(invocation, null)
方法:
private Result doMockInvoke(Invocation invocation, RpcException e) {
Result result = null;
Invoker<T> minvoker;
List<Invoker<T>> mockInvokers = selectMockInvoker(invocation);//@1
if (mockInvokers == null || mockInvokers.isEmpty()) {
minvoker = (Invoker<T>) new MockInvoker(directory.getUrl());//@2
} else {
minvoker = mockInvokers.get(0);//@3
}
try {
result = minvoker.invoke(invocation);//@4
} catch (RpcException me) {
if (me.isBiz()) {
result = new RpcResult(me.getCause());
} else {
throw new RpcException(me.getCode(),
getMockExceptionMessage(e, me), me.getCause());
}
} catch (Throwable me) {
throw new RpcException(getMockExceptionMessage(e, me), me.getCause());
}
return result;
}
代码@1:获取Mock类型Invoker集合,下面会详细介绍。
代码@2:如果mockInvokers为空,那么将实例化MockInvoker对象并返回,否则执行代码@3。
代码@3:获取第一个Mock类型Invoker,赋值给minvoker。
代码@4:通过minvoker执行invoke()调用,并且捕获RpcException,如果是业务异常则包装成RpcResult对象返回,否则抛出RpcException异常。
private List<Invoker<T>> selectMockInvoker(Invocation invocation) {
List<Invoker<T>> invokers = null;
//TODO generic invoker?
if (invocation instanceof RpcInvocation) {
//Constants.INVOCATION_NEED_MOCK = "invocation.need.mock"
((RpcInvocation) invocation).setAttachment(Constants.INVOCATION_NEED_MOCK,
Boolean.TRUE.toString());//@1
try {
invokers = directory.list(invocation);//@2
} catch (RpcException e) {
if (logger.isInfoEnabled()) {
logger.info("Exception when try to invoke mock.
Get mock invokers error for service:"
+ directory.getUrl().getServiceInterface() + ",
+ method:" + invocation.getMethodName()
+ ", will contruct a new mock with 'new MockInvoker()'.", e);
}
}
}
return invokers;
}
代码@1:主动让Invocation对象添加参数项invocation.need.mock=true
。
代码@2:交给RegistryDirectory的list()
方法获取Mock类型的Invoker。
@Override
public List<Invoker<T>> list(Invocation invocation) throws RpcException {
if (destroyed) {
throw new RpcException("Directory already destroyed .url: " + getUrl());
}
List<Invoker<T>> invokers = doList(invocation);//@1
List<Router> localRouters = this.routers; // local reference
if (localRouters != null && !localRouters.isEmpty()) {
for (Router router : localRouters) {
try {
if (router.getUrl() == null ||
router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) {
invokers = router.route(invokers, getConsumerUrl(), invocation);//@2
}
} catch (Throwable t) {
logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
}
}
}
return invokers;
}
代码@1:获取Invoker集合,作者这里使用前面的例子,所以获取到149和205两个Invoker。
代码@2:router的实例对象是MockInvokersSelector,执行路由过滤route()
。
@Override
public <T> List<Invoker<T>> route(final List<Invoker<T>> invokers,
URL url, final Invocation invocation) throws RpcException {
if (invocation.getAttachments() == null) {
return getNormalInvokers(invokers);
} else {
String value = invocation.getAttachments().get(Constants.INVOCATION_NEED_MOCK);
if (value == null)
return getNormalInvokers(invokers);
else if (Boolean.TRUE.toString().equalsIgnoreCase(value)) {
return getMockedInvokers(invokers);
}
}
return invokers;
}
该方法根据Constants.INVOCATION_NEED_MOCK选择调用getNormalInvokers()
或者getMockedInvokers()
。
-
getNormalInvokers()
:获取所有非mock协议的Invoker。 -
getMockedInvokers()
:获取所有mock协议的Invoker。
作者通过dubbo-admin配置了mock,所以没有获取到mock协议的Invoker,这样149和205的两个Invoker都将被过滤掉。
回到MockClusterInvoker的doMockInvoke()
方法,这样selectMockInvoker(invocation)
就获取为空,那么将会创建MockInvoker实例对象,执行minvoker.invoke(invocation)
,进入MockInvoker的invoke()
方法:
@Override
public Result invoke(Invocation invocation) throws RpcException {
String mock = getUrl().getParameter(invocation.getMethodName() + "." +
Constants.MOCK_KEY);//@1
if (invocation instanceof RpcInvocation) {
((RpcInvocation) invocation).setInvoker(this);
}
if (StringUtils.isBlank(mock)) {
mock = getUrl().getParameter(Constants.MOCK_KEY);//@2
}
if (StringUtils.isBlank(mock)) {
throw new RpcException(new IllegalAccessException("mock can not be null. url :" + url));
}
mock = normallizeMock(URL.decode(mock));//@3
if (Constants.RETURN_PREFIX.trim().equalsIgnoreCase(mock.trim())) {//@4
RpcResult result = new RpcResult();
result.setValue(null);
return result;
} else if (mock.startsWith(Constants.RETURN_PREFIX)) {//@5
mock = mock.substring(Constants.RETURN_PREFIX.length()).trim();
mock = mock.replace('`', '"');
try {
Type[] returnTypes = RpcUtils.getReturnTypes(invocation);
Object value = parseMockValue(mock, returnTypes);
return new RpcResult(value);
} catch (Exception ew) {
throw new RpcException("mock return invoke error. method :" + invocation.getMethodName() + ", mock:" + mock + ", url: " + url, ew);
}
} else if (mock.startsWith(Constants.THROW_PREFIX)) {//@6
mock = mock.substring(Constants.THROW_PREFIX.length()).trim();
mock = mock.replace('`', '"');
if (StringUtils.isBlank(mock)) {
throw new RpcException(" mocked exception for Service degradation. ");
} else { // user customized class
Throwable t = getThrowable(mock);
throw new RpcException(RpcException.BIZ_EXCEPTION, t);
}
} else { //impl mock
try {
Invoker<T> invoker = getInvoker(mock);//@7
return invoker.invoke(invocation);
} catch (Throwable t) {
throw new RpcException("Failed to create mock implemention class " + mock, t);
}
}
}
代码@1:获取方法名+Constants.MOCK_KEY参数值赋值给mock。
代码@2:如果代码@1获取为空,从URL地址获取Constants.MOCK_KEY参数值。
代码@3:解析出mock的值,比如“return null”。
代码@4:如果mock的值为“return”,那么构建RpcResult对象返回。
代码@5:如果mock的值以“return”开头,那么解析后面的返回值类型,构建RpcResult对象返回。
代码@6:如果mock的值以“throw”开头,那么抛出RpcException异常。
代码@7:获取mock类型Invoker,执行invoke()调用。
那么如何才能有mock协议的Invoker呢?答案是通过自定义服务接口的Mock实现类:
public class UserServiceMock implements UserService {
@Override
public List<UserAddress> getUserAddressList(String userId) {
System.out.println("返回容错数据");
return new ArrayList<>();
}
}
<dubbo:reference id="userService" interface="com.luke.dubbo.api.service.UserService"
loadbalance="roundrobin" cluster="failfast"
mock="com.luke.dubbo.order.service.mock.UserServiceMock"/>
消费者服务在<dubbo:reference>
标签通过mock属性指定服务接口的Mock实现类。
自定义了Mock类之后,消费者服务调用时将执行到MockInvoker.invke()
方法时,将执行代码@7逻辑,获取一个Mock类的Invoker,进入MockInvoker的getInvoker()
方法:
@SuppressWarnings("unchecked")
private Invoker<T> getInvoker(String mockService) {
Invoker<T> invoker = (Invoker<T>) mocks.get(mockService);//@1
if (invoker != null) {
return invoker;
} else {
Class<T> serviceType = (Class<T>) ReflectUtils.
forName(url.getServiceInterface());//@2
if (ConfigUtils.isDefault(mockService)) {
mockService = serviceType.getName() + "Mock";
}
Class<?> mockClass = ReflectUtils.forName(mockService);//@3
if (!serviceType.isAssignableFrom(mockClass)) {
throw new IllegalArgumentException("The mock implemention class " + mockClass.getName() + " not implement interface " + serviceType.getName());
}
if (!serviceType.isAssignableFrom(mockClass)) {
throw new IllegalArgumentException("The mock implemention class " + mockClass.getName() + " not implement interface " + serviceType.getName());
}
try {
T mockObject = (T) mockClass.newInstance();//@4
invoker = proxyFactory.getInvoker(mockObject, (Class<T>) serviceType, url);
if (mocks.size() < 10000) {
mocks.put(mockService, invoker);//@5
}
return invoker;
} catch (InstantiationException e) {
throw new IllegalStateException("No such empty constructor \"public " + mockClass.getSimpleName() + "()\" in mock implemention class " + mockClass.getName(), e);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
}
代码@1:从缓存mocks获取该服务接口的mock类型Invoker。
代码@2:获取服务接口Class。
代码@3:获取Mock实现类的Class。
代码@4:实例化Mock实现类对象。
代码@5:通过代理工厂利用mockObject,serviceType和url构建Invoker实例对象。
所以消费者服务调用UserService接口服务时,一旦远程调用发生RpcException异常,将会执行UserServiceMock,返回自定义Mock结果。