1.什么是分布式系统?
1>.分布式系统是若干个独立计算机的集合,这些计算机对于用户来说就像单个相关系统; --<<分布式系统原理与范式>>
2>.分布式系统是建立在网络之上的软件系统;
3>.当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率.此时,用于提高机器利用率的资源调度和治理中心(SOA : Service Oriented
Architecture)是关键;
2.Dubbo简介
1>.Apache Dubbo(incubating)是一款高性能的,轻量级的开源Java RPC框架,
它提供了三大核心能力;面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现;
2>.Apache Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,
同时他也是一个SOA服务治理方案;
3.Dubbo能做什么?
1>.透明化的远程方法调用,就像调用本地方法一样调用远程方法,只需要简单配置,没有任何API侵入;
2>.软负载均衡及其容错机制,可在内网替代F5等硬件负载均衡器,降低成本,减少单点;
软负载均衡:在分布式环境中,为了保证高可用性,通常同一个应用或同一个服务的提供方都会部署多份,达到对等服务(接受请求的几率是一样的).而消费者就须要在这些对等的服务器中选择一个来执行相关的业务逻辑;
3>.服务自动注册与发现,不再需要将服务提供方地址写死,注册中心基于接口名查询服务提供者的ip地址,并且能够平滑添加或者删除服务提供者;
4>.dubbo采用全spring配置方式,透明化接入应用,对应用没有任何API侵入,只需要用spring加载dubbo的配置即可;
3.Dubbo的核心组件
1>.如图:
2>.说明:
①.Registry: 注册中心(如zookeeper,redis);注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者;
②.Provider: 服务提供方; 暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务;
③.Consumer: 服务消费方; 调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用;
④.Monitor: 监控中心,统计服务的调用次数和调用时间;服务消费者和提供者会在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心;
⑤.Container: 提供服务的容器;
4.Dubbo运行流程
1>.如图:
2>.流程说明:
①.服务容器container负责启动,加载,运行服务提供者;
②.服务提供者provider在启动时,向注册中心注册自己要提供的服务,同时dubbo会生成对应的代理对象,这些代理对象会监听网络请求(看看是否有网络请求过来);
③.服务消费者consumer在启动时,向注册中心订阅自己所需要的服务,同时dubbo会生成对应的代理对象;
④.注册中心返回服务提供者注册的服务地址列表给消费者,如果注册中心里面的服务有变更,注册中心将基于长连接推送变更数据给消费者;
⑤.服务消费者通过代理对象从服务提供者注册的服务地址列表中基于软负载均衡算法
选取一台服务提供者进行rpc远程调用,如果调用失败,再选取另一台调用;
⑥.服务消费者和提供者在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心;
5.Dubbo原理
5.1.Dubbo的设计架构
1>.如图:
2>.各层说明:
①.Service层:provider和consumer及相关接口,留给开发人员自己去实现的!
②.Config配置层:对外配置接口,以ServiceConfig,ReferenceConfig为中心,可以直接初始化配置类,也可以通过spring解析配置生成配置类;
③.Proxy服务代理层:服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton,以ServiceProxy为中心,扩展接口为ProxyFactory;
④.Registry注册中心层:封装服务地址的注册与发现,以服务URL 为中心,扩展接口为RegistryFactory, Registry, RegistryService;
⑤.Cluster路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker 为中心,扩展接口为Cluster, Directory, Router, LoadBalance;
⑥.Monitor监控层:RPC 调用次数和调用时间监控,以Statistics 为中心,扩展接口为MonitorFactory, Monitor, MonitorService;
⑦.Protocol远程调用层:封装 RPC 调用,以Invocation, Result 为中心,扩展接口为Protocol, Invoker, Exporter;
⑧.Exchange信息交换层:封装请求响应模式,同步转异步,以Request,Response为中心,扩展接口为Exchanger,ExchangeChannel,ExchangeClient,ExchangeServer;
⑨.Transport网络传输层:抽象mina和netty为统一接口,以Message 为中心,扩展接口为Channel,Transporter,Client,Server,Codec;
⑩.Serialize数据序列化层:可复用的一些工具,扩展接口Serialization,ObjectInput,ObjectOutput,ThreadPool;
5.2.Dubbo标签解析流程
1>.如图:
2>.说明:
①.当spring容器启动时候,会加载配置文件,然后dubbo的名称空间处理器DubboNameSpaceHandler对象就会创建出dubbo的很多标签解析器(DubboBeanDefinitionParser);
②.标签解析器(DubboBeanDefinitionParser)会挨个解析dubbo的每一个标签,由于每一个标签都有自己对应的xxxcofig组件对象(例如,"application"标签对应着ApplicationConfig对象),所以会在对应的xxxcofig组件对象中(生成)装配一个对应的config对象;但是如果是"service"标签或者"reference"标签,则会在ServiceBean或者ReferenceBean组件对象中(生成)装配一个ServiceBean或者ReferenceBean对象;
注意:解析之后(生成)装配的对象会注入到spring容器中,由spring进行管理;
5.3.服务暴露流程
1>.如图:
2>.说明:
①.当spring容器启动时候,会加载配置文件,然后dubbo的解析器会解析配置文件中dubbo标签,由于配置文件中配置了"service"标签指定要暴露的服务,因此会在对应的ServiceBean组件对象中一个对应的ServiceBean对象;
②.当ServiceBean组件对象创建(/装配)完对象,所有的属性初始化完成之后会回调InitializingBean类中的afterpropertiesset()方法,将读取到的服务提供者service标签中的属性设置到ServiceBean对象中;
③.当ServiceBean组件对象将服务提供者的信息(/属性)都初始化完成,IOC容器被刷新之后会回调onApplicationEvent()方法调用ServiceConfig对象中的export()方法暴露服务(封装服务提供者的信息);
④.在ServiceConfig对象的export()方法中,调用doExport()方法执行暴露服务的业务逻辑.其中在方法中再通过doExportUrls()方法里面的doExportUrlsForProtocal()方法将用户配置的通信协议,注册中心地址信息,服务接口,服务接口的实现类等信息利用代理工厂包装成一个Invoker对象(wrapperInvoker),然后利用[用户配置的协议对应的protocol对象(例如dubboProtocol)]
将invoker对象暴露出去;
⑤.在协议对应的protocol对象(如:RegistryProtocol)
的在export()方法里面,先调用doLocalExport()方法做本地暴露,然后再调用ProviderConsumerRegTable.registerProvider()方法将服务提供者信息(包括服务提供者信息包装的invoker,注册中心地址信息,服务提供者地址信息等)注册到ProviderConsumerRegTable中,最后调用RegistryProtocol.register()方法根据注册中心地址信息,服务提供者地址信息将服务提供者信息注册到注册中心;
⑥.在doLocalExport()方法中:I).先根据服务提供者信息包装的invoker创建一个invoker执行器(InvokerDelegete);
II).然后再调用[用户配置的协议对应的protocol对象(如:dubboProtocol)]
的export(创建好的Invoker执行器)方法来初始化一个exporter对象实例,放入容器中,方便下次使用;
III).在[用户配置的协议对应的protocol对象(如:dubboProtocol)]
的export(创建好的Invoker执行器)方法里面,先将传入的Invoker执行器的信息包装成一个DubboExporter对象,然后再调用openSever()方法,根据Invoker执行器中的服务提供者信息(服务提供者地址信息
)开启/创建服务器(NettyServer),监听指定的端口,等待客户端的请求;⑦.在ProviderConsumerRegTable类的registerProvider()方法中:
I).先根据传入的
服务提供者信息(包括服务提供者信息包装的invoker,注册中心地址信息,服务提供者地址信息等)
创建一个ProviderInvokerWrapper执行器对象;
II).再将这个ProviderInvokerWrapper执行器对象保存到一个set<>集合中,这个set集合是根据服务名称从一个全局的ConcurrentHashMap容器中获取的;
III).然后再把这个set<>集合保存到全局的ConcurrentHashMap容器中,将服务提供者信息缓存起来,当客户端要远程调用服务提供者的服务,就可以根据url地址从ProviderConsumerRegTable的ConcurrentHashMap容器中找到对应的服务的执行器,然后就可以调用了;
5.3.1.源码分析
1>.spring容器启动,会加载BeanDefinitionParser类来解析配置文件,dubbo配置文件的加载依赖实现类DubboBeanDefinitionParser,DubboBeanDefinitionParser解析器会将配置文件中不同的标签解析成不同的xxxConfig对象,而<dubbo:service/> 、<dubbo:reference/>标签分别解析成serviceBean和referenceBean对象
2>.serviceBean对象实现了InitializingBean和ApplicationListener接口,在afterPropertiesSet()方法中主要将配置文件中的属性依次配置到对应的bean中,在Spring上下文刷新事件后会回调onApplicationEvent()方法
3>.调用父类ServiceConfig对象的export()方法,检查延迟和是否导出,最后执行doExportUrls()方法
4>.doExportUrls()方法首先是通过loadRegistries()方法加载注册中心链接,然后再遍历 ProtocolConfig集合导出每个服务.并在导出服务的过程中,将服务注册到注册中心
- loadRegistries()方法的逻辑
protected List<URL> loadRegistries(boolean provider) {
// check && override if necessary
List<URL> registryList = new ArrayList<URL>();
if (CollectionUtils.isNotEmpty(registries)) {
for (RegistryConfig config : registries) {
String address = config.getAddress();
if (StringUtils.isEmpty(address)) {
// 若 address 为空,则将其设为 0.0.0.0
address = ANYHOST_VALUE;
}
if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
Map<String, String> map = new HashMap<String, String>();
appendParameters(map, application);
appendParameters(map, config);
map.put(PATH_KEY, RegistryService.class.getName());
appendRuntimeParameters(map);
if (!map.containsKey(PROTOCOL_KEY)) {
map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);
}
// 解析得到 URL 列表,address 可能包含多个注册中心 ip
// 因此解析得到的是一个 URL 列表
List<URL> urls = UrlUtils.parseURLs(address, map);
for (URL url : urls) {
// 将 URL 协议头设置为 registry
url = URLBuilder.from(url)
.addParameter(REGISTRY_KEY, url.getProtocol())
.setProtocol(REGISTRY_PROTOCOL)
.build();
// 通过判断条件,决定是否添加 url到registryList中,条件如下
// (服务提供者 && register = true 或 null)
// || (非服务提供者 && subscribe = true 或 null)
if ((provider && url.getParameter(REGISTER_KEY, true))
|| (!provider && url.getParameter(SUBSCRIBE_KEY, true))) {
registryList.add(url);
}
}
}
}
}
return registryList;
}
- doExportUrlsFor1Protocol()方法主要将版本、时间戳、方法名以及各种配置对象的字段信息放入到map 中,map 中的内容将作为 URL 的查询字符串.构建好 map 后,紧接着是获取上下文路径、主机名以及端口号等信息,最后将 map 和主机名等数据传给 URL 构造方法创建 URL 对象
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
String name = protocolConfig.getName();
if (StringUtils.isEmpty(name)) {
name = DUBBO;
}
Map<String, String> map = new HashMap<String, String>();
map.put(SIDE_KEY, PROVIDER_SIDE);
appendRuntimeParameters(map);
appendParameters(map, metrics);
appendParameters(map, application);
appendParameters(map, module);
// remove 'default.' prefix for configs from ProviderConfig
// appendParameters(map, provider, Constants.DEFAULT_KEY);
appendParameters(map, provider);
appendParameters(map, protocolConfig);
appendParameters(map, this);
if (CollectionUtils.isNotEmpty(methods)) {
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");
}
}
List<ArgumentConfig> arguments = method.getArguments();
if (CollectionUtils.isNotEmpty(arguments)) {
for (ArgumentConfig argument : arguments) {
// convert argument type
if (argument.getType() != null && argument.getType().length() > 0) {
Method[] methods = interfaceClass.getMethods();
// visit all methods
if (methods != null && methods.length > 0) {
for (int i = 0; i < methods.length; i++) {
String methodName = methods[i].getName();
// target the method, and get its signature
if (methodName.equals(method.getName())) {
Class<?>[] argtypes = methods[i].getParameterTypes();
// one callback in the method
if (argument.getIndex() != -1) {
if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
appendParameters(map, argument, method.getName() + "." + argument.getIndex());
} else {
throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
}
} else {
// multiple callbacks in the method
for (int j = 0; j < argtypes.length; j++) {
Class<?> argclazz = argtypes[j];
if (argclazz.getName().equals(argument.getType())) {
appendParameters(map, argument, method.getName() + "." + j);
if (argument.getIndex() != -1 && argument.getIndex() != j) {
throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
}
}
}
}
}
}
}
} else if (argument.getIndex() != -1) {
appendParameters(map, argument, method.getName() + "." + argument.getIndex());
} else {
throw new IllegalArgumentException("Argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
}
}
}
} // end of methods for
}
if (ProtocolUtils.isGeneric(generic)) {
map.put(GENERIC_KEY, generic);
map.put(METHODS_KEY, ANY_VALUE);
} else {
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put(REVISION_KEY, revision);
}
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
if (methods.length == 0) {
logger.warn("No method found in service interface " + interfaceClass.getName());
map.put(METHODS_KEY, ANY_VALUE);
} else {
map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
if (!ConfigUtils.isEmpty(token)) {
if (ConfigUtils.isDefault(token)) {
map.put(TOKEN_KEY, UUID.randomUUID().toString());
} else {
map.put(TOKEN_KEY, token);
}
}
// 导出服务
String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
Integer port = this.findConfigedPorts(protocolConfig, name, map);
URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
String scope = url.getParameter(SCOPE_KEY);
// scope=none 不做处理
if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
// scope !=remote 导出本地服务
if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
exportLocal(url);
}
// scope!=local 导出远程服务
if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
if (!isOnlyInJvm() && logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
if (CollectionUtils.isNotEmpty(registryURLs)) {
for (URL registryURL : registryURLs) {
//if protocol is only injvm ,not register
if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
continue;
}
url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
}
if (logger.isInfoEnabled()) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
}
// For providers, this is used to enable custom proxy to generate invoker
String proxy = url.getParameter(PROXY_KEY);
if (StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter(PROXY_KEY, proxy);
}
//为服务提供类(ref)生成Invoker
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
//导出服务,生成Exporter
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
} else {
//不存在注册中心,仅导出服务
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
/**
* @since 2.7.0
* ServiceData Store
*/
MetadataReportService metadataReportService = null;
if ((metadataReportService = getMetadataReportService()) != null) {
metadataReportService.publishProvider(url);
}
}
}
this.urls.add(url);
}
无论是导出服务到本地还是远程都需要使用Invoker对象,Invoker是ProxyFactory 代理工厂创建的对象,invoke封装了调用实体.然后根据scope参数,决定导出服务到本地还是导出到远程.
5>.我们重点讨论到导出服务到远程,其中包含服务导出和服务注册两个过程
- RegistryProtocol的export()方法
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//获取注册中心 URL
URL registryUrl = getRegistryUrl(originInvoker);
// url to export locally
URL providerUrl = getProviderUrl(originInvoker);
// Subscribe the override data
// FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call
// the same service. Because the subscribed is cached key with the name of the service, it causes the
// subscription information to cover.
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
//export invoker 导出服务
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
// 根据 URL 加载 Registry 实现类
final Registry registry = getRegistry(originInvoker);
final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
//注册服务
ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
registryUrl, registeredProviderUrl);
//to judge if we need to delay publish
boolean register = registeredProviderUrl.getParameter("register", true);
if (register) {
register(registryUrl, registeredProviderUrl);
providerInvokerWrapper.setReg(true);
}
// Deprecated! Subscribe to override rules in 2.6.x or before.
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
exporter.setRegisterUrl(registeredProviderUrl);
exporter.setSubscribeUrl(overrideSubscribeUrl);
//Ensure that a new exporter instance is returned every time export
return new DestroyableExporter<>(exporter);
}
- 在doLocalExport()方法中导出服务,其中包含创建DubboExporter 和openServer()方法
- DubboProtocol的export()方法
- openServer()方法中通过createServer方法创建服务实例,dubbo服务实例默认使用NettyServer
6>.服务注册调用getRegistry()方法,创建连接注册中心,调用register()方法注册服务,如果是zookeeper做为注册中心,调用zookeeper客户端创建服务节点,服务注册成功,在ZooInspector工具上可以查看注册服务的节点数据
5.4.服务引用流程
1>.如图:
2>.说明:
①.当spring容器启动时候,会加载配置文件,然后dubbo的解析器会解析配置文件中dubbo标签,由于配置文件中配置了"reference"标签指定要引用的服务,因此会在对应的ReferenceBean组件对象中一个对应的ReferenceBean对象;
②.当ReferenceBean组件对象创建(/装配)完对象,所有的属性初始化完成之后会回调InitializingBean类中的afterpropertiesset()方法,将读取到的服务提供者"reference"标签中的属性设置到ReferenceBean对象中;
③.当在项目中通过"@Autowired"注解自动注入服务提供者暴露的服务(接口)时,spring就要从容器中查找对应的对象实例,由于ReferenceBean组件对象实现了FactoryBean接口,在内部他会调用ReferenceBean对象中的getObject()方法通过调用ReferenceConfig类的get()方法获取或者初始化对象引用:I).
如果是初始化操作,那么就会通过ReferenceConfig类的createProxy()方法去创建一个对应的远程服务接口代理对象;
④.在createProxy()方法的内部:
I).先根据配置文件中配置的信息(
包括通信协议,注册中心地址,要调用的服务提供者接口等信息
),利用Protocol协议对象(如:RegistryProtocol)
的远程引用方法refer()从注册中心获取要引用的远程服务接口信息.在refer()中,通过doRefer()方法根据注册中心地址,远程引用的服务类型(即接口信息),服务提供者地址等信息
去注册中心订阅服务提供者提供(暴露)的服务;
II).同时调用Protocol协议对象(如:DubboProtocol)
的远程引用方法refer()根据要远程引用的服务类型(即接口信息),从注册中心获取到的远程引用的服务的地址信息等信息封装一个DubboInvoker执行器对象;在创建DubboInvoker执行器对象的过程中需要通过一个getClients(从注册中心获取到的远程引用的服务的地址信息
)方法获取一个远程服务引用的客户端,通过这个客户端(客户端内部使用NettyClient进行通信)
去和远程服务建立连接,进行通信;然后对这个DubboInvoker执行器对象进行多次包装,返回,最后在RegistryProtocol协议对象的doRefer()方法中被获取到,之后将包装之后的Invoker对象,远程注册中心里面对应的服务(接口)信息,远程引用的服务(接口)地址信息等信息
注册到ProviderConsumerRegTable中,最终将包装之后的Invoker对象返回;⑤.最后回到ReferenceConfig类的createProxy()方法,拿到Invoker对象,通过ProxyFactory.getProxy(Invoker)方法创建远程服务接口代理对象,最终把这个远程服务接口代理对象赋值给对象引用;这个代理对象中封装了要调用的远程服务对应的Invoker对象,而Invoker对象中又封装了远程服务的调用信息,包括Invoker的代理对象;
5.4.1.服务引用时机
Dubbo 服务引用的时机有两个:
第一个是在Spring容器调用ReferenceBean的afterPropertiesSet()方法时引用服务;
第二个是在ReferenceBean对应的服务被注入到其他类中时引用;
这两个引用服务的时机区别在于:第一个是饿汉式的,第二个是懒汉式的.默认情况下,Dubbo 使用懒汉式引用服务.如果需要使用饿汉式,可通过配置<dubbo:reference>标签的"init"属性开启;
5.4.2.源码分析
当我们的服务被注入到其他类中时,Spring 会第一时间调用getObject()方法,并由该方法执行服务引用逻辑.按照惯例,在进行具体工作之前,需先进行配置检查与收集工作.接着根据收集到的信息决定服务用的方式,有三种:
第一种是引用本地 (JVM)服务;
第二种是通过直连方式引用远程服务;
第三种是通过注册中心引用远程服务;
不管是哪种引用方式,最后都会得到一个 Invoker 实例.如果有多个注册中心,多个服务提供者,这个时候会得到一组 Invoker 实例,此时需要通过集群管理类 Cluster 将多个Invoker 合并成一个实例.合并后的 Invoker 实例已经具备调用本地或远程服务的能力了,但并不能将此实例暴露给用户使用,这会对用户业务代码造成侵入.此时框架还需要通过代理工厂类(ProxyFactory)为服务接口生成代理类,并让代理类去调用Invoker逻辑.避免了Dubbo框架代码对业务代码的侵入;
1>.服务引用的入口方法为ReferenceBean的getObject()方法,该方法定义在Spring的FactoryBean接口中,ReferenceBean实现了这个方法
- ReferenceConfig的get()方法
2>.Dubbo在引用或导出服务时,首先会对这些配置进行检查和处理,以保证配置的正确性.配置解析逻辑封装在ReferenceConfig的init()方法
private void init() {
//避免重复初始化
if (initialized) {
return;
}
checkStubAndLocal(interfaceClass);
checkMock(interfaceClass);
Map<String, String> map = new HashMap<String, String>();
//添加 side、协议等信息到 map 中
map.put(SIDE_KEY, CONSUMER_SIDE);
appendRuntimeParameters(map);
if (!isGeneric()) {
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put(REVISION_KEY, revision);
}
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
if (methods.length == 0) {
logger.warn("No method found in service interface " + interfaceClass.getName());
map.put(METHODS_KEY, ANY_VALUE);
} else {
map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), COMMA_SEPARATOR));
}
}
// 将 ApplicationConfig、ConsumerConfig、ReferenceConfig 等对象的字段信息添加到map 中
map.put(INTERFACE_KEY, interfaceName);
appendParameters(map, metrics);
appendParameters(map, application);
appendParameters(map, module);
// remove 'default.' prefix for configs from ConsumerConfig
// appendParameters(map, consumer, Constants.DEFAULT_KEY);
appendParameters(map, consumer);
appendParameters(map, this);
Map<String, Object> attributes = null;
if (CollectionUtils.isNotEmpty(methods)) {
attributes = new HashMap<String, Object>();
//遍历 MethodConfig 列表
for (MethodConfig methodConfig : methods) {
appendParameters(map, methodConfig, methodConfig.getName());
String retryKey = methodConfig.getName() + ".retry";
// 检测 map 是否包含 methodName.retry
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
if ("false".equals(retryValue)) {
// 添加重试次数配置 methodName.retries
map.put(methodConfig.getName() + ".retries", "0");
}
}
attributes.put(methodConfig.getName(), convertMethodConfig2AyncInfo(methodConfig));
}
}
//获取服务消费者ip地址
String hostToRegistry = ConfigUtils.getSystemProperty(DUBBO_IP_TO_REGISTRY);
if (StringUtils.isEmpty(hostToRegistry)) {
hostToRegistry = NetUtils.getLocalHost();
} else if (isInvalidLocalHost(hostToRegistry)) {
throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
}
map.put(REGISTER_IP_KEY, hostToRegistry);
//创建代理类
ref = createProxy(map);
String serviceKey = URL.buildKey(interfaceName, group, version);
ApplicationModel.initConsumerModel(serviceKey, buildConsumerModel(serviceKey, attributes));
initialized = true;
}
- 进入到ReferenceConfig的createProxy()方法中,发现该方法在创建代理对象的同时调用其他方法构建以及合并Invoker实例
private T createProxy(Map<String, String> map) {
//本地引入服务
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 {//远程引入服务
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 { // 加载注册中心
// 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) {
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));
}
}
}
if (shouldCheck() && !invoker.isAvailable()) {
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());
}
/**
* @since 2.7.0
* ServiceData Store
*/
MetadataReportService metadataReportService = null;
if ((metadataReportService = getMetadataReportService()) != null) {
URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map);
metadataReportService.publishConsumer(consumerURL);
}
// create service proxy 创建服务代理对象
return (T) PROXY_FACTORY.getProxy(invoker);
}
3>.重点来看创建Invoker实例的过程,Invoker是Dubbo的核心模型,代表一个可执行体.在服务提供方,Invoker用于调用服务提供类.在服务消费方,Invoker用于执行远程调用,Invoker 是由Protocol实现类DubboProtocol调用refer()方法内部通过protocolBindingRefer()方法获取到的
4>.Dubbo使用NettyClient进行通信,DubboProtocol的getClients()方法逻辑如下
private ExchangeClient[] getClients(URL url) {
// whether to share connection
boolean useShareConnect = false;
int connections = url.getParameter(CONNECTIONS_KEY, 0);
List<ReferenceCountExchangeClient> shareClients = null;
// 根据connections数量决定是获取共享客户端还是创建新的客户端实例,默认情况下,使用共享客户端实例.getSharedClient()方法中也会调用initClient()方法;
if (connections == 0) {
useShareConnect = true;
/**
* The xml configuration should have a higher priority than properties.
*/
String shareConnectionsStr = url.getParameter(SHARE_CONNECTIONS_KEY, (String) null);
connections = Integer.parseInt(StringUtils.isBlank(shareConnectionsStr) ? ConfigUtils.getProperty(SHARE_CONNECTIONS_KEY,
DEFAULT_SHARE_CONNECTIONS) : shareConnectionsStr);
shareClients = getSharedClient(url, connections);
}
ExchangeClient[] clients = new ExchangeClient[connections];
for (int i = 0; i < clients.length; i++) {
if (useShareConnect) {
clients[i] = shareClients.get(i);
} else {
clients[i] = initClient(url);
}
}
return clients;
}
5>.getSharedClient()方法先访问缓存,若缓存未命中,则通过initClient()方法创建新的 ExchangeClient 实例,并将该实例传给ReferenceCountExchangeClient构造方法创建一个带有引用计数功能的ExchangeClient实例
private List<ReferenceCountExchangeClient> getSharedClient(URL url, int connectNum) {
String key = url.getAddress();
List<ReferenceCountExchangeClient> clients = referenceClientMap.get(key);
if (checkClientCanUse(clients)) {
batchClientRefIncr(clients);
return clients;
}
locks.putIfAbsent(key, new Object());
synchronized (locks.get(key)) {
clients = referenceClientMap.get(key);
// dubbo check
if (checkClientCanUse(clients)) {
batchClientRefIncr(clients);
return clients;
}
// connectNum must be greater than or equal to 1
connectNum = Math.max(connectNum, 1);
// If the clients is empty, then the first initialization is
if (CollectionUtils.isEmpty(clients)) {
clients = buildReferenceCountExchangeClientList(url, connectNum);
referenceClientMap.put(key, clients);
} else {
for (int i = 0; i < clients.size(); i++) {
ReferenceCountExchangeClient referenceCountExchangeClient = clients.get(i);
// If there is a client in the list that is no longer available, create a new one to replace him.
if (referenceCountExchangeClient == null || referenceCountExchangeClient.isClosed()) {
clients.set(i, buildReferenceCountExchangeClient(url));
continue;
}
referenceCountExchangeClient.incrementAndGetCount();
}
}
/**
* I understand that the purpose of the remove operation here is to avoid the expired url key
* always occupying this memory space.
*/
locks.remove(key);
return clients;
}
}
6>.initClient()方法首先获取用户配置的客户端类型,默认为netty.然后检测用户配置的客户端类型是否存在,不存在则抛出异常.最后根据lazy配置决定创建什么类型的客户端.这里的LazyConnectExchangeClient会在request()方法被调用时通过Exchangers的connect()方法创建ExchangeClient客户端
private ExchangeClient initClient(URL url) {
// client type setting.
String str = url.getParameter(CLIENT_KEY, url.getParameter(SERVER_KEY, DEFAULT_REMOTING_CLIENT));
url = url.addParameter(CODEC_KEY, DubboCodec.NAME);
// enable heartbeat by default
url = url.addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(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 {
// connection should be lazy
if (url.getParameter(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;
}
7>.connect()连接方法中,getExchanger()会通过 SPI 加载 HeaderExchangeClient 实例,Transporters的connect()方法中调用getTransporter().connect(url, handler)方法,getTransporter()方法是自适应扩展类,默认加载NettyTransporter,调用该类的connect()方法,往下就是通过Netty API创建Netty客户端了
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");
return getExchanger(url).connect(url, handler);
}
@Override
public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
}
8>.接下来就是为服务接口生成代理对象,代理对象生成的入口方法为ProxyFactory的getProxy()方法*[ProxyFactory接口的实现类AbstractProxyFactory中的getProxy()方法]*
@Override
public <T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException {
Class<?>[] interfaces = null;
String config = invoker.getUrl().getParameter(INTERFACES);
if (config != null && config.length() > 0) {
String[] types = COMMA_SPLIT_PATTERN.split(config);
if (types != null && types.length > 0) {
interfaces = new Class<?>[types.length + 2];
interfaces[0] = invoker.getInterface();
interfaces[1] = EchoService.class;
for (int i = 0; i < types.length; i++) {
// TODO can we load successfully for a different classloader?.
interfaces[i + 2] = ReflectUtils.forName(types[i]);
}
}
}
if (interfaces == null) {
interfaces = new Class<?>[]{invoker.getInterface(), EchoService.class};
}
if (!GenericService.class.isAssignableFrom(invoker.getInterface()) && generic) {
int len = interfaces.length;
Class<?>[] temp = interfaces;
interfaces = new Class<?>[len + 1];
System.arraycopy(temp, 0, interfaces, 0, len);
interfaces[len] = com.alibaba.dubbo.rpc.service.GenericService.class;
}
return getProxy(invoker, interfaces);
}
9>.最后getProxy(Invoker, Class<?>[]) 这个方法是一个抽象方法,实现类JavassistProxyFactory对该方法的实现
public class JavassistProxyFactory extends AbstractProxyFactory {
@Override
@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
// 生成 Proxy 子类(Proxy 是抽象类).并调用Proxy子类的newInstance方法创建Proxy实例
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);
}
};
}
}
- 以上代码通过 Proxy的getProxy方法获取Proxy子类,然后创建InvokerInvocationHandler对象,并将该对象传给 newInstance 生成 Proxy 实例.InvokerInvocationHandler实现自JDK的InvocationHandler接口;
5.5.服务调用流程
1>.如图:
2>.说明:
通过服务引用最终会得到一个(远程服务的)代理对象,当执行远程服务接口中的方法时:
I).先调用InvokerInvocationHandler.invoke()方法,获取到要执行的方法,参数等信息,然后将方法,参数等信息封装成一个RpcInvocation对象;
II).然后调用MockClusterInvoker.invoke(RpcInvocation)方法;
III).再调用invoker父类AbstractClusterInvoker.invoke(invocation)方法,在invoke(invocation)方法内部,调用List(invocation)方法,根据invocation对象从注册中心中获取最新的invoker列表(即有多少个可以执行的Invoker对象)以及负载均衡策略;
IV).再调用FailoverClusterInvoke.doInvoke(invocation,Invoker,loadbalance)方法,在doInvoke()方法内部,根据负载均衡机制选择一个真正要执行的Invoker对象,然后调用InvokerWrapper.invoke(invocation)方法…(跳过中间的组件)
V).来到AbstractInvoker.invoke(invocation)方法,在invoke()方法内部,调用DubboInvoker.doInvoke(invocation)方法,在方法里面执行真正的远程调用的逻辑;
VI).在DubboInvoker的doInvoke(invocation)方法中,首先获取到方法名称等信息,然后获取到在服务引用阶段封装到DubboInvoker执行器对象中的一个远程服务引用的客户端,
通过这个客户端(客户端内部使用NettyClient进行通信)去和远程服务建立连接,发起请求,获取结果,然后进行解码,最终响应给客户端;
注意:在请求过程中如果请求某一台服务器上的服务失败了,那么他会重新请求另一台服务器上的服务;如果请求超时了,会根据配置进行重试!
扩展:注册中心挂了,还可以继续通信吗?
答:可以,因为刚开始初始化的时候,消费者会从注册中心将提供者的地址等信息拉取到本地进行缓存
,如果注册中心挂了,还可以从本地缓存中找到对应服务的地址信息,继续进行通信;