服务暴露

Posted qiaozhuangshi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了服务暴露相关的知识,希望对你有一定的参考价值。

须知 强烈建议看官方文档

服务暴露文档地址

服务发布-原理
发布步骤:

1. 暴露本地服务
2. 暴露远程服务
3. 启动netty
4. 打开连接zookeeper
5. 到zk注册
6. 监听zk

暴露本地服务和暴露远程服务有什么区别?

  1. 暴露本地服务:指暴露在一个JVM里面,不用通过zk来进行远程调用。例如:在同一个服务,自己调用自己的接口,就i没必要进行IP连接来通信
  2. 暴露远程服务:指暴露给远程客户端的IP和端口号,通过网络来实现通信

zk持久化节点 和 临时节点有什么区别?
持久化节点: 一旦被创建,除非主动删除,否则一直存储在zk里面
临时节点:与客户端会话绑定,一旦客户端会话失效,这个客户端所创建的所有临时节点都会被删除

服务暴露(导出)

Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑。整个逻辑大致可分为三个部分,第一部分是前置工作,主要用于检查参数,组装 URL。第二部分是导出服务,包含导出服务到本地 (JVM),和导出服务到远程两个过程。第三部分是向注册中心注册服务,用于服务发现析。

服务导出的入口方法是 ServiceBean 的 onApplicationEvent。onApplicationEvent 是一个事件响应方法,该方法会在收到 Spring 上下文刷新事件后执行服务导出操作。

ServiceBean.onApplicationEvent
-->ServiceBean.export
  -->ServiceConfig.export()
    --> ServiceConfig.doExport
      -->ServiceConfig.doExportUrls
        -->loadRegistries(true)//组装registry的url信息
          -->doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs)
            -->exportLocal(URL url) //本地暴露
             -->proxyFactory.getInvoker(ref, (Class) interfaceClass, local)
               -->ExtensionLoader.getExtensionLoader(ProxyFactory.class)
                                .getExtension("javassist");
               -->extension.getInvoker(arg0, arg1, arg2)
                  -->StubProxyFactoryWrapper.getInvoker(T proxy, Class<T> type, URL url)
                        //AOP的原因进入到了StubProxyFactoryWrapper中
                    -->proxyFactory.getInvoker(proxy, type, url)
                      -->JavassistProxyFactory.getInvoker(T proxy, Class<T> type, URL url)
                        -->Wrapper.getWrapper(com.alibaba.dubbo.demo.provider.DemoServiceImpl)
                            //这里getWrapper方法会使用JavassistProxy生成一个动态类
                          -->Wrapper.makeWrapper(Class<?> c)
                        -->return new AbstractProxyInvoker<T>(proxy, type, url)
                -->protocol.export
                 -->Protocol$Adpative.export
                   -->ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("injvm");  
                  -->extension.export(arg0)
                    -->ProtocolFilterWrapper.export
                       -->ProtocolFilterWrapper.buildInvokerChain //获取8个filter, 形成过滤连
                      -->ProtocolListenerWrapper.export
                        -->QosProtocolWrapper.export
                          -->InjvmProtocol.export
                             -->return new InjvmExporter<T>
                                (invoker, invoker.getUrl().getServiceKey(), exporterMap)
            //如果配置不是local则暴露为远程服务.(配置为local,则表示只暴露本地服务)
            -->proxyFactory.getInvoker
            -->protocol.export(invoker)
                //远程暴露 原理和本地暴露一样都是为了获取一个Invoker对象
              -->Protocol$Adpative.export
                -->ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("registry");
                 -->extension.export(arg0)
                     -->ProtocolFilterWrapper.export
                       -->ProtocolListenerWrapper.export
                         -->QosProtocolWrapper.export
                           -->RegistryProtocol.export
                             -->doLocalExport(originInvoker)
                                //读取 dubbo://192.168.100.51:20880/
                               -->getCacheKey(originInvoker) 
                               -->protocol.export
                                 -->Protocol$Adpative.export
                                   -->ExtensionLoader
                                    .getExtensionLoader(Protocol.class).getExtension("dubbo");
                                  -->extension.export(arg0)
                                    // new ListenerExporterWrapper
                                    -->ProtocolListenerWrapper.export
                                      -->QosProtocolWrapper.export
                                        // 这个aop内会生成过滤连
                                        //buildInvokerChain(invoker, service.filter, provider)
                                        -->ProtocolFilterWrapper.export
----1、netty服务暴露开始------------    -->DubboProtocol.export(invoker, key, exporterMap)
                                       // invoker对象是
                                         -->serviceKey(url) 
                                            组装key=com.alibaba.dubbo.demo.DemoService:20880

关于getExtension方法,会调用createExtension会给目标扩展类添加链式的对象 实现AOP

本地暴露和远程暴露都将 需要暴露的对象添加到了exporterMap中, 在远程调用时,会从exporterMap中获取暴露的对象来执行。

代码里本地暴露目的

? exporterMap.put(key,this)//key=com.alibaba.dubbo.demo.DemoService, this=InjvmExporter

远程暴露的目的:

? exporterMap.put(key, this)//key=com.alibaba.dubbo.demo.DemoService:20880, this=DubboExporter

下面继续

-->serviceKey(url)
-->openServer(url)
  -->createServer(url)
    -->Exchangers.bind(url, requestHandler) //exchaanger是一个信息交换层  -- 2、信息交换层开始 ---
    // bind方法里的requestHandler用于服务调用时调用目标发方法
      -->getExchanger(url)
        -->getExchanger(type)
          -->ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension("header")
      -->HeaderExchanger.bind
        -->Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))
          -->new ChannelHandlerDispatcher(handlers);
          -->new DecodeHandler
            -->new AbstractChannelHandlerDelegate //this.handler = handler
----3.网络传输层开始------
        -->Transporters.bind
          -->getTransporter()
            -->ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension()
          -->Transporter$Adpative.bind
            -->ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class)
                .getExtension("netty");
            -->extension.bind(arg0, arg1)
              -->NettyTransporter.bind
                -->new NettyServer(url, listener);
                  -->AbstractPeer //this.url = url;    this.handler = handler;
                  -->AbstractEndpoint//codec  timeout=1000  connectTimeout=3000
                  -->AbstractServer //bindAddress accepts=0 idleTimeout=600000                                     -->doOpen  ------------------- 4、打开通道,暴露netty服务------------------
                      -->bootstrap.bind(getBindAddress());
        -->new HeaderExchangeServer
            (Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
          -->this.server=NettyServer
          -->this.heartbeat = server.getUrl().getParameter("heartbeat", 0); //心跳检查周期时间
          -->heartbeatTimeout  默认180000
          -->startHeatbeatTimer()//这是一个心跳定时器,采用了线程池,如果断开就心跳重连。

到这里,关于暴露部分的服务端netty就开启了 ,绑定的地址为URL里的IP+URL里的端口

接下来是zookeeper的连接

RegistryProtocol.export
-->RegistryProtocol.getRegistry(registryUrl)
  -->registryFactory.getRegistry(registryUrl)
    -->RegistryFactory$Adaptive.getRegistry
      -->ExtensionLoader.getExtensionLoader(RegistryFactory.class).getExtension("zookeeper");
      -->extension.getRegistry(arg0)
        -->AbstractRegistryFactory.getRegistry//创建一个注册中心,存储在REGISTRIES
          -->createRegistry(url)
            -->new ZookeeperRegistry(url, zookeeperTransporter)
              -->AbstractRegistry() 调用无参构造
                -->loadProperties() //加载 “暴露信息的缓存文件” 中的信息, 见下面说明
                -->notify(url.getBackupUrls())  //回调订阅的listner (如果配置了的话)
              -->FailbackRegistry() 调用无参构造
                -->retryExecutor.scheduleWithFixedDelay(new Runnable()
                    //建立线程池,检测并连接注册中心,如果失败了就重连
              -->ZookeeperRegistry() 调用无参构造
                -->zookeeperTransporter.connect(url)
                  -->ZookeeperTransporter$Adaptive.connect
                    -->ExtensionLoader.getExtensionLoader(ZookeeperTransporter.class)
                                    .getExtension("zkclient");
                    -->extension.connect(arg0);
                      -->ZkclientZookeeperTransporter.connect
                        -->new ZkclientZookeeperClient(url)
                          -->AbstractZookeeperClient(url) //构造器 this.url = url
                          -->ZkclientZookeeperClient  //构造器
                            -->ZkClientWrapper //构造器
                              -->new ZkClient  //zookeeper客户端
                        -->zkClient.addStateListener(new StateListener() 
                            stateChanged() recover()  //失败重连
                           
-->RegistryProtocol.register  (export方法里面调用register)
  -->registry.register(registedProviderUrl)//创建节点
    -->AbstractRegistry.register
    -->FailbackRegistry.register
      -->doRegister(url)  //向zk服务器端发送注册请求
        -->ZookeeperRegistry.doRegister
          -->zkClient.create
            -->AbstractZookeeperClient.create
                 //dubbo/per.qiao.service.TestService/providers/                                            dubbo%3A%2F%2F192.168.100.52%3A20880%2F.............
              -->createEphemeral(path)  //创建临时节点
                 dubbo%3A%2F%2F192.168.100.52%3A20880%2F.............
              -->createPersistent(path);//创建永久节点 
                 dubbo/per.qiao.service.TestService/providers
-->registry.subscribe   //订阅ZK
  -->AbstractRegistry.subscribe
  -->FailbackRegistry.subscribe
    -->doSubscribe(url, listener)// 向服务器端发送订阅请求
      -->ZookeeperRegistry.doSubscribe
        -->new ChildListener() //订阅的节点发生变化时通知的具体方法
        -->zkClient.create(path, false);
          //第一步:先创建持久化节点/dubbo/per.qiao.service.TestService/configurators
        -->zkClient.addChildListener(path, zkListener)
          -->AbstractZookeeperClient.addChildListener
            -->createTargetChildListener(path, listener)
              //第三步:收到订阅后的处理,交给FailbackRegistry.notify处理
              -->ZkclientZookeeperClient.createTargetChildListener
                -->new IZkChildListener()
                  //实现了 handleChildChange //收到订阅后的处理
                  //listener.childChanged(parentPath, currentChilds);
                  //实现并执行ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url,                   //parentPath, currentChilds));    也就是上面new ChildListener()这个方法  
                 //具体是调用的FailbackRegistry.notify(收到订阅后处理)
              -->addTargetChildListener(path, targetListener)  //第二步
                -->ZkclientZookeeperClient.addTargetChildListener
                  -->ZkClientWrapper.subscribeChildChanges
                    -->client.subscribeChildChanges(path, listener)
                       // 注意: 这里才是真正的订阅zk的configurators目录 这里的client是ZkClient对象
        -->notify(url, listener, urls);
          -->FailbackRegistry.notify
            -->doNotify(url, listener, urls);
              -->AbstractRegistry.notify
                -->saveProperties(url); //把服务端注册url的信息更新到“暴露信息的缓存文件” 见下面说明
                  -->registryCacheExecutor.execute(new SaveProperties(version));
                     //采用线程池来处理保存操作

说明:

暴露信息的缓存文件路径:为:System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getParameter(Constants.APPLICATION_KEY) + "-" + url.getAddress() + ".cache"

比如我的用户为qiao,暴露的服务名为serviceImpl,暴露在地址为127.0.0.1; 那么路径为C:\\Users\\qiao.dubbo\\dubbo-registry-serviceImpl-127.0.0.1.cache

以上就是服务暴露(导出)的所有操作

主要操作有:

1. 本地暴露
2. 远程暴露
3. 启动netty
4. 注册暴露地址到zookeeper
5. 订阅(监听)zk的configurators节点  (详见 AbstractRegistry#notify)

步骤很多很复杂,建议边debug跟着断点走

主要是RegistryProtocol#export方法和DubboProtocol#export方法

RegistryProtocol#export

@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException 
    //暴露服务  委托DubboProtocol使用netty暴露
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);

    URL registryUrl = getRegistryUrl(originInvoker);

    // 注册到zookeeper和订阅事件
    final Registry registry = getRegistry(originInvoker);
    final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker);

    //是否已注册到zookeeper
    boolean register = registeredProviderUrl.getParameter("register", true);
    //保存 服务暴露信息 到 本地映射表
    ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl);
    //如果已经注册了 创建节点
    if (register) 
        register(registryUrl, registeredProviderUrl);
        ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
    

    // 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(registeredProviderUrl);
    final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
    //订阅ZK
    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
  
    //返回服务暴露后的实例
    //p1: 已暴露结果对象, p2: 原需要暴露的对象, p3: 订阅地址,  p4: 服务注册地址
    return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);

Registryprotocol进入到DubboProtocol的中间方法

Registryprotocol#doLocalExport

private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) 
    //暴露的服务信息
    String key = getCacheKey(originInvoker);
    ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
    if (exporter == null) 
        synchronized (bounds) 
            exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
            if (exporter == null) 
                //将暴露信息封装成一个invoker(包含url和invoker对象)
                //里面的url实际上是配置的暴露信息,toString后与上面key的字符串一模一样
                final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
               //protocol.export调用到Protocol$Adaptive的export然后再调到DubboProtocol#export
                exporter = new ExporterChangeableWrapper<T>
                  ((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
                bounds.put(key, exporter);
            
        
    
    return exporter;

暴露的服务信息(url):

dubbo://192.168.199.223:9000/per.qiao.service.TestService?anyhost=true&application=serviceImpl&bean.name=per.qiao.service.TestService&bind.ip=192.168.199.223&bind.port=9000&dubbo=2.0.2&generic=false&interface=per.qiao.service.TestService&methods=getData&pid=13604&revision=1.0-SNAPSHOT&side=provider&timeout=10000&timestamp=1558757097945

DubboProtocol#export

/**
 * @param invoker 需要暴露的对象
 * @return 返回暴露结果对象
 */
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException 
    //暴露的URL对象
    URL url = invoker.getUrl();

    // export service.
    String key = serviceKey(url);
    //暴露的结果对象
    DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
    //保存暴露的对象
    exporterMap.put(key, exporter);

    //export an stub service for dispatching event
    Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT);
    Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
    if (isStubSupportEvent && !isCallbackservice) 
        String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
        if (stubServiceMethods == null || stubServiceMethods.length() == 0) 
            ...
         else 
            stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
        
    
    // 打开netty服务
    openServer(url);
    optimizeSerialization(url);
    return exporter;

Dubbo使用javassist生成动态类

以上是关于服务暴露的主要内容,如果未能解决你的问题,请参考以下文章

c++中的纯虚函数机制如何从DLL中暴露函数

Dubbo服务暴露分析

暴露的下拉菜单不显示项目

dubbo起停之服务暴露

7.6 服务远程暴露 - 注册服务到zookeeper

面试官问我:解释一下Dubbo服务暴露