在消费端基于Dubbo API的方式调用服务:
public class ApiConsumerApplication {
public static void main(String[] args) {
// 1. 创建服务引用对象实例
ReferenceConfig<IGreetingService> referenceConfig = new ReferenceConfig<IGreetingService>();
// 2. 设置应用程序信息
referenceConfig.setApplication(new ApplicationConfig("deep-in-dubbo-first-consumer"));
// 3. 设置注册中心
referenceConfig.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181/"));
// 4. 设置服务接口和超时时间
referenceConfig.setInterface(IGreetingService.class);
// 默认重试3次
referenceConfig.setTimeout(5000);
// 5 设置服务分组和版本
referenceConfig.setGroup("dubbo-sxzhongf-group");
referenceConfig.setVersion("1.0.0");
// 6. 引用服务
IGreetingService greetingService = referenceConfig.get();
// 7. 设置隐式参数
RpcContext.getContext().setAttachment("company", "sxzhongf");
// 获取provider端传递的隐式参数(FIXME: 需要后续追踪)
// System.out.println("年龄是:" + RpcContext.getContext().getAttachment("age"));
//8. 调用服务
System.out.println(greetingService.sayHello("pan"));
}
}
一、生成透明代理
我们知道消费端依赖的只是服务端的一个接口,具体服务实现类在服务端,那么上面代码的第6步:引用服务一定生成的是类似透明代理的东西。接下来就从referenceConfig.get()开始,看下调用链:
ReferenceConfig#get
->ReferenceConfig#init
->ReferenceConfig#createProxy
->DubboProtocol#protocolBindingRefer —— 生成Invoker
->AbstractProxyFactory#getProxy(org.apache.dubbo.rpc.Invoker<T>) —— 根据Invoker生成透明代理
->JavassistProxyFactory#getProxy
借用网上的一张图片,也可以直观的看到透明代理的生成过程:
再看下ReferenceConfig#createProxy的核心源码:
据是否存在registry注册中心和url个数,决定是否创建cluster invoker,cluster invoker会在服务调用的时候用到。另外,这个透明代理一定封装了远端服务的IP端口、地址信息和服务类名、方法名等信息。
private T createProxy(Map<String, String> map) {
// JVM内部引用
if (shouldJvmRefer(map)) {
URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
invoker = REF_PROTOCOL.refer(interfaceClass, url);
if (logger.isInfoEnabled()) {
logger.info("Using injvm service " + interfaceClass.getName());
}
} else {
// P2P 直连url
urls.clear(); // reference retry init will add url to urls, lead to OOM
if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
String[] us = SEMICOLON_SPLIT_PATTERN.split(url);
if (us != null && us.length > 0) {
for (String u : us) {
URL url = URL.valueOf(u);
if (StringUtils.isEmpty(url.getPath())) {
url = url.setPath(interfaceName);
}
if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
urls.add(url.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
} else {
urls.add(ClusterUtils.mergeUrl(url, map));
}
}
}
// 注册中心模式
} else { // assemble URL from register center's configuration
// if protocols not injvm checkRegistry
if (!LOCAL_PROTOCOL.equalsIgnoreCase(getProtocol())){
checkRegistry();
List<URL> us = loadRegistries(false);
if (CollectionUtils.isNotEmpty(us)) {
for (URL u : us) {
URL monitorUrl = loadMonitor(u);
if (monitorUrl != null) {
map.put(MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
urls.add(u.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
}
}
if (urls.isEmpty()) {
throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
}
}
}
// 如果只有一个
if (urls.size() == 1) {
invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
} else {
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
for (URL url : urls) {
// Dubbo SPI自适应拓展点,根据url的协议类型registry指定处理类RegistryProtocol
invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
registryURL = url; // use last registry url
}
}
if (registryURL != null) { // registry url is available
// use RegistryAwareCluster only when register's CLUSTER is available
URL u = registryURL.addParameter(CLUSTER_KEY, RegistryAwareCluster.NAME);
// 通过英文注释可知,这个是集群模式,需要进行路由负载均衡
// The invoker wrap relation would be: RegistryAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, will execute route) -> Invoker
invoker = CLUSTER.join(new StaticDirectory(u, invokers));
} else { // not a registry url, must be direct invoke.
invoker = CLUSTER.join(new StaticDirectory(invokers));
}
}
}
// 省略部分代码
// 通过JavassistProxyFactory创建透明代理
// create service proxy
return (T) PROXY_FACTORY.getProxy(invoker);
}
JavassistProxyFactory#getProxy
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
看下 InvokerInvocationHandler类的继承关系:
联想到JDK的动态代理,这个InvokerInvocationHandler的invoke()方法是执行代理方法的地方,也就是说Dubbo服务调用就是从此开始的!
二、服务调用流程
先看下调用链:
InvokerInvocationHandler#invoke
->MockClusterInvoker#invoke —— 服务降级相关
->AbstractClusterInvoker#invoke —— 集群负载均衡
->FailoverClusterInvoker#doInvoke
->AbstractInvoker#invoke
->DubboInvoker#doInvoke —— dubbo协议调用
AbstractClusterInvoker#invoke:
public Result invoke(final Invocation invocation) throws RpcException {
checkWhetherDestroyed();
// 隐式传参
// binding attachments into invocation.
Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
if (contextAttachments != null && contextAttachments.size() != 0) {
((RpcInvocation) invocation).addAttachments(contextAttachments);
}
List<Invoker<T>> invokers = list(invocation);
// 根据Dubbo SPI 自适应拓展点指定负载均衡策略,模式是random随机
LoadBalance loadbalance = initLoadBalance(invokers, invocation);
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
return doInvoke(invocation, invokers, loadbalance);
}
FailoverClusterInvoker#doInvoke
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 = 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++) {
//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.getContext().setInvokers((List) invoked);
// 执行协议调用
Result result = invoker.invoke(invocation);
return result;
}
}
DubboInvoker#doInvoke
protected Result doInvoke(final Invocation invocation) throws Throwable {
RpcInvocation inv = (RpcInvocation) invocation;
final String methodName = RpcUtils.getMethodName(invocation);
inv.setAttachment(PATH_KEY, getUrl().getPath());
inv.setAttachment(VERSION_KEY, version);
ExchangeClient currentClient;
if (clients.length == 1) {
currentClient = clients[0];
} else {
currentClient = clients[index.getAndIncrement() % clients.length];
}
try {
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
int timeout = getUrl().getMethodPositiveParameter(methodName, TIMEOUT_KEY, DEFAULT_TIMEOUT);
// 异步无返回结果
if (isOneway) {
boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
currentClient.send(inv, isSent);
return AsyncRpcResult.newDefaultAsyncResult(invocation);
// 异步转同步
} else {
AsyncRpcResult asyncRpcResult = new AsyncRpcResult(inv);
CompletableFuture<Object> responseFuture = currentClient.request(inv, timeout);
asyncRpcResult.subscribeTo(responseFuture);
// save for 2.6.x compatibility, for example, TraceFilter in Zipkin uses com.alibaba.xxx.FutureAdapter
FutureContext.getContext().setCompatibleFuture(responseFuture);
return asyncRpcResult;
}
} catch (TimeoutException e) {
throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
} catch (RemotingException e) {
throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
注意这里的所谓的异步是指,调用线程、网络IO线程这两个线程默认是解耦的,如果是同步调用,则调用线程在调用完网络IO线程之后,会阻塞等待网络IO线程的返回结果。