zoukankan      html  css  js  c++  java
  • Dubbo 服务暴露过程(Version2.7.3)

    入口

    在Dubbo jar包目录下我们可以找到

    其中配置了处理接口DubboNamespaceHandler,可以看到,ServiceBean是用来处理service的。

    public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware, ApplicationEventPublisherAware 

    它实现了ApplicationListener接口,所以它可以监听容器事件,当容器发出刷新完毕事件后,ServiceBean捕获事件,执行发布服务的动作。

    发布

    public void export() {
        // 暴露服务交给了父类:org.apache.dubbo.config.ServiceConfig#export
        super.export();
        // 暴露完成后发布对应的事件。因为实现了ApplicationEventPublisherAware接口,所以能获取到ApplicationEventPublisher对象来完成事件的发布。
        this.publishExportEvent();
    }

    进入ServiceConfig

    public synchronized void export() {
        // 检查更新子配置
        checkAndUpdateSubConfigs();
        // 不需要暴露,则返回
        if (!shouldExport()) {
            return;
        }
        // 延迟暴露服务
        if (shouldDelay()) {
            DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
        } else {
            // 执行暴露
            doExport();
        }
    }

    进入doExport

    protected synchronized void doExport() {
        if (unexported) {
            throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
        }
        if (exported) {
            return;
        }
        exported = true;
        if (StringUtils.isEmpty(path)) {
            path = interfaceName;
        }
        // 走这里
        doExportUrls();
    }

    进入doExportUrls

    private void doExportUrls() {
        // 获取注册中心地址,URL内容:registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=service-provider&client=curator&dubbo=2.0.2&pid=285268&qos.enable=false&registry=zookeeper&release=2.7.3&timeout=5000&timestamp=1583994352918
        List<URL> registryURLs = loadRegistries(true);
        // 在每个协议下导出服务
        for (ProtocolConfig protocolConfig : protocols) {
            // pathKey比如:com.demo.common.HelloService:1.0
            String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
            // ProviderModel存储服务提供者信息
            ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
            // 注册服务提供者信息,存储到内部的一个ConcurrentHashMap。
            // ApplicationModel表示使用Dubbo的应用程序,并存储基本元数据信息,以便在处理RPC调用时使用。
            // ApplicationModel包括许多关于发布服务的提供者模型和许多关于订阅服务的消费者模型。
            ApplicationModel.initProviderModel(pathKey, providerModel);
            // 在当前协议下发布服务
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

    这里拉一下大体步骤:

    0. Dubbo捕获Spring容器刷新事件(ServiceBean),执行暴露服务逻辑(ServiceConfig)。

    1. 检查用户的配置是否合理,或者为用户补充缺省配置。

    2. 发布服务(org.apache.dubbo.config.ServiceConfig#doExportUrls)

    • 获取注册中心地址
    • 遍历协议,在每个协议下缓存服务提供者信息并执行发布逻辑

    进入doExportUrlsFor1Protocol

    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        // 获取协议名称,比如:dubbo
        String name = protocolConfig.getName();
        if (StringUtils.isEmpty(name)) {
            name = DUBBO;
        }
    
        Map<String, String> map = new HashMap<String, String>();
        // 添加 side、版本、时间戳以及进程号等信息到 map 中
        map.put(SIDE_KEY, PROVIDER_SIDE);
        appendRuntimeParameters(map);
        // 通过反射将对象的字段信息添加到 map 中
        appendParameters(map, metrics);
        appendParameters(map, application);
        appendParameters(map, module);
        appendParameters(map, provider);
        appendParameters(map, protocolConfig);
        appendParameters(map, this);
        // 处理Method配置
        // 方法级配置。对应的配置类: org.apache.dubbo.config.MethodConfig。同时该标签为 <dubbo:service> 或 <dubbo:reference> 的子标签,用于控制到方法级。
        if (CollectionUtils.isNotEmpty(methods)) {
            for (MethodConfig method : methods) {
                appendParameters(map, method, method.getName());
                String retryKey = method.getName() + ".retry";
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    if ("false".equals(retryValue)) {
                        map.put(method.getName() + ".retries", "0");
                    }
                }
                List<ArgumentConfig> arguments = method.getArguments();
                if (CollectionUtils.isNotEmpty(arguments)) {
                    for (ArgumentConfig argument : arguments) {
                        // convert argument type
                        if (argument.getType() != null && argument.getType().length() > 0) {
                            Method[] methods = interfaceClass.getMethods();
                            // visit all methods
                            if (methods != null && methods.length > 0) {
                                for (int i = 0; i < methods.length; i++) {
                                    String methodName = methods[i].getName();
                                    // target the method, and get its signature
                                    if (methodName.equals(method.getName())) {
                                        Class<?>[] argtypes = methods[i].getParameterTypes();
                                        // one callback in the method
                                        if (argument.getIndex() != -1) {
                                            if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
                                                appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                                            } else {
                                                throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
                                            }
                                        } else {
                                            // multiple callbacks in the method
                                            for (int j = 0; j < argtypes.length; j++) {
                                                Class<?> argclazz = argtypes[j];
                                                if (argclazz.getName().equals(argument.getType())) {
                                                    appendParameters(map, argument, method.getName() + "." + j);
                                                    if (argument.getIndex() != -1 && argument.getIndex() != j) {
                                                        throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        } else if (argument.getIndex() != -1) {
                            appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                        } else {
                            throw new IllegalArgumentException("Argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
                        }
    
                    }
                }
            } // end of methods for
        }
    
        // 如果是个泛型服务
        if (ProtocolUtils.isGeneric(generic)) {
            map.put(GENERIC_KEY, generic);
            map.put(METHODS_KEY, ANY_VALUE);
        } else {
            // 不是泛型服务
    
            // 获取服务提供者的版本,并添加到map中
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put(REVISION_KEY, revision);
            }
    
            // 获取接口的方法集合,并添加到map中
            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if (methods.length == 0) {
                logger.warn("No method found in service interface " + interfaceClass.getName());
                map.put(METHODS_KEY, ANY_VALUE);
            } else {
                map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
            }
        }
        // 添加 token 到 map 中
        if (!ConfigUtils.isEmpty(token)) {
            if (ConfigUtils.isDefault(token)) {
                map.put(TOKEN_KEY, UUID.randomUUID().toString());
            } else {
                map.put(TOKEN_KEY, token);
            }
        }
    
        // 获取host,这里的是服务主机地址,比如 10.204.241.66
        String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
        // 获取post,比如 20880
        Integer port = this.findConfigedPorts(protocolConfig, name, map);
        // 组装成URL:dubbo://10.204.241.66:20880/com.demo.common.HelloService?actives=5&anyhost=true&application=service-provider&bean.name=ServiceBean:com.demo.common.HelloService:1.0&bind.ip=10.204.241.66&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.demo.common.HelloService&methods=hello&pid=285268&qos.enable=false&register=true&release=2.7.3&retries=3&revision=1.0&side=provider&timeout=3000&timestamp=1583996355545&version=1.0
        URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
    
        // 上面的代码:
        // 首先是将一些信息,比如版本、时间戳、方法名以及各种配置对象的字段信息放入到 map 中,map 中的内容将作为 URL 的查询字符串。
        // 构建好 map 后,紧接着是获取上下文路径、主机名以及端口号等信息。最后将 map 和主机名等数据传给 URL 构造方法创建 URL 对象。
        // -------------------------------
    
        // ExtensionLoader是Dubbo SPI的核心体现,这里是获取ConfiguratorFactory的具体实现类
        if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .hasExtension(url.getProtocol())) {
            // 利用Configurator实例配置 url
            // dubbo://10.204.241.66:20880/com.demo.common.HelloService?actives=5&anyhost=true&application=service-provider&bean.name=ServiceBean:com.demo.common.HelloService:1.0&bind.ip=10.204.241.66&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.demo.common.HelloService&methods=hello&pid=285268&qos.enable=false&register=true&release=2.7.3&retries=3&revision=1.0&side=provider&timeout=3000&timestamp=1583996355545&version=1.0
            url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                    .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
        }
    
        String scope = url.getParameter(SCOPE_KEY);
        // 如果 scope = none,则什么都不做
        if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
    
            // scope != remote,导出到本地
            if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
            // scope != local,导出到远程
            if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
                if (!isOnlyInJvm() && logger.isInfoEnabled()) {
                    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                }
    
                if (CollectionUtils.isNotEmpty(registryURLs)) {
                    // 存在注册中心
                    for (URL registryURL : registryURLs) {
                        // 如果协议是injvm,不注册
                        if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
                            continue;
                        }
                        url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
                        // 加载监视器链接
                        URL monitorUrl = loadMonitor(registryURL);
                        if (monitorUrl != null) {
                            // 将监视器链接作为参数添加到 url 中
                            url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
                        }
                        if (logger.isInfoEnabled()) {
                            logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                        }
    
    
                        String proxy = url.getParameter(PROXY_KEY);
                        if (StringUtils.isNotEmpty(proxy)) {
                            registryURL = registryURL.addParameter(PROXY_KEY, proxy);
                        }
    
                        // 利用代理工厂根据服务提供类生成Invoker对象
                        Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
    
                        // 导出服务,并生成 Exporter
                        Exporter<?> exporter = protocol.export(wrapperInvoker);
                        exporters.add(exporter);
                    }
                } else {
                    // 不存在注册中心,仅导出服务
                    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
    
                    Exporter<?> exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
                /**
                 * @since 2.7.0
                 * ServiceData Store
                 */
                MetadataReportService metadataReportService = null;
                if ((metadataReportService = getMetadataReportService()) != null) {
                    metadataReportService.publishProvider(url);
                }
            }
        }
        this.urls.add(url);
    }

    这么一大段,主要做了2件事

    1. 创建URL

    • 首先将版本、时间戳、方法名以及各种配置对象的字段信息放入到 map 中,map 中的内容将作为 URL 的查询字符串。
    • 构建好 map 后,紧接着是获取主机名以及端口号等信息。
    • 最后将 map 和主机名等数据传给 URL 构造方法创建 URL 对象。

    2. 暴露服务

    • 根据 url 中的 scope 参数决定服务导出方式
    • 把服务对象转换成Invoker对象
    • 把Invoker对象转换成Exporter对象

    ❤把服务对象转换成Invoker对象(org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory#getInvoker

    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // 获取Wrapper对象
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        // new一个AbstractProxyInvoker对象并覆盖抽象方法 doInvoke,将调用请求交给wrapper.invokeMethod。
        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);
            }
        };
    }
    getWrapper
    public static Wrapper getWrapper(Class<?> c) {
        while (ClassGenerator.isDynamicClass(c)) // can not wrapper on dynamic class.
        {
            c = c.getSuperclass();
        }
        if (c == Object.class) {
            return OBJECT_WRAPPER;
        }
        // 从缓存中获取
        Wrapper ret = WRAPPER_MAP.get(c);
        if (ret == null) {
            // 缓存没有则创建
            ret = makeWrapper(c);
            // 放进缓存
            WRAPPER_MAP.put(c, ret);
        }
        // 返回
        return ret;
    }

    看一下Wrapper对象的创建过程

    // 这么多代码,看起来复杂,实际上很简单,一句话:利用字节码类库javassist 来动态创建一个Wrapper对象。
    // 仔细看一下,这里大部分代码都是手动拼接字符串,然后利用javassist来生成。这么麻烦,为什么不实现实现Wrapper类呢?因为Wrapper类的结构要根据实际传入的Class来决定。
    // 这里的代码不做解释,看一篇入门文章即可了解:https://www.cnblogs.com/rickiyang/p/11336268.html
    private static Wrapper makeWrapper(Class<?> c) {
        if (c.isPrimitive()) {
            throw new IllegalArgumentException("Can not create wrapper for primitive type: " + c);
        }
    
        String name = c.getName();
        ClassLoader cl = ClassUtils.getClassLoader(c);
    
        StringBuilder c1 = new StringBuilder("public void setPropertyValue(Object o, String n, Object v){ ");
        StringBuilder c2 = new StringBuilder("public Object getPropertyValue(Object o, String n){ ");
        StringBuilder c3 = new StringBuilder("public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws " + InvocationTargetException.class.getName() + "{ ");
    
        c1.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
        c2.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
        c3.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
    
        Map<String, Class<?>> pts = new HashMap<>(); // <property name, property types>
        Map<String, Method> ms = new LinkedHashMap<>(); // <method desc, Method instance>
        List<String> mns = new ArrayList<>(); // method names.
        List<String> dmns = new ArrayList<>(); // declaring method names.
    
        // get all public field.
        for (Field f : c.getFields()) {
            String fn = f.getName();
            Class<?> ft = f.getType();
            if (Modifier.isStatic(f.getModifiers()) || Modifier.isTransient(f.getModifiers())) {
                continue;
            }
    
            c1.append(" if( $2.equals("").append(fn).append("") ){ w.").append(fn).append("=").append(arg(ft, "$3")).append("; return; }");
            c2.append(" if( $2.equals("").append(fn).append("") ){ return ($w)w.").append(fn).append("; }");
            pts.put(fn, ft);
        }
    
        Method[] methods = c.getMethods();
        // get all public method.
        boolean hasMethod = hasMethods(methods);
        if (hasMethod) {
            c3.append(" try{");
            for (Method m : methods) {
                //ignore Object's method.
                if (m.getDeclaringClass() == Object.class) {
                    continue;
                }
    
                String mn = m.getName();
                c3.append(" if( "").append(mn).append("".equals( $2 ) ");
                int len = m.getParameterTypes().length;
                c3.append(" && ").append(" $3.length == ").append(len);
    
                boolean override = false;
                for (Method m2 : methods) {
                    if (m != m2 && m.getName().equals(m2.getName())) {
                        override = true;
                        break;
                    }
                }
                if (override) {
                    if (len > 0) {
                        for (int l = 0; l < len; l++) {
                            c3.append(" && ").append(" $3[").append(l).append("].getName().equals("")
                                    .append(m.getParameterTypes()[l].getName()).append("")");
                        }
                    }
                }
    
                c3.append(" ) { ");
    
                if (m.getReturnType() == Void.TYPE) {
                    c3.append(" w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");").append(" return null;");
                } else {
                    c3.append(" return ($w)w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");");
                }
    
                c3.append(" }");
    
                mns.add(mn);
                if (m.getDeclaringClass() == c) {
                    dmns.add(mn);
                }
                ms.put(ReflectUtils.getDesc(m), m);
            }
            c3.append(" } catch(Throwable e) { ");
            c3.append("     throw new java.lang.reflect.InvocationTargetException(e); ");
            c3.append(" }");
        }
    
        c3.append(" throw new " + NoSuchMethodException.class.getName() + "("Not found method \""+$2+"\" in class " + c.getName() + "."); }");
    
        // deal with get/set method.
        Matcher matcher;
        for (Map.Entry<String, Method> entry : ms.entrySet()) {
            String md = entry.getKey();
            Method method = entry.getValue();
            if ((matcher = ReflectUtils.GETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
                String pn = propertyName(matcher.group(1));
                c2.append(" if( $2.equals("").append(pn).append("") ){ return ($w)w.").append(method.getName()).append("(); }");
                pts.put(pn, method.getReturnType());
            } else if ((matcher = ReflectUtils.IS_HAS_CAN_METHOD_DESC_PATTERN.matcher(md)).matches()) {
                String pn = propertyName(matcher.group(1));
                c2.append(" if( $2.equals("").append(pn).append("") ){ return ($w)w.").append(method.getName()).append("(); }");
                pts.put(pn, method.getReturnType());
            } else if ((matcher = ReflectUtils.SETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
                Class<?> pt = method.getParameterTypes()[0];
                String pn = propertyName(matcher.group(1));
                c1.append(" if( $2.equals("").append(pn).append("") ){ w.").append(method.getName()).append("(").append(arg(pt, "$3")).append("); return; }");
                pts.put(pn, pt);
            }
        }
        c1.append(" throw new " + NoSuchPropertyException.class.getName() + "("Not found property \""+$2+"\" field or setter method in class " + c.getName() + "."); }");
        c2.append(" throw new " + NoSuchPropertyException.class.getName() + "("Not found property \""+$2+"\" field or setter method in class " + c.getName() + "."); }");
    
        // make class
        long id = WRAPPER_CLASS_COUNTER.getAndIncrement();
        ClassGenerator cc = ClassGenerator.newInstance(cl);
        cc.setClassName((Modifier.isPublic(c.getModifiers()) ? Wrapper.class.getName() : c.getName() + "$sw") + id);
        cc.setSuperClass(Wrapper.class);
    
        cc.addDefaultConstructor();
        cc.addField("public static String[] pns;"); // property name array.
        cc.addField("public static " + Map.class.getName() + " pts;"); // property type map.
        cc.addField("public static String[] mns;"); // all method name array.
        cc.addField("public static String[] dmns;"); // declared method name array.
        for (int i = 0, len = ms.size(); i < len; i++) {
            cc.addField("public static Class[] mts" + i + ";");
        }
    
        cc.addMethod("public String[] getPropertyNames(){ return pns; }");
        cc.addMethod("public boolean hasProperty(String n){ return pts.containsKey($1); }");
        cc.addMethod("public Class getPropertyType(String n){ return (Class)pts.get($1); }");
        cc.addMethod("public String[] getMethodNames(){ return mns; }");
        cc.addMethod("public String[] getDeclaredMethodNames(){ return dmns; }");
        cc.addMethod(c1.toString());
        cc.addMethod(c2.toString());
        cc.addMethod(c3.toString());
    
        try {
            Class<?> wc = cc.toClass();
            // setup static field.
            wc.getField("pts").set(null, pts);
            wc.getField("pns").set(null, pts.keySet().toArray(new String[0]));
            wc.getField("mns").set(null, mns.toArray(new String[0]));
            wc.getField("dmns").set(null, dmns.toArray(new String[0]));
            int ix = 0;
            for (Method m : ms.values()) {
                wc.getField("mts" + ix++).set(null, m.getParameterTypes());
            }
            return (Wrapper) wc.newInstance();
        } catch (RuntimeException e) {
            throw e;
        } catch (Throwable e) {
            throw new RuntimeException(e.getMessage(), e);
        } finally {
            cc.release();
            ms.clear();
            mns.clear();
            dmns.clear();
        }
    }

    小结:

    1. 利用字节码类库Javassist手动创建Wrapper类。

    2. 创建AbstractProxyInvoker对象,覆盖doInvoke方法并直接将调用请求交给Wrapper对象的invokeMethod方法。

    ❤把Invoker对象转换成Exporter对象(org.apache.dubbo.registry.integration.RegistryProtocol#export

    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        // 获取注册中心 URL
        // 比如:zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=service-provider&client=curator&dubbo=2.0.2&export=dubbo%3A%2F%2F10.204.241.66%3A20880%2Fcom.demo.common.HelloService%3Factives%3D5%26anyhost%3Dtrue%26application%3Dservice-provider%26bean.name%3DServiceBean%3Acom.demo.common.HelloService%3A1.0%26bind.ip%3D10.204.241.66%26bind.port%3D20880%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dcom.demo.common.HelloService%26methods%3Dhello%26pid%3D285268%26qos.enable%3Dfalse%26register%3Dtrue%26release%3D2.7.3%26retries%3D3%26revision%3D1.0%26side%3Dprovider%26timeout%3D3000%26timestamp%3D1583996355545%26version%3D1.0&pid=285268&qos.enable=false&release=2.7.3&timeout=5000&timestamp=1583994352918
        URL registryUrl = getRegistryUrl(originInvoker);
        // 获取用来本地发布的URL
        // 比如:dubbo://10.204.241.66:20880/com.demo.common.HelloService?actives=5&anyhost=true&application=service-provider&bean.name=ServiceBean:com.demo.common.HelloService:1.0&bind.ip=10.204.241.66&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.demo.common.HelloService&methods=hello&pid=285268&qos.enable=false&register=true&release=2.7.3&retries=3&revision=1.0&side=provider&timeout=3000&timestamp=1583996355545&version=1.0
        URL providerUrl = getProviderUrl(originInvoker);
    
        // 获取订阅 URL
        // 比如:provider://10.204.241.66:20880/com.demo.common.HelloService?actives=5&anyhost=true&application=service-provider&bean.name=ServiceBean:com.demo.common.HelloService:1.0&bind.ip=10.204.241.66&bind.port=20880&category=configurators&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.demo.common.HelloService&methods=hello&pid=285268&qos.enable=false&register=true&release=2.7.3&retries=3&revision=1.0&side=provider&timeout=3000&timestamp=1583996355545&version=1.0
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
        // 创建监听器
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
    
        // dubbo://10.204.241.66:20880/com.demo.common.HelloService?actives=5&anyhost=true&application=service-provider&bean.name=ServiceBean:com.demo.common.HelloService:1.0&bind.ip=10.204.241.66&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.demo.common.HelloService&methods=hello&pid=285268&qos.enable=false&register=true&release=2.7.3&retries=3&revision=1.0&side=provider&timeout=3000&timestamp=1583996355545&version=1.0
        providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
        // *** 执行本地服务的暴露,并返回Exporter
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
    
        // 获取Registry
        final Registry registry = getRegistry(originInvoker);
        // 获取注册表中注册的URL并过滤一次url参数
        final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
        ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
                registryUrl, registeredProviderUrl);
        // 判断是否需要推迟发布
        boolean register = registeredProviderUrl.getParameter("register", true);
        if (register) {
            // *** 获取注册中心Registry,并向注册中心注册registeredProviderUrl
            register(registryUrl, registeredProviderUrl);
            providerInvokerWrapper.setReg(true);
        }
    
        // Deprecated! Subscribe to override rules in 2.6.x or before.
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
    
        // 给exporter设置提供者URL和订阅URL
        exporter.setRegisterUrl(registeredProviderUrl);
        exporter.setSubscribeUrl(overrideSubscribeUrl);
        //Ensure that a new exporter instance is returned every time export
        return new DestroyableExporter<>(exporter);
    }

    这个做了什么事呢?

    1. 利用Invoker获取注册中心URL、服务提供者URL。

    2. 获取override订阅URL,创建监听器,监听URL的变化。这里为什么服务提供者也需要监听URL的变化,在集群环境下,某个节点改变了zk数据,其它节点能够迅速得到更新。

    3. 执行本地服务的暴露并返回Exporter。

    4. 获取注册中心Registry,将服务地址注册到Registry。

     这部分又能提炼出两大核心

    1. 本地服务的暴露过程

    2. 注册中心注册服务的过程

    1. 本地服务的暴露过程

    private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
        // 根据Invoker获取缓存的key
        // dubbo://10.204.241.66:20880/com.demo.common.HelloService?actives=5&anyhost=true&application=service-provider&bean.name=ServiceBean:com.demo.common.HelloService:1.0&bind.ip=10.204.241.66&bind.port=20880&deprecated=false&dubbo=2.0.2&generic=false&interface=com.demo.common.HelloService&methods=hello&pid=285268&qos.enable=false&register=true&release=2.7.3&retries=3&revision=1.0&side=provider&timeout=3000&timestamp=1583996355545&version=1.0
        String key = getCacheKey(originInvoker);
    
        // 这里的代码用了Java8的新特性:computeIfAbsent,代替了官网文档中这部分的双重检查锁代码
        return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
            // 创建 Invoker 为委托类对象
            Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
            // 调用 protocol 的 export 方法导出服务
            return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
        });
    }

    进入org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#export

    // 进入org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#export
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        // 获取服务地址
        // dubbo://10.204.241.66:20880/com.demo.common.HelloService?actives=5&anyhost=true&application=service-provider&bean.name=ServiceBean:com.demo.common.HelloService:1.0&bind.ip=10.204.241.66&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.demo.common.HelloService&methods=hello&pid=35584&qos.enable=false&register=true&release=2.7.3&retries=3&revision=1.0&side=provider&timeout=3000&timestamp=1584011280884&version=1.0
        URL url = invoker.getUrl();
        // 获取key:com.demo.common.HelloService:1.0:20880
        String key = serviceKey(url);
        // 创建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 && !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);
            }
        }
        // 启动服务器,监听消费者客户端的请求
        openServer(url);
        // 优化序列化
        optimizeSerialization(url);
        return exporter;
    }

    这里真相渐渐浮出水面

    1. Exporter持有什么呢?不过是具体服务的Invoker对象,标识服务的key,以及所有Exporter的Map集合。

    2. 暴露服务呢?不过是根据服务URL解析出用于启动Netty服务器的地址,启动服务器监听来自客户端的请求。所以Dubbo服务暴露和消费不过是Netty服务器和客户端之间的通信罢了,而注册中心的作用就是告诉客户端:服务器的地址是什么。服务器接收到客户端的请求,解析出目标服务,调用之后返回给客户端。

    看一下启动服务器的过程

    // dubbo默认采用dubbo协议,dubbo协议采用单一长连接和NIO异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况 
    private void openServer(URL url) {
        // key:10.204.241.66:20880
        // 这里可以看出,一台机器只会启动一个Netty服务(ip+端口唯一),下面加锁的过程保证了这一点。
        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) {
            // 查看缓存
            ExchangeServer server = serverMap.get(key);
            if (server == null) {
                synchronized (this) {
                    server = serverMap.get(key);
                    if (server == null) {
                        // 缓存没有服务器实例,创建并放入缓存
                        serverMap.put(key, createServer(url));
                    }
                }
            } else {
                // 服务器已创建,则根据 url 中的配置重置服务器
                server.reset(url);
            }
        }
    }

    这里逻辑:

    根据服务URL解析出ip:端口作为key,先查看缓存有没有对应的ExchangeServer,没有则创建,有则重置服务器。

    创建服务器

    private ExchangeServer createServer(URL url) {
        url = URLBuilder.from(url)
                // send readonly event when server closes, it's enabled by default
                .addParameterIfAbsent(CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString())
                // 添加心跳检测配置到 url 中
                .addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(DEFAULT_HEARTBEAT))
                // 添加编码解码器参数
                .addParameter(CODEC_KEY, DubboCodec.NAME)
                .build();
        // 获取server参数,比如:netty
        String str = url.getParameter(SERVER_KEY, DEFAULT_REMOTING_SERVER);
    
        // 通过 SPI 检测是否存在 server 参数所代表的 Transporter 拓展,不存在则抛出异常
        if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
            throw new RpcException("Unsupported server type: " + str + ", url: " + url);
        }
    
        ExchangeServer server;
        try {
            // 创建 ExchangeServer
            server = Exchangers.bind(url, requestHandler);
        } catch (RemotingException e) {
            throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
        }
    
        // 获取client参数,比如:可能为null,或者netty或者mina
        str = url.getParameter(CLIENT_KEY);
        if (str != null && str.length() > 0) {
            Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
            if (!supportedTypes.contains(str)) {
                throw new RpcException("Unsupported client type: " + str);
            }
        }
    
        return server;
    }

    这部分做了启动之前的工作:

    1. 给URL配置服务器参数

    2. 调用Exchangers.bind(url, requestHandler)方法来创建ExchangeServer

    public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
        if (url == null) {
            throw new IllegalArgumentException("url == null");
        }
        if (handler == null) {
            throw new IllegalArgumentException("handler == null");
        }
        url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
        // 获取 Exchanger,默认为 HeaderExchanger。这里利用了Dubbo SPI 获取具体的实现类。
        // 紧接着调用 HeaderExchanger 的 bind 方法创建 ExchangeServer 实例
        return getExchanger(url).bind(url, handler);
    }
    
    // org.apache.dubbo.remoting.exchange.support.header.HeaderExchanger#bind
    public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
        return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
    }

    1. 利用Dubbo SPI来获取Exchanger的具体实现类,并调用其中的bind方法

    2. 默认new一个HeaderExchangeServer,而真正让服务器启动的是Transporters.bind方法

    // org.apache.dubbo.remoting.Transporters#bind(org.apache.dubbo.common.URL, org.apache.dubbo.remoting.ChannelHandler...)
    public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
        if (url == null) {
            throw new IllegalArgumentException("url == null");
        }
        if (handlers == null || handlers.length == 0) {
            throw new IllegalArgumentException("handlers == null");
        }
        ChannelHandler handler;
        if (handlers.length == 1) {
            handler = handlers[0];
        } else {
            // 如果 handlers 元素数量大于1,则创建 ChannelHandler 分发器
            handler = new ChannelHandlerDispatcher(handlers);
        }
        // 获取自适应 Transporter 实例,这里利用了Dubbo SPI 获取具体的实现类。
        // 并调用实例方法bind。默认NettyTransporter,有两个版本,一个在netty包下,一个在netty4包下
        return getTransporter().bind(url, handler);
    }
    //org.apache.dubbo.remoting.transport.netty4.NettyTransporter#bind
    public Server bind(URL url, ChannelHandler listener) throws RemotingException {
        return new NettyServer(url, listener);
    }

    这部分和Exchanger的逻辑类似

    1. 利用Dubbo SPI来获取Transporter的具体实现类,并调用其中的bind方法

    2. 默认new一个NettyServer

    // org.apache.dubbo.remoting.transport.netty4.NettyServer
    public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
        // you can customize name and type of client thread pool by THREAD_NAME_KEY and THREADPOOL_KEY in CommonConstants.
        // the handler will be warped: MultiMessageHandler->HeartbeatHandler->handler
        super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
    }
    // org.apache.dubbo.remoting.transport.AbstractServer
    public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
        super(url, handler);
        localAddress = getUrl().toInetSocketAddress();
    
        String bindIp = getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());
        int bindPort = getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());
        if (url.getParameter(ANYHOST_KEY, false) || NetUtils.isInvalidLocalHost(bindIp)) {
            bindIp = ANYHOST_VALUE;
        }
        bindAddress = new InetSocketAddress(bindIp, bindPort);
        this.accepts = url.getParameter(ACCEPTS_KEY, DEFAULT_ACCEPTS);
        this.idleTimeout = url.getParameter(IDLE_TIMEOUT_KEY, DEFAULT_IDLE_TIMEOUT);
        try {
            // 看这里就可以了
            doOpen();
            if (logger.isInfoEnabled()) {
                logger.info("Start " + getClass().getSimpleName() + " bind " + getBindAddress() + ", export " + getLocalAddress());
            }
        } catch (Throwable t) {
            throw new RemotingException(url.toInetSocketAddress(), null, "Failed to bind " + getClass().getSimpleName()
                    + " on " + getLocalAddress() + ", cause: " + t.getMessage(), t);
        }
        //fixme replace this with better method
        DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
        executor = (ExecutorService) dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY, Integer.toString(url.getPort()));
    }

    这部分不过是例行公事,设置属性,关键一步在doOpen,这里体现了模板模式

    扩展

    模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行

    下面看启动服务器的过程

    // org.apache.dubbo.remoting.transport.netty4.NettyServer#doOpen
    protected void doOpen() throws Throwable {
        bootstrap = new ServerBootstrap();
    
        bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("NettyServerBoss", true));
        workerGroup = new NioEventLoopGroup(getUrl().getPositiveParameter(IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),
                new DefaultThreadFactory("NettyServerWorker", true));
    
        final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);
        channels = nettyServerHandler.getChannels();
    
        bootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
                .childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
                .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        // FIXME: should we use getTimeout()?
                        int idleTimeout = UrlUtils.getIdleTimeout(getUrl());
                        NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
                        ch.pipeline()//.addLast("logging",new LoggingHandler(LogLevel.INFO))//for debug
                                .addLast("decoder", adapter.getDecoder())
                                .addLast("encoder", adapter.getEncoder())
                                .addLast("server-idle-handler", new IdleStateHandler(0, 0, idleTimeout, MILLISECONDS))
                                .addLast("handler", nettyServerHandler);
                    }
                });
        // bind
        ChannelFuture channelFuture = bootstrap.bind(getBindAddress());
        channelFuture.syncUninterruptibly();
        channel = channelFuture.channel();
    
    }

    对,我没有做注释,有Netty入门的一看就明白:比如ServerBootstrap、NioEventLoopGroup(Boss 和 Worker)

    小结服务暴露过程:

    类的调用层次:DubboProtocol#export-->openServer-->createServer-->Exchangers#bind-->HeaderExchanger#bind-->Transporters#bind-->NettyTransporter#bind-->NettyServer#doOpen

    1. 调用具体的协议实现类比如DubboProtocol的导出方法

    2. 创建Exporter对象并缓存

    3. 启动服务器

      - 根据服务URL解析出ip:端口作为key,先查看缓存有没有对应的ExchangeServer,没有则创建,有则重置服务器。

    4. 创建服务器

      - 给URL配置服务器参数

      - 利用Dubbo SPI调用Exchanger具体实现类(比如:HeaderExchanger)的bind方法来创建ExchangeServer对象

      - 利用Dubbo SPI调用Transporter具体实现类(比如:NettyTransporter)的bind方法来创建Server对象

      - new一个NettyServer对象,其构造方法除了初始化一些属性外,还调用了模板方法doOpen,doOpen里的逻辑即启动一个Netty服务器的底层方法

    2. 注册中心注册服务的过程

    // org.apache.dubbo.registry.integration.RegistryProtocol#register
    public void register(URL registryUrl, URL registeredProviderUrl) {
        // 获取注册中心
        Registry registry = registryFactory.getRegistry(registryUrl);
        // 注册服务地址
        registry.register(registeredProviderUrl);
    }

    获取注册中心

    // org.apache.dubbo.registry.support.AbstractRegistryFactory#getRegistry
    public Registry getRegistry(URL url) {
        url = URLBuilder.from(url)
                .setPath(RegistryService.class.getName())
                .addParameter(INTERFACE_KEY, RegistryService.class.getName())
                .removeParameters(EXPORT_KEY, REFER_KEY)
                .build();
        String key = url.toServiceStringWithoutResolving();
        // Lock the registry access process to ensure a single instance of the registry
        LOCK.lock();
        try {
            // 查看缓存
            Registry registry = REGISTRIES.get(key);
            if (registry != null) {
                return registry;
            }
            // 通过Dubbo SPI或者IOC类创建
            registry = createRegistry(url);
            if (registry == null) {
                throw new IllegalStateException("Can not create registry " + url);
            }
            // 放进缓存
            REGISTRIES.put(key, registry);
            return registry;
        } finally {
            // Release the lock
            LOCK.unlock();
        }
    }

    这部分逻辑在export过程是不是也见过?先看缓存有没有,有则返回无则创建。

    看下创建

    // org.apache.dubbo.registry.zookeeper.ZookeeperRegistryFactory#createRegistry
    public Registry createRegistry(URL url) {
        return new ZookeeperRegistry(url, zookeeperTransporter);
    }
    // org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#ZookeeperRegistry
    public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
        super(url);
        if (url.isAnyHost()) {
            throw new IllegalStateException("registry address == null");
        }
        String group = url.getParameter(GROUP_KEY, DEFAULT_ROOT);
        if (!group.startsWith(PATH_SEPARATOR)) {
            group = PATH_SEPARATOR + group;
        }
        this.root = group;
        // 创建客户端
        zkClient = zookeeperTransporter.connect(url);
        // 监听状态变化
        zkClient.addStateListener(state -> {
            if (state == StateListener.RECONNECTED) {
                try {
                    recover();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        });
    }

    这部分也就是创建zookeeper客户端的过程。而这一步的zkClient是Dubbo的包装类,你继续走下去就能看见原生代码了,这不不做赘述。如果你了解ZkClient或者Curator,就能看懂。

    下面看下服务注册

    // org.apache.dubbo.registry.support.FailbackRegistry#register
    public void register(URL url) {
        super.register(url);
        removeFailedRegistered(url);
        removeFailedUnregistered(url);
        try {
            // 模板方法,具体子类实现
            doRegister(url);
        } catch (Exception e) {
            Throwable t = e;
    
            // If the startup detection is opened, the Exception is thrown directly.
            boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
                    && url.getParameter(Constants.CHECK_KEY, true)
                    && !CONSUMER_PROTOCOL.equals(url.getProtocol());
            boolean skipFailback = t instanceof SkipFailbackWrapperException;
            if (check || skipFailback) {
                if (skipFailback) {
                    t = t.getCause();
                }
                throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t);
            } else {
                logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t);
            }
    
            // Record a failed registration request to a failed list, retry regularly
            addFailedRegistered(url);
        }
    }
    // org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#doRegister
    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);
        }
    }

    这里的逻辑也很容易懂

    // org.apache.dubbo.remoting.zookeeper.support.AbstractZookeeperClient#create(java.lang.String, boolean)
    public void create(String path, boolean ephemeral) {
        if (!ephemeral) {
            if (checkExists(path)) {
                return;
            }
        }
        int i = path.lastIndexOf('/');
        if (i > 0) {
            create(path.substring(0, i), false);
        }
        if (ephemeral) {
            // 创建临时节点
            createEphemeral(path);
        } else {
            // 创建永久节点
            createPersistent(path);
        }
    }
    // org.apache.dubbo.remoting.zookeeper.curator.CuratorZookeeperClient#createPersistent(java.lang.String)
    public void createPersistent(String path) {
        try {
            // 这里的client就是原生代码了 org.apache.curator.framework.CuratorFramework
            client.create().forPath(path);
        } catch (NodeExistsException e) {
        } catch (Exception e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }

    小结

    1. 获取注册中心

      - 上锁,先查看缓存中是否存在。没有则创建注册中心并放入缓存。

    2. 把服务地址写入注册中心

      - 利用上一步获取的注册中心,调用内部create方法完成数据节点的创建。

    总结

    0. Dubbo捕获Spring容器刷新事件(ServiceBean),执行暴露服务逻辑(ServiceConfig)。

    1. 检查用户的配置是否合理,或者为用户补充缺省配置。

    2. 发布服务(org.apache.dubbo.config.ServiceConfig#doExportUrls)

    - 获取注册中心地址
    - 遍历协议,在每个协议下缓存服务提供者信息并执行发布逻辑
      ①. 创建URL
      - 首先将版本、时间戳、方法名以及各种配置对象的字段信息放入到 map 中,map 中的内容将作为 URL 的查询字符串。
      - 构建好 map 后,紧接着是获取主机名以及端口号等信息。
      - 最后将 map 和主机名等数据传给 URL 构造方法创建 URL 对象。

      ②. 暴露服务
      - 根据 url 中的 scope 参数决定服务导出方式(本地还是远程)
      * 把服务对象转换成Invoker对象(org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory#getInvoker)
        // Invoker对象存储了元数据信息,可以利用它获取服务地址,注册中心地址等
        - 利用字节码类库Javassist手动创建Wrapper类
        - 创建AbstractProxyInvoker对象,覆盖doInvoke方法并直接将调用请求交给Wrapper对象的invokeMethod方法

      * 把Invoker对象转换成Exporter对象(org.apache.dubbo.registry.integration.RegistryProtocol#export)
        // 做了两件大事:1. 向注册中心注册服务地址 2. 根据协议选择具体的实现(比如DubboProtocol),说是export服务,不如说是启动了服务器,监听消费者的请求
        // 所谓的Dubbo协议,就是通讯逻辑的实现。Dubbo 协议的 Invoker 转为 Exporter 发生在 DubboProtocol 类的 export 方法,它主要是打开 socket 侦听服务,并接收客户端发来的各种请求,通讯细节由 Dubbo 自己实现。
        // 核心代码2部分:1. 注册服务,操作Zookeeper,写入节点&监听节点变化 2. 启动Netty服务器,监听客户端连接
        (一)启动服务器
          1. 调用具体的协议实现类比如DubboProtocol的导出方法
          2. 创建Exporter对象并缓存
          3. 启动服务器:根据服务URL解析出ip:端口作为key,先查看缓存有没有对应的ExchangeServer,没有则创建,有则重置服务器。
          4. 创建服务器
            - 给URL配置服务器参数
            - 利用Dubbo SPI调用Exchanger具体实现类(比如:HeaderExchanger)的bind方法来创建ExchangeServer对象
            - 利用Dubbo SPI调用Transporter具体实现类(比如:NettyTransporter)的bind方法来创建Server对象
            - new一个NettyServer对象,其构造方法除了初始化一些属性外,还调用了模板方法doOpen,doOpen里的逻辑即启动一个Netty服务器的底层方法
        (二)注册服务
          * 获取注册中心:上锁,先查看缓存中是否存在。没有则创建注册中心并放入缓存。
          * 把服务地址写入注册中心:利用上一步获取的注册中心,调用内部create方法完成数据节点的创建。

     另附官网图片

     ==================================2020-05-22更:服务暴露流程图

  • 相关阅读:
    java架构之路-(面试篇)Mysql面试大全
    web工程的路径问题详解
    SQL知识点总结
    Mybatis简介
    mysql大全
    配置心得
    spring4.1.3+springmvc+mybatis3.2.1整合
    jstl标签
    jstl标签库
    Java并发控制机制
  • 原文地址:https://www.cnblogs.com/LUA123/p/12463748.html
Copyright © 2011-2022 走看看