[dubbo源码之]1.服务提供方如何发布服务

编程

  • Java API:

// 1. 创建ServiceConfig实例

ServiceConfig<igreetingservice> serviceConfig = new ServiceConfig<>();

// 2. 设置应用程序配置

serviceConfig.setApplication(new ApplicationConfig("deep-in-dubbo-first-provider"));

// 3. 设置注册中心

RegistryConfig registryConfig = new RegistryConfig("zookeeper://127.0.0.1:2181/");

serviceConfig.setRegistry(registryConfig);

// 4. 设置接口和实现类

// 5. 设置服务分组和版本

// dubbo中,服务接口+服务分组+服务版本 唯一的确定一个服务,同一个接口可以有不同版本,方便维护升级

serviceConfig.setInterface(IGreetingService.class);

serviceConfig.setRef(new GreetingServiceImpl());

serviceConfig.setVersion("1.0.0");

serviceConfig.setGroup("dubbo-sxzhongf-group");

RpcContext.getContext().setAttachment("age","18");

        // 7. 导出服务,启动Netty监听链接请求,并将服务注册到注册中心

serviceConfig.export();

// 8. 挂起线程,避免服务停止

System.out.println("api provider service is started...");

System.in.read();

```

  • XML

    <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xmlns="http://www.springframework.org/schema/beans" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd

    http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <!-- provider"s application name, used for tracing dependency relationship -->

    <dubbo:application name="first-xml-provider" />

    <!-- use multicast registry center to export service -->

    <dubbo:registry address="zookeeper://127.0.0.1:2181/" />

    <!-- use dubbo protocol to export service on port 20880 -->

    <dubbo:protocol name="dubbo" port="20880" />

    <!-- service implementation, as same as regular local bean -->

    <bean id="demoService" class="com.sxzhongf.deep.in.dubbo.provider.service.impl.GreetingServiceImpl" />

    <!-- declare the service interface to be exported -->

    <dubbo:service interface="com.sxzhongf.deep.in.dubbo.api.service.IGreetingService" ref="demoService" version="1.0.0" group="dubbo-sxzhongf-group">

    <dubbo:method name="sayHello" async="false" timeout="0" retries="3"></dubbo:method>

    <dubbo:method name="testGeneric" async="false" timeout="10000" retries="3"></dubbo:method>

    </dubbo:service>

    </beans>

查看export源码可知,总共有三种服务导出选项:

java public synchronized void export() { //1. 是否导出 if (!shouldExport()) { return; } ... //2.延迟导出 if (shouldDelay()) { DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS); } else { //3.立刻导出 doExport(); } }

2.ServiceConfig#doExport

> 此方法主要是根据设置的属性进行合法性检查,主要包含是否已被导出,doExportUrls();

3.doExportUrls

4.ConfigValidationUtils#loadRegistries

> 此方法用来加载所有的服务注册中心对象,在dubbo中,一个service可以被注册到多个注册中心。

>

> 通过doExportUrlsFor1Protocol(protocolConfig, registryURLs);

5.doExportUrlsFor1Protocol

> 在此方法中会将所有的参数封装成org.apache.dubbo.common.URL对象,然后执行具体的服务导出。

具体过程分为:

  • 1.解析MethodConfig配置(单独的方法调用参数设置)

  • 2.泛型调用类型设置

  • 3.拼接URL参数

  • 4.导出具体服务

    导出又分为四种范围(scope):

    • SCOPE_NONE = "none",如果设定为none,表示该服务不导出。

    • SCOPE_LOCAL = "local" ,如果设定为local,表示该服务导出到本地(injvm--伪协议,实现类为:org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol

      • SCOPE_REMOTE = "remote",如果设定为remote,表示该服务导出到远程。

    • 如果有注册中心,发布到注册中心

    • 如果没有注册中心,则表示服务是直连方式

    • dubbo-2.7.0开始,新增加了WritableMetadataService 来存储dubbo 服务的元数据,元数据可以存储在远端配置中心和本地,默认是存储在本地,通过设置:METADATA_KEY = "metadata"

      • DEFAULT_METADATA_STORAGE_TYPE = "local"

      • REMOTE_METADATA_STORAGE_TYPE = "remote"

            /**

        * @since 2.7.0

        * ServiceData Store

        */

        WritableMetadataService metadataService = WritableMetadataService.getExtension(url.getParameter(METADATA_KEY, DEFAULT_METADATA_STORAGE_TYPE));

        if (metadataService != null) {

        metadataService.publishServiceDefinition(url);

        }

      • 不设置,导出到本地和远端

    • 最终执行导出的代码如下

      // 扩展适配类

      private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

      /**

      * A {@link ProxyFactory} implementation that will generate a exported service proxy,the JavassistProxyFactory is its

      * default implementation

      */

      // 扩展适配类

      private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

      ...

      Invoker<!--?--> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));

      DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

      Exporter<!--?--> exporter = protocol.export(wrapperInvoker);

      exporters.add(exporter);

      由于protocolPROXY_FACTORY都是扩展适配类,跟踪代码我们可以发现:

      • 执行PROXY_FACTORY.getInvoker的时候实际上首先执行扩展接口ProxyFactory的适配类ProxyFactory$AdaptivegetInvoker方法,根据URL中参数proxy的设置类型选择具体的代理工厂,默认使用的是javassist,,因此又调用了org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory#getInvoker来获取代理实现类,代码如下:

        /**

        * JavaassistRpcProxyFactory

        */

        public class JavassistProxyFactory extends AbstractProxyFactory {

        ...

        @Override

        public <t> Invoker<t> getInvoker(T proxy, Class<t> type, URL url) {

        // TODO Wrapper cannot handle this scenario correctly: the classname contains "$"

        // 这里使用javassist动态代理生成serviceImpl实现类的包装类`Wraaper...`

        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf("$") &lt; 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);

        }

        };

        }

        ...

        }

        上面代码有2个目的:

        1. inal Wrapper wrapper = Wrapper.getWrapper(...);用来生成具体serviceImpl的包装类,减少反射的性能损耗;
        2. return new AbstractProxyInvoker<t>... 返回了一个抽象的代理invoker,并且重写了doInvoker方法,重写之后使用包装类中的invokeMethod来调用方法。

        经过上述2步,服务提供方就将具体的实现类转换为Invoker代理。

      • 然后,当执行protocol.export(),实际上也是调用了Protocol$Adaptive#export()方法,同时也分为两种情况

        • 如果为远程暴露,则执行RegistryProtocol#export
        • 如果为本地暴露,则只需InjvmProtocol#export

        由于dubbo的增强SPI特性支持,injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));,则在调用之前会一层一层调用,ProtocolFilterWrapper->ProtocolListenerWrapper->QosProtocolWrapper,最后会调用export方法,此方法会将Invoker转换为Exporter对象,在org.apache.dubbo.registry.integration.RegistryProtocol#export方法中,org.apache.dubbo.registry.integration.RegistryProtocol#doLocalExport方法启NettyServer来监听服务,org.apache.dubbo.registry.integration.RegistryProtocol#register将当前的服务注册到注册中心。

        • doLocalExport 是如何启动NettyServer呢?

              private <t> ExporterChangeableWrapper<t> doLocalExport(final Invoker<t> originInvoker, URL providerUrl) {

          String key = getCacheKey(originInvoker);

          return (ExporterChangeableWrapper<t>) bounds.computeIfAbsent(key, s -&gt; {

          Invoker<!--?--> invokerDelegate = new InvokerDelegate&lt;&gt;(originInvoker, providerUrl);

          return new ExporterChangeableWrapper&lt;&gt;((Exporter<t>) protocol.export(invokerDelegate), originInvoker);

          });

          }

          此时URL中的protocol类型为默认的dubbo,因此会执行DubboProtocol#export进行转换,如下:

          @Override

          public <t> Exporter<t> export(Invoker<t> invoker) throws RpcException {

          URL url = invoker.getUrl();

          // export service.

          String key = serviceKey(url);

          // invoker-&gt;exporter

          DubboExporter<t> exporter = new DubboExporter<t>(invoker, key, exporterMap);

          exporterMap.put(key, exporter);

          //export an stub service for dispatching event

          Boolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT);

          Boolean isCallbackservice = url.getParameter(IS_CALLBACK_SERVICE, false);

          if (isStubSupportEvent &amp;&amp; !isCallbackservice) {

          String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY);

          if (stubServiceMethods == null || stubServiceMethods.length() == 0) {

          if (logger.isWarnEnabled()) {

          logger.warn(new IllegalStateException("consumer [" + url.getParameter(INTERFACE_KEY) +

          "], has set stubproxy support event ,but no stub methods founded."));

          }

          } else {

          stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);

          }

          }

          //创建server

          openServer(url);

          //序列化提示

          optimizeSerialization(url);

          return exporter;

          }

          可以看到代码执行到openServer,因为key=getAddress()=ip+port,因此,同一台机器只会开启一个NettyServer.

              private void openServer(URL url) {

          // find server.

          String key = url.getAddress();

          //client can export a service which"s only for server to invoke

          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 supports reset, use together with override

          server.reset(url);

          }

          }

          }

          对于org.apache.dubbo.remoting.Transporter 的适配类选择有三种:MinaTransporterNettyTransporterGrizzlyTransporter,关于JavaNIO:Apache Mina、JBoss Netty、Sun Grizzly 框架对比:传送门

        • NettyServer启动之后,回到org.apache.dubbo.registry.integration.RegistryProtocol#export方法,继续执行将服务注册到注册中心,我们以Zookeeper为例:

          • 1.首先查找所有注册中心

            final Registry registry = getRegistry(originInvoker);

            ...

            protected Registry getRegistry(final Invoker<!--?--> originInvoker) {

            URL registryUrl = getRegistryUrl(originInvoker);

            return registryFactory.getRegistry(registryUrl);

            }

            因为RegistryFactory是一个SPI扩展接口,代码中设置的为zookeeper,因此这里调用的是ZookeeperRegistryFactory,继承自:org.apache.dubbo.registry.support.AbstractRegistryFactory#getRegistry(org.apache.dubbo.common.URL),在此方法中调用了createRegistry,但是ZookeeperRegistryFactory重写了createRegistry,因此具体调用的是ZookeeperRegistryFactory#createRegistry,该方法返回了一个new ZookeeperRegistry(url, zookeeperTransporter)实例对象。

          • 2.开始注册,RegistryProtocol#register方法执行注册动作,首先获取到我们在上一步找到的注册中心ZookeeperRegistry,ZookeeperRegistry 执行父类org.apache.dubbo.registry.support.FailbackRegistry#register,在该方法中会调用抽象方法:doRegister,ZookeeperRegistry 重写了改方法,则执行ZookeeperRegistry#doRegister ,如下:

            @Override

            public void doRegister(URL url) {

            try {

            zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));

            } catch (Throwable e) {

            throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);

            }

            }

          • 3.toUrlPath方法会把org.apache.dubbo.common.URL转换格式后存储到zookeeper,如下:

            dubbo://172.16.44.21:20880/com.sxzhongf.deep.in.dubbo.api.service.IGreetingService?anyhost=true&amp;application=deep-in-dubbo-first-provider&amp;default=true&amp;deprecated=false&amp;dubbo=2.0.2&amp;dynamic=true&amp;generic=false&amp;group=dubbo-sxzhongf-group&amp;interface=com.sxzhongf.deep.in.dubbo.api.service.IGreetingService&amp;methods=sayHello,testGeneric&amp;pid=8480&amp;release=2.7.5&amp;revision=1.0.0&amp;side=provider&amp;timestamp=1582872610313&amp;version=1.0.0

            -----------------------转换------------------------

            /dubbo/com.sxzhongf.deep.in.dubbo.api.service.IGreetingService/providers/dubbo%3A%2F%2F172.16.44.21%3A20880%2Fcom.sxzhongf.deep.in.dubbo.api.service.IGreetingService%3Fanyhost%3Dtrue%26application%3Ddeep-in-dubbo-first-provider%26default%3Dtrue%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26group%3Ddubbo-sxzhongf-group%26interface%3Dcom.sxzhongf.deep.in.dubbo.api.service.IGreetingService%26methods%3DsayHello%2CtestGeneric%26pid%3D8480%26release%3D2.7.5%26revision%3D1.0.0%26side%3Dprovider%26timestamp%3D1582872610313%26version%3D1.0.0

            转换之后的格式其实就是我们在zookeeper中看到的一样了,不过有几个目录:

            • dubbo
            • com.sxzhongf.deep.in.dubbo.api.service.IGreetingService
            • providers

            [zk: localhost:2181(CONNECTED) 2] ls  /dubbo/com.sxzhongf.deep.in.dubbo.api.service.IGreetingService/providers

            [dubbo%3A%2F%2F172.16.44.21%3A20880%2Fcom.sxzhongf.deep.in.dubbo.api.service.IGreetingService%3Fanyhost%3Dtrue%26application%3Ddeep-in-dubbo-first-provider%26default%3Dtrue%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26group%3Ddubbo-sxzhongf-group%26interface%3Dcom.sxzhongf.deep.in.dubbo.api.service.IGreetingService%26methods%3DsayHello%2CtestGeneric%26pid%3D15716%26release%3D2.7.5%26revision%3D1.0.0%26side%3Dprovider%26timestamp%3D1582872850187%26version%3D1.0.0]

至此,服务消费端就可以从注册中心获取服务提供service进行调用了,下节我们继续来分析,消费端是如何从注册中心拉取service进行处理的。


奔跑的人生 | 博客园 | segmentfault | spring4all | csdn | 掘金 | OSChina | 简书 | 头条 | 知乎 | 51CTO</t></t></t></t></t></t></t></t></t></t></t></t></t></t></t></igreetingservice>

以上是 [dubbo源码之]1.服务提供方如何发布服务 的全部内容, 来源链接: utcz.com/z/513921.html

回到顶部