1. 基于注解配置解析的实现原理
Dubbo框架支持几种配置解析如Schema/xml/注解等方式进行配置解析,基于注解的配置解析不仅简单易用,而且更灵活代码量更少。Spring框架预留了非常多的接口可以进行配置资源的导入与解析、bean扫描与生成注入等,这里关于Dubbo的注解解析的流程大部分都是在invokeBeanFactoryPostProcessor中完成。Dubbo框架主要依靠@EnableDubbo注解/ServiceAnotationBeanPostProcessor/ReferenceAnnotationBeanPostProcessor前置处理器和DubboConfigConfigurationSelector配置收集器等核心组件完成解析,如果用户使用了配置文件,则框架按需的生成对应的bean,如在服务提供者端则需要将使用Dubbo的注解@Service的类导入为Bean,在服务消费者端为使用了@Reference注解的字段或方法注入代理对象。
当Spring容器启动时间,如果注解上使用了@import,则会触发注解的selectImports方法,比如EnableDubboConfig注解中指定的DubboConfigConfigurationRegister,会自动调用registerBeanDefinations方法。
@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {
....
}
@Import(DubboConfigConfigurationRegistrar.class)
public @interface EnableDubboConfig {
...
}
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
...
}
DubboConfigConfigurationRegister配置注册处理类中,向Spring容器注册了DubboConfiguration bean,还注册了其他如@Reference注解处理前置处理器,框架启动事件监听器等。
public class DubboConfigConfigurationRegistrar implements
ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//获取注解上的配置信息
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
importingClassMetadata.getAnnotationAttributes(
EnableDubboConfig.class.getName()));
boolean multiple = attributes.getBoolean("multiple");
registerBeans(registry, DubboConfigConfiguration.Single.class);
if (multiple) {
registerBeans(registry, DubboConfigConfiguration.Multiple.class);
}
//向Spring容器注入通用的bean
registerCommonBeans(registry);
}
}
static void registerCommonBeans(BeanDefinitionRegistry registry) {
//注入Reference注解前置处理器bean
registerInfrastructureBean(registry,
ReferenceAnnotationBeanPostProcessor.BEAN_NAME,
ReferenceAnnotationBeanPostProcessor.class);
.......
//注入Dubbo应用框架启动监听器bean,这里面进行Dubbo应用启动
registerInfrastructureBean(registry,
DubboBootstrapApplicationListener.BEAN_NAME,
DubboBootstrapApplicationListener.class);
.......
}
如果用户配置了属性, 比如dubbo.application.name,则自动会创建的Spring Bean到容器中。注册和配置对象Bean属性绑定在registerConfigurationBeanDefination方法中完成。
public class ApplicationConfig extends AbstractConfig {
....
private String name;
private String version;
....
}
public class DubboConfigConfiguration {
@EnableConfigurationBeanBindings({
@EnableConfigurationBeanBinding(prefix = "dubbo.application",
type = ApplicationConfig.class),
.....})
public static class Single {
}
@EnableConfigurationBeanBindings({
@EnableConfigurationBeanBinding(prefix = "dubbo.applications",
type = ApplicationConfig.class, multiple = true),
.....})
public static class Multiple {
}
}
@Import(ConfigurationBeanBindingsRegister.class)
public @interface EnableConfigurationBeanBindings {
EnableConfigurationBeanBinding[] value();
}
public class ConfigurationBeanBindingsRegister
implements ImportBeanDefinitionRegistrar, EnvironmentAware {
....
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
//获取注解相关属性
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
importingClassMetadata.getAnnotationAttributes(
EnableConfigurationBeanBindings.class.getName()));
//获取注解相关值
AnnotationAttributes[] annotationAttributes =
attributes.getAnnotationArray("value");
ConfigurationBeanBindingRegistrar registrar =
new ConfigurationBeanBindingRegistrar();
registrar.setEnvironment(environment);
//完成属性值与配置bean的绑定
for (AnnotationAttributes element : annotationAttributes) {
registrar.registerConfigurationBeanDefinitions(element, registry);
}
}
...
}
当用户使用@DubboComponentScan时,就会激活DubboComponentScanRegister,同时生成ServiceClassPostProcessor和ReferenceAnnotationBeanPostProcessor两种处理器,用于处理生产和消费注解。
public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
//扫描包路径
Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
//生成生成注解处理器
registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
//生成消费注解处理器
registerCommonBeans(registry);
}
}
其中ServiceClassPostProcessor处理器实现了BeanDefinitionRegistryPostProcessor接口,Spring容器中所有的Bean注册之后回调postProcessBeanDefinitionRegistry方法开始扫描@DubboService注解并注入容器中。
public class ServiceClassPostProcessor
implements BeanDefinitionRegistryPostProcessor, EnvironmentAware,
ResourceLoaderAware, BeanClassLoaderAware {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
//注入容器启动事件监听器,当收到fresh事件后,将启动Dubbo应用框架
registerBeans(registry, DubboBootstrapApplicationListener.class);
//获取用户注解配置的包扫描路径
Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);
if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
//触发ServiceBean定义和注入
registerServiceBeans(resolvedPackagesToScan, registry);
}
}
private final static List<Class<? extends Annotation>> serviceAnnotationTypes = asList(
DubboService.class,
Service.class,
com.alibaba.dubbo.config.annotation.Service.class
);
private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
DubboClassPathBeanDefinitionScanner scanner =
new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);
scanner.setBeanNameGenerator(beanNameGenerator);
//指定扫描的三种注解方式,而不会扫描其他类型的注解
serviceAnnotationTypes.forEach(annotationType -> {
scanner.addIncludeFilter(new AnnotationTypeFilter(annotationType));
});
for (String packageToScan : packagesToScan) {
//将@DubboService等作为不同Bean注入容器中
scanner.scan(packageToScan);
Set<BeanDefinitionHolder> beanDefinitionHolders =
findServiceBeanDefinitionHolders(scanner, packageToScan,
registry, beanNameGenerator);
if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {
for (BeanDefinitionHolder beanDefinitionHolder :
beanDefinitionHolders) {
//注册ServiceBean定义并做数据绑定和解析
registerServiceBean(beanDefinitionHolder, registry, scanner);
}
}
}
}
}
在实际使用中,会在使用类中注入@Reference注解,可以方便地发起远程调用,其中Dubbo的属性注入是通过ReferenceAnnotationProcessor处理,主要是通过获取类中的@DubboRefence注解的字段或者方法,并通过反射设置字段或者方法的引用完成,带有注解的对象都会转换为ReferenceBean对象。
public class ReferenceAnnotationBeanPostProcessor
extends AbstractAnnotationBeanPostProcessor implements
ApplicationContextAware, ApplicationListener<ServiceBeanExportedEvent> {
@Override
protected Object doGetInjectedBean(AnnotationAttributes attributes,
Object bean, String beanName,
Class<?> injectedType,
InjectionMetadata.InjectedElement injectedElement) throws Exception {
....
//注册ReferenceBean
registerReferenceBean(referencedBeanName, referenceBean, attributes,
localServiceBean, injectedType);
//缓存注入ReferenceBean
cacheInjectedReferenceBean(referenceBean, injectedElement);
//创建代理,最后返回的是Reference#getObject()
return getOrCreateProxy(referencedBeanName, referenceBean,
localServiceBean, injectedType);
}
@Override
public PropertyValues postProcessPropertyValues(
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
//查找出bean所有标注了@Reference标注的字段和方法
InjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
try {
//对字段或者方法进行反射绑定
metadata.inject(bean, beanName, pvs);
} catch (BeanCreationException ex) {
throw ex;
} catch (Throwable ex) {
.....
}
return pvs;
}
}
2.服务暴露的实现原理
2.1 服务暴露机制
在DubboUtils#registerCommonBeans方法中注入了Dubbo启动监听器,在Spring应用启动和销毁时会调用DubboBootstrap#start和DubboBootstrap#stop方法。
public class DubboBootstrapApplicationListener extends
OneTimeExecutionApplicationContextEventListener
implements Ordered {
....
private void onContextRefreshedEvent(ContextRefreshedEvent event) {
//dubbo框架应用启动
dubboBootstrap.start();
}
private void onContextClosedEvent(ContextClosedEvent event) {
//dubbo框架应用停止
dubboBootstrap.stop();
}
....
}
public DubboBootstrap start() {
....
//初始化配置和监听器,拉取注册中心的配置信息,初始化服务元数据
initialize();
//暴露服务
exportServices();
if (!isOnlyRegisterProvider() || hasExportedServices()) {
//暴露服务元数据
exportMetadataService();
//注册服务提供者实例到注册中心
registerServiceInstance();
}
referServices();
....
}
Dubbo框架将远程服务暴露分为两个部分,第一部分将持有服务实例通过代理转换为Invoker,第二部会把Invoker通过具体的通信协议如Dubbo/Thrift等转换为Exporter,其中Invoker是整个框架比较重要的组成部分,具有“承上启下”的作用,它可能是一个本地的实现也可以是一个远程的实现,也可以是一个集群实现。
protocol实例会自动根据服务暴露URL自动做适配,会取出具体协议,比如zookeeper,首先会创建注册中心实例,然后取出export对应的具体服务URL,最后用服务URL对应的协议(默认协议为Dubbo)进行服务暴露,当服务暴露成功后会把服务元数据注册到zookeeper,其中URL信息如下所示。
registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?
application=dubbo-demo-annotation-provider&dubbo=2.0.2&pid=51310
®istry=zookeeper&release=2.7.7×tamp=1613395854976
在将服务实例ref转换成Invoker后,通过RegistryProtocol#export进行更细粒度的控制,比如先进行服务暴露再注册服务元数据。注册中心在服务暴露时依次做了以下几件事情:
-
委托具体协议(Dubbo)进行服务暴露,并创建NettyServer监听端口和保存服务实例;
-
创建服务中心对象,与注册中心建立TCP连接;
-
注册服务元数据到注册中心;
-
订阅configurators节点,监听服务动态属性变更事件;
-
服务销毁收尾工作,比如关闭端口、反注册服务信息等;
public class ServiceConfig extends ServiceConfigBase {
public synchronized void export() {
if (bootstrap == null) { bootstrap = DubboBootstrap.getInstance(); bootstrap.init(); } //初始化服务元数据 serviceMetadata.setVersion(version); serviceMetadata.setGroup(group); serviceMetadata.setDefaultGroup(group); serviceMetadata.setServiceType(getInterfaceClass()); serviceMetadata.setServiceInterfaceName(getInterface()); serviceMetadata.setTarget(getRef()); doExport();}
private void doExportUrls() { ..... //获取当前服务对应的注册中心实例 List registryURLs = ConfigValidationUtils.loadRegistries(this, true);
//如果服务指定暴露多个协议(Dubbo、REST),则依次暴露服务 for (ProtocolConfig protocolConfig : protocols) { doExportUrlsFor1Protocol(protocolConfig, registryURLs); } } private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) { ... Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())); DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); //指定协议暴露服务,如DubboProtocol Exporter<?> exporter = PROTOCOL.export(wrapperInvoker); exporters.add(exporter); ...}
}
public class JavassistProxyFactory extends AbstractProxyFactory { @Override public Invoker getInvoker(T proxy, Class type, URL url) { final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName() .indexOf('$') < 0 ? proxy.getClass() : type); return new AbstractProxyInvoker(proxy, type, url) { @Override protected Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable { return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); } }; } }
通过反射获取配置对象并放到map中用于后续构造URL参数(比如应用名等),主要区分全局配置,默认在属性前面增加default前缀,当框架获取URl参数,会通过动态代理创建Invoker对象,在服务端生成AbstractProxyInvoker实例,所欲真实的方法调用都会委托给代理,然后代理转发给服务ref调用,目前有两种代理方式: JavassistProxyFactory和JdkProxyFactory。其中JavassistProxyFactory模式原理是创建Wrapper子类,在子类中实现invokeMethod方法,方法体内为每个ref方法做方法名和方法参数匹配校验,如果匹配则直接调用即可,相比JdkProxyFactory省去了反射调用的开销。
在构造调用拦截器之后会调用Dubbo协议进行服务暴露,下面是DubboProtocol#export代码。其中会根据服务分组、版本、服务接口和暴露端口作为key用于关联具体服务Iovoker,只有在初次暴露的接口才需要打开端口监听,并触发Exchange中的绑定方法,最后调用NettyServer进行处理,在初始化Server过程中会初始化很多Handler用于支持心跳、业务线程池处理编解码的Handler和响应方法调用的Handler。
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
//根据服务分组、版本、接口和端口构造的key
String key = serviceKey(url);
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
//把exporter存储到单例DubboProtocol中
exporterMap.put(key, exporter);
.....
//服务初次暴露会创建监听服务器
openServer(url);
optimizeSerialization(url);
.....
return exporter;
}
private void openServer(URL url) {
String key = url.getAddress();
boolean isServer = url.getParameter(IS_SERVER_KEY, true);
if (isServer) {
//服务器实例会缓存
ProtocolServer server = serverMap.get(key);
if (server == null) {
synchronized (this) {
server = serverMap.get(key);
if (server == null) {
serverMap.put(key, createServer(url));
}
}
} else {
server.reset(url);
}
}z
}
private ProtocolServer createServer(URL url) {
....
ExchangeServer server;
try {
//创建NettyServer并初始化Handler
server = Exchangers.bind(url, requestHandler);
} catch (RemotingException e) {
....
}
...
return new DubboProtocolServer(server);
}
当服务真实调用时会触发各种拦截器Filter,在进行服务暴露前,框架会做拦截器初始化,Dubbo在加载protocol扩展点时会自动注入ProtocalListenerWrapper,真实暴露按照ProtocolListenerWrapper --> ProtocolFilterWrapper --> DubboProtocol的调用流程进行,在ProtocolListenerWrapper实现中,在对服务提供者进行暴露时回调对应的监听器方法
public class ProtocolListenerWrapper implements Protocol {
...
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (UrlUtils.isRegistry(invoker.getUrl())) {
return protocol.export(invoker);
}
return new ListenerExporterWrapper<T>(protocol.export(invoker),
Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
.getActivateExtension(invoker.getUrl(), EXPORTER_LISTENER_KEY)));
}
...
}
public class ProtocolFilterWrapper implements Protocol {
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
Invoker<T> last = invoker;
//加载所有的拦截器类实例
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
if (!filters.isEmpty()) {
for (int i = filters.size() - 1; i >= 0; i--) {
final Filter filter = filters.get(i);
final Invoker<T> next = last;
//将真实的Invoker放在拦截器的末尾
last = new Invoker<T>() {
@Override
public Result invoke(Invocation invocation) throws RpcException {
Result asyncResult;
//每次调用都会传递到下一个拦截器
asyncResult = filter.invoke(next, invocation);
return asyncResult.whenCompleteWithContext((r, t) -> {...});
.....
});
}
....
}
}
return last;
}
}
2.2 服务注册
总体流程如下图所示,其中官网上也有较详细的说明。
- 服务提供者启动时,会向注册中心写入自己的元数据信息,同时订阅配置元数据信息;
- 消费者启动时,也会向注册中心写入自己的元数据信息,并订阅服务提供者、路由和配置元数据信息;
- 服务治理中心启动时,会同时订阅所有消费者、服务提供者、路由和配置元数据;
- 当有服务提供者离开或有新的服务提供者加入时,注册中心服务提供目录会发生变化,变化信息会动态通知给消费者、服务治理中心;
- 当消费者发起服务调用时,会异步将调用、统计信息等上报给监控中心。
我们一般使用zookeeper作为服务注册中心,zookeeperd是树形结构的注册中心,每个节点可分为持久节点、持久顺序节点、临时节点和临时顺序节点。
- 树的根节点是注册中心分组,下面有多个服务接口,分组来自group属性,默认是/dubbo;
- 服务接口下包含4类子目录,分别是providers、consumers、routers、configuratiors,这个是路径是持久节点;
- 服务提供者目录(/dubbo/service/providers)下面包含的接口有多个服务URL元数据信息;
- 服务消费者目录(/dubbo/service/consumers)下面包含的接口有多个消费者URL元数据信息;
- 路由配置目录(/dubbo/service/routers)下面包含了多个用于消费者路由策略URL元数据信息;
- 动态配置目录(/dubbo/service/configurators)下面包含了多个服务动态配置URL元数据信息。
2.2.1 注册中心总体实现原理
服务订阅与发布是整个注册中心的核心功能之一,当一个已有的服务提供节点下线,或者一个新的服务提供者节点加入微服务环境时,订阅对应接口的消费者都能及时收到注册中心的通知,并更新本地信息。Dubbo框架有专门的Registry功能层去实现服务的订阅与发布。目前Dubbo支持Zookeeper/Redis/euraka/nacos等多种中间件作为注册中心,目前我们常用的是Zookeeper注册中心。Dubbo的注册中心模块使用使用了模版模式和工厂模式等设计模式来保证扩展性。
AbstractRegistryFactory实现了RegistryFactory接口的getRegistry(URL url)方法,主要完成了加锁,以及调用模版方法createRegistry(URL url)创建具体实现等操作,并缓存在内存中。注册中心有好几种具体的工厂类,如zookeeperRegistryFactory/RedisRegistryFactory等,框架默认为zookkeeperRegistryFactory工厂类实现。
public abstract class AbstractRegistryFactory implements RegistryFactory {
@Override
public Registry getRegistry(URL url) {
url = URLBuilder.from(url)
.setPath(RegistryService.class.getName())
.addParameter(INTERFACE_KEY, RegistryService.class.getName())
.removeParameters(EXPORT_KEY, REFER_KEY)
.build();
String key = createRegistryCacheKey(url);
LOCK.lock();
try {
//缓存中有则直接返回
Registry registry = REGISTRIES.get(key);
if (registry != null) {
return registry;
}
//如果注册中心还没创建过,则调用抽象方法createRegistry(url)重新创建一个
//createRegistry方法由具体的子类如ZookeeperRegistryFactory实现
registry = createRegistry(url);
if (registry == null) {
throw new IllegalStateException("Can not create registry " + url);
}
REGISTRIES.put(key, registry);
return registry;
} finally {
LOCK.unlock();
}
}
}
public class ZookeeperRegistryFactory extends AbstractRegistryFactory {
private ZookeeperTransporter zookeeperTransporter;
public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
this.zookeeperTransporter = zookeeperTransporter;
}
@Override
public Registry createRegistry(URL url) {
//创建Zookeeper的注册中心实例
return new ZookeeperRegistry(url, zookeeperTransporter);
}
}
2.2.2 服务发布的实现原理
服务提供者和消费者都需要把自己注册到注册中心,使用zookeeper发布的实现代码非常简单,只是调用了zk客户端在注册中心上创建了一个目录。
//zkClient创建目录
zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
//zkClient删除路径
zkClient.delete(toUrlPath(url);
AbstractRegistry实现了Registry接口中的注册、订阅、查询、通知等方法,还实现了磁盘文件持久化注册信息等方法。FailbackRegistry又继承了AbstractRegistry,重写了父类的注册、订阅、查询和通知等方法,并且添加了的重试机制。
//FailbackRegistry抽象类中未实现的抽象模版方法
public abstract void doRegister(URL url);
public abstract void doUnregister(URL url);
public abstract void doSubscribe(URL url, NotifyListener listener);
public abstract void doUnsubscribe(URL url, NotifyListener listener);
以订阅逻辑为例,FailbackRegistry重写了subscribe方法,只实现了订阅的大体逻辑以及异常处理情况等通用性的东西,但具体如何实现,交给继承的子类实现,以ZookeeperRegistry实现类为例,在具体的订阅逻辑中使用的zk客户端相关api接口进行节点的操作与变更事件订阅。
public void subscribe(URL url, NotifyListener listener) {
super.subscribe(url, listener);
removeFailedSubscribed(url, listener);
try {
//子类实现
doSubscribe(url, listener);
} catch (Exception e) {
......
}
}
}
2.2.3 服务订阅的实现原理
订阅通常有pull和push两种方式,一种是客户端定时轮询注册中心拉取配置,另一种注册中心主动推送数据给客户端。目前Dubbo采用的是第一次启动拉取方式, 后续接收事件重新拉取数据。在服务暴露时,服务端会订阅configurators用于监听动态配置,消费端启动时,消费端会订阅provicers、routers和configurators三个目录,对应应用服务提供者、路由和动态配置变更通知。
zookeeper注册中心采用的是“事件通知”+“客户端拉取”的方式,客户端在第一次连接注册中心时,会获取对应目录下的全量数据,并在订阅的节点上注册一个watch监听,客户端与注册中心之间保持TCP长连接,后续每个节点有任何数据变化的时候,注册中心都会根据watcher的回调主动通知客户端,在接收到通知后, 会把对应节点下的全量数据都拉取过来(客户端拉取),这一点在NotifyListener#notify(List urls)接口上体现。
客户端第一次连接注册中心,订阅时会获取全的数据,后续通过监听器事件进行更新,服务治理中心会处理所有订阅的service层的订阅,service被设置成特殊值,此外,服务治理中心除了订阅当前节点,还会订阅这个节点下的所有子节点。
//ZookeeperRegistry中的订阅具体逻辑
@Override
public void doSubscribe(final URL url, final NotifyListener listener) {
try {
//订阅所有的数据
if (ANY_VALUE.equals(url.getServiceInterface())) {
String root = toRootPath();
ConcurrentMap<NotifyListener, ChildListener> listeners =
zkListeners.computeIfAbsent(url, k -> new ConcurrentHashMap<>());
//第一次则会新建一个listener监听器
ChildListener zkListener =
listeners.computeIfAbsent(listener, k -> (parentPath, currentChilds) -> {
//遍历子节点,有变化则会收到通知
for (String child : currentChilds) {
child = URL.decode(child);
if (!anyServices.contains(child)) { //如果还没被订阅,则订阅
anyServices.add(child);
subscribe(url.setPath(child)
.addParameters(INTERFACE_KEY, child,
Constants.CHECK_KEY, String.valueOf(false)), k);
}
}
});
//创建持久节点,并订阅子节点
zkClient.create(root, false);
List<String> services = zkClient.addChildListener(root, zkListener);
if (CollectionUtils.isNotEmpty(services)) {
for (String service : services) {
service = URL.decode(service);
anyServices.add(service);
subscribe(url.setPath(service)
.addParameters(INTERFACE_KEY, service,
Constants.CHECK_KEY, String.valueOf(false)), listener);
}
}
} else {
List<URL> urls = new ArrayList<>();
for (String path : toCategoriesPath(url)) {
ConcurrentMap<NotifyListener, ChildListener> listeners =
zkListeners.computeIfAbsent(url, k -> new ConcurrentHashMap<>());
ChildListener zkListener =
listeners.computeIfAbsent(listener, k -> (parentPath, currentChilds) -> ZookeeperRegistry.this.notify(url, k, toUrlsWithEmpty(url, parentPath, currentChilds)));
zkClient.create(path, false);
List<String> children =
zkClient.addChildListener(path, zkListener);
if (children != null) {
urls.addAll(toUrlsWithEmpty(url, path, children));
}
}
//回调NotifyListener,更新本地缓存信息
notify(url, listener, urls);
}
} catch (Throwable e) {
throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
2.2.4 缓存机制
缓存就是空间换取时间,如果每次远程调用都要先从注册中心获取一次可调用的服务列表,则会让注册中心承受巨大的流量压力。每次额外的网络请求也会让整个服务网络性能下降,所以在AbstractRegistry里面封装了本地磁盘与内存缓存,内存缓存主要保存在Properties对象,磁盘上是持久化一份文件,通过File对象引用。File文件存储在在用户home目录下的/.dubbo目录下以服务接口-服务实例ip:端口命名。
文件内容如下所示
com.seewo.demo.service.interfaces.DemoService=empty\://192.168.0.109/
com.seewo.demo.service.interfaces.DemoService?
application\=dubbo-demo-annotation-consumer&category\=routers&check\=false
&dubbo\=2.0.2&init\=false
&interface\=com.seewo.demo.service.interfaces.DemoService
&methods\=sayHello,sayHelloAsync&pid\=88004
&release\=2.7.7&revision\=1.0-SNAPSHOT
&side\=consumer
&sticky\=false×tamp\=1613461973362
......
在服务初始化的时候,AbstractRegistry构造函数里会从本地磁盘文件中把持久化的注册数据读到Properties对象中,并加载到内存缓存中。
public abstract class AbstractRegistry implements Registry {
private final Properties properties = new Properties();
//内存中的监听器对象
private final ConcurrentMap<URL, Set<NotifyListener>> subscribed
= new ConcurrentHashMap<>();
//内存中的服务缓存对象
private final ConcurrentMap<URL, Map<String, List<URL>>> notified
= new ConcurrentHashMap<>();
//磁盘文件服务缓存对象
private File file;
public AbstractRegistry(URL url) {
setUrl(url);
if (url.getParameter(REGISTRY__LOCAL_FILE_CACHE_ENABLED, true)) {
//从url中指定是否为同步保存文件
syncSaveFile = url.getParameter(REGISTRY_FILESAVE_SYNC_KEY, false);
String defaultFilename = System.getProperty("user.home") +
"/.dubbo/dubbo-registry-" + url.getParameter(APPLICATION_KEY) +
"-" + url.getAddress().replaceAll(":", "-") + ".cache";
String filename = url.getParameter(FILE_KEY, defaultFilename);
File file = null;
if (ConfigUtils.isNotEmpty(filename)) {
file = new File(filename);
if (!file.exists() && file.getParentFile() != null && !file.getParentFile().exists()) {
if (!file.getParentFile().mkdirs()) {
throw new IllegalArgumentException("Invalid registry cache file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!");
}
}
}
this.file = file;
loadProperties();
notify(url.getBackupUrls());
}
private void loadProperties() {
if (file != null && file.exists()) {
InputStream in = null;
try {
in = new FileInputStream(file);
properties.load(in); //读取磁盘中的文件
...
} catch (Throwable e) {
...
} finally {
...
}
}
}
}
缓存的保存有同步和异步两种方式的,异步会使用线程池异步保存,如果线程在执行过程中出现异常,则会再次调用线程池不断重试。
private void saveProperties(URL url) {
...
if (syncSaveFile) {
doSaveProperties(version);
} else {
registryCacheExecutor.execute(new SaveProperties(version));
}
}
..
}
3.服务消费的实现原理
Dubbo框架做服务消费也分为两大部分,第一步通过持有的远程服务实例生成Invoker,这个Invoker在客户端是核心的远程代理对象。第二步会把Invoker通过动态代理转换成实现用户接口的动态代理引用。这里的Invoker承载了网络连接通信、服务调用和重试等功能。
Dubbo框架真正进行服务引用的入口点在ReferenceBean#getObject,继承自ReferenceConfig类。经过ReferenceAnnotationBeanProcessor注解器处理后,使用@Reference注解标识的对象时,Spring容器会自定注入ReferenceBean对象。
public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean,
ApplicationContextAware, InitializingBean, DisposableBean {
...
@Override
public void afterPropertiesSet() throws Exception {
....
if (shouldInit()) {
//调用ReferenceConfig#get方法
getObject();
}
}
....
}
在ReferenceConfig中会生成ReferenceBean对象并初始化。Dubbo支持多注册中心同时消费,如果配置了服务同时注册多个注册中心,则会在ReferenceConfig#createProxy中合并成一个Invoker。在createProxy方法中实现完成了远程代理对象的创建以及代理对象的转换等工作,客户端启动拉取服务元数据,订阅provider、路由和配置变更,当经过注册中心消费时,主要通过RegistryProtocol#refer触发数据拉取、订阅和服务Invoker转换等操作。
public class ReferenceConfig<T> extends ReferenceConfigBase<T> {
....
public synchronized void init() {
//创建代理对象
ref = createProxy(map);
....
}
private T createProxy(Map<String, String> map) {
//单注册中心消费
if (urls.size() == 1) {
//进行接口与url关联并生成invoker
invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
} else {
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
for (URL url : urls) {
//逐个获取注册中心的服务,并添加到Invoker列表中
invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
if (UrlUtils.isRegistry(url)) {
registryURL = url;
}
}
if (registryURL != null) {
//通过Cluster将多个Invoker转换成一个Invoker
URL u = registryURL.addParameterIfAbsent(CLUSTER_KEY, ZoneAwareCluster.NAME);
invoker = CLUSTER.join(new StaticDirectory(u, invokers));
} else {
invoker = CLUSTER.join(new StaticDirectory(invokers));
}
}
...
// 把Invoke转换成接口代理
return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
}
}
public class JdkProxyFactory extends AbstractProxyFactory {
@Override
@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.newProxyInstance(Thread.currentThread().
getContextClassLoader(), interfaces,
new InvokerInvocationHandler(invoker));
}
}
public class InvokerInvocationHandler implements InvocationHandler {
.....
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
.....
return invoker.invoke(rpcInvocation).recreate();
}
}
在createProxy方法中会调用RegistryProtocol#refer方法,触发数据拉取、订阅和服务Invoker转换等操作,其中最核心的数据结构是RegistryDirector,该类持有Invoker和Url的对应关系。
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
//获取具体注册中心协议,比如zookeeper
url = getRegistryUrl(url);
//创建具体注册中心实例
Registry registry = registryFactory.getRegistry(url);
if (RegistryService.class.equals(type)) {
return proxyFactory.getInvoker((T) registry, type, url);
}
...
//处理订阅数据并通过Cluster合并多个Invoker
return doRefer(cluster, registry, type, url);
}
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry,
Class<T> type, URL url) {
//持有Invoker和接收订阅通知
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
directory.setRegistry(registry);
directory.setProtocol(protocol);
Map<String, String> parameters = new HashMap<String, String>(directory.getConsumerUrl().getParameters());
URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
if (directory.isShouldRegister()) {
directory.setRegisteredConsumerUrl(subscribeUrl);
//注册消费者信息到消费中心
registry.register(directory.getRegisteredConsumerUrl());
}
directory.buildRouterChain(subscribeUrl);
//订阅服务提供者、路由和动态配置
directory.subscribe(toSubscribeUrl(subscribeUrl));
Invoker<T> invoker = cluster.join(directory);
List<RegistryProtocolListener> listeners = findRegistryProtocolListeners(url);
if (CollectionUtils.isEmpty(listeners)) {
return invoker;
}
RegistryInvokerWrapper<T> registryInvokerWrapper = new RegistryInvokerWrapper<>(directory, cluster, invoker, subscribeUrl);
for (RegistryProtocolListener listener : listeners) {
//触发监听器
listener.onRefer(this, registryInvokerWrapper);
}
return registryInvokerWrapper;
}
上面的逻辑完成了注册中心实例的创建,将元数据注册到注册中心以及订阅的功能,根据用户指定的注册中心进行协议转换(比如zookeeper协议),具体注册中心协议会在启动时用registry存储对应值。创建中心实例后,这里的URL其实是注册中心地址,真实消费方的是放在refer属性中存储的。在doSubscribe方法中会触发nofity方法。
private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<>();
if (urls == null || urls.isEmpty()) {
return newUrlInvokerMap;
}
Set<String> keys = new HashSet<>();
String queryProtocols = this.queryMap.get(PROTOCOL_KEY);
for (URL providerUrl : urls) {
...
//合并provider端配置数据,比如服务端IP和port等
URL url = mergeUrl(providerUrl);
String key = url.toFullString();
if (keys.contains(key)) { //如果已推送过的服务列表则忽略
continue;
}
keys.add(key);
Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap;
Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key);
if (invoker == null) {
try {
boolean enabled = true;
if (url.hasParameter(DISABLED_KEY)) {
enabled = !url.getParameter(DISABLED_KEY, false);
} else {
enabled = url.getParameter(ENABLED_KEY, true);
}
if (enabled) {
//使用具体协议创建远程连接
invoker = new InvokerDelegate<>(
protocol.refer(serviceType, url), url, providerUrl);
}
} catch (Throwable t) {
}
if (invoker != null) { // Put new invoker in cache
newUrlInvokerMap.put(key, invoker);
}
} else {
newUrlInvokerMap.put(key, invoker);
}
}
keys.clear();
return newUrlInvokerMap;
}
具体Invoker创建都是在DubboProtocol#refer中实现的,当第一次消费端发起订阅时进行第一次数据拉取操作时间,同时会触发RegistryDirectory#nofity方法,这里的通知数据是某一个类别的全量数据,比如providers和routers类别数据。当通知providers数据时,在RegistryDirectory#toInvoker方法内完成Invoker转换。
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
return new AsyncToSyncInvoker<>(protocolBindingRefer(type, url));
}
public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
optimizeSerialization(url);
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url,
getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
Dubbo协议在返回DubboInvoker对象之前就初始化客户端连接对象,Dubbo支持客户端是否立即和远程服务建立TCP连接是由参数是否配置了lazy属性决定的,默认会全部连接。DubboProtocol#refer内部会调用DubboProtocol#initClient负责建立客户端和初始化Handler。
private ExchangeClient initClient(URL url) {
.....
ExchangeClient client;
try {
//如果设置了lazzy属性,则真实调用的时候才会创建Tcp连接
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;
}
}
4.总结
本篇章首先对Dubbo框架与Spring框架结合注解的核心解析流程进行了探讨,并对Dubbo服务的生产与服务,服务注册发现/订阅/本地缓存等机制流程进行了讲解。