🌟 欢迎关注个人技术公众号:野区杰西
前言
上篇文章已经讲完了 Dubbo
在服务提供者的服务暴露过程,这一篇主要是将服务的引用过程。
服务引用的时机点主要有两个
- 依赖
Spring
容器的afterPropertiesSet
方法进行调用 - 在
ReferenceBean
对应的服务被注入到其他类中时引用
而在引用的过程中,主要做几个事情:
- 配置检查与收集工作,收集的信息包括了服务调用的方式(引用本地 (
JVM
) 服务| 直连方式引用远程服务 | 通过注册中心引用远程服务) - 决定方式后生成一个或多个
Invoker
(由于集群的原因),此时Invoker
已经有了调用的能力 - 通过
ProxyFactory
对Invoker
生成代理类,并让代理类去调用Invoker
逻辑。避免了Dubbo
框架代码对业务代码的侵入
好,大致上流程是非常清晰的! Let s Go!
主体流程讲解
我们从 ReferenceBean
的 afterPropertiesSet
开始。为了不贴上大量的代码,我只讲解重要的地方。
afterPropertiesSet
方法主要是将 application
、module
、Registry
以及 Monitor
的配置文件信息封装配置类。
//从 Spring 获取
if (getApplication() == null
&& (getConsumer() == null || getConsumer().getApplication() == null)) {
Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ApplicationConfig.class, false, false);
if (applicationConfigMap != null && applicationConfigMap.size() > 0) {
ApplicationConfig applicationConfig = null;
for (ApplicationConfig config : applicationConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (applicationConfig != null) {
throw new IllegalStateException("Duplicate application configs: " + applicationConfig + " and " + config);
}
applicationConfig = config;
}
}
// 设置 application 的配置信息
if (applicationConfig != null) {
setApplication(applicationConfig);
}
}
}
//... Module、Registry、Monitor 同理
设置完后,然后判断是否懒加载,最后调用 getObject
方法。
public Object getObject() throws Exception {
return get();
}
public synchronized T get() {
// 判断是否初始的过程中被销毁
if (destroyed) {
throw new IllegalStateException("Already destroyed!");
}
if (ref == null) {
//调用 init
init();
}
return ref;
}
init
方法主要是对配置进行检查和处理,以保证配置的正确性。代码有点长,可以细看注释
private void init() {
//判断是否当前有线程正在初始化,如果有就停止
if (initialized) {
return;
}
initialized = true;
//判断接口名称
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!");
}
// 检测 consumer 变量是否为空,为空则创建
checkDefault();
appendProperties(this);
if (getGeneric() == null && getConsumer() != null) {
setGeneric(getConsumer().getGeneric());
}
if (ProtocolUtils.isGeneric(getGeneric())) {
interfaceClass = GenericService.class;
} else {
try {
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
checkInterfaceAndMethods(interfaceClass, methods);
}
String resolve = System.getProperty(interfaceName);
String resolveFile = null;
if (resolve == null || resolve.length() == 0) {
resolveFile = System.getProperty("dubbo.resolve.file");
if (resolveFile == null || resolveFile.length() == 0) {
File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");
if (userResolveFile.exists()) {
resolveFile = userResolveFile.getAbsolutePath();
}
}
if (resolveFile != null && resolveFile.length() > 0) {
Properties properties = new Properties();
FileInputStream fis = null;
try {
fis = new FileInputStream(new File(resolveFile));
properties.load(fis);
} catch (IOException e) {
throw new IllegalStateException("Unload " + resolveFile + ", cause: " + e.getMessage(), e);
} finally {
try {
if (null != fis) fis.close();
} catch (IOException e) {
logger.warn(e.getMessage(), e);
}
}
resolve = properties.getProperty(interfaceName);
}
}
if (resolve != null && resolve.length() > 0) {
url = resolve;
if (logger.isWarnEnabled()) {
if (resolveFile != null) {
logger.warn("Using default dubbo resolve file " + resolveFile + " replace " + interfaceName + "" + resolve + " to p2p invoke remote service.");
} else {
logger.warn("Using -D" + interfaceName + "=" + resolve + " to p2p invoke remote service.");
}
}
}
//下面都是设置各种配置
if (consumer != null) {
if (application == null) {
application = consumer.getApplication();
}
if (module == null) {
module = consumer.getModule();
}
if (registries == null) {
registries = consumer.getRegistries();
}
if (monitor == null) {
monitor = consumer.getMonitor();
}
}
if (module != null) {
if (registries == null) {
registries = module.getRegistries();
}
if (monitor == null) {
monitor = module.getMonitor();
}
}
if (application != null) {
if (registries == null) {
registries = application.getRegistries();
}
if (monitor == null) {
monitor = application.getMonitor();
}
}
//
checkApplication();
//检查根服务
checkStub(interfaceClass);
//检查
checkMock(interfaceClass);
//
Map<String, String> map = new HashMap<String, String>();
Map<Object, Object> attributes = new HashMap<Object, Object>();
//设置 SIDE_KEY、Dubbo 版本、时间戳
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()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
//是否是泛化服务
if (!isGeneric()) {
//
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put("revision", revision);
}
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
if (methods.length == 0) {
logger.warn("NO method found in service interface " + interfaceClass.getName());
map.put("methods", Constants.ANY_VALUE);
} else {
map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
//设置接口名称,application、module等配置信息
map.put(Constants.INTERFACE_KEY, interfaceName);
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, consumer, Constants.DEFAULT_KEY);
appendParameters(map, this);
String prefix = StringUtils.getServiceKey(map);
if (methods != null && !methods.isEmpty()) {
for (MethodConfig method : methods) {
appendParameters(map, method, method.getName());
String retryKey = method.getName() + ".retry";
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
if ("false".equals(retryValue)) {
map.put(method.getName() + ".retries", "0");
}
}
appendAttributes(attributes, method, prefix + "." + method.getName());
checkAndConvertImplicitConfig(method, map, attributes);
}
}
//从环境获取 Registry 的信息
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);
//在系统环境上下文保存上面设置的信息
StaticContext.getSystemContext().putAll(attributes);
//开始创建代理
ref = createProxy(map);
//封装成 ConsumerModel,全局保存
ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);
}
createProxy
方法在翻译上就是创建代理,实际上还有很多附加的功能意义。我们来看看代码
private T createProxy(Map<String, String> map) {
URL tmpUrl = new URL("temp", "localhost", 0, map);
final boolean isJvmRefer;
//判断配置文件是否配置了 injvm
// 如果是 null
if (isInjvm() == null) {
// 如果 url 被指定 说明不是本地引用
if (url != null && url.length() > 0) {
isJvmRefer = false;
} else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
//默认是本地引用
isJvmRefer = true;
} else {
isJvmRefer = false;
}
} else {
isJvmRefer = isInjvm().booleanValue();
}
if (isJvmRefer) {
//配置本地引用的 URL
URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
//生成 invoker
invoker = refprotocol.refer(interfaceClass, url);
if (logger.isInfoEnabled()) {
logger.info("Using injvm service " + interfaceClass.getName());
}
} else {
//这时候判断是否 url 不为空,如果不为空有可能是点对点的连接 or 注册中心的地址
if (url != null && url.length() > 0) {
String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);
if (us != null && us.length > 0) {
for (String u : us) {
URL url = URL.valueOf(u);
if (url.getPath() == null || url.getPath().length() == 0) {
url = url.setPath(interfaceName);
}
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
} else {
urls.add(ClusterUtils.mergeUrl(url, map));
}
}
}
} 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()));
}
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) {
invoker = refprotocol.refer(interfaceClass, urls.get(0));
} else {
//有多个 URL 需要生成 多个 Invoker
//而且如果 url 中是 registry 协议的,保留最后一个
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
for (URL url : urls) {
// 通过 refprotocol 调用 refer 构建 Invoker,refprotocol 会在运行时
// 根据 url 协议头加载指定的 Protocol 实例,并调用实例的 refer 方法
invokers.add(refprotocol.refer(interfaceClass, url));
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
registryURL = url;
}
}
// registryURL 不为空的话
if (registryURL != null) {
// 如果注册中心链接不为空,则将使用 AvailableCluster
URL u = registryURL.addParameterIfAbsent(Constants.CLUSTER_KEY, AvailableCluster.NAME);
// 创建 StaticDirectory 实例,并由 Cluster 对多个 Invoker 进行合并
invoker = cluster.join(new StaticDirectory(u, invokers));
} else {
invoker = cluster.join(new StaticDirectory(invokers));
}
}
}
Boolean c = check;
if (c == null && consumer != null) {
c = consumer.isCheck();
}
if (c == null) {
c = true; // default true
}
// invoker 可用性检查
if (c && !invoker.isAvailable()) {
// make it possible for consumer to retry later if provider is temporarily unavailable
initialized = false;
throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
}
if (logger.isInfoEnabled()) {
logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
}
// 将 Invoker 创建代理类
return (T) proxyFactory.getProxy(invoker);
}
创建 invoker
Invoker
是 Dubbo
的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体。所以我在这里最常见的协议 RegistryProtocol
和 DubboProtocol
来看看如何创建 Invoker
。
我们先看 DubboProtocol
类的 refer
方法
public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
//优化序列化
optimizeSerialization(url);
// 创建 rpc invoker
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
接下来我们看 getClients
方法。getClients
的作用在于
private ExchangeClient[] getClients(URL url) {
// 是否是共享连接
boolean service_share_connect = false;
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;
}
上面代码主要是判断是否是共享客户端。如果 connections
为 0 说明是共享。我们先看 getSharedClient
的代码。
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;
}
}
上面主要是通过缓存来看看 ExchangeClient 实例,并将实例传给 ReferenceCountExchangeClient 构造方法创建一个带有引用计数功能的 ExchangeClient
实例。
无论是共享还是非共享,都会调用 initClient
方法。initClient
方法比较重要。因为这里涉及了客户端的远程调用。
private ExchangeClient initClient(URL url) {
// 客户端设置
String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));
url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
// 心跳设置,默认是允许的
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
// BIO is not allowed since it has severe performance issue.
if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
throw new RpcException("Unsupported client type: " + str + "," +
" supported client type is " + StringUtils.join(ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(), " "));
}
ExchangeClient client;
try {
// 查看是否延迟加载
if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
client = new LazyConnectExchangeClient(url, requestHandler);
} else {
client = Exchangers.connect(url, requestHandler);
}
} catch (RemotingException e) {
throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e);
}
return client;
}
上面的 Exchangers.connnect
中的 Exchangers
不是直接负责连接的,它是一个包装器,负责屏蔽底层的连接客户端。我们快速看一下 Exchangers
public static Exchanger getExchanger(URL url) {
//从 url 获取对应的值,利用 Adaptive 机制获取对应的 Exchangers
String type = url.getParameter(Constants.EXCHANGER_KEY, Constants.DEFAULT_EXCHANGER);
return getExchanger(type);
}
Exchangers
默认只有 HeaderExchanger
。HeaderExchanger
主要是封装了一个 Client
以及实现心跳机制的类。我们继续看看其 connect
方法。
public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
}
Transporters
在 Dubbo
也非常重要,它相当于是最底层与其他服务进行通信的组件。按照官档来说主要有 netty
和 mina
。 我们来看看其 connect
方法。
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 = new ChannelHandlerDispatcher(handlers);
}
//通过 spi 机制获取到对应的实现
return getTransporter().connect(url, handler);
}
如果学过 netty
的同学的话,可能很快可以理解 handler
相当于往 Transporter
加一个流程处理,这样子实现可以非常灵活的添加自定义处理。注意,这里的 ChannelHandler
是 Dubbo
自己实现了一遍,所以可以说是对于其他通信组件同样适用。
结语
Dubbo 服务引用流程还是非常清晰的。
下节继续讲 Dubbo 的调用过程。
下期见!