開局一張圖,內容全靠編
總的概括
先說一下dubbo的服務端初始化過程
1.在serviceConfig裏面組裝配置參數;
2.獲取到對外提供服務的接口,實現類以及註冊url交給ProcxyFactory生成本地代理invoker;當消費者請求過來的時候,最後都是交給invoker去執行,然後invoker通過反射調用真正的實例;
3.生成完本地代理Invoker後,在DubboProtocol中對invoker進行暴露,先將invoker包裝生成exporter,再將export作爲value,服務接口和端口號組合成key存入到DubboProtocol的map中,這個可以看成是本地註冊。
4.在DubboProtocol中生存開啓netty通信生存ExchangeServer,ExchangeServer是負責底層通信的,將ip加端口號作爲key,ExchangeServer爲value存入到map中。ExchangeServer負責相同的ip端口號服務通信。
5.通過invoker裏面的url信息獲取獲取註冊中心,將服務者信息註冊到註冊中心,此節點存儲了服務提供方ip、端口、group、接口名稱、版本、應用名稱,這樣可以讓消費者從註冊中心獲取到服務者的信息。
6.爲了感知註冊中心的一些配置變化,提供者會監聽註冊中心路徑/dubbo/${interfaceClass}/configurators的節點,監聽該節點在註冊中心的一些配置信息變更。zookeeper註冊中心通過zookeeper框架的監聽回調接口進行監聽(redis註冊中心通過訂閱命令(subscribe)監聽),服務器緩存註冊中心的配置,當配置發生變更時,服務會刷新本地緩存,代碼在ZookeeperRegistry的doSubscribe方法。
源碼分析
下面是dubbo服務端的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd ">
<!-- 具體的實現bean -->
<bean id="testService" class="com.ts.services.impl.TestServiceImpl" />
<!-- 提供方應用信息,用於計算依賴關係 -->
<dubbo:application name="provider" />
<!-- 使用zookeeper註冊中心暴露服務地址 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<!-- 用dubbo協議在20880端口暴露服務 -->
<dubbo:protocol name="dubbo" port="29014" />
<!-- 聲明需要暴露的服務接口 -->
<dubbo:service interface="com.ts.service.TestService" ref="testService" timeout="300"/>
</beans>
spring會對dubbo:service標籤進行解析生成serviceBean,serviceBean實現了ApplicationListener,重寫了onApplicationEvent方法,當所有bean刷新完畢後,會調用onApplicationEvent方法,在onApplicationEvent方法中會調用ServiceConfig的export方法
ServiceConfig主要做了三件事
1.組裝和檢查配置文件裏面配置的註冊信息,接口信息和協議信息
2.讓proxyFactory對要暴露的服務生成Invoker
3.將Invoker交給proctol暴露,開啓netty通信,將invoker信息註冊到註冊中心方便消費者調用,並監聽註冊中心服務者的配置,
當服務者的配置改變後能動態刷新本地緩存
if (! Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
//配置不是remote的情況下做本地暴露 (配置爲remote,則表示只暴露遠程服務)
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
exportLocal(url);
}
//如果配置不是local則暴露爲遠程服務.(配置爲local,則表示只暴露遠程服務)
if (! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope) ){
if (logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
//當配置的有註冊中心的時候
if (registryURLs != null && registryURLs.size() > 0
&& url.getParameter("register", true)) {
for (URL registryURL : registryURLs) {
url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
}
if (logger.isInfoEnabled()) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
}
//生成本地代理
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
//進行服務暴露和註冊
Exporter<?> exporter = protocol.export(invoker);
exporters.add(exporter);
}
} else {
//沒有註冊中心,直接連接消費者端
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
Exporter<?> exporter = protocol.export(invoker);
exporters.add(exporter);
}
}
}
1.生成本地代理Invoker
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
上面是通過proxyFactory對ref,也就是接口的實現類生成代理invoker,proxyFactory默認的是JavassistProxyFactory
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);
}
};
}
可看到getInvoker方法返回的是一個重寫了AbstractProxyInvoker doInvoker方法的實例,在doInvoker方法裏調用了wapper.invoker方法,這裏wapper.invoker方法運用javassist動態編碼技術形成新的代理類。記住這裏生成的invoker就是對服務端要暴露的服務的一種代理,這樣是爲了解耦dubbo和要暴露的服務,讓所有客戶端的請求先交給Invoker處理,invoker再根據客戶端的請求信息利用反射調用真正的服務。
2.對代理進行暴露
Exporter<?> exporter = protocol.export(invoker);
這裏的protocol是通過spi機制動態生成的,它會返回ProtocolListenerWrapper->ListenerExporterWrapper->RegistryProtocol對象鏈,對於registry協議,兩個Wrapper都不會做任何處理,會直接調到RegistryProtocol.export方法。
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//export invoker,進行暴露,也就是開啓了netty通信
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
//registry provider 獲取註冊中心
final Registry registry = getRegistry(originInvoker);
final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
//註冊服務者節點
registry.register(registedProviderUrl);
// 訂閱override數據
// FIXME 提供者訂閱時,會影響同一JVM即暴露服務,又引用同一服務的的場景,因爲subscribed以服務名爲緩存的key,導致訂閱信息覆蓋。
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
//監聽服務者在註冊中心的配置,當配置改變的時候能刷新本地緩存
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
//保證每次export都返回一個新的exporter實例
return new Exporter<T>() {
public Invoker<T> getInvoker() {
return exporter.getInvoker();
}
public void unexport() {
try {
exporter.unexport();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
registry.unregister(registedProviderUrl);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
overrideListeners.remove(overrideSubscribeUrl);
registry.unsubscribe(overrideSubscribeUrl, overrideSubscribeListener);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
};
}
doLocalExport方法
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker){
String key = getCacheKey(originInvoker);
//exporter代理,建立返回的exporter與protocol export出的exporter的對應關係,在override時可以進行關係修改.
ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
synchronized (bounds) {
exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
//聲明一個invoker的代理
final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
//利用dubboProtocol對invoker進行暴露
exporter = new ExporterChangeableWrapper<T>((Exporter<T>)protocol.export(invokerDelegete), originInvoker);
bounds.put(key, exporter);
}
}
}
return (ExporterChangeableWrapper<T>) exporter;
}
進入dubboproctol的export方法
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
//dubbo://202.106.199.34:29014/com.ts.service.TestService?anyhost=true&application=provider&dubbo=2.5.3&interface=com.ts.service.TestService&methods=getName&pid=59736&side=provider&timeout=300×tamp=1563845476832
URL url = invoker.getUrl();
// export service. key的值:com.ts.service.TestService:29014
//key其實包括接口名,端口號,以及服務的版本號,分組名
String key = serviceKey(url);
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
//將key和export存入map中,這樣可以根據客戶端發送過來的invocation解析得到取出哪一個export
exporterMap.put(key, exporter);
//export an stub service for dispaching event
Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY,Constants.DEFAULT_STUB_EVENT);
Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
if (isStubSupportEvent && !isCallbackservice){
String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
if (stubServiceMethods == null || stubServiceMethods.length() == 0 ){
if (logger.isWarnEnabled()){
logger.warn(new IllegalStateException("consumer [" +url.getParameter(Constants.INTERFACE_KEY) +
"], has set stubproxy support event ,but no stub methods founded."));
}
} else {
stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
}
}
openServer(url);
return exporter;
}
openServer方法,生成一個ExchangeServer,ExchangeServer負責底層通信
private void openServer(URL url) {
// find server. 202.106.199.34:29014
String key = url.getAddress();
//client 也可以暴露一個只有server可以調用的服務。
boolean isServer = url.getParameter(Constants.IS_SERVER_KEY,true);
if (isServer) {
//同一個ip和端口號的服務都交給同一個ExchangeServer處理
ExchangeServer server = serverMap.get(key);
if (server == null) {
serverMap.put(key, createServer(url));
} else {
//server支持reset,配合override功能使用
server.reset(url);
}
}
}
createServer(url)方法
private ExchangeServer createServer(URL url) {
//默認開啓server關閉時發送readonly事件
url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
//默認開啓heartbeat
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);
if (str != null && str.length() > 0 && ! ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
throw new RpcException("Unsupported server type: " + str + ", url: " + url);
url = url.addParameter(Constants.CODEC_KEY, Version.isCompatibleVersion() ? COMPATIBLE_CODEC_NAME : DubboCodec.NAME);
ExchangeServer server;
try {
//開啓netty通信
server = Exchangers.bind(url, requestHandler);
} catch (RemotingException e) {
throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
}
str = url.getParameter(Constants.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;
}
先看看requestHandler這個DubboProcto裏面的內部類,這個類是處理接受從客戶端來的請求,並通過invocation裏面所帶的參數組長key取出對應的export,再利用export裏面的invoker處理請求
private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {
public Object reply(ExchangeChannel channel, Object message) throws RemotingException {
if (message instanceof Invocation) {
Invocation inv = (Invocation) message;
//通過invocation獲取invoker
Invoker<?> invoker = getInvoker(channel, inv);
//如果是callback 需要處理高版本調用低版本的問題
if (Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INVOKE))){
String methodsStr = invoker.getUrl().getParameters().get("methods");
boolean hasMethod = false;
if (methodsStr == null || methodsStr.indexOf(",") == -1){
hasMethod = inv.getMethodName().equals(methodsStr);
} else {
String[] methods = methodsStr.split(",");
for (String method : methods){
if (inv.getMethodName().equals(method)){
hasMethod = true;
break;
}
}
}
if (!hasMethod){
logger.warn(new IllegalStateException("The methodName "+inv.getMethodName()+" not found in callback service interface ,invoke will be ignored. please update the api interface. url is:" + invoker.getUrl()) +" ,invocation is :"+inv );
return null;
}
}
RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
//執行具體的請求
return invoker.invoke(inv);
}
throw new RemotingException(channel, "Unsupported request: " + message == null ? null : (message.getClass().getName() + ": " + message) + ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress());
}
.......
}
Invoker<?> getInvoker(Channel channel, Invocation inv) throws RemotingException{
boolean isCallBackServiceInvoke = false;
boolean isStubServiceInvoke = false;
int port = channel.getLocalAddress().getPort();
String path = inv.getAttachments().get(Constants.PATH_KEY);
//如果是客戶端的回調服務.
isStubServiceInvoke = Boolean.TRUE.toString().equals(inv.getAttachments().get(Constants.STUB_EVENT_KEY));
if (isStubServiceInvoke){
port = channel.getRemoteAddress().getPort();
}
//callback
isCallBackServiceInvoke = isClientSide(channel) && !isStubServiceInvoke;
if(isCallBackServiceInvoke){
path = inv.getAttachments().get(Constants.PATH_KEY)+"."+inv.getAttachments().get(Constants.CALLBACK_SERVICE_KEY);
inv.getAttachments().put(IS_CALLBACK_SERVICE_INVOKE, Boolean.TRUE.toString());
}
//端口號,接口服務,版本號,分組組合成一個key
String serviceKey = serviceKey(port, path, inv.getAttachments().get(Constants.VERSION_KEY), inv.getAttachments().get(Constants.GROUP_KEY));
//通過key獲取到對應的export
DubboExporter<?> exporter = (DubboExporter<?>) exporterMap.get(serviceKey);
if (exporter == null)
throw new RemotingException(channel, "Not found exported service: " + serviceKey + " in " + exporterMap.keySet() + ", may be version or group mismatch " + ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress() + ", message:" + inv);
return exporter.getInvoker();
}
下面看一下dubbo服務端是如何開啓netty通信的
server = Exchangers.bind(url, requestHandler);
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");
//getExchanger(url) 獲取到HeaderExchanger
//url的值是:dubbo://202.106.199.34:29014/com.ts.service.TestService?anyhost=true&application=provider&channel.readonly.sent=true&codec=dubbo&dubbo=2.5.3&heartbeat=60000&interface=com.ts.service.TestService&methods=getName&pid=59736&side=provider&timeout=300×tamp=1563845476832
return getExchanger(url).bind(url, handler);
}
HeaderExchanger的bind方法
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
可以看到這裏是一個裝飾模式的調用鏈,HeaderExchangeServer--->NettyServer--->DecodeHandler--->HeaderExchangeHandler-->ExchangeHandler。
Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))
返回的是一個nettyservice,這個地方的調用鏈比想象的要複雜,只能淺嘗輒止了。
在開啓netty通信後,下面再看服務的註冊和監聽,RegistryProcotol的export方法
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//export invoker
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
//registry provider
final Registry registry = getRegistry(originInvoker);
final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
//將服務提供者信息註冊到註冊中心,方便消費者能在註冊中心訂閱到服務
registry.register(registedProviderUrl);
// 訂閱override數據
// FIXME 提供者訂閱時,會影響同一JVM即暴露服務,又引用同一服務的的場景,因爲subscribed以服務名爲緩存的key,導致訂閱信息覆蓋。
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
//訂閱服務者配置信息,這樣當服務者配置信息發生改變的時候,能刷新本地緩存,重新生成export
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
//保證每次export都返回一個新的exporter實例
return new Exporter<T>() {
public Invoker<T> getInvoker() {
return exporter.getInvoker();
}
public void unexport() {
try {
exporter.unexport();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
registry.unregister(registedProviderUrl);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
overrideListeners.remove(overrideSubscribeUrl);
registry.unsubscribe(overrideSubscribeUrl, overrideSubscribeListener);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
};
}
最後總結一下dubbo裏面比較重要的組建
Invoker
Invoker起到代理處理器的作用,在消費者端,Invoker負責和遠程服務端通信
在服務者端,Invoker服務利用反射調用具體的服務實力處理客戶端的請求
ProxyFactory
從JavassistProxyFactory就可以看出來,在客戶端ProxyFactory會將invoker代理成一個符合客戶端要求的實例,比如,客戶端要調用遠程服務DemoService,那ProxyFactory就會將invoker作爲一個被代理的目標生成DemoService代理對象。
在服務端,ProxyFactory會將具體的服務,例如DemoServiceImpl代理成invoker
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);
}
};
}
}
Procotol
@SPI("dubbo")
public interface Protocol {
/**
* 獲取缺省端口,當用戶沒有配置端口時使用。
*
* @return 缺省端口
*/
int getDefaultPort();
/**
* 暴露遠程服務:<br>
* 1. 協議在接收請求時,應記錄請求來源方地址信息:RpcContext.getContext().setRemoteAddress();<br>
* 2. export()必須是冪等的,也就是暴露同一個URL的Invoker兩次,和暴露一次沒有區別。<br>
* 3. export()傳入的Invoker由框架實現並傳入,協議不需要關心。<br>
*
* @param <T> 服務的類型
* @param invoker 服務的執行體
* @return exporter 暴露服務的引用,用於取消暴露
* @throws RpcException 當暴露服務出錯時拋出,比如端口已佔用
*/
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
/**
* 引用遠程服務:<br>
* 1. 當用戶調用refer()所返回的Invoker對象的invoke()方法時,協議需相應執行同URL遠端export()傳入的Invoker對象的invoke()方法。<br>
* 2. refer()返回的Invoker由協議實現,協議通常需要在此Invoker中發送遠程請求。<br>
* 3. 當url中有設置check=false時,連接失敗不能拋出異常,並內部自動恢復。<br>
*
* @param <T> 服務的類型
* @param type 服務的類型
* @param url 遠程服務的URL地址
* @return invoker 服務的本地代理
* @throws RpcException 當連接服務提供方失敗時拋出
*/
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
/**
* 釋放協議:<br>
* 1. 取消該協議所有已經暴露和引用的服務。<br>
* 2. 釋放協議所佔用的所有資源,比如連接和端口。<br>
* 3. 協議在釋放後,依然能暴露和引用新的服務。<br>
*/
void destroy();
}
procotol通常就是暴露遠程服務和引用遠程服務,但在RegistryProcotol中的代碼,可以看到除了暴露遠程服務和引用遠程服務外還有將服務註冊到註冊中心方便訂閱,然後監聽註冊中心,當註冊中心裏面有什麼變化的時候可以刷新本地緩存。
在DubboProctol裏面負責客戶端底層通信的是ExchangeClient,在服務端負責底層通信的是ExchangeServer