转载

理解 Dubbo 服务暴露

说明

本文只探讨 dubbo 服务暴露的相关内容 , 服务暴露可以划分为本地暴露 , 网络暴露 , 注册中心暴露这三部分 , 对照 Dubbo Architecture 中的 0. start , 1.register 这两部分。也可以说是 provider 的发布过程。本人能力有限不能将所有细节探究的很清楚,希望能和大家多交流相互学习。

理解 Dubbo 服务暴露

dubbo 服务暴露顺序

服务的暴露过程大致上分为四个过程 ,容器初始化 , 服务本地暴露 , 服务网络暴露 , 服务注册中心暴露。

理解 Dubbo 服务暴露

在容器初始化时会读取配置文件并解析 , dubbo 使用

com.alibaba.dubbo.config.spring.schema.DubboBeanDefinitionParser
com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler复制代码

来进行配置文件的解析 , 解析每一个 <dubbo:service /> 配置时都会对应的在 spring 容器中初始化一个 ServiceBean 的实例对象 , ServiceBean 中封装了用户自定义的服务接口实现类的实例对象。 ServiceBean 实现了 spring 的 ApplicationListener 接口 , 会对容器发生的事件进行处理 , 当有事件发布时 spring 调用每一个 Listener 的 onApplicationEvent 方法 , dubbo 服务暴露就是在这个过程中完成的。服务暴露的范围是可以配置的 , 在 <dubbo:service scope="" /> scope 可选配置 "none" (不暴露服务) , "local" (本地暴露), "remote" (网络暴露,注册中心暴露)。没有指定默认进行 remote 暴露处理。

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());
                }

                // dubbo 服务暴露
                export();
            }
        }
    }复制代码

dubbo 服务本地暴露

dubbo 在进行服务的本地暴露时会将 host , port , protocol (dubbo 中的 protocol) 进行修改,host 会改为 127.0.0.1 , port 改为 0 , protocol 改为 injvm , 得到的 URL 类似这样 :

injvm://127.0.0.1/net.j4love.dubbo.quickstart.provider.DemoService?anyhost=true&application=hello-world-app&dubbo=2.5.4&generic=false&interface=net.j4love.dubbo.quickstart.provider.DemoService&methods=echoWithCurrentTime&pid=9196&revision=1.0.0&side=provider&timestamp=1526463775888&version=1.0.0 , ServiceConfig 服务本地暴露代码 :

private void exportLocal(URL url) {
        if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {

            // 得到一个本地暴漏服务的 URL 
            URL local = URL.valueOf(url.toFullString())
                    .setProtocol(Constants.LOCAL_PROTOCOL)
                    .setHost(NetUtils.LOCALHOST)
                    .setPort(0);

            // 进行服务的本地暴漏 
            // ref 服务接口实现类实例对象 , interfaceClass 服务接口 Class 实例 
            Exporter<?> exporter = protocol.export(
                    proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
            exporters.add(exporter);
            logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
        }
    }    复制代码

先要通过 ProxyFactory 获取到一个 Invoker 实例 , Invoker 中封装了服务接口实现类的实例和服务接口类型的 Class 实例,Invoker 中会去调用服务中的函数。

理解 Dubbo 服务暴露

ProxyFactory 扩展点默认使用的是 JavassistProxyFactory 。JdkProxyFactory 实现很简单,每次Invoker 调用服务API(服务接口中的函数)的时候都会去反射的进行调用 :

public class JdkProxyFactory extends AbstractProxyFactory {

    @SuppressWarnings("unchecked")
    public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
        return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new InvokerInvocationHandler(invoker));
    }

    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName,
                                      Class<?>[] parameterTypes,
                                      Object[] arguments) throws Throwable {
                Method method = proxy.getClass().getMethod(methodName, parameterTypes);
                return method.invoke(proxy, arguments);
            }
        };
    }

}复制代码

JavassistProxyFactory 在创建 Invoker 之前会先获取一个 Wrapper 对象 , Wrapper 的作用是动态的生成代码,动态的生成 Class 实例,获取一个 Wrappr 对象的实例 , 使得服务接口中的方法都能用对象进行直接调用,不通过反射的方式,并且会被已经生成的 Wrapper 实例进行缓存,这样性能就提高了。

public class JavassistProxyFactory extends AbstractProxyFactory {

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

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

}复制代码

Wrapper 的 invokeMethod 是动态生成的 , 在调用 invokeMethod 方法时 , invokeMethod 方法内部是用对象直接去调用方法,不用进行反射。 动态生成的 invokeMethod 函数代码 :

public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws java.lang.reflect.InvocationTargetException {
        net.j4love.dubbo.quickstart.provider.DemoService w;
        try {
            w = ((net.j4love.dubbo.quickstart.provider.DemoService) $1);
        } catch (Throwable e) {
            throw new IllegalArgumentException(e);
        }
        try {

            // 在 net.j4love.dubbo.quickstart.provider.DemoService 接口中
            // 申明了一个函数叫 "echoWithCurrentTime" 
            if ("echoWithCurrentTime".equals($2) && $3.length == 1) {
                return ($w) w.echoWithCurrentTime((java.lang.String) $4[0]);
            }
        } catch (Throwable e) {
            throw new java.lang.reflect.InvocationTargetException(e);
        }
        throw new com.alibaba.dubbo.common.bytecode.NoSuchMethodException("Not found method /"" + $2 + "/" in class net.j4love.dubbo.quickstart.provider.DemoService.");
    }复制代码

有了 Invoker 之后就该获取一个 Exporter 。

理解 Dubbo 服务暴露

ExtensionLoader 在获取一个扩展点的扩展时,会检查该扩展点类型有没有包装类型的扩展,如果有的话会用包装类型的扩展对获取到的源扩展进行层层包装 (参考 ExtensionLoader 的 createExtension 函数)。InJvmProtocol 被包装了两层。ProtocolListenerWrapper 对 Exporter 进行了包装,目的是获取到一组 ExporterListener 对服务的暴漏做出响应 , 使用观察者模式当有服务暴漏时就可以针对服务暴漏这件事进行一些操作。ProtocolFilterWrapper 没有对 Exporter 进行任何包装 , 它对 Invoker 做了处理 , 用一组 Filter 将 Invoker 层层包装 , 在 Invoker 被调用时 , 要经过 Filter 的链条最终调用到 Invoker。结构是这样 :

理解 Dubbo 服务暴露

ProtocolFilterWrapper #buildInvokerChain 源码 :

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.size() > 0) {
            for (int i = filters.size() - 1; i >= 0; i--) {
                final Filter filter = filters.get(i);
                final Invoker<T> next = last;
                last = new Invoker<T>() {

                    public Class<T> getInterface() {
                        return invoker.getInterface();
                    }

                    public URL getUrl() {
                        return invoker.getUrl();
                    }

                    public boolean isAvailable() {
                        return invoker.isAvailable();
                    }

                    // 由 filter 进行调用把下一个 Invoker 作为参数传入 
                    // 只有这个函数会进行过滤器链的调用 , 其他函数都由 Invoker 直接调用
                    public Result invoke(Invocation invocation) throws RpcException {
                        return filter.invoke(next, invocation);
                    }

                    public void destroy() {
                        invoker.destroy();
                    }

                    @Override
                    public String toString() {
                        return invoker.toString();
                    }
                };
            }
        }
        return last;
    }复制代码

经过这些过程之后服务本地暴漏完成。自定义的服务接口 , Invoker , Exporter 之间的关系是这样的 :

理解 Dubbo 服务暴露

dubbo 服务网络暴露

dubbo 的网络暴漏也是通过 Invoker 到 Exporter 的过程 , 在这个过程中要加入网络通信的处理。

理解 Dubbo 服务暴露

服务的网络暴漏过程是在由 Invoker 到 Exporter 这个过程中完成的。

Transporter , ChannelHandler 是用来处理底层的网络连接建立、断开,消息接收、发送,通信过程中的异常状况 , 数据的编码、解码,序列化、反序列化,做一些比较底层的工作。

Exchanger , ExchangeHandler 进行信息交换,单向的通信进行服务调用。

ExchangerServer 服务端抽象层,实现可以是 Netty , Mina , Grizzly 的服务端。

Transporter 、 Exchanger 、 ExchangerServer 都是和网络通信相关。

关于服务暴漏的 ip 地址,dubbo 默认是用 InetAddress.getLocalHost() 来获取本机IP地址 (这是从系统检索主机名称,然后将该名称解析为InetAddress), 如果要指定 IP 需要在 <dubbo:protocol host="" /> 进行配置。

dubbo 服务注册中心暴漏

服务注册到注册中心的过程是使用服务的 URL 字符串向注册中心进行注册。可用的实现有 dubbo , multicast , zookeeper , redis 。 注册中心要做的事情有服务的注册、发现、对已注册的节点进行数据的同步。概括起来很简短,实现中涉及的细节有很多需要注意和考虑的。

以上讨论的内容都是从全局角度出发,没有涉及到很多的细节问题,实际上实现中涉及很多的细节点。这是根据我个人的学习习惯,先整体后具体的顺序实践的。以后会分小块的讨论涉及到的细节问题。

看完本文有收获?请分享给更多人

微信关注「黑帽子技术」加星标,看精选 IT 技术文章

理解 Dubbo 服务暴露

原文  https://juejin.im/post/5e7c90bc6fb9a009527f4f3b
正文到此结束
Loading...