一Dubbo架构设计
一Dubbo架构设计
1.1结构设计
官网用户手册的介绍如下:
https://dubbo.gitbooks.io/dubbo-user-book/content/demos/fault-tolerent-strategy.html

上述过程是服务调用call过程,consumer端invoker.invoke,调用provider端的invoker对象的方法,就是RPC调用主干流程。
各节点关系:
- 这里的
Invoker是Provider的一个可调用Service的抽象,Invoker封装了Provider地址及Service接口信息 Directory代表多个Invoker,可以把它看成List<Invoker>,但与List不同的是,它的值可能是动态变化的,比如注册中心推送变更Cluster将Directory中的多个Invoker伪装成一个Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个Router负责从多个Invoker中按路由规则选出子集,比如读写分离,应用隔离等LoadBalance负责从多个Invoker中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选
1.2 调用的代码流程
上述过程主要是技术设计角度绘出,不是实际调用。该过程只是调用proxy的方法的容错架构设计。不包含refer的初始化过程(初始化过程对directory、registry分别进行初始化)
源码中具体调用过程如下:
代码流程:
proxy0.xxxMethod()
-->InvokerInvocationHandler.invoke
// RpcInvocation [methodName=sayHello, parameterTypes=[class java.lang.String], arguments=[world], attachments={}]
-->MockClusterInvoker.invoke(Invocation invocation)
-->FailoverClusterInvoker.invoke(final Invocation invocation)
-->RegistryDirectory.list(Invocation invocation) //根据RpcInvocation中的methodName获取Invoker
-->router过滤
-->loadBalancer选取一个Invoker
-->执行filter链
// RpcInvocation [methodName=sayHello, parameterTypes=[class java.lang.String], arguments=[world], attachments={path=com.alibaba.dubbo.demo.DemoService, interface=com.alibaba.dubbo.demo.DemoService, version=2.0.0, timeout=60000, group=dev}]
-->DubboInvoker.invoke(Invocation inv)
1.3 源码追踪
本文代码使用DatatablesDemo代码。
用applicationContext启动spring,并通过interface加载服务端实现的bean。看出实例对象为dubbo的代理生成的proxy对象

看出该proxy被传入一个invocationHaldler实例InvokerInvocationHandler(该过程就是java的动态代理)

动态代理实现(InvocationHandlerImpl其内含有被代理的实例对象,这里,就是Invoker实例。通过调用invoke方法,调用引用服务端的方法)
// 3.设置一个来自代理传过来的方法调用请求处理器,处理所有的代理对象上的方法调用
InvocationHandler handler = new InvocationHandlerImpl(car);
/*
4.根据上面提供的信息,创建代理对象 在这个过程中,
a.JDK会通过根据传入的参数信息动态地在内存中创建和.class 文件等同的字节码
b.然后根据相应的字节码转换成对应的class,
c.然后调用newInstance()创建实例
*/
Object o = Proxy.newProxyInstance(classLoader, interfaces, handler);
A1 接下来进入dubbo的consumer调用过程,来看容错架构设计
//该类为动态代理的handler对象
public class InvokerInvocationHandler implements InvocationHandler {
// 该处为MockClusterInvoker对象,就是动态代理invocationhandler中实际代理的对象car
private final Invoker<?> invoker;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//调用Invoker实现代理
return invoker.invoke(new RpcInvocation(method, args)).recreate();

B1 此时,进入MockClusterInvoker类
public class MockClusterInvoker<T> implements Invoker<T> {
private final Directory<T> directory;
private final Invoker<T> invoker;
public Result invoke(Invocation invocation) throws RpcException {
Result result = null;
//获取directory中注册的URL内,方法参数是否有mock
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);
}
}

MockClusterInvoker是针对集群部署的注册中心,提供的可以根据URL中mock参数,支持服务降级的集群管理功能的invoker。(是cluster.join返回的逻辑整合为一个invoker)。mockClusterInvoker内部持有的invoker类型为FailoverClusterInvoker类。
FailoverClusterInvoker采用模板方法,基本实现由抽象类AbstractClusterInvoker实现。

进入AbstractClusterInvoker(此时就进入到集群,cluster)
TAG1 AbstractClusterInvoker.invoke()
public abstract class AbstractClusterInvoker<T> implements Invoker<T> {
protected final Directory<T> directory;
//TAG1 AbstractClusterInvoker.invoke(invocation)
public Result invoke(final Invocation invocation) throws RpcException {
checkWhetherDestroyed();
//
LoadBalance loadbalance;
//TAG1.1 list(invocation)
//从directory中获取可以正常执行的invoker
List<Invoker<T>> invokers = list(invocation);
if (invokers != null && invokers.size() > 0) {
//如果有可用invoker,根据invoker.url的loadbalance参数,决定负载均衡的自适应类
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
.getMethodParameter(invocation.getMethodName(), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
} else {
//如果没有invoker服务,采用默认的负载均衡类
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
}
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
//TAG1.2 doInvoke(invocation, invokers, loadbalance)
return doInvoke(invocation, invokers, loadbalance);
}
protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
//从directory中获取invoker服务
List<Invoker<T>> invokers = directory.list(invocation);
return invokers;
}
跟入debug代码,分析执行中的数据


TAG1.1 list(invocation)
list内,RegistryDirectory.list(invocation)获取可用服务invokers


进入模板设计模式的AbstractDirectory中
public abstract class AbstractDirectory<T> implements Directory<T> {
private final URL url;
private volatile URL consumerUrl;
private volatile List<Router> routers;
从这里看出,Router在Directory内


分析directory的抽象类内模板方法list
public abstract class AbstractDirectory<T> implements Directory<T> {
public List<Invoker<T>> list(Invocation invocation) throws RpcException {
if (destroyed) {
throw new RpcException("Directory already destroyed .url: " + getUrl());
}
//TAG1.1.1 AbstractDirectory.doList(invocation)
//先通过directory的模板方法获取可以正常执行的Invoker
List<Invoker<T>> invokers = doList(invocation);
//router位于directory内
List<Router> localRouters = this.routers; // local reference
if (localRouters != null && localRouters.size() > 0) {
for (Router router : localRouters) {
try {
if (router.getUrl() == null || router.getUrl().getParameter(Constants.RUNTIME_KEY, true)) {
//TAG1.1.2 router.route(invokers, getConsumerUrl(), invocation)
//通过router路由过滤invoker
invokers = router.route(invokers, getConsumerUrl(), invocation);
}
} catch (Throwable t) {
logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
}
}
}
return invokers;
}
TAG1.1.1 AbstractDirectory.doList()
doList由RegistryDirectory实现(主要作用就是从directory查找正常执行的所有Invoker)
RegistryDirectory内各属性的意义,见[模块详解章节](#4.3 zk订阅)

上面过程是从methodInvokerMap中取出invokers。以kv存储,如下图所示

dolist就是从RegistryDirectory的methodInvokerMap中取出invokers。
methodInvokerMap内是以方法名为key,以List

RegistryDirectory.doList执行逻辑,是从registryDirectory的属性methodInvokerMap的属性中,根据methodName,获取invokers集合。获取的invokers类型为RegistryDirectory\(InvokerDelegate类型,详见[invoker章节内容](#1 RegistryDirectory\)InvokerDelegate)。
该invokerDelegate有三个参数,provideUrl存储,注册中心下发的provider的服务地址。可以在refer调用服务时候,根据provideUrl、methodparm等参数,重新构造调用服务地址。
三个参数,providerUrl代表当前服务,注册在注册中心中,服务提供者provider的真实地址。
其中,delegate代理类内传入的invoker是[ListenerInvokerWrapper](#2 ListenerInvokerWrapper)的invoker,其内添加了invoker调用和销毁监听器listener。
然后,返回InvokerDelegate类型的invoker实例
TAG1.1.2 router.route()
此时,将invokers返回后,从AbstractDirectory的list继续执行list,到达Router进入路由。


此时的router为MockInvokerSelector(也是router)

public interface Router extends Comparable<Router> {
//
URL getUrl();
//route路由方法,根据dubbo中配置的路由规则(如读写分离等),获取对应的可用的invokers服务
<T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}
进入route方法

url为consumer的url

MockInvokerSelector.route,根据参数invocation,是否有mock,选择创建normal还是mocked的invoker。
getNormalInvokers,创建正常的invokers,

判断invokers中是否有mock过的提供者,如果没有,直接返回。
回到AbstarctClusterInvoker类,此时从directory执行list获取可用的服务类,已经完成,然后需要执行对应的调用invoke。
总结:
directory中找出本次集群中的全部Invokers;
router中,将上一步的Invokers挑选出正常执行的invokers。(getNomalInvokers)。
TAG 1.2 doInvoke()
AbstractClusterInvoker.invoke()
上面挑选出了可以正常执行的invokers,但是多个做集群,此时该执行哪一个。需要如下:

这里仍然采用模板方法模式,doInvoke由子类实现。

在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。集群对应的容错方案,可以生成对应的Invoker类。
跟踪debug,查看源码走到该步骤,数据情况。


跟入FailOverClusterInvoker.doInvoke
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
List<Invoker<T>> copyinvokers = invokers;
checkInvokers(copyinvokers, invocation);
//获取invocation调用中,关于retries重试次数的参数
int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;
if (len <= 0) {
len = 1;
}
// 循环执行
RpcException le = null; // 记录loop循环中上一次exception内容
List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyinvokers.size()); // invoked invokers.
Set<String> providers = new HashSet<String>(len);
//失败重试failover的for循环
for (int i = 0; i < len; i++) {
//重试时,进行重新选择,避免重试时invoker列表已发生变化.
//注意:如果列表发生了变化,那么invoked判断会失效,因为invoker示例已经改变
if (i > 0) {
checkWheatherDestoried();
//重新获取invokers列表
copyinvokers = list(invocation);
//重新检查一下
checkInvokers(copyinvokers, invocation);
}
//TAG 1.2.1 select
Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
//将select选出来的invoker,加入invoked队列,表示选出来过,调用过(无论invoker.invoke是否成功)
invoked.add(invoker);
RpcContext.getContext().setInvokers((List)invoked);
try {
//实际调用
Result result = invoker.invoke(invocation);
if (le != null && logger.isWarnEnabled()) {
//当重试时,ls不为null,此时log记录,前一次失败信息记录
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);}
return result; //如果invoker.invoke异常,不执行return,继续loop重试
} catch (RpcException e) {
if (e.isBiz()) { // biz exception.
throw e;
}
le = e;
} catch (Throwable e) {
le = new RpcException(e.getMessage(), e);
} finally {
//loop执行中,记录每次调用服务的provider的address地址
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);
}
注意,这里实现的是failover策略,失败重试,设置重试次数len。失败重试时候,需要从新list进行选择,防止重试时候invokers列表(invokers列表,是从directory.list获得的)发生变化。
doInvoke()主要逻辑:
1 从invocation中获取重试次数参数retries=len;
2 for循环,进行失败重试逻辑:
a 判断循环是否是第一次,如果不是第一次执行,表示失败重试过,避免list获取的invokers列表变化,需要重新list获取;
b 执行select逻辑,从invokers中依据loadbanlance规则,选择一个可执行的invoker。然后记录选出来的invoker到invoked,避免再次选到;
c log(error) 失败重试时,此处log记录上次失败的信息
d invoker.invoke调用服务
上述是failover失败重试的主要逻辑
TAG1.2.1 select
然后select,通过loadbalance选择invoker

select是AbstraceClusterInvoker内的方法,doselect也是其内的方法。
接下来就是LoadBalance模块出现。


public abstract class AbstractClusterInvoker<T> implements Invoker<T> {
private Invoker<T> doselect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
if (invokers == null || invokers.size() == 0)
return null;
//当invokers有一个,直接返回
if (invokers.size() == 1)
return invokers.get(0);
// 如果只有两个invoker,退化成轮循
if (invokers.size() == 2 && selected != null && selected.size() > 0) {
return selected.get(0) == invokers.get(0) ? invokers.get(1) : invokers.get(0);
}
//TAG1.2.1.1 loadbalance.select
//当invokers数目,超过两个,负载均衡获取(默认负载均衡为random)
Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);
//如果 selected中包含(优先判断) 或者 不可用&&availablecheck=true 则重试.
if( (selected != null && selected.contains(invoker))
||(!invoker.isAvailable() && getUrl()!=null && availablecheck)){
try{
//重新选择reselect
Invoker<T> rinvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
if(rinvoker != null){
//当重新选择出invoker
invoker = rinvoker;
}else{
//当重新未选择出invoker,看下第一次选的位置,如果不是最后,选+1位置.
int index = invokers.indexOf(invoker);
try{
//最后在避免碰撞
invoker = index <invokers.size()-1?invokers.get(index+1) :invoker;
}catch (Exception e) {
logger.warn(e.getMessage()+" may because invokers list dynamic change, ignore.",e);
}
}
}catch (Throwable t){
logger.error("clustor relselect fail reason is :"+t.getMessage() +" if can not slove ,you can set cluster.availablecheck=false in url",t);
}
}
return invoker;
}
doSelect逻辑如下:
1 Dubbo提供了多种均衡策略,缺省为随机调用,但是如果集群的数量为2,那么将退化成轮询。——(面试题:dubbo的负载均衡策略是怎么样的?)
2 当传入invokers参数大于两个,执行负载均衡选择出一个invoker;
3 如果invoked包含该invoker,执行重选。如果重选成功,赋值;如果重选出为null,则根据上一次选择invoker的位置,选择invokers列表中的下一个节点。
TAG 1.2.1.1 loadBalance.select
这时候进入loadBalance选择出Invoker。(相关具体算法看后面的负载均衡分析中)

此时先执行抽象类中的select方法

当invokers大于两个,执行LoadBalance实现类的doSelect方法。
仍旧是模板方法,doselect由子类实现

总结:
在集群容错的整体架构中,consumer端invoker.invoke()的执行过程,dubbo做了如下三件事:

1 在 Directory中找出本次集群中的全部 invokers
2 在 Router中,将上一步的全部 invokers挑选出能正常执行的 invokers
3 在 LoadBalance中,将上一步的能正常的执行 invokers中,根据配置的负载均衡策略,挑选出需要执行的 invoker。
注意:
到此为止,只是loadbalance选出一个可以执行的invoker。具体的执行invoker,其中涉及的通信、连接等,详细见[第五章]

浙公网安备 33010602011771号