Dubbo服務導出如何實現的?我們從中能夠學習到什麼?

Dubbo通過註解或者xml形式標識Dubbo服務,在Spring 容器發佈刷新事件,會立即執行服務導出邏輯,示例如下:

import com.alibaba.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;
import java.util.stream.Collectors;

/**
 * 價格服務
 **/
@Service
@Component
public class PriceFacade implements IPriceFacade {
}

Dubbo服務導出分爲本地導出和遠程導出,本地服務導出就是本地方法的調用,遠程導出就是通過設置的通信方式進行遠程服務調用,服務導出後,Dubbo還支持向不同的註冊中心註冊導出的服務。以下重點分析多協議多註冊中心服務導出的過程,其大致流程如下:

簡單總結服務導出的流程,就是Dubbo根據設置的協議導出服務,如果是遠程服務導出,則根據設置的協議,例如TCP/IP或者HTTP協議進行遠程通信。如果設置了服務註冊中心,則會在服務導出後,向註冊中心註冊服務。

在Dubbo中,對於協議Protocol,通信Transporter,以及服務註冊Registry的選擇都是基於Dubbo SPI自適應機制實現的。如果,配置了服務導出的過濾器或者監聽器,也會根據Dubbo SPI自動激活的機制加載對應的集合對象。Dubbo SPI的機制詳解可以參考Dubbo SPI是什麼?Dubbo SPI如何實現?我們從中能夠學習到什麼?

多協議多註冊中心

在Spring 容器發佈刷新事件,會調用Dubbo配置ServiceConfig的export方法進行服務導出。在doExportUrls方法中執行多協議多註冊中心的服務導出邏輯。在doExportUrlsFor1Protocol方法中,根據到協議導出服務,根據配置執行本地服務導出或遠程服務導出,並且根據是否有註冊中心的配置執行舒服註冊邏輯,其部分源碼如下:

// ServiceConfig.class
// 服務導出
public synchronized void export() {
        if (!shouldExport()) {
            return;
        }

        if (bootstrap == null) {
            bootstrap = DubboBootstrap.getInstance();
            bootstrap.init();
        }

        //------- 省略 ---------//

        if (shouldDelay()) {
            DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
        } else {
            doExport();
        }

        exported();
    }

// ServiceConfig.class
// 多協議服務導出,多註冊中心註冊
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();
    }

// ServiceConfig.class
// 服務導出
private void doExportUrls() {
        ServiceRepository repository = ApplicationModel.getServiceRepository();
        ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass());
        repository.registerProvider(
                getUniqueServiceName(),
                ref,
                serviceDescriptor,
                this,
                serviceMetadata
        );
        // 註冊中心URL
        List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
        // 多協議
        for (ProtocolConfig protocolConfig : protocols) {
            String pathKey = URL.buildKey(getContextPath(protocolConfig)
                    .map(p -> p + "/" + path)
                    .orElse(path), group, version);
         repository.registerService(pathKey, interfaceClass);
            serviceMetadata.setServiceKey(pathKey);
            // 單個協議的服務導出
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

// ServiceConfig.class
// 單個協議的服務導出
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        String name = protocolConfig.getName();
        if (StringUtils.isEmpty(name)) {
            name = DUBBO;
        }

        //------- 省略參數設置部分 --------//
        // 服務導出
        if (!SCOPE_NONE.equalsIgnoreCase(scope)) {

            // 本地服務導出
            if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
            // 遠程服務導出
            if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
                // 包含註冊中心配置
                if (CollectionUtils.isNotEmpty(registryURLs)) {
                    for (URL registryURL : registryURLs) {
                        if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
                            continue;
                        }
                        url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
                        // 監視器配置
                        URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);
                        if (monitorUrl != null) {
                            url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
                        } 
                        String proxy = url.getParameter(PROXY_KEY);
                        if (StringUtils.isNotEmpty(proxy)) {
                            registryURL = registryURL.addParameter(PROXY_KEY, proxy);
                        }
                        // 動態代理生成Invoker
                        Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) 
                        // Invoker包裝類
interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
                        // 協議導出服務
                        Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
                        exporters.add(exporter);
                    }
                }
// 沒有註冊中心直接導出服務
 else {
                    // 動態代理生成Invoker
                    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
                    // 協議導出服務
                    Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
                    exporters.add(exporter);
                }

                WritableMetadataService metadataService = WritableMetadataService.getExtension(url.getParameter(METADATA_KEY, DEFAULT_METADATA_STORAGE_TYPE));
                if (metadataService != null) {
                    metadataService.publishServiceDefinition(url);
                }
            }
        }
        this.urls.add(url);
    }

動態代理Invoker

在服務導出過程中,使用動態代理的技術,把所有dubbo服務都轉換爲Invoker對象,通過調用invoke方法,Invoker統一了所有服務的調用方式,Invoker接口定義如下:

public interface Invoker<T> extends Node {

    /**
     * 調用服務Class對象
     */
    Class<T> getInterface();

    /**
     * 代理方法,invocation封裝了服務調用參數
     */
    Result invoke(Invocation invocation) throws RpcException;

}

在Dubbo中,使用Javassist和JDK動態代理兩種方式,實現了服務代理類生成,Dubbo默認使用Javassist方式。具體的動態代理定義可以參考代理(Proxy)是什麼?爲什麼要使用代理模式?Spring與Dubbo如何運用代理模式的?。Javassist和JDK動態代理兩種生成Invoker的源碼如下:

/**
 * javassist
 */
public class JavassistProxyFactory extends AbstractProxyFactory {

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

    @Override
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
        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);
            }
        };
    }

}

/**
 * Jdk動態代理
 */
public class JdkProxyFactory extends AbstractProxyFactory {

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

    @Override
    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);
            }
        };
    }

}

服務導出協議

在Dubbo中,支持不同協議的服務導出,主要的包括本地服務導出,基於TCP/IP,HTTP,RMI,THRIFT等等。定義了Protocol接口統一了對不同協議的封裝,接口其服務導出,引用的定義如下:

public interface Protocol {

    /**
     * 導出服務
     */
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    /**
     * 引用服務
     */
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
}

 在Dubbo中,分別對不同基礎協議定義了不同的實現類,如下:

  • InjvmProtocol本地導出
  • DubboProtocol默認協議設置,使用TCP/IP通信協議導出服務
  • HttpProtocol,使用HTTP協議導出服務
  • RestProtocol,基於Rest 風格導出服務
  • HessianProtocol,RmiProtocol,ThriftProtocol,WebServiceProtocol,XmlRpcProtocol

其類結構圖如下:

使用裝飾模式(具體如何使用可以參考裝飾器模式-Mybatis教你如何玩),對Protocol進行裝飾,其實現的裝飾類如下:

  • ProtocolFilterWrapper,過濾器裝飾類,對Protocol的export和refer方法,增加鏈式的過濾處理。例如,日誌打印,併發訪問控制,類加載轉換等等操作。
  • ProtocolListenerWrapper,事件監聽裝飾類,定義ExporterListener監聽服務導出成功或者失敗的事件。

其類結構圖如下:

通信協議

不同的協議使用不同的通信方式進行服務的遠程調用,主要包括TCP/IP,HTTP通信。DubboProtocol採用的TCP/IP協議通信,HttpProtocol採用HTTP協議進行通信。Dubbo定義Transporter接口,監聽服務端口以及鏈接客戶端,其接口定義如下:

public interface Transporter {

    /**
     * 啓動服務端,綁定監聽端口
     */
    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
    RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException;

    /**
     * 鏈接服務端,創建客戶端
     */
    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    Client connect(URL url, ChannelHandler handler) throws RemotingException;

}

採用mina和netty兩種框架進行實現,其實現類分別爲MinaTransporter,基於netty3的NettyTransporter 和基於netty4的NettyTransporter,其類結構圖如下:

定義HttpBinder接口,定義基於HTTP通信協議,其接口定義如下:

public interface HttpBinder {

    /**
     * 綁定HTTP服務器
     */
    @Adaptive({Constants.SERVER_KEY})
    HttpServer bind(URL url, HttpHandler handler);

}

其實現類分別爲JettyHttpBinder,ServletHttpBinder,TomcatHttpBinder,其類結構圖如下:

註冊中心

如果導出服務時,配置了註冊中心,則Dubbo會根據SPI機制,動態的選擇RegistryProtocol。RegistryProtocol使用了裝飾模式,具體的服務導出邏輯,交由其設置的Protocol。在export中,定義向註冊中心的邏輯。其部分源碼如下:

// 實現Protocol接口
public class RegistryProtocol implements Protocol {

// 被裝飾的Protocol
private Protocol protocol;


 @Override
    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {

        // 註冊中心配置
        URL registryUrl = getRegistryUrl(originInvoker);
        URL providerUrl = getProviderUrl(originInvoker);

        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);

        providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
        // 使用protocol導出服務
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
        final Registry registry = getRegistry(originInvoker);
        final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);
        boolean register = providerUrl.getParameter(REGISTER_KEY, true);
        if (register) {
            // 向設置的註冊中心註冊服務
            register(registryUrl, registeredProviderUrl);
        }
        registerStatedUrl(registryUrl, registeredProviderUrl, register);
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
        exporter.setRegisterUrl(registeredProviderUrl);
        exporter.setSubscribeUrl(overrideSubscribeUrl);
        notifyExport(exporter);
        return new DestroyableExporter<>(exporter);
    }

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

            // 使用被裝飾的protocol導出服務
            return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
        });
    }

/**
 * 使用Registry向註冊中心註冊服務
 */
private void register(URL registryUrl, URL registeredProviderUrl) {
        Registry registry = registryFactory.getRegistry(registryUrl);
        registry.register(registeredProviderUrl);
    }
}

在Dubbo中,定義Registry接口,向註冊中心註冊服務,其接口定義如下:

public interface Registry extends Node, RegistryService {
    default void reExportRegister(URL url) {
        register(url);
    }

    default void reExportUnregister(URL url) {
        unregister(url);
    }
}

 其具體的實現類包括,NacosRegistry,ZookeeperRegistry,SofaRegistry和RedisRegistry,其類結構圖如下:

 

我們從中能夠學習到什麼 

設計模式的使用

動態代理的使用

使用Javassit或者JDK動態代理技術生成Invoker實例,統一了對所有服務方法的執行,並且方便對Invoker進行其他邏輯的裝飾。具體的動態代理理解可以參考代理(Proxy)是什麼?爲什麼要使用代理模式?Spring與Dubbo如何運用代理模式的?

通信協議的使用

在遠程服務綁定服務時,使用了mina與netty框架,實現了對TCP/IP協議,基於NIO進行遠程服務通信,NIO的詳細使用可以參考Netty如何實現常見的兩種線程模式?dubbo基於Netty發佈服務時如何選擇線程模式?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章