RPC框架(六)dubbo源码分析--dubbo服务提供者初始化

  • 一、概述
  • 二、选用服务端口
  • 三、生成URL对象
  • 四、生成本地服务代理
  • 五、生成远程服务代理
  • 六、启动服务监听
  • 七、服务注册到注册中心
  • 八、dubbo服务初始化与暴露服务总结

一、概述

dubbo服务提供者由dubbo:service来定义,从前面可以看到,Spring把dubbo:service解析成一个ServiceBean,ServiceBean实现了ApplicationListener和InitializingBean接口,ServiceBean有个核心方法export,在这个方法中初始化服务提供者并且暴露远程服务。这个方法在bean初始化或容器中所有bean刷新完毕时被调用,根据provider的延迟设置决定,如果设置了延迟(delay属性)在bean初始化结束之后调用否则容易刷新事件中被调用,默认会延迟export,即在所有bean的刷新结束时间中被调用。
在com.alibaba.dubbo.config.spring.ServiceBean类下

public void onApplicationEvent(ApplicationEvent event) {
    if (ContextRefreshedEvent.class.getName().equals(event.getClass().getName())) {
        if (isDelay() && ! isExported() && ! isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + getInterface());
            }
            export();
        }
    }
}

在export方法中,总体上export方法暴露服务时主要做了下列这些步骤:

  • 选用服务端口
  • 生成URL对象
  • 生成本地服务代理(如果需要)
  • 生成远程服务代理
  • 启用服务监听
  • 服务注册到注册中心

二、选用服务端口

服务需要一个端口来进行服务调用侦听,框架默认会从20880开始选定一个未被占用的端口来提供服务,也可以在配置中的port参数指定服务的端口:
com.alibaba.dubbo.config.ServiceConfig.doExportUrlsFor1Protocol(ProtocolConfig, List)

final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort();
if (port == null || port == 0) {
    port = defaultPort;
}
if (port == null || port <= 0) {
    port = getRandomPort(name);
    if (port == null || port < 0) {
        port = NetUtils.getAvailablePort(defaultPort);
        putRandomPort(name, port);
    }
    logger.warn("Use random available port(" + port + ") for protocol " + name);
}

三、生成URL对象

URL对象是dubbo框架中的核心模型,它携带了调用链路中注册中心、消费端配置、提供端配置等全部信息。无论是消费端代理还是提供端代理都需持有一个URL对象携带服务调用时的上下文信息。
在服务初始化时需要对注册中心进行访问,所以需要一个URL来跟注册中心通信,根据应用的注册中心配置来生成这个URL,URL协议是registry,包含了注册中心地址、端口,path部分是com.alibaba.dubbo.registry.RegistryService类名,其中还有dubbo版本、应用名称等信息放在URL的参数中,格式化之后是这种形式:registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=…

除了注册中心URL,还需要生成一个URL来携带服务本身的信息,协议由应用侧指定,在我们的示例配置中是dubbo协议,host是本机ip,端口是上面选中的随机或配置中指定的端口,path是服务对应的接口class全名(含包路径),添加side参数provider,dubbo版本号、服务方法名称、group等等,格式化之后是这种形式:

dubbo://127.0.0.1:20880/com.netease.haitao.mykaola.generic.api.InternalStaffServiceFacade?…&createInternalStaffOperDetail.timeout=10000&…&default.group=hzchenyunyun1&…&dubbo=3.0.0&…&methods=…&…&side=provider&… 

这个url会被设置到registry URL的export属性中,这点很重要,后面的初始化过程是围绕registry URL,需要从这个export属性中拿到提供者服务URL。

四、生成本地服务代理

如果scope属性没有被设置成remote,服务同时会在本地暴露,生成一个本地服务代理对象,这里会生成一个新的URL,协议是代表本地服务的injvm,host是127.0.0.1端口是0,生成一个InjvmExporter,这时本地调用dubbo接口时直接调用本地代理不走网络请求。生成Exporter的过程和生成远程Exporter的过程类似,在后面详细描述。

com.alibaba.dubbo.config.ServiceConfig.exportLocal(URL)

private void exportLocal(URL url) {
    if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
        URL local = URL.valueOf(url.toFullString())
                .setProtocol(Constants.LOCAL_PROTOCOL)
                .setHost(NetUtils.LOCALHOST)
                .setPort(0);
        Exporter exporter = protocol.export(
                proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
        exporters.add(exporter);
        logger.info("Export dubbo service " + interfaceClass.getName() +" to local registry");
    }
}

五、生成远程服务代理

如果scope属性没有被设置成local,生成远程服务代理,框架提供了ProxyFactory生成服务代理(dubbo提供JDK动态代理和Javaassist代理,默认使用Javaassist,使用者也可以替换成其他的代理,这里不展开),它会生成一个Invoker,该Invoker是服务ref的一个代理包含了携带服务信息的URL对象。这里的ref就是在dubbo:service中配置的ref属性,指定服务的具体实现类,Invoker的invoke方法被调用时,最终会调用到ref指定的服务实现。这里的ProxyFactory使用ExtensionLoader的自适应扩展点加载,如应用侧没有特别指定,默认的是JavassistProxyFactory。

com.alibaba.dubbo.config.ServiceConfig.doExportUrlsFor1Protocol(ProtocolConfig, List)

//如果配置不是local则暴露为远程服务.(配置为local,则表示只暴露远程服务)
if (! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope) ){
    if (logger.isInfoEnabled()) {
        logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
    }
    if (registryURLs != null && registryURLs.size() > 0
            && url.getParameter("register", true)) {
        for (URL registryURL : registryURLs) {
            url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
            URL monitorUrl = loadMonitor(registryURL);
            if (monitorUrl != null) {
                url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
            }
            if (logger.isInfoEnabled()) {
                logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
            }
            Invoker invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));

            Exporter exporter = protocol.export(invoker);
            exporters.add(exporter);
        }
    } else {
        Invoker invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);

        Exporter exporter = protocol.export(invoker);
        exporters.add(exporter);
    }
}
public class JavassistProxyFactory extends AbstractProxyFactory {

   @SuppressWarnings("unchecked")
   public  T getProxy(Invoker invoker, Class[] interfaces) {
       return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
   }

   public  Invoker getInvoker(T proxy, Class type, URL url) {
       // TODO Wrapper类不能正确处理带$的类名
       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);
           }
       };
   }
}

从上面的代码里面看到,创建Invoker之后接下来就是创建一个Exporter,由Protocol组件来创建。这里的Protocol扩展点也是自适应加载,而当前的Url的协议是registry,自适应protocol最终会调用ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(“registry”), Protocol定义了ProtocolListenerWrapper和ListenerExporterWrapper两个Wrapper扩展点,而当前的Url的协议是registry,根据ExtensionLoader的加载规则,它会返回ProtocolListenerWrapper->ListenerExporterWrapper->RegistryProtocol对象链,对于registry协议,两个Wrapper都不会做任何处理,会直接调到RegistryProtocol.export方法。

com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper

public  Exporter export(Invoker invoker) throws RpcException {
    if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
        return protocol.export(invoker);
    }
    return new ListenerExporterWrapper(protocol.export(invoker), 
            Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
                    .getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));
}

com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper

public  Exporter export(Invoker invoker) throws RpcException {
    if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
        return protocol.export(invoker);
    }
    return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}

RegistryProtocol.export会调用doLocalExport方法,执行服务暴露逻辑:
com.alibaba.dubbo.registry.integration.RegistryProtocol.doLocalExport(Invoker)

@SuppressWarnings("unchecked")
private  ExporterChangeableWrapper  doLocalExport(final Invoker originInvoker){
    String key = getCacheKey(originInvoker);
    ExporterChangeableWrapper exporter = (ExporterChangeableWrapper) bounds.get(key);
    if (exporter == null) {
        synchronized (bounds) {
            exporter = (ExporterChangeableWrapper) bounds.get(key);
            if (exporter == null) {
                final Invoker invokerDelegete = new InvokerDelegete(originInvoker, getProviderUrl(originInvoker));
                exporter = new ExporterChangeableWrapper((Exporter)protocol.export(invokerDelegete), originInvoker);
                bounds.put(key, exporter);
            }
        }
    }
    return (ExporterChangeableWrapper) exporter;
}

接着调用protocol.export,这时候url发生了变化不再是registryUrl了,而是存放在registryUrl的export参数中的providerUrl,根据不同协议会定位到不同的扩展,示例配置是dubbo协议,对应的实现是DubboProtocol,同样地根据Wrapper扩展点加载机制会加载出ProtocolListenerWrapper和 ListenerExporterWrapper两个Wrapper,然后依次调用ProtocolListenerWrapper->ListenerExporterWrapper->DubboProtocol的export方法。从上面的代码可以看到,ProtocolListenerWrapper.export会创建一个ListenerExporterWrapper实例,并添加所有激活的ExporterListener到ListenerExporterWrapper实例中,再经过ProtocolFilterWrapper处理,加载所有激活的Filter,并且构建Invoker-Filter链包装成新的Invoker。接着会调用DubboProtocol.export方法生成一个DubboExporter对象,Exporter中持有上面包装Filter链后的Invoker对象引用和url对应的key(key的生成规则在com.alibaba.dubbo.rpc.support.ProtocolUtils.serviceKey(URL)方法中),该Exporter对象会注册到DubboProtocol的exporterMap中,服务器监听到服务调用之后会在这个map中查找Exporter,并封装的Invoker。
com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol

public  Exporter export(Invoker invoker) throws RpcException {
    URL url = invoker.getUrl();

    // export service.
    String key = serviceKey(url);
    DubboExporter exporter = new DubboExporter(invoker, key, exporterMap);
    exporterMap.put(key, exporter);

    //export an stub service for dispaching 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 ){
            if (logger.isWarnEnabled()){
                logger.warn(new IllegalStateException("consumer [" +url.getParameter(Constants.INTERFACE_KEY) +
                        "], has set stubproxy support event ,but no stub methods founded."));
            }
        } else {
            stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
        }
    }

    openServer(url);

    return exporter;
}

六、启动服务监听

上面已经生成了服务代理,为了能收到消费端的服务请求,还要在上面选中的端口上监听消费端的请求,调用DubboProtocol的openServer方法,dubbo使用了Netty、grizzly、Mina三种网络框架,框架默认使用Netty,示例配置没有特殊配置,主要看一下Netty。启动服务时组件的调用顺序:Exchanger.bind->Transporter.bind->Server,Exchanger和Transporter也是通过ExtensionLoader来加载,会加载到NettyTransporter实现,它会创建生成一个NettyServer实例,在构造NettyServer实例时调用doOpen打开端口监听, 并添加三个IO事件处理器,NettyCodecAdapter负责请求解码、响应编码,nettyHandler处理请求,把NettyCodecAdapter.decoder->NettyCodecAdapter.encoder->NettyHandler IO事件处理器链到Netty框架中,服务调用消息由多层框架内部的Handler转发最终会转发到DubboProcotol的requestHandler中处理,服务的调用流程后面再详细分析。

com.alibaba.dubbo.remoting.transport.netty.NettyServer.doOpen()

@Override
protected void doOpen() throws Throwable {
    NettyHelper.setNettyLoggerFactory();
    ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true));
    ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));
    ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));
    bootstrap = new ServerBootstrap(channelFactory);

    final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
    channels = nettyHandler.getChannels();
    // https://issues.jboss.org/browse/NETTY-365
    // https://issues.jboss.org/browse/NETTY-379
    // final Timer timer = new HashedWheelTimer(new NamedThreadFactory("NettyIdleTimer", true));
    bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
        public ChannelPipeline getPipeline() {
            NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec() ,getUrl(), NettyServer.this);
            ChannelPipeline pipeline = Channels.pipeline();
            /*int idleTimeout = getIdleTimeout();
            if (idleTimeout > 10000) {
                pipeline.addLast("timer", new IdleStateHandler(timer, idleTimeout / 1000, 0, 0));
            }*/
            pipeline.addLast("decoder", adapter.getDecoder());
            pipeline.addLast("encoder", adapter.getEncoder());
            pipeline.addLast("handler", nettyHandler);
            return pipeline;
        }
    });
    // bind
    channel = bootstrap.bind(getBindAddress());
}

七、服务注册到注册中心

服务起好之后,接下来是把提供者注册到注册中心,和注册中心建立联系。回到RegistryProtocol.export方法,框架由RegistryFactory加载出一个Registry组件来处理注册中心相关的逻辑。

/**
 * 根据invoker的地址获取registry实例
 * @param originInvoker
 * @return
 */
private Registry getRegistry(final Invoker originInvoker){
    URL registryUrl = originInvoker.getUrl();
    if (Constants.REGISTRY_PROTOCOL.equals(registryUrl.getProtocol())) {
        String protocol = registryUrl.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_DIRECTORY);
        registryUrl = registryUrl.setProtocol(protocol).removeParameter(Constants.REGISTRY_KEY);
    }
    return registryFactory.getRegistry(registryUrl);
}

registryFactory是啥时候设置进来的?dubbo框架在创建组件实例的时候,它会扫描所有的set方法,如果set方法的参数类型是可扩展的组件类型,会加载对应的扩展点实例并设置进去。

com.alibaba.dubbo.common.extension.ExtensionLoader

private T injectExtension(T instance) {
    try {
        if (objectFactory != null) {
            for (Method method : instance.getClass().getMethods()) {
                if (method.getName().startsWith("set")
                        && method.getParameterTypes().length == 1
                        && Modifier.isPublic(method.getModifiers())) {
                    Class pt = method.getParameterTypes()[0];
                    try {
                        String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            method.invoke(instance, object);
                        }
                    } catch (Exception e) {
                        logger.error("fail to inject via method " + method.getName()
                                + " of interface " + type.getName() + ": " + e.getMessage(), e);
                    }
                }
            }
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}

Dubbo内置支持zookeeper、redis、广播注册中心。实例配置的zookeeper注册中心,对应ZookeeperRegistry。提供者会在zookeeper注册中心生成一个节点,节点路径为/dubbo/interfaceClass/providers/interfaceClass/providers/{providerUrl},存储了服务提供方ip、端口、group、接口名称、版本、应用名称(redis注册中心通过set命令把这些信息缓存到redis服务器)。

com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry.doRegister(URL)

protected void doRegister(URL url) {
    try {
        zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
    } catch (Throwable e) {
        throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

为了感知注册中心的一些配置变化,提供者会监听注册中心路径/dubbo/${interfaceClass}/configurators的节点,监听该节点在注册中心的一些配置信息变更。zookeeper注册中心通过zookeeper框架的监听回调接口进行监听(redis注册中心通过订阅命令(subscribe)监听),服务器缓存注册中心的配置,当配置发生变更时,服务会刷新本地缓存,代码在ZookeeperRegistry的doSubscribe方法。

protected void doSubscribe(final URL url, final NotifyListener listener) {
    try {
        if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
            String root = toRootPath();
            ConcurrentMap listeners = zkListeners.get(url);
            if (listeners == null) {
                zkListeners.putIfAbsent(url, new ConcurrentHashMap());
                listeners = zkListeners.get(url);
            }
            ChildListener zkListener = listeners.get(listener);
            if (zkListener == null) {
                listeners.putIfAbsent(listener, new ChildListener() {
                    public void childChanged(String parentPath, List currentChilds) {
                        for (String child : currentChilds) {
                            if (! anyServices.contains(child)) {
                                anyServices.add(child);
                                subscribe(url.setPath(child).addParameters(Constants.INTERFACE_KEY, child, 
                                        Constants.CHECK_KEY, String.valueOf(false)), listener);
                            }
                        }
                    }
                });
                zkListener = listeners.get(listener);
            }
            zkClient.create(root, false);
            List services = zkClient.addChildListener(root, zkListener);
            if (services != null && services.size() > 0) {
                anyServices.addAll(services);
                for (String service : services) {
                    subscribe(url.setPath(service).addParameters(Constants.INTERFACE_KEY, service, 
                            Constants.CHECK_KEY, String.valueOf(false)), listener);
                }
            }
        } else {
            List urls = new ArrayList();
            for (String path : toCategoriesPath(url)) {
                ConcurrentMap listeners = zkListeners.get(url);
                if (listeners == null) {
                    zkListeners.putIfAbsent(url, new ConcurrentHashMap());
                    listeners = zkListeners.get(url);
                }
                ChildListener zkListener = listeners.get(listener);
                if (zkListener == null) {
                    listeners.putIfAbsent(listener, new ChildListener() {
                        public void childChanged(String parentPath, List currentChilds) {
                            ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
                        }
                    });
                    zkListener = listeners.get(listener);
                }
                zkClient.create(path, false);
                List children = zkClient.addChildListener(path, zkListener);
                if (children != null) {
                    urls.addAll(toUrlsWithEmpty(url, path, children));
                }
            }
            notify(url, listener, urls);
        }
    } catch (Throwable e) {
        throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

上面是服务提供者在初始化时做的一些主要动作,其实框架做的事远比这多了,先暂时先忽略一些细节,专项功能放到后面再逐个击破。

八、dubbo服务初始化与暴露服务总结

  • spring容器启动时会解析dubbo的自定义标签,解析的最终目的是返回 RootBeanDefinition
    对象,RootBeanDefinition包含了解析出来的关于bean的所有信息,注意,在bean的解析完后其实只是spring将其转化成spring内部的一种抽象的数据对象结构,bean的创建(实例化)是第一次调用 getBean 时创建的。

  • 例如dubbo:service标签被解析成一个ServiceBean,ServiceBean实现了ApplicationListener和InitializingBean接口,ServiceBean有个核心方法export,在这个方法中初始化服务提供者并且暴露远程服务。这个方法在bean初始化或容器中所有bean刷新完毕时被调用,根据provider的延迟设置决定,如果设置了延迟(delay属性)在bean初始化结束之后调用否则容易刷新事件中被调用,默认会延迟export,即在所有bean的刷新结束时间中被调用。

  • 生成url,
    a.注册中心的url:在服务初始化时需要对注册中心进行访问,所以需要一个URL来跟注册中心通信,根据应用的注册中心配置来生成这个URL,URL协议是registry,包含了注册中心地址、端口,path部分是com.alibaba.dubbo.registry.RegistryService类名,其中还有dubbo版本、应用名称等信息放在URL的参数中,格式化之后是这种形式:registry://10.165.124.205:2181/com.alibaba.dubbo.registry.RegistryService?application=…
    b.服务本身的url:还需要生成一个URL来携带服务本身的信息,协议由应用侧指定,在我们的示例配置中是dubbo协议,host是本机ip,端口是上面选中的随机或配置中指定的端口,path是服务对应的接口class全名(含包路径),添加side参数provider,dubbo版本号、服务方法名称、group等等,格式化之后是这种形式:

dubbo://10.240.176.159:20880/com.netease.haitao.mykaola.generic.api.InternalStaffServiceFacade?…&createInternalStaffOperDetail.timeout=10000&…&default.group=hzchenyunyun1&…&dubbo=3.0.0&…&methods=…&…&side=provider&… 
  • 生成远程服务代理,生成代理的目的是你调用 invoker 的相关函数后,就会等于是调用 DubboInvoker 中的相关函数;也就是将本地调用转为网络调用并获得结果;

  • 启动服务监听,框架默认使用Netty,打开端口监听,
    并添加三个IO事件处理器,NettyCodecAdapter负责请求解码、响应编码,nettyHandler处理请求。

  • 暴露服务(将服务注册到注册中心),提供者会在zookeeper注册中心生成一个节点,此节点存储了服务提供方ip、端口、group、接口名称、版本、应用名称(redis注册中心通过set命令把这些信息缓存到redis服务器)。

    为了感知注册中心的一些配置变化,提供者会监听注册中心路径/dubbo/${interfaceClass}/configurators的节点,监听该节点在注册中心的一些配置信息变更。zookeeper注册中心通过zookeeper框架的监听回调接口进行监听(redis注册中心通过订阅命令(subscribe)监听),服务器缓存注册中心的配置,当配置发生变更时,服务会刷新本地缓存,代码在ZookeeperRegistry的doSubscribe方法。

你可能感兴趣的:(DUBBO,Dubbo)