時序圖
在講解原始碼前,先看下官方文件提供的時序圖,後面的講解基本是這個路線,但是會更細節化
大致邏輯
首先服務的實現bean在我們的spring容器中,我們會建立一個Invoker通過代理呼叫ref中的方法,同時Invoker會在protocol的export方法中會轉換為Exporter,並且儲存在protocol物件的exporterMap中,然後進行暴露。
重要概念
Protocol
Protocol 是服務域,它是 Invoker 暴露和引用的主功能入口,它負責 Invoker 的生命週期管理。 介面定義如下
@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();
}
複製程式碼
Invoker
Invoker 是實體域,它是 Dubbo 的核心模型,其它模型都向它靠擾,或轉換成 它,它代表一個可執行體,可向它發起 invoke 呼叫,它有可能是一個本地的 實現,也可能是一個遠端的實現,也可能一個叢集實現。 介面定義如下
public interface Invoker<T> extends Node {
Class<T> getInterface();
Result invoke(Invocation invocation) throws RpcException;
}
複製程式碼
Invocation
Invocation 是會話域,它持有呼叫過程中的變數,比如方法名,引數等。 介面定義如下
public interface Invocation {
String getMethodName();
Class<?>[] getParameterTypes();
Object[] getArguments();
Map<String, String> getAttachments();
String getAttachment(String key);
String getAttachment(String key, String defaultValue);
Invoker<?> getInvoker();
}
複製程式碼
Exporter
Exporter用來封裝不同協議暴露的Invoker,因為Invoker可以被多個Protocol暴露,因為每種Protocol都有各自的Exproter子類 介面定義如下
public interface Exporter<T> {
Invoker<T> getInvoker();
void unexport();
}
複製程式碼
我的一些定義
本地暴露
本地暴露分為兩種,通過遠端協議還是本地協議暴露 本地協議的話,基於程式通訊,所以不需要進行遠端暴露,具體實現只有InjvmProtocol 而基於遠端協議的暴露,需要開啟服務監聽,處理其他程式發來的rpc請求,同時可以選擇進行遠端暴露,具體實現有DubboProtocol,HessianProtocol等
遠端暴露
遠端暴露,就是將本地暴露的url釋出到註冊中心,這個暴露為了讓服務引用者感知到服務的存在 遠端暴露對應RegistryProtocol
遠端暴露URL和本地暴露URL
進行遠端暴露的時候,要先進行本地暴露,所以遠端暴露URL裡面有一個export引數會包含本地暴露URL 遠端暴露URL主要是用來選擇暴露的註冊中心,註冊本地暴露URL,以及增加事件監聽
原始碼分析
解析配置
先看下我們平時是如何配置dubbo服務暴露的
<dubbo:service interface="com.alibaba.dubbo.demo.bid.BidService" ref="bidService" protocol="dubbo" />
複製程式碼
上面的配置會通過自定義解析器DubboNamespaceHandler解析到ServiceBean物件
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
複製程式碼
然後在ServiceBean初始化完成後進行服務暴露
可以看到ServiceBean實現了Initializing介面,可以在afterPropertiesSet看到服務暴露的邏輯if (! isDelay()) {
export();
}
複製程式碼
這邊的Delay並不是服務具體的暴露行為進行延遲,而是控制這個暴露行為在什麼時候觸發
private boolean isDelay() {
Integer delay = getDelay();
ProviderConfig provider = getProvider();
if (delay == null && provider != null) {
delay = provider.getDelay();
}
return supportedApplicationListener && (delay == null || delay.intValue() == -1);
}
複製程式碼
上述程式碼的意思是,如果支援Spring的事件監聽,並且沒有配置延遲暴露,推遲到容器refresh完成的時候觸發服務暴露邏輯,如果配置了delay,那麼直接在afterPropertiesSet內呼叫暴露方法 我理解為一個是容器級別的delay,一個是服務級別的delay
export方法
export方法在ServiceConfig中
public synchronized void export() {
if (provider != null) {
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
if (export != null && ! export.booleanValue()) {
return;
}
if (delay != null && delay > 0) {
Thread thread = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(delay);
} catch (Throwable e) {
}
doExport();
}
});
thread.setDaemon(true);
thread.setName("DelayExportServiceThread");
thread.start();
} else {
doExport();
}
}
複製程式碼
這邊會根據是否配置了delay引數,進行延遲暴露,通過執行緒休眠來實現 doExport方法涉及很多引數的校驗與設定,遇到具體功能點再做分析,具體暴露邏輯呼叫了方法doExportUrls
private void doExportUrls() {
List<URL> registryURLs = loadRegistries(true);
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
複製程式碼
doExportUrls方法首先會獲取註冊中心的URL,雖說可以配置很多個註冊中心,但是我們就把它當成一個好了 然後根據service配置的不同協議,呼叫doExportUrlsFor1Protocol方法分別進行暴露 在doExportUrlsFor1Protocol的前半部分又是各種引數的提取,用來生成最終暴露的URL,我們關注核心的暴露邏輯
//1
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
//2
String scope = url.getParameter(Constants.SCOPE_KEY);
//配置為none不暴露
if (! Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
//配置不是remote的情況下做本地暴露 (配置為remote,則表示只暴露遠端服務)
//3
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
exportLocal(url);
}
//如果配置不是local則暴露為遠端服務.(配置為local,則表示只暴露遠端服務)
//4
if (! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope) ){
if (logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
//5
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);
}
}
}
this.urls.add(url);
複製程式碼
注意上面程式碼1處,就是使用之前提取的引數生成本地暴露url的邏輯 而程式碼2處的scope也還是比較重要的,它控制了服務應該怎麼暴露,我們專案中一般對service不進行scope配置,那麼取到的值為null,程式碼3和程式碼4的條件都會滿足,既會進行本地協議的本地暴露,也會進行遠端暴露 而程式碼5,我們可以配置register="false",直接進行遠端協議的本地暴露,不記錄到註冊中心上去,但是我們還是可以通過在消費者強制配置url來呼叫
<dubbo:service interface="com.alibaba.dubbo.demo.bid.BidService" ref="bidService" protocol="dubbo" register="false"/>
複製程式碼
服務暴露的邏輯其實是同一套
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
Exporter<?> exporter = protocol.export(invoker);
複製程式碼
主要的區別點還是在於Url的不同,因為url帶了不同的protocol以及其他配置,然後具體暴露時,使用之前講的SPI來呼叫不同實現 比如在exportLocal方法裡,其實會把url的protocol修改為injvm
private void exportLocal(URL url) {
if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
//修改protocol為Injvm
URL local = URL.valueOf(url.toFullString())
.setProtocol(Constants.LOCAL_PROTOCOL)
.setHost(NetUtils.LOCALHOST)
.setPort(0);
// modified by lishen
ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref));
Exporter<?> exporter = protocol.export(
proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() +" to local registry");
}
}
複製程式碼
在存在註冊中心,並且服務的Registry屬性不為false的情況下會進行遠端暴露,會在註冊中心url的export引數帶上原先的本地暴露url進行遠端暴露,因此暴露使用的protocol也相應變為RegistryProtocol
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
複製程式碼
下面講解具體暴露的邏輯
ref轉換為Invoker
在進行暴露之前,我們需要將spring容器內的介面實現ref轉換為invoker,通過proxyFactory.getInvoker(ref, (Class) interfaceClass, local)方法
proxyFactory是一個擴充套件點,有javaassist和jdk動態代理兩種實現,預設實現為javaassist,並且提供一個包裝類StubProxyFactoryWrapper用於提供降級服務(以後單獨講解)
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);
}
};
}
}
複製程式碼
JavassistProxyFactory 中的Wapper類是動態生成的,可以針對介面的每個方法生成直接呼叫的程式碼,避免了反射,因為做了快取,多次呼叫的情況下,會加快效率,而jdk實現用的反射效率應該差多了
//JdkProxyFactory的AbstractProxyInvoker實現
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);
}
};
複製程式碼
通過proxyFactory我們會得到一個父類為AbstractProxyInvoker的匿名Invoker類,內部通過反射或者動態生成位元組碼來呼叫目標ref的方法
通過protocol暴露
在得到Invoker後,我們通過protocol去進行服務暴露,暴露成功後得到Exporter引用
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
複製程式碼
通過Protocol的export方法,需要將invoker轉換為exporter,為什麼? 因為Invoker只負責對具體方法的呼叫,但是方法的呼叫可以暴露到多個Protocol,所以需要有具體的Exporter來對應,比如Dubbo暴露得到DubboExporter,injvm暴露得到InjvmExporter
下面講解每種protocol的暴露
通過InjvmProtocol暴露
InjvmProtocol是本地暴露中唯一使用本地協議的,意思就是說這個服務的url不能釋出到註冊中心,只能本地消費,在dubbo引用服務的邏輯中,如果發現本地InjvmProtocol中有所需要的Exproter,會優先選擇引用本地 看下暴露的程式碼
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}
複製程式碼
exporterMap為InjvmProtocol繼承AbstractProtocol的一個引數,用來儲存Exproter引用,同時exporterMap也會在InjvmExporter內被引用,主要用於解除安裝功能
class InjvmExporter<T> extends AbstractExporter<T> {
private final String key;
private final Map<String, Exporter<?>> exporterMap;
InjvmExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap){
super(invoker);
this.key = key;
this.exporterMap = exporterMap;
exporterMap.put(key, this);
}
public void unexport() {
super.unexport();
exporterMap.remove(key);
}
}
複製程式碼
關於這個exporterMap,由於每個Protocol實現都繼承了AbstractProtocol,所以都會有exporterMap屬性,並且每種Protocol在容器內只存在一個,我們可以在每個Protocol物件的exporterMap中拿到這個Protocol暴露的所有Exproter
通過RegistryProtocol暴露
這是遠端暴露,在進行本地暴露的同時將本地暴露的url註冊到註冊中心同時也註冊事件監聽
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//export invoker
//1
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);
//註冊監聽事件,用於url被修改時回撥,進行exporter重新暴露
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);
}
}
};
}
複製程式碼
注意程式碼1處,有一個本地暴露,看下程式碼
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker){
String key = getCacheKey(originInvoker);
ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
synchronized (bounds) {
exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
exporter = new ExporterChangeableWrapper<T>((Exporter<T>)protocol.export(invokerDelegete), originInvoker);
bounds.put(key, exporter);
}
}
}
return (ExporterChangeableWrapper<T>) exporter;
}
複製程式碼
getProviderUrl用於從遠端暴露url中的export引數中獲取本地暴露的url
private URL getProviderUrl(final Invoker<?> origininvoker){
String export = origininvoker.getUrl().getParameterAndDecoded(Constants.EXPORT_KEY);
if (export == null || export.length() == 0) {
throw new IllegalArgumentException("The registry export url is null! registry: " + origininvoker.getUrl());
}
URL providerUrl = URL.valueOf(export);
return providerUrl;
}
複製程式碼
export引數對應的url才是需要實際本地暴露的,而作為export方法的遠端暴露url只是為了註冊提供者url到註冊中心以及增加事件監聽
同時注意一下bounds引數的校驗是為了防止同一個invoker重複暴露,而ExporterChangeableWrapper封裝是為了zookeeper中url發生改變時能修改Exporter
在完成本地暴露之後,會通過遠端暴露url獲取註冊中心物件,然後把本地暴露url註冊上去,同時也會給zookeeper中本地暴露url對應路徑註冊監聽器,用於監聽zookeeper上面的暴露url發生變化的時候,重新export(比如我們的控制檯可以對引數進行調整)
最後把export返回
通過DubboProtocol暴露
DubboProtocol是使用遠端協議的本地暴露,所以可以將暴露url註冊到註冊中心 看下它的export方法
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// export service.
String key = serviceKey(url);
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
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);
// modified by lishen
optimizeSerialization(url);
return exporter;
}
複製程式碼
首先會把invoker轉換為DubboExporter,放到exporterMap中 然後有一些stub的邏輯,這個以後單獨再講 接下來就是開啟netty服務,用於監聽服務引用者的請求,開啟伺服器邏輯在openServer中
private void openServer(URL url) {
// find server.
String key = url.getAddress();
//client 也可以暴露一個只有server可以呼叫的服務。
boolean isServer = url.getParameter(Constants.IS_SERVER_KEY,true);
if (isServer) {
ExchangeServer server = serverMap.get(key);
if (server == null) {
serverMap.put(key, createServer(url));
} else {
//server支援reset,配合override功能使用
server.reset(url);
}
}
}
複製程式碼
從String key = url.getAddress();以及ExchangeServer server = serverMap.get(key);可以看出來在一個應用中,netty伺服器針對每種協議只會起一個,因為每種協議只能配置一個埠 而reset方法,會使用之後暴露url的引數,覆蓋已經開啟netty服務內的引數 那麼我們的伺服器是怎麼處理接收的rpc請求並呼叫對應exporter呼叫呢,進入createServer方法我們可以看到會netty服務的開啟時會繫結一個requestHandler
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 {
//1 繫結requestHandler
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就是用來處理接收到的rpc呼叫請求的,看下它內部的邏輯
private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {
public Object reply(ExchangeChannel channel, Object message) throws RemotingException {
if (message instanceof Invocation) {
Invocation inv = (Invocation) message;
//通過inv獲取對應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());
//用invoker執行呼叫,返回結果
return invoker.invoke(inv);
}
throw new RemotingException(channel, "Unsupported request: " + message == null ? null : (message.getClass().getName() + ": " + message) + ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress());
}
@Override
public void received(Channel channel, Object message) throws RemotingException {
if (message instanceof Invocation) {
reply((ExchangeChannel) channel, message);
} else {
super.received(channel, message);
}
}
@Override
public void connected(Channel channel) throws RemotingException {
invoke(channel, Constants.ON_CONNECT_KEY);
}
@Override
public void disconnected(Channel channel) throws RemotingException {
if(logger.isInfoEnabled()){
logger.info("disconected from "+ channel.getRemoteAddress() + ",url:" + channel.getUrl());
}
invoke(channel, Constants.ON_DISCONNECT_KEY);
}
private void invoke(Channel channel, String methodKey) {
Invocation invocation = createInvocation(channel, channel.getUrl(), methodKey);
if (invocation != null) {
try {
received(channel, invocation);
} catch (Throwable t) {
logger.warn("Failed to invoke event method " + invocation.getMethodName() + "(), cause: " + t.getMessage(), t);
}
}
}
private Invocation createInvocation(Channel channel, URL url, String methodKey) {
String method = url.getParameter(methodKey);
if (method == null || method.length() == 0) {
return null;
}
RpcInvocation invocation = new RpcInvocation(method, new Class<?>[0], new Object[0]);
invocation.setAttachment(Constants.PATH_KEY, url.getPath());
invocation.setAttachment(Constants.GROUP_KEY, url.getParameter(Constants.GROUP_KEY));
invocation.setAttachment(Constants.INTERFACE_KEY, url.getParameter(Constants.INTERFACE_KEY));
invocation.setAttachment(Constants.VERSION_KEY, url.getParameter(Constants.VERSION_KEY));
if (url.getParameter(Constants.STUB_EVENT_KEY, false)){
invocation.setAttachment(Constants.STUB_EVENT_KEY, Boolean.TRUE.toString());
}
return invocation;
}
};
複製程式碼
這邊主要講下replay方法,這個方法用來處理使用者的rpc請求,請求的序列化dubbo封裝的netty服務已經處理,所在在這個方法傳入的message直接就是Invocation物件,在getInvoker中,通過invocation物件可以我們可以生成exporterMap的key,用來拿到對應的Exporter
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());
}
String serviceKey = serviceKey(port, path, inv.getAttachments().get(Constants.VERSION_KEY), inv.getAttachments().get(Constants.GROUP_KEY));
//2 通過invocation生成的key獲取exporter
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);
//轉換為invoker
return exporter.getInvoker();
}
複製程式碼
拿到Exporter之後,轉換為Invoker,直接呼叫invoke方法返回Result,之後返回給呼叫者的序列化等邏輯dubbo封裝的netty服務也幫我們處理了,我們不用關注 這章主要講解的是服務暴露,關於netty服務的實現不多分析(我也還沒怎麼看過),理解這個requestHandler處理器即可,知道它會怎麼處理rpc請求對應的Invocation即可,什麼序列化,加密解密全都當作黑盒。
服務引用,下章見
最後
希望大家關注下我的公眾號