系列文章
Dubbo分析Serialize層
Dubbo分析之Transport層
Dubbo分析之Exchange 層
Dubbo分析之Protocol層
前言
緊接着上文Dubbo分析之Exchange層,繼續分析protocol遠程調用層,官方介紹:封裝RPC調用,以Invocation, Result爲中心,擴展接口爲Protocol, Invoker, Exporter;
Protocol接口類分析
Protocol可以說是Dubbo的核心層了,在此基礎上可以擴展很多主流的服務,比如:redis,Memcached,rmi,WebService,http(tomcat,jetty)等等;下面看一下接口類源碼:
public interface Protocol {
/**
* 暴露遠程服務:<br>
* 1. 協議在接收請求時,應記錄請求來源方地址信息:RpcContext.getContext().setRemoteAddress();<br>
* 2. export()必須是冪等的,也就是暴露同一個URL的Invoker兩次,和暴露一次沒有區別。<br>
* 3. export()傳入的Invoker由框架實現並傳入,協議不需要關心。<br>
*
* @param <T> 服務的類型
* @param invoker 服務的執行體
* @return exporter 暴露服務的引用,用於取消暴露
* @throws RpcException 當暴露服務出錯時拋出,比如端口已佔用
*/
<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 當連接服務提供方失敗時拋出
*/
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
}
主要定義了2個接口,一個是暴露遠程服務,另一個是引用遠程服務,其實就是服務端和客戶端;dubbo提供了對多種服務的擴展,可以查看META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol:
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol
memcached=com.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol
rest=com.alibaba.dubbo.rpc.protocol.rest.RestProtocol
registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
qos=com.alibaba.dubbo.qos.protocol.QosProtocolWrapper
dubbo協議是默認提供的協議,其他擴展的協議包括:hessian,http(tomcat,jetty),injvm,memcached,redis,rest,rmi,thrift,webservice;以上擴展的協議有些僅僅是作爲引用遠程服務存在(客戶端),比如redis,memcached,通過特定的命令對緩存進行操作;當然也可以擴展自己的協議,分別實現接口類Protocol, Invoker, Exporter;之前分別介紹的serialize層,transport層以及exchange層主要是在使用默認的DubboProtocol才依賴這幾個底層,其他擴展協議直接依賴第三方擴展包;
下面重點分析一下DubboProtocol類,首先看一下refer實現方法:
public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
optimizeSerialization(url);
// create rpc invoker.
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
在客戶端定一個的每個dubbo:reference,都會在此處實例化一個對應的DubboInvoker;在方法內部首先對序列化優化進行處理,主要是對Kryo,FST等序列化方式進行優化,此方法不僅在客戶端,同時服務器端也存在;接下來就是創建了一個DubboInvoker,同時創建與服務器端的連接:
private ExchangeClient[] getClients(URL url) {
// whether to share connection
boolean service_share_connect = false;
int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
// if not configured, connection is shared, otherwise, one connection for one service
if (connections == 0) {
service_share_connect = true;
connections = 1;
}
ExchangeClient[] clients = new ExchangeClient[connections];
for (int i = 0; i < clients.length; i++) {
if (service_share_connect) {
clients[i] = getSharedClient(url);
} else {
clients[i] = initClient(url);
}
}
return clients;
}
默認向指定的服務器創建一個連接,可以通過指定connections設置建立多個連接,在併發比較大的情況下可以設置多個;
private ExchangeClient initClient(URL url) {
// client type setting.
String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));
url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
// enable heartbeat by default
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
// BIO is not allowed since it has severe performance issue.
if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
throw new RpcException("Unsupported client type: " + str + "," +
" supported client type is " + StringUtils.join(ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(), " "));
}
ExchangeClient client;
try {
// connection should be lazy
if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
client = new LazyConnectExchangeClient(url, requestHandler);
} else {
client = Exchangers.connect(url, requestHandler);
}
} catch (RemotingException e) {
throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e);
}
return client;
}
此方法主要通過Exchange層接口來和服務端建立連接,同時提供了懶連接的方式,要等到真正發送請求的時候才建立連接,返回ExchangeClient;DubboInvoker內部通過ExchangeClient來發送請求給服務端;再來看一下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 dispatching 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);
optimizeSerialization(url);
return exporter;
}
每個dubbo:service都會綁定一個Exporter,首先通過url獲取一個key(包括:port,serviceName,serviceVersion,serviceGroup),然後將實例化的DubboExporter通過key值保存在一個Map中,後續在接收到消息的時候從新定位到具體的Exporter;接下來就是創建服務器:
private void openServer(URL url) {
// find server.
String key = url.getAddress();
//client can export a service which's only for server to invoke
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 supports reset, use together with override
server.reset(url);
}
}
}
private ExchangeServer createServer(URL url) {
// send readonly event when server closes, it's enabled by default
url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
// enable heartbeat by default
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, DubboCodec.NAME);
ExchangeServer server;
try {
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;
}
以上主要就是通過Exchangers的bind方法來啓動服務器,並返回對應的ExchangeServer,同樣也保存在本地的Map中;最後同樣做了序列化優化處理;
Invoker類分析
refer()返回的Invoker由協議實現,協議通常需要在此Invoker中發送遠程請求,export()傳入的Invoker由框架實現並傳入,協議不需要關心;接口類如下:
public interface Invoker<T> extends Node {
Class<T> getInterface();
Result invoke(Invocation invocation) throws RpcException;
}
本節介紹的是refer方法返回的Invoker,默認的dubbo協議下,實現了DubboInvoker,實現了其中的invoke方法,此方法在客戶端調用遠程方法的時候會被調用;
public Result invoke(Invocation inv) throws RpcException {
if (destroyed.get()) {
throw new RpcException("Rpc invoker for service " + this + " on consumer " + NetUtils.getLocalHost()
+ " use dubbo version " + Version.getVersion()
+ " is DESTROYED, can not be invoked any more!");
}
RpcInvocation invocation = (RpcInvocation) inv;
invocation.setInvoker(this);
if (attachment != null && attachment.size() > 0) {
invocation.addAttachmentsIfAbsent(attachment);
}
Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
if (contextAttachments != null) {
/**
* invocation.addAttachmentsIfAbsent(context){@link RpcInvocation#addAttachmentsIfAbsent(Map)}should not be used here,
* because the {@link RpcContext#setAttachment(String, String)} is passed in the Filter when the call is triggered
* by the built-in retry mechanism of the Dubbo. The attachment to update RpcContext will no longer work, which is
* a mistake in most cases (for example, through Filter to RpcContext output traceId and spanId and other information).
*/
invocation.addAttachments(contextAttachments);
}
if (getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, false)) {
invocation.setAttachment(Constants.ASYNC_KEY, Boolean.TRUE.toString());
}
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
try {
return doInvoke(invocation);
} catch (InvocationTargetException e) { // biz exception
Throwable te = e.getTargetException();
if (te == null) {
return new RpcResult(e);
} else {
if (te instanceof RpcException) {
((RpcException) te).setCode(RpcException.BIZ_EXCEPTION);
}
return new RpcResult(te);
}
} catch (RpcException e) {
if (e.isBiz()) {
return new RpcResult(e);
} else {
throw e;
}
} catch (Throwable e) {
return new RpcResult(e);
}
}
protected abstract Result doInvoke(Invocation invocation) throws Throwable;
在DubboInvoker的抽象類中提供了invoke方法,做統一的附件(Attachment)處理,方法傳入的參數是一個RpcInvocation對象,包含了方法調用的相關參數:
public class RpcInvocation implements Invocation, Serializable {
private static final long serialVersionUID = -4355285085441097045L;
private String methodName;
private Class<?>[] parameterTypes;
private Object[] arguments;
private Map<String, String> attachments;
private transient Invoker<?> invoker;
....省略...
}
包含了方法名稱,方法參數,參數值,附件信息;可能你會發現沒有接口,版本等信息,這些信息其實包含在附件中;在invoke方法中首先處理的就是把attachment信息保存到RpcInvocation中;接下來就是調用DubboInvoker中的doInvoke方法:
protected Result doInvoke(final Invocation invocation) throws Throwable {
RpcInvocation inv = (RpcInvocation) invocation;
final String methodName = RpcUtils.getMethodName(invocation);
inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
inv.setAttachment(Constants.VERSION_KEY, version);
ExchangeClient currentClient;
if (clients.length == 1) {
currentClient = clients[0];
} else {
currentClient = clients[index.getAndIncrement() % clients.length];
}
try {
boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
if (isOneway) {
boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
currentClient.send(inv, isSent);
RpcContext.getContext().setFuture(null);
return new RpcResult();
} else if (isAsync) {
ResponseFuture future = currentClient.request(inv, timeout);
RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
return new RpcResult();
} else {
RpcContext.getContext().setFuture(null);
return (Result) currentClient.request(inv, timeout).get();
}
} catch (TimeoutException e) {
throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
} catch (RemotingException e) {
throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
此方法首先獲取ExchangeClient,如果實例化了多個ExchangeClient,會通過順序的方式遍歷使用ExchangeClient;通過ExchangeClient將RpcInvocation發送給服務器端,提供了三種發送方式:單邊通信方式,雙邊通信(同步),雙邊通信(異步);在上文Dubbo分析之Exchange層中,發送完請求之後直接返回DefaultFuture參數,如果調用get方法將阻塞直到返回結果或者超時,同步方式就是直接調用get方法,阻塞等待結果,下面重點看一下異步方式;異步方式將返回的DefaultFuture放入了RpcContext中,然後返回了一個空對象,這裏其實使用了ThreadLocal功能,所以每次在客戶端業務代碼中,調用完異步請求,都需要通過RpcContext獲取ResponseFuture,比如:
// 此調用會立即返回null
fooService.findFoo(fooId);
// 拿到調用的Future引用,當結果返回後,會被通知和設置到此Future
Future<Foo> fooFuture = RpcContext.getContext().getFuture();
// 此調用會立即返回null
barService.findBar(barId);
// 拿到調用的Future引用,當結果返回後,會被通知和設置到此Future
Future<Bar> barFuture = RpcContext.getContext().getFuture();
// 此時findFoo和findBar的請求同時在執行,客戶端不需要啓動多線程來支持並行,而是藉助NIO的非阻塞完成
// 如果foo已返回,直接拿到返回值,否則線程wait住,等待foo返回後,線程會被notify喚醒
Foo foo = fooFuture.get();
// 同理等待bar返回
Bar bar = barFuture.get();
// 如果foo需要5秒返回,bar需要6秒返回,實際只需等6秒,即可獲取到foo和bar,進行接下來的處理。
官網的一個列子,很好的說明了異步的使用方式以及其優勢;
Exporter類分析
在上文Dubbo分析之Exchange層中,服務端接收到消息之後,調用handler的reply方法處理消息,而此handler定義在DubboProtocol中,如下:
private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {
@Override
public Object reply(ExchangeChannel channel, Object message) throws RemotingException {
if (message instanceof Invocation) {
Invocation inv = (Invocation) message;
Invoker<?> invoker = getInvoker(channel, inv);
// need to consider backward-compatibility if it's a 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());
}
...省略...
}
服務端接收到message就是上面的RpcInvocation,裏面包含了接口,方法,參數等信息,服務器端通過反射的方式來處理;首先獲取了對應的DubboExporter,如果獲取,通過key(包括:port,serviceName,serviceVersion,serviceGroup)獲取對應的DubboExporter,然後調用DubboExporter中的invoker,此時的invoker是系統傳過來的,不像客戶端Invoker是協議端自己創建的,系統創建的invoker,以鏈表的方式存在,內部調用對應的filter,具體有哪些filter,在啓動服務時已經初始化好了在ProtocolFilterWrapper的buildInvokerChain中,具體有哪些filter可以查看META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter:
cache=com.alibaba.dubbo.cache.filter.CacheFilter
validation=com.alibaba.dubbo.validation.filter.ValidationFilter
echo=com.alibaba.dubbo.rpc.filter.EchoFilter
generic=com.alibaba.dubbo.rpc.filter.GenericFilter
genericimpl=com.alibaba.dubbo.rpc.filter.GenericImplFilter
token=com.alibaba.dubbo.rpc.filter.TokenFilter
accesslog=com.alibaba.dubbo.rpc.filter.AccessLogFilter
activelimit=com.alibaba.dubbo.rpc.filter.ActiveLimitFilter
classloader=com.alibaba.dubbo.rpc.filter.ClassLoaderFilter
context=com.alibaba.dubbo.rpc.filter.ContextFilter
consumercontext=com.alibaba.dubbo.rpc.filter.ConsumerContextFilter
exception=com.alibaba.dubbo.rpc.filter.ExceptionFilter
executelimit=com.alibaba.dubbo.rpc.filter.ExecuteLimitFilter
deprecated=com.alibaba.dubbo.rpc.filter.DeprecatedFilter
compatible=com.alibaba.dubbo.rpc.filter.CompatibleFilter
timeout=com.alibaba.dubbo.rpc.filter.TimeoutFilter
trace=com.alibaba.dubbo.rpc.protocol.dubbo.filter.TraceFilter
future=com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter
monitor=com.alibaba.dubbo.monitor.support.MonitorFilter
這裏列出了所有的filter,包含消費端和服務端,具體使用哪些,通過filter的註解@Activate來進行過濾,每個filter就行了分組;具體執行的順序是怎麼樣的,同樣在註解裏面指定了,格式如下:
@Activate(group = Constants.PROVIDER, order = -110000)
@Activate(group = Constants.PROVIDER, order = -10000)
@Activate(group = Constants.CONSUMER, value = Constants.GENERIC_KEY, order = 20000)
每個固定的filter有各自的功能,同樣也可以進行擴展,處理完了交給下一個,最後通過反射調用返回RpcResult;
總結
本文大體介紹了一下Protocol層使用的默認dubbo協議介紹,Protocol層還對其他第三方協議進行了擴展,後面會繼續介紹;另外關於filter還可以在詳細介紹一下;
示例代碼地址
https://github.com/ksfzhaohui...
https://gitee.com/OutOfMemory...