乐趣区

关于dubbo:Dubbo架构设计与源码解析二-服务注册

作者:黄金

一、Dubbo 简介

Dubbo 是一款典型的高扩大、高性能、高可用的 RPC 微服务框架,用于解决微服务架构下的服务治理与通信问题。其外围模块蕴含 【RPC 通信】【服务治理】,其中服务治理又分为服务注册与发现、服务容错、负载平衡、流量调度等。明天将重点介绍 Dubbo 的 服务注册与发现

二、SPI 机制

在介绍服务注册发现之前,先简略介绍一下贯通整个 Dubbo 源码,也是 Dubbo 实现自适应扩大的外围 –SPI 机制,下图为 Dubbo SPI 实现的简略类图。





1、Dubbo SPI 原理 :通过读取相应的配置文件找到具体实现类,而后通过以下两种形式实例化对象:(1)通过 自适应 动静字节码编译 技术,生成相应的 动静代理类,(2)利用反射机制实现实例化。相较于 Java SPI,Dubbo SPI 实现了外部的 IoC 和 Aop

2、Dubbo SPI 长处 :(1) 高扩大 :用户能够依据理论业务需要扩大相应的实现模块,蕴含字节码编译技术、rpc 协定、通信形式、注册形式等,(2) 解耦: 通过封装 SPI 调用机制,架构上实现了下层利用与底层逻辑之间的解耦,为高扩大提供了撑持条件

3、Dubbo SPI 罕用样例(以 getExtension 和 getAdaptiveExtension 为例)

配置文件内容
test1=com.dubbo.demo.service.TestServiceimpl
test2=com.dubbo.demo.service.TestServiceImpl2

一、通过 getExtension 办法生成实例
    ExtensionLoader<TestService> extensionLoader = ExtensionLoader.getExtensionLoader(TestService.class);
    TestService t1 = extensionLoader.getExtension("test1");
    TestService t2 = extensionLoader.getExtension("test2");
   
二、通过 getAdaptiveExtension 生成实例(办法中须要 @Adaptive 注解,参数会对 URL 校验)TestService testService = ExtensionLoader.getExtensionLoader(TestService.class).getAdaptiveExtension();
    URL url = new URL("test", "localhost", 8080, new String[]{"test.service", "test1"});
    testService.sayHello("bbb", url);

调用 getAdaptiveExtension 办法最终会生成相应的代理类,最终生成的代理类会依据 URL 参数外面的 protocol 决定(以外部 Protocol 为例)



三、服务注册

1、服务注册流程





2、服务注册类图详解





3、服务注册步骤

(1)步骤一:初始化配置(类图:形象 Config 与初始化配置)

首先须要实例化 ServiceConfig 实例,申明“注册接口、接口实例、注册核心配置”,其中“ServiceBean”是实现 Spring 与 Dubbo 整合的桥梁。而后会由 DubboBootstrap 调用 initialize 办法 实现 configManager 和 Environment 的初始化,其中就包含将 ServiceConfig 中的配置转换成外部封装的协定(ApplicationModel、ProviderModel 等)

private static void startWithExport() throws InterruptedException {
    // 初始化配置
    ServiceConfig<DemoServiceImpl> service = new ServiceConfig<>();
    service.setInterface(DemoService.class);
    service.setRef(new DemoServiceImpl());
    service.setApplication(new ApplicationConfig("dubbo-demo-api-provider"));
    service.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
    // 服务注册入口
    service.export();}
public synchronized void export() {if (bootstrap == null) {bootstrap = DubboBootstrap.getInstance();
        // compatible with api call.
        if (null != this.getRegistry()) {bootstrap.registries(this.getRegistries());
        }
        // 初始化配置()
        bootstrap.initialize();}
    ......        
    if (shouldDelay()) {DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
    } else {
        // 服务注册
        doExport();}

    exported();}

(2)步骤二:组装 URL

依据初始化配置组转注册接口服务的 URL。其中 URL 也是 Dubbo 外部通过 @Adaptive 注解实现 SPI 的外围,通过批改 URL 的头部协定(如:register、dubbo、injvm 等),在调用

private static final Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
PROTOCOL.export(wrapperInvoker)

该办法的时候,会依据不同的协定切换不通的实现类,实现了 Dubbo 技术架构与业务逻辑的解耦。

private void doExportUrls() {
    // 组装后的 URL 格局样例
    //registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-api-provider&dubbo=2.0.2&pid=26212®istry=zookeeper×tamp=1663049763199
    List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);

    int protocolConfigNum = protocols.size();
    for (ProtocolConfig protocolConfig : protocols) {
        // 组装 pathKey : org.apache.dubbo.demo.DemoService
        String pathKey = URL.buildKey(getContextPath(protocolConfig)
                .map(p -> p + "/" + path)
                .orElse(path), group, version);
        // 保留接口服务
        repository.registerService(pathKey, interfaceClass);
        // 服务注册
        doExportUrlsFor1Protocol(protocolConfig, registryURLs, protocolConfigNum);
    }
}

(3)步骤三:Invoker 封装(类图:Ref -> Invoker)

通过内置的动静字节码编译(默认 javassist)生成 Invoker 代理类,而后通过反射机制生成 Wrapper 实例。其中 Invoker 是 Dubbo 的外围模型,Invoker 是 Dubbo 中的实体域,也就是实在存在的。其余模型都向它聚拢或转换成它

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs, int protocolConfigNum) {
    ......
    // 组装新的 URL
    //dubbo://2.0.0.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&bind.ip=2.0.0.1&bind.port=20880&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=46528&release=&service.name=ServiceBean:/org.apache.dubbo.demo.DemoService&side=provider×tamp=1663051456562
    URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
    ......
    //Invoker 封装
    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass,
             registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
    //wrapper
    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

    // 服务注册(此时 URL 头部协定变成了 register,理论会调用 RegistryProtocol)
    Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
    exporters.add(exporter);
}

# PROXY_FACTORY
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
    // 动静代理类生成,反射生成实例
    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);
        }
    };
}

(4)步骤四:Exporter 封装(类图:Invoker-> Exporter)

此时会顺次调用 RegistryProtocol、DubboProtocol 将 Invoker 封装成 Exporter,并将封装后的 Exporter 存储到本地 map 中(相似于 spring bean)。而后会调用底层通信服务(默认 netty)进行端口监听,此时会通过 责任链模式 封装 Exchanger 与 Transporter,用于解决网络传输音讯的编码 / 解码。

# RegistryProtocol : export
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
    ......
    // 此时 URL 头部协定已变成 dubbo
    //dubbo://2.0.0.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&bind.ip=2.0.0.1&bind.port=20880&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=56036&release=&service.name=ServiceBean:/org.apache.dubbo.demo.DemoService&side=provider×tamp=1663052353098
    providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
    // export invoker
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);

    // 此时 Registry 实例默认是 ZookeeperRegistry
    final Registry registry = getRegistry(originInvoker);

    final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);
    
    // decide if we need to delay publish
    boolean register = providerUrl.getParameter(REGISTER_KEY, true);
    if (register) {
        // 底层调用 ZK,创立 node 节点
        registry.register(registeredProviderUrl);
    }
    ....
}

# RegistryProtocol : doLocalExport
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {String key = getCacheKey(originInvoker);

    return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
        // 此时会调用 DubboProtocol 进行 exporter 封装
        return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
    });
}
# DubboProtocol : export
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { 
    ......
    // export service.
    String key = serviceKey(url);
    //exporter 封装
    DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
    exporterMap.put(key, exporter);
    ......
    // 开启服务监听
    openServer(url);
    optimizeSerialization(url);
    
    return exporter;
}

(5)步骤五:注册服务节点

封装 Exporter 并开启服务端口监听后,会调用注册核心(默认 Zookeeper)注册服务节点信息

# RegistryProtocol : export
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
    ......
    // 此时 URL 头部协定已变成 dubbo
    //dubbo://2.0.0.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&bind.ip=2.0.0.1&bind.port=20880&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=56036&release=&service.name=ServiceBean:/org.apache.dubbo.demo.DemoService&side=provider×tamp=1663052353098
    providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
    // export invoker
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);

    // 此时 Registry 实例默认是 ZookeeperRegistry
    final Registry registry = getRegistry(originInvoker);

    final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);
    
    // decide if we need to delay publish
    boolean register = providerUrl.getParameter(REGISTER_KEY, true);
    if (register) {
        // 底层调用 ZK,创立 node 节点
        registry.register(registeredProviderUrl);
    }
    ....
}

四、总结

至此,Dubbo 服务注册的整体流程已大抵完结,文中如有不当或者错误观点,欢送大家评论区指出。感兴趣的同学,能够关注后续“Dubbo 架构设计与源码解析”系列的文章。

退出移动版