Dubbo架构设计与源码解析(二) 服务注册

作者:黄金

一、Dubbo简介

Dubbo是一款典型的高扩展、高性能、高可用的RPC微服务框架,用于解决微服务架构下的服务治理与通信问题。其核心模块包含 【RPC通信】【服务治理】 ,其中服务治理又分为服务注册与发现、服务容错、负载均衡、流量调度等。今天将重点介绍Dubbo的服务注册与发现

二、SPI机制

在介绍服务注册发现之前,先简单介绍一下贯穿整个Dubbo源码,也是Dubbo实现自适应扩展的核心--SPI机制,下图为Dubbo SPI实现的简单类图。





1、Dubbo SPI原理:通过读取相应的配置文件找到具体实现类,然后通过以下两种方式实例化对象:(1)通过自适应动态字节码编译技术,生成相应的动态代理类,(2)利用反射机制实现实例化。相较于Java SPI,Dubbo SPI实现了内部的IoC和Aop

2、Dubbo SPI 优点:(1)高扩展:用户可以根据实际业务需求扩展相应的实现模块,包含字节码编译技术、rpc协议、通信方式、注册方式等,(2)解耦: 通过封装SPI调用机制,架构上实现了上层应用与底层逻辑之间的解耦,为高扩展提供了支撑条件

3、Dubbo SPI 常用样例(以getExtension和getAdaptiveExtension为例)

1配置文件内容 2test1=com.dubbo.demo.service.TestServiceimpl 3test2=com.dubbo.demo.service.TestServiceImpl2 4 5一、通过getExtension方法生成实例 6 ExtensionLoader<TestService> extensionLoader = ExtensionLoader.getExtensionLoader(TestService.class); 7 TestService t1 = extensionLoader.getExtension("test1"); 8 TestService t2 = extensionLoader.getExtension("test2"); 9 10二、通过getAdaptiveExtension生成实例(方法中需要@Adaptive注解,参数会对URL校验) 11 TestService testService = ExtensionLoader.getExtensionLoader(TestService.class).getAdaptiveExtension(); 12 URL url = new URL("test", "localhost", 8080, new String[]{"test.service", "test1"}); 13 testService.sayHello("bbb", url);

调用getAdaptiveExtension方法最终会生成相应的代理类,最终生成的代理类会根据URL参数里面的protocol决定(以内部Protocol为例)



三、服务注册

1、服务注册流程





2、服务注册类图详解





3、服务注册步骤

(1)步骤一:初始化配置(类图:抽象Config与初始化配置)

首先需要实例化ServiceConfig实例,声明“注册接口、接口实例、注册中心配置”,其中“ServiceBean”是实现Spring与Dubbo整合的桥梁。然后会由DubboBootstrap调用initialize方法实现configManager和Environment的初始化,其中就包括将ServiceConfig中的配置转换成内部封装的协议(ApplicationModel、ProviderModel等)

1private static void startWithExport() throws InterruptedException { 2 //初始化配置 3 ServiceConfig<DemoServiceImpl> service = new ServiceConfig<>(); 4 service.setInterface(DemoService.class); 5 service.setRef(new DemoServiceImpl()); 6 service.setApplication(new ApplicationConfig("dubbo-demo-api-provider")); 7 service.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181")); 8 //服务注册入口 9 service.export(); 10}
1public synchronized void export() { 2 if (bootstrap == null) { 3 bootstrap = DubboBootstrap.getInstance(); 4 // compatible with api call. 5 if (null != this.getRegistry()) { 6 bootstrap.registries(this.getRegistries()); 7 } 8 //初始化配置() 9 bootstrap.initialize(); 10 } 11 ...... 12 if (shouldDelay()) { 13 DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS); 14 } else { 15 //服务注册 16 doExport(); 17 } 18 19 exported(); 20 }

(2)步骤二:组装URL

根据初始化配置组转注册接口服务的URL。其中URL也是Dubbo内部通过@Adaptive注解实现SPI的核心,通过修改URL的头部协议(如:register、dubbo、injvm等),在调用

1private static final Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); 2PROTOCOL.export(wrapperInvoker)

该方法的时候,会根据不同的协议切换不通的实现类,实现了Dubbo技术架构与业务逻辑的解耦。

1private void doExportUrls() { 2 //组装后的URL格式样例 3 //registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-api-provider&dubbo=2.0.2&pid=26212®istry=zookeeper×tamp=1663049763199 4 List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true); 5 6 int protocolConfigNum = protocols.size(); 7 for (ProtocolConfig protocolConfig : protocols) { 8 //组装pathKey : org.apache.dubbo.demo.DemoService 9 String pathKey = URL.buildKey(getContextPath(protocolConfig) 10 .map(p -> p + "/" + path) 11 .orElse(path), group, version); 12 //保存接口服务 13 repository.registerService(pathKey, interfaceClass); 14 //服务注册 15 doExportUrlsFor1Protocol(protocolConfig, registryURLs, protocolConfigNum); 16 } 17}

(3)步骤三:Invoker封装(类图:Ref -> Invoker)

通过内置的动态字节码编译(默认javassist)生成Invoker代理类,然后通过反射机制生成Wrapper实例。其中Invoker是Dubbo的核心模型,Invoker是Dubbo中的实体域,也就是真实存在的。其他模型都向它靠拢或转换成它

1private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs, int protocolConfigNum) { 2 ...... 3 //组装新的URL 4 //dubbo://2.0.0.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&bind.ip=2.0.0.1&bind.port=20880&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=46528&release=&service.name=ServiceBean:/org.apache.dubbo.demo.DemoService&side=provider×tamp=1663051456562 5 URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map); 6 ...... 7 //Invoker封装 8 Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, 9 registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())); 10 //wrapper 11 DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); 12 13 //服务注册(此时URL头部协议变成了register,实际会调用RegistryProtocol) 14 Exporter<?> exporter = PROTOCOL.export(wrapperInvoker); 15 exporters.add(exporter); 16} 17 18# PROXY_FACTORY 19public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) { 20 // 动态代理类生成,反射生成实例 21 final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type); 22 return new AbstractProxyInvoker<T>(proxy, type, url) { 23 @Override 24 protected Object doInvoke(T proxy, String methodName, 25 Class<?>[] parameterTypes, 26 Object[] arguments) throws Throwable { 27 return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); 28 } 29 }; 30}

(4)步骤四:Exporter封装(类图:Invoker-> Exporter)

此时会依次调用RegistryProtocol 、DubboProtocol 将Invoker封装成Exporter,并将封装后的Exporter存储到本地map中(类似于spring bean)。然后会调用底层通信服务(默认netty)进行端口监听,此时会通过责任链模式封装Exchanger与Transporter,用于处理网络传输消息的编码/解码。

1# RegistryProtocol : export 2public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException { 3 ...... 4 //此时URL头部协议已变成dubbo 5 //dubbo://2.0.0.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&bind.ip=2.0.0.1&bind.port=20880&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=56036&release=&service.name=ServiceBean:/org.apache.dubbo.demo.DemoService&side=provider×tamp=1663052353098 6 providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener); 7 // export invoker 8 final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl); 9 10 // 此时Registry实例默认是ZookeeperRegistry 11 final Registry registry = getRegistry(originInvoker); 12 13 final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl); 14 15 // decide if we need to delay publish 16 boolean register = providerUrl.getParameter(REGISTER_KEY, true); 17 if (register) { 18 //底层调用ZK,创建node节点 19 registry.register(registeredProviderUrl); 20 } 21 .... 22} 23 24# RegistryProtocol : doLocalExport 25private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) { 26 String key = getCacheKey(originInvoker); 27 28 return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> { 29 Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl); 30 //此时会调用DubboProtocol进行exporter封装 31 return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker); 32 }); 33}
1# DubboProtocol : export 2public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { 3 ...... 4 // export service. 5 String key = serviceKey(url); 6 //exporter封装 7 DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap); 8 exporterMap.put(key, exporter); 9 ...... 10 //开启服务监听 11 openServer(url); 12 optimizeSerialization(url); 13 14 return exporter; 15}

(5)步骤五:注册服务节点

封装Exporter并开启服务端口监听后,会调用注册中心(默认Zookeeper)注册服务节点信息

1# RegistryProtocol : export 2public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException { 3 ...... 4 //此时URL头部协议已变成dubbo 5 //dubbo://2.0.0.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&bind.ip=2.0.0.1&bind.port=20880&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=56036&release=&service.name=ServiceBean:/org.apache.dubbo.demo.DemoService&side=provider×tamp=1663052353098 6 providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener); 7 // export invoker 8 final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl); 9 10 // 此时Registry实例默认是ZookeeperRegistry 11 final Registry registry = getRegistry(originInvoker); 12 13 final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl); 14 15 // decide if we need to delay publish 16 boolean register = providerUrl.getParameter(REGISTER_KEY, true); 17 if (register) { 18 //底层调用ZK,创建node节点 19 registry.register(registeredProviderUrl); 20 } 21 .... 22}

四、总结

至此,Dubbo服务注册的整体流程已大致结束,文中如有不当或者错误观点,欢迎大家评论区指出。感兴趣的同学,可以关注后续“Dubbo架构设计与源码解析”系列的文章。

点赞
收藏

评论区

加载中...

相关推荐

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

springcloud知识点总结

一.SpringCloud面试题口述1.SpringCloud和DubboSpringCloud和Dubbo都是现在主流的微服务架构SpringCloud是Apache旗下的Spring体系下的微服务解决方案Dubbo是阿里系的分布式服务治理框架从技术维度上,其实SpringCloud远远的超过Dubbo,Dubbo本身只是实现

Dubbo学习总结(1)——Dubbo入门基础与实例讲解

Dubbo是阿里巴巴SOA服务化治理方案的核心框架,每天为2,000个服务提供3,000,000,000次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。一、Dubbo简介1.1、Dubbo是什么?

Java系统和PHP系统相互调用

一、HTTPJSON方式的缺点1.JSON序列化效率低2.多语言服务治理功能低二、关于RPC框架RPC框架大致分为两类,一种是偏重服务治理,另一种侧重跨语言调用2.1服务治理型特点功能丰富,提供高性能的远程调用、服务发现及服务治理能力,适用于大型服

SpringCloud(一)之微服务核心组件Eureka(注册中心)的介绍和使用

一Eureka服务治理体系1.1服务治理服务治理是微服务架构中最为核心和基础的模块,它主要用来实现各个微服务实例的自动化注册和发现。SpringCloudEureka是SpringCloudNetflix微服务套件中的一部分,它基于NetflixEureka做了二次封装。主要负责完成微服务架构中的服务治理功能。Eur

Spring Cloud系列教程(六):服务注册与发现Consul(Finchley版本)

一、前言在微服务领域,服务注册与发现是其中很重要的一个模块,主要用于服务治理问题;在分布式Dubbo中常用的服务发现与注册中心是Zookeeper,Cosul与其类似,在SpringCloud刚占领市场的时候,SpringCloud微服务框架默认使用的注册中心组建是Eureka,总所周知,Eureka已经开始闭源了,