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,其類結構圖如下:
我們從中能夠學習到什麼
設計模式的使用
- 裝飾模式,Protocol協議的裝飾,具體實現可以參考裝飾器模式-Mybatis教你如何玩。
- 責任鏈模式,ProtocolFilterWrapper,對Protocol進行過濾裝飾時,採用鏈表的形式實現了責任鏈的過濾。具體的責任鏈設計可以參考責任鏈如何設計?Tomcat和Netty分別是如何實現過濾器鏈和事件鏈?
- 事件監聽模式,ProtocolFilterWrapper,對Protocol成功導出服務和導出服務失敗進行事件的監聽。具體的事件監聽設計可以參考事件驅動模式-Tomcat和zookeeper教你如何玩
動態代理的使用
使用Javassit或者JDK動態代理技術生成Invoker實例,統一了對所有服務方法的執行,並且方便對Invoker進行其他邏輯的裝飾。具體的動態代理理解可以參考代理(Proxy)是什麼?爲什麼要使用代理模式?Spring與Dubbo如何運用代理模式的?
通信協議的使用
在遠程服務綁定服務時,使用了mina與netty框架,實現了對TCP/IP協議,基於NIO進行遠程服務通信,NIO的詳細使用可以參考Netty如何實現常見的兩種線程模式?dubbo基於Netty發佈服務時如何選擇線程模式?