In the last article, we learned about the process of dubbo service publishing. This article will briefly analyze the process of dubbo service reference.
1. What should the service consumer do?
- Generate proxy object (help us implement communication details)
- Establish a communication connection (netty)
- Get service provider address (subscription provider) from zk
- load balancing
- fault tolerance
- Serialization
- ...
Two, two steps
The above logic can be roughly divided into two steps
-
service startup phase
构建通信连接,创建代理对象
-
remote call phase
远程调用服务提供者方法
3. Service startup stage
There are two occasions for Dubbo service references:
- The first is to reference the service when the Spring container calls the afterPropertiesSet method of the ReferenceBean
- The second is referenced when the service corresponding to the ReferenceBean is injected into other classes
The difference in the timing of these two citation services is that the first one is hungry and the second is lazy.
By default, Dubbo uses the lazy citation service. If you need to use the hungry Chinese style, you can enable it by configuring the init attribute of dubbo:reference .
Usually we use the dubbo service in the code as follows:
@Reference
private ISayHelloService iSayHelloService;
ISayHelloService.hello();
You can use annotations @Reference
or configuration files dubbo-consumer.xml
. I use annotations here.
Let's first debug to see what this ISayHelloService
object is.
As you can see, it
ISayHelloService
is a proxy object and a wrapper class with many layers in it.
ReferenceAnnotationBeanPostProcessor -> ReferenceBean -> InvokerHandler -> mockClusterInvoker -> RegistryDirectory
Students who are familiar with spring should know that BeanPostProcessor
such a class will be executed when spring starts, so we start with this class.
//ReferenceAnnotationBeanPostProcessor.java
private static class ReferenceBeanInvocationHandler implements InvocationHandler {
private final ReferenceBean referenceBean;
private Object bean;
private ReferenceBeanInvocationHandler(ReferenceBean referenceBean) {
this.referenceBean = referenceBean;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(bean, args);
}
//初始化方法
private void init() {
this.bean = referenceBean.get();
}
}
ReferenceBean.get()
Keep following referenceBean.get():
//ReferenceConfig.java
//获取服务引用
public synchronized T get() {
if (destroyed) {
throw new IllegalStateException("Already destroyed!");
}
// 检测 ref 是否为空,为空则通过 init 方法创建
if (ref == null) {
// init 方法主要用于处理配置,以及调用 createProxy 生成代理类
init();
}
return ref;
}
Keep following init():
//ReferenceConfig.java
private void init() {
if (initialized) {
return;
}
initialized = true;
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!");
}
// get consumer's global configuration
checkDefault();
//填充ConsumerConfig
appendProperties(this);
//...省略
//组装URL
Map<String, String> map = new HashMap<String, String>();
Map<Object, Object> attributes = new HashMap<Object, Object>();
map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
//...省略
//获取注册中心host
String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);
if (hostToRegistry == null || hostToRegistry.length() == 0) {
hostToRegistry = NetUtils.getLocalHost();
} else if (isInvalidLocalHost(hostToRegistry)) {
throw new IllegalArgumentException("Specified invalid registry ip from property:" + Constants.DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
}
map.put(Constants.REGISTER_IP_KEY, hostToRegistry);
//attributes are stored by system context.
StaticContext.getSystemContext().putAll(attributes);
//重点!! 创建代理对象
ref = createProxy(map);
ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);
}
There is a lot of code here, I omitted some, we mainly look at the general process.
The first is to populate the fields of the ConsumerConfig via system variables or the dubbo.properties configuration file.
Various configurations are then collected and stored in a map for assembling URLs.
Finally create the proxy object.
createProxy()
Let's focus on how to create a proxy object:
//ReferenceConfig.java
private T createProxy(Map<String, String> map) {
URL tmpUrl = new URL("temp", "localhost", 0, map);
final boolean isJvmRefer;
//...省略
//本地引用
if (isJvmRefer) {
//...省略
} else {
//远程引用-直连
if (url != null && url.length() > 0) {
//...省略
} else { //远程引用-走注册中心
// 加载注册中心 url
List<URL> us = loadRegistries(false);
if (us != null && !us.isEmpty()) {
for (URL u : us) {
URL monitorUrl = loadMonitor(u);
if (monitorUrl != null) {
map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
// 添加 refer 参数到 url 中,并将 url 添加到 urls 中
urls.add(u.addParameterAndEncoded(Constants.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) {
//自适应 -> wrapper(filter(RegisterProtocol)).refer
// 调用 RegistryProtocol 的 refer 构建 Invoker 实例
invoker = refprotocol.refer(interfaceClass, urls.get(0));
} else {
//...省略
}
}
//生成代理类
// create service proxy
return (T) proxyFactory.getProxy(invoker);
}
The logic here is relatively simple. For the logical processing of the method of service invocation, we directly look at the most important refprotocol.refer(interfaceClass, urls.get(0))
method.
The input parameter of this method urls.get(0)
is the url of the registry, which should be parsed likeregistry://registry-host/org.apache.dubbo.registry.RegistryService?refer=URL.encode("consumer://consumer-host/com.foo.FooService?version=1.0.0")
Based on the extension point adaptive mechanism, identified by the registry:// protocol header of the URL, the RegistryProtocol.refer()
method query the provider URL based on the conditions in the refer parameter, such as:dubbo://service-host/com.foo.FooService?version=1.0.0
Then through the dubbo:// protocol header of the provider URL, the DubboProtocol.refer()
method get the provider reference.
RegistryProtocol.refer()
So let's look at the refer()
method next:
//RegistryProtocol.java
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
//获取注册中心对象
Registry registry = registryFactory.getRegistry(url);
if (RegistryService.class.equals(type)) {
return proxyFactory.getInvoker((T) registry, type, url);
}
//...省略
// 调用 doRefer 继续执行服务引用逻辑
return doRefer(cluster, registry, type, url);
}
keep followingdoRefer()
//RegistryProtocol.java
/**
*
* @param cluster
* @param registry 注册中心对象
* @param type
* @param url 注册中心url
* @param <T>
* @return
*/
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
//获得 RegistryDirectory 对象,即服务目录
//服务目录类似于注册中心,管理生产者ip、port等,实际上是一个invoker集合
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
directory.setRegistry(registry); //设置注册中心
directory.setProtocol(protocol); //设置服务提供者
// 创建订阅 URL 即 消费者url
Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
// 向注册中心注册自己(服务消费者)
//把自己写入zk中的 /dubbo/com.foo.BarService/consumers 目录
if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
&& url.getParameter(Constants.REGISTER_KEY, true)) {
registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
Constants.CHECK_KEY, String.valueOf(false)));
}
// 订阅 providers、configurators、routers 等节点数据
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
Constants.PROVIDERS_CATEGORY
+ "," + Constants.CONFIGURATORS_CATEGORY
+ "," + Constants.ROUTERS_CATEGORY));
// 一个注册中心可能有多个服务提供者,因此这里需要将多个服务提供者合并为一个
//MockClusterWrapper(FailoverCluster)
Invoker invoker = cluster.join(directory);
// 向本地注册表,注册消费者
ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
return invoker;
}
The main things here are:
- Connect to the registry
- Register yourself with the registry (service consumer)
- Subscribe to nodes such as registry providers
subscribe()
Let's look at the most important directory.subscribe()
method, the subscription:
//ZookeeperRegistry.java
/**
*
* @param url 消费者url consumer://
* @param listener RegistryDirectory
*/
@Override
protected void doSubscribe(final URL url, final NotifyListener listener) {
try {
// interface = *,即订阅全局
if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
//...省略
} else {
// interface = 特定接口,只有这个接口的子节点改变时,才触发回调
// 如:interface = com.lol.test.SayFacade
// Service 层下的所有 URL
List<URL> urls = new ArrayList<URL>();
//path是zk中生产者的目录:path -> /dubbo/com.foo.BarService/providers
for (String path : toCategoriesPath(url)) {
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
if (listeners == null) {
zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
listeners = zkListeners.get(url);
}
// 获得 ChildListener 对象
ChildListener zkListener = listeners.get(listener);
if (zkListener == null) {
listeners.putIfAbsent(listener, new ChildListener() {
@Override
public void childChanged(String parentPath, List<String> currentChilds) {
// 服务提供者url变更时,调用 `#notify(...)` 方法,通过监听器回调
ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
}
});
zkListener = listeners.get(listener);
}
zkClient.create(path, false);
//children 是 子节点 -> 服务提供者
List<String> children = zkClient.addChildListener(path, zkListener);
if (children != null) {
//urls 是真正的服务提供者url
urls.addAll(toUrlsWithEmpty(url, path, children));
}
}
//第一次订阅,全量通知,创建对应invoker
notify(url, listener, urls);
}
}
}
The general logic here is:
- Obtain the service provider urls of the corresponding service through zk
- Then by
RegistryDirectory
monitoring these urls, if there is a change, call thenotify()
method notify()
The first time the method is called on all urls
notify()
Next we focus on notify()
:
//AbstractRegistry.java
/**
*
* @param url 服务消费者url consumer://
* @param listener RegistryDirectory
* @param urls 服务提供者urls
*/
protected void notify(URL url, NotifyListener listener, List<URL> urls) {
//...省略
for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
String category = entry.getKey();
//categoryList 服务提供者url
List<URL> categoryList = entry.getValue();
categoryNotified.put(category, categoryList);
saveProperties(url);
listener.notify(categoryList);
}
}
keep following
//RegistryDirectory.java
/**
* 接收服务变更通知
* @param urls 服务提供者url集合
*/
@Override
public synchronized void notify(List<URL> urls) {
// 定义三个集合,分别用于存放服务提供者 url,路由 url,配置器 url
List<URL> invokerUrls = new ArrayList<URL>();
List<URL> routerUrls = new ArrayList<URL>();
List<URL> configuratorUrls = new ArrayList<URL>();
for (URL url : urls) {
String protocol = url.getProtocol();
// 获取 category 参数
String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
// 根据 category 参数将 url 分别放到不同的列表中
if (Constants.ROUTERS_CATEGORY.equals(category)
|| Constants.ROUTE_PROTOCOL.equals(protocol)) {
routerUrls.add(url);
} else if (Constants.CONFIGURATORS_CATEGORY.equals(category)
|| Constants.OVERRIDE_PROTOCOL.equals(protocol)) {
configuratorUrls.add(url);
} else if (Constants.PROVIDERS_CATEGORY.equals(category)) {
// 添加服务提供者 url
invokerUrls.add(url);
} else {
logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost());
}
}
// 将 url 转成 Configurator
if (configuratorUrls != null && !configuratorUrls.isEmpty()) {
this.configurators = toConfigurators(configuratorUrls);
}
// 将 url 转成 Router
if (routerUrls != null && !routerUrls.isEmpty()) {
List<Router> routers = toRouters(routerUrls);
if (routers != null) { // null - do nothing
setRouters(routers);
}
}
List<Configurator> localConfigurators = this.configurators; // local reference
// merge override parameters
this.overrideDirectoryUrl = directoryUrl;
if (localConfigurators != null && !localConfigurators.isEmpty()) {
for (Configurator configurator : localConfigurators) {
this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl);
}
}
//重点!! 刷新 Invoker 列表
refreshInvoker(invokerUrls);
}
Here we only need to look at the last line of code refreshInvoker(invokerUrls)
, its role is to refresh the invoker.
This approach is key to ensuring RegistryDirectory
that the set of service providers in the methodInvokerMap
registry changes as the registry changes.
refreshInvoker()
//RegistryDirectory.java
/**
*
* @param invokerUrls 服务提供者url
*/
private void refreshInvoker(List<URL> invokerUrls) {
// 如果invokerUrls 仅有一个元素,且 url 协议头为 empty,此时表示禁用所有服务
if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null
&& Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
// 设置 forbidden 为 true
this.forbidden = true;
this.methodInvokerMap = null;
// 销毁所有 Invoker
destroyAllInvokers();
} else {
this.forbidden = false;
Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap;
if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) {
// 添加缓存 url 到 invokerUrls 中
invokerUrls.addAll(this.cachedInvokerUrls);
} else {
this.cachedInvokerUrls = new HashSet<URL>();
// 缓存 invokerUrls
this.cachedInvokerUrls.addAll(invokerUrls);
}
if (invokerUrls.isEmpty()) {
return;
}
// 将 url 转成 Invoker
Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map
// 将 newUrlInvokerMap 转成方法名到 Invoker 列表的映射
Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap);
// state change
if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) {
logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls.toString()));
return;
}
// 合并多个组的 Invoker
this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
this.urlInvokerMap = newUrlInvokerMap;
try {
// 销毁无用 Invoker
destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
} catch (Exception e) {
logger.warn("destroyUnusedInvokers error. ", e);
}
}
}
The general logic here is:
- Determine whether to disable the service according to the protocol header
- Convert url to invoker
- Destroy the useless Invoker
First, when the url protocol header is empty://
, it means that all services are disabled and all Invokers will be destroyed.
Then convert the url to the invoker to get the mapping relationship of <url, Invoker>. Then further conversion is performed to get the mapping relationship of <methodName, Invoker list>.
After that, multiple groups of Invoker merge operations are performed, and the merge result is assigned to methodInvokerMap.
Finally destroy the useless Invoker to avoid the service consumer calling the service of the offline service
Invoker is the core model of Dubbo and represents an executable. On the service provider side, the Invoker is used to invoke the service provider class. On the service consumer side, Invoker is used to perform remote calls.
The key point at this time is the process of converting url to invoker, because dubbo remote call is made through invoker, so there must be a lot of important content in the process of conversion.
toInvokers()
Let's see next toInvokers(invokerUrls)
:
//RegistryDirectory.java
private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<String, Invoker<T>>();
if (urls == null || urls.isEmpty()) {
return newUrlInvokerMap;
}
Set<String> keys = new HashSet<String>();
// 获取服务消费端配置的协议
String queryProtocols = this.queryMap.get(Constants.PROTOCOL_KEY);
for (URL providerUrl : urls) {
if (queryProtocols != null && queryProtocols.length() > 0) {
boolean accept = false;
String[] acceptProtocols = queryProtocols.split(",");
// 检测服务提供者协议是否被服务消费者所支持
for (String acceptProtocol : acceptProtocols) {
if (providerUrl.getProtocol().equals(acceptProtocol)) {
accept = true;
break;
}
}
if (!accept) {
// 若服务消费者协议头不被消费者所支持,则忽略当前 providerUrl
continue;
}
}
// 忽略 empty 协议
if (Constants.EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) {
continue;
}
// 通过 SPI 检测服务端协议是否被消费端支持,不支持则抛出异常
if (!ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) {
logger.error(new IllegalStateException("Unsupported protocol..."));
continue;
}
// 合并 url
URL url = mergeUrl(providerUrl);
String key = url.toFullString();
if (keys.contains(key)) {
// 忽略重复 url
continue;
}
keys.add(key);
// 将本地 Invoker 缓存赋值给 localUrlInvokerMap
Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap;
// 获取与 url 对应的 Invoker
Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key);
// 缓存未命中
if (invoker == null) {
try {
boolean enabled = true;
if (url.hasParameter(Constants.DISABLED_KEY)) {
// 获取 disable 配置,取反,然后赋值给 enable 变量
enabled = !url.getParameter(Constants.DISABLED_KEY, false);
} else {
// 获取 enable 配置,并赋值给 enable 变量
enabled = url.getParameter(Constants.ENABLED_KEY, true);
}
if (enabled) {
// 调用 refer 获取 Invoker
invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);
}
} catch (Throwable t) {
logger.error("Failed to refer invoker for interface...");
}
if (invoker != null) {
// 缓存 Invoker 实例
newUrlInvokerMap.put(key, invoker);
}
// 缓存命中
} else {
// 将 invoker 存储到 newUrlInvokerMap 中
newUrlInvokerMap.put(key, invoker);
}
}
keys.clear();
return newUrlInvokerMap;
}
The logic here is simple:
-
First, the service provider url will be detected. If the configuration of the service consumer does not support the server's protocol, or the server url protocol header is empty, toInvokers will ignore the service provider url.
-
Merge the url, then access the cache and try to get the invoker corresponding to the url.
-
If the cache hits, directly store the Invoker in newUrlInvokerMap.
-
If it is not hit, a new Invoker needs to be created.
DubboProtocol.refer()
invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);
This code is the code to create the invoker, which is obtained through adaptation DubboProtocol
, so let's take a deeper look DubboProtocol.refer()
:
//DubboProtocol.java
//客户端实例
private final ExchangeClient[] clients;
private final AtomicPositiveInteger index = new AtomicPositiveInteger();
private final String version;
private final ReentrantLock destroyLock = new ReentrantLock();
private final Set<Invoker<?>> invokers;
/**
* url 服务提供者url
*/
public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
optimizeSerialization(url);
// 创建 DubboInvoker
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
The code here is very simple, just a new Invoker. The point is to DubboInvoker
populate the properties of .
Let's first look at the ExchangeClient[] clients
property, which is a collection of client instances, obtained by getClients(url)
methods.
ExchangeClient does not actually have the ability to communicate, it needs to communicate based on the lower-level client instance. Such as NettyClient, MinaClient, etc. By default, Dubbo uses NettyClient for communication. Next, we briefly look at the logic of the getClients
method .
getClients()
//DubboProtocol.java
private ExchangeClient[] getClients(URL url) {
// 是否共享连接
boolean service_share_connect = false;
// 获取连接数,默认为0,表示未配置
int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
// 如果未配置 connections,则共享连接
if (connections == 0) {
service_share_connect = true;
connections = 1;
}
ExchangeClient[] clients = new ExchangeClient[connections];
for (int i = 0; i < clients.length; i++) {
if (service_share_connect) {
// 获取共享客户端
clients[i] = getSharedClient(url);
} else {
// 初始化新的客户端
clients[i] = initClient(url);
}
}
return clients;
}
Here it connections
is decided whether to obtain a shared client or create a new client instance according to the number. By default, a shared client instance is used. getSharedClient
The method also calls the initClient
method , so let's take a look at the two methods together.
//DubboProtocol.java
private ExchangeClient getSharedClient(URL url) {
String key = url.getAddress();
// 获取带有“引用计数”功能的 ExchangeClient
ReferenceCountExchangeClient client = referenceClientMap.get(key);
if (client != null) {
if (!client.isClosed()) {
// 增加引用计数
client.incrementAndGetCount();
return client;
} else {
referenceClientMap.remove(key);
}
}
locks.putIfAbsent(key, new Object());
synchronized (locks.get(key)) {
if (referenceClientMap.containsKey(key)) {
return referenceClientMap.get(key);
}
// 创建 ExchangeClient 客户端
ExchangeClient exchangeClient = initClient(url);
// 将 ExchangeClient 实例传给 ReferenceCountExchangeClient,这里使用了装饰模式
client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap);
referenceClientMap.put(key, client);
ghostClientMap.remove(key);
locks.remove(key);
return client;
}
}
Here, first try to obtain a shared instance with reference counting function, if not, initClient(url)
create a new one.
initClient()
//DubboProtocol.java
private ExchangeClient initClient(URL url) {
// 获取客户端类型,默认为 netty4
String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));
// 添加编解码和心跳包参数到 url 中
url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
// 检测客户端类型是否存在,不存在则抛出异常
if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
throw new RpcException("Unsupported client type: ...");
}
ExchangeClient client;
try {
// 获取 lazy 配置,并根据配置值决定创建的客户端类型
if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
// 创建懒加载 ExchangeClient 实例
client = new LazyConnectExchangeClient(url, requestHandler);
} else {
// 创建普通 ExchangeClient 实例
client = Exchangers.connect(url, requestHandler);
}
} catch (RemotingException e) {
throw new RpcException("Fail to create remoting client for service...");
}
return client;
}
initClient
The method first obtains the client type configured by the user, which is netty4 by default. Then check whether the client type configured by the user exists, and throw an exception if it does not exist. Finally, decide what type of client to create based on the lazy configuration.
Next, let's look at the method of creating a clientExchangers.connect(url, requestHandler)
//Exchangers.java
public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handler == null) {
throw new IllegalArgumentException("handler == null");
}
url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
// 获取 Exchanger 实例,默认为 HeaderExchangeClient
return getExchanger(url).connect(url, handler);
}
Here through adaptation, callHeaderExchanger.connect()
//HeaderExchanger.java
public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
// 这里包含了多个调用,分别如下:
// 1. 创建 HeaderExchangeHandler 对象
// 2. 创建 DecodeHandler 对象
// 3. 通过 Transporters 构建 Client 实例
// 4. 创建 HeaderExchangeClient 对象
return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
}
There are many calls here. Let's focus on the connect method of Transporters. as follows:
//Transporters.java
public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
ChannelHandler handler;
if (handlers == null || handlers.length == 0) {
handler = new ChannelHandlerAdapter();
} else if (handlers.length == 1) {
handler = handlers[0];
} else {
// 如果 handler 数量大于1,则创建一个 ChannelHandler 分发器
handler = new ChannelHandlerDispatcher(handlers);
}
// 获取 Transporter 自适应拓展类,并调用 connect 方法生成 Client 实例
return getTransporter().connect(url, handler);
}
getTransporter()
The method returns an adaptive extension class, which loads the specified Transporter implementation class according to the client type at runtime. If the user does not configure the client type, NettyTransporter is loaded by default, and the connect method of this class is called. as follows:
connect()
//NettyTransporter.java
public Client connect(URL url, ChannelHandler listener) throws RemotingException {
// 创建 NettyClient 对象
return new NettyClient(url, listener);
}
I won't continue to follow it here. The next step is to build a Netty client through the API provided by Netty. If you are interested, you can take a look for yourself.
createProxy()
At this point, the Invoker of the service provider has been created, and the next step is to create the proxy object.
That is the ReferenceConfig.createProxy()
method, so I won't go into details here.
总结
After the proxy object is created, the service startup phase of the dubbo service reference has been completed. Let's review what we have done:
- Connect to the registry
- Register yourself with the registry (service consumer)
- Subscribe to the registry service provider and get the service provider url
- Create Invoker
- Establish communication, connect netty
- Create proxy object
After the service is started, the next step is the remote invocation phase, which we will analyze in detail in the next article.
Finally, simply draw a flowchart for easy understanding.
Reference:
Dubbo official website