Dubbo服务调用源码浅析

39 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情

前言

前几周因为看问题简单熟悉了Dubbo的服务暴露过程,虽然公司不在使用,但是觉得还是有必要多了解了解。

这两天有时间,就想着了解一下Dubbo的服务消费过程。之前对RPC框架也有相关了解,深知作为一款优秀的RPC框架,服务的消费过程必然存在着很多的设计。如远程调用代理层,路由层,容错重试逻辑等等。下面就通过阅读Dubbo的源码来一点点体会吧。

服务调用原理

案例

从最简单的demo入手

private static void runWithRefer() {
	ReferenceConfig<DemoService> reference = new ReferenceConfig<>();
	reference.setApplication(new ApplicationConfig("dubbo-demo-api-consumer"));
	reference.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
	reference.setInterface(DemoService.class);
	DemoService service = reference.get();
	String message = service.sayHello("dubbo");
	System.out.println(message);
}

可以看到调用服务会指定注册中心,以及需要消费的接口。比较关键的代码就是reference.get(); 这里其实会返回一个代理对象,在内部实现远程调用。

代理对象的创建

reference.get()方法最终会返回ref。

/**
 * The interface proxy reference
 */
private transient volatile T ref;

public synchronized T get() {
	if (destroyed) {
		throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
	}
	if (ref == null) {
		init();
	}
	return ref;
}

在init方法中会去创建ref。

ref = createProxy(map);

private T createProxy(Map<String, String> map) {	  
        //省略代码...
		if (urls.size() == 1) {
			invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
		} else {
		  
		}
	// create service proxy
	return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
}

通过上面代码我们需要了解invoker是如何生成的,先来看看REF_PROTOCOL的实现类是哪一个吧

/**
 * The {@link Protocol} implementation with adaptive functionality,it will be different in different scenarios.
 * A particular {@link Protocol} implementation is determined by the protocol attribute in the {@link URL}.
 * For example:
 *
 * <li>when the url is registry://224.5.6.7:1234/org.apache.dubbo.registry.RegistryService?application=dubbo-sample,
 * then the protocol is <b>RegistryProtocol</b></li>
 *
 * <li>when the url is dubbo://224.5.6.7:1234/org.apache.dubbo.config.api.DemoService?application=dubbo-sample, then
 * the protocol is <b>DubboProtocol</b></li>
 * <p>
 * Actually,when the {@link ExtensionLoader} init the {@link Protocol} instants,it will automatically wraps two
 * layers, and eventually will get a <b>ProtocolFilterWrapper</b> or <b>ProtocolListenerWrapper</b>
 */
private static final Protocol REF_PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

通过上面的注释可以了解到会调用RegistryProtocol中的refer方法。最终返回MigrationInvoker。然后会创建代理

/**
 * JavassistRpcProxyFactory
 */
public class JavassistProxyFactory extends AbstractProxyFactory {

    @Override
    @SuppressWarnings("unchecked")
    public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
        return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    }

    @Override
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName,
                                      Class<?>[] parameterTypes,
                                      Object[] arguments) throws Throwable {
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }

}

有了这个代理对象就可以像调用本地方法一样去进行远程调用了。其本质是调用了InvokerInvocationHandler中的invoke方法。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	if (method.getDeclaringClass() == Object.class) {
		return method.invoke(invoker, args);
	}
	String methodName = method.getName();
	Class<?>[] parameterTypes = method.getParameterTypes();
	if (parameterTypes.length == 0) {
		if ("toString".equals(methodName)) {
			return invoker.toString();
		} else if ("$destroy".equals(methodName)) {
			invoker.destroy();
			return null;
		} else if ("hashCode".equals(methodName)) {
			return invoker.hashCode();
		}
	} else if (parameterTypes.length == 1 && "equals".equals(methodName)) {
		return invoker.equals(args[0]);
	}
	RpcInvocation rpcInvocation = new RpcInvocation(method, invoker.getInterface().getName(), protocolServiceKey, args);
	String serviceKey = invoker.getUrl().getServiceKey();
	rpcInvocation.setTargetServiceUniqueName(serviceKey);

	// invoker.getUrl() returns consumer url.
	RpcContext.setRpcContext(invoker.getUrl());

	if (consumerModel != null) {
		rpcInvocation.put(Constants.CONSUMER_MODEL, consumerModel);
		rpcInvocation.put(Constants.METHOD_MODEL, consumerModel.getMethodModel(method));
	}

	return invoker.invoke(rpcInvocation).recreate();
}

到此,会发现最终还是调用了Invoker中的invoke方法。在Invoker中做了容错,路由,远程调用。

路由+容错处理

到此我们需要重点关注代理类中的Invoker。MigrationInvoker的invoke方法:

public Result invoke(Invocation invocation) throws RpcException {
	if (!checkInvokerAvailable(serviceDiscoveryInvoker)) {
		if (logger.isDebugEnabled()) {
			logger.debug("Using interface addresses to handle invocation, interface " + type.getName() + ", total address size " + (invoker.getDirectory().getAllInvokers() == null ? "is null" : invoker.getDirectory().getAllInvokers().size()));
		}
		return invoker.invoke(invocation);
	}

	if (!checkInvokerAvailable(invoker)) {
		if (logger.isDebugEnabled()) {
			logger.debug("Using instance addresses to handle invocation, interface " + type.getName() + ", total address size " + (serviceDiscoveryInvoker.getDirectory().getAllInvokers() == null ? " is null " : serviceDiscoveryInvoker.getDirectory().getAllInvokers().size()));
		}
		return serviceDiscoveryInvoker.invoke(invocation);
	}

	return currentAvailableInvoker.invoke(invocation);
}

这里的invoker是在如下方法中进行的初始化

public synchronized void refreshInterfaceInvoker() {
        clearListener(invoker);
        if (needRefresh(invoker)) {
            // FIXME invoker.destroy();
            if (logger.isDebugEnabled()) {
                logger.debug("Re-subscribing interface addresses for interface " + type.getName());
            }
            invoker = registryProtocol.getInvoker(cluster, registry, type, url);

            if (migrationMultiRegistry) {
                setListener(serviceDiscoveryInvoker, () -> {
                    this.setAddressChanged();
                });
            }
        }
    }

最终获取到默认的invoker是FailoverClusterInvoker。ClusterInvoker有多种实现,默认是FailoverClusterInvoker。(这种策略简单来说就是当调用一个服务提供者失败后,会继续选择其他节点的服务提供者进行调用,When invoke fails, log the initial error and retry other invokers)。

public class FailoverCluster extends AbstractCluster {

    public static final String NAME = "failover";

    @Override
    public <T> AbstractClusterInvoker<T> doJoin(Directory<T> directory) throws RpcException {
        return new FailoverClusterInvoker<>(directory);
    }

}

上面的invoker.invoke(invocation);方法最终会先调用到父类AbstractClusterInvoker的invoke方法,然后调用到实现类FailoverClusterInvoker中的invoke方法中。

AbstractClusterInvoker.invoke

@Override
public Result invoke(final Invocation invocation) throws RpcException {
	checkWhetherDestroyed();

	// binding attachments into invocation.
	Map<String, Object> contextAttachments = RpcContext.getContext().getObjectAttachments();
	if (contextAttachments != null && contextAttachments.size() != 0) {
		((RpcInvocation) invocation).addObjectAttachments(contextAttachments);
	}

	List<Invoker<T>> invokers = list(invocation);
	LoadBalance loadbalance = initLoadBalance(invokers, invocation);
	RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
	return doInvoke(invocation, invokers, loadbalance);
}

可以看到在父类中先获取到了所有的invokers,这里的invokers代表了已经建立远程连接的服务提供者列表。并且初始化了LocaBanlace,用于根据不同策略选择服务地址进行调用,如轮训,随机。这里有多种实现,基于SPI进行配置。

protected LoadBalance initLoadBalance(List<Invoker<T>> invokers, Invocation invocation) {
	if (CollectionUtils.isNotEmpty(invokers)) {
		return ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
				.getMethodParameter(RpcUtils.getMethodName(invocation), LOADBALANCE_KEY, DEFAULT_LOADBALANCE));
	} else {
		return ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(DEFAULT_LOADBALANCE);
	}
}

接着来看看FailoverClusterInvoker.invoker的实现(When invoke fails, log the initial error and retry other invokers)

@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.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 (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);
}

可以看到其实现是在一个循环中来进行的,首先基于loadbalance进行select,选择一个invoker。这里的invoker其实对应的是DubboInvoker(已经和对应的服务提供者建立好了netty 连接)。然后调用其invoke方法。如果失败了,则会继续循环,重新根据loadbalance进行select获取另一个DubboInvoker(另一个连接)。

DubboInvoker远程调用

最后我们来看看其做了什么吧

@Override
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 = calculateTimeout(invocation, methodName);
		invocation.put(TIMEOUT_KEY, timeout);
		if (isOneway) {
			boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
			currentClient.send(inv, isSent);
			return AsyncRpcResult.newDefaultAsyncResult(invocation);
		} else {
			ExecutorService executor = getCallbackExecutor(getUrl(), inv);
			CompletableFuture<AppResponse> appResponseFuture =
					currentClient.request(inv, timeout, executor).thenApply(obj -> (AppResponse) obj);
			// save for 2.6.x compatibility, for example, TraceFilter in Zipkin uses com.alibaba.xxx.FutureAdapter
			FutureContext.getContext().setCompatibleFuture(appResponseFuture);
			AsyncRpcResult result = new AsyncRpcResult(appResponseFuture, inv);
			result.setExecutor(executor);
			return result;
		}
	} 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);
	}
}

这里就是向服务端发送请求,进行远程调用。服务端收到请求后,就会找到对应的服务实现类进行调用了。