RegistryProtocol.export服務導出流程:導出服務ExporterChangeableWrapper->註冊服務到註冊中心->訂閱註冊中心overrideSubscribeUrl數據;篇幅有限,本篇幅主要分析導出服務ExporterChangeableWrapper源碼實現
RegistryProtocol.export(final Invoker<T> originInvoker)
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//export invoker
//導出服務
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
// 獲取註冊中心 URL,以 zookeeper 註冊中心爲例,得到的示例 URL 如下:
// zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F172.17.48.52%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider
URL registryUrl = getRegistryUrl(originInvoker);
//registry provider
// 根據 URL 加載 Registry 實現類,比如 ZookeeperRegistry
final Registry registry = getRegistry(originInvoker);
// 獲取已註冊的服務提供者 URL,比如:
// dubbo://172.17.48.52:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello
final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker);
//to judge to delay publish whether or not
//獲取register參數;register表示是否註冊到註冊中心
boolean register = registeredProviderUrl.getParameter("register", true);
//緩存到ProviderConsumerRegTable的表中
ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl);
//註冊服務到zookeeper
if (register) {
register(registryUrl, registeredProviderUrl);
//找到該originInvoker對應的ProviderInvokerWrapper設置reg屬性爲true
ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
}
// Subscribe the override data
// FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call the same service. Because the subscribed is cached key with the name of the service, it causes the subscription information to cover.
// FIXME 提供者訂閱時,會影響同一JVM即暴露服務,又引用同一服務的的場景,因爲subscribed以服務名爲緩存的key,導致訂閱信息覆蓋
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl);
//創建監聽器
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
//放入overrideSubscribeUrl對應的OverrideListener
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
// 向註冊中心進行訂閱 override 數據
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
//Ensure that a new exporter instance is returned every time export
//創建並返回DestroyableExporter
return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);
}
該方法核心代碼可以簡化爲
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//1.導出invoker;
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker);
....
boolean register = registeredProviderUrl.getParameter("register", true);
//2.註冊服務到zookeeper
if (register) {
register(registryUrl, registeredProviderUrl);
}
....
//3.向註冊中心進行訂閱overrideSubscribeUrl
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl);
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);
}
doLocalExport(final Invoker<T> originInvoker)
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) {
//獲取originInvoker對應的緩存key
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) {
//創建invoker委託類對象
final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
//調用protocol的export方法導出服務
//這個時候返回的url肯定是類似dubbo://....這樣子的所以protocol
//由於dubbo的spi機制,此時肯定是通過DubboProtocol的export方法
exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
bounds.put(key, exporter);
}
}
}
return exporter;
}
通過originInvoker獲取緩存key,然後就是dubbo慣用的緩存伎倆,從緩存中獲取,如果沒有成功獲取到,調用成員變量protocol的export方法導出invoker,這裏的protocol還是Protocol$Adaptive,所以又會經過https://blog.csdn.net/qq_23536449/article/details/102639888文中描述的
QosProtocolWrapper,ProtocolFilterWrapper,ProtocolListenerWrapper,通過debug代碼可以清楚看到執行流程:
- QosProtocolWrapper
- ProtocolFilterWrapper
- ProtocolListenerWrapper
- 最後會進入到DubboProtocol的export方法中,下文會分析
getCacheKey(originInvoker)
private String getCacheKey(final Invoker<?> originInvoker) {
URL providerUrl = getProviderUrl(originInvoker);
String key = providerUrl.removeParameters("dynamic", "enabled").toFullString();
return key;
}
/**
* Get the address of the providerUrl through the url of the invoker
*
* 通過調用者的地址獲取providerUrl的地址
* @param origininvoker
* @return
*/
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;
}
- key類似:dubbo://ip:port/com.alibaba.dubbo.study.day01.xml.service.EchoService?addListener.1.callback=true&addListener.retries=2&anyhost=true&application=echo-provider&bean.name=com.alibaba.dubbo.study.day01.xml.service.EchoService&bind.ip=169.254.22.149&bind.port=20880&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.study.day01.xml.service.EchoService&methods=echo,addListener&pid=3600&side=provider×tamp=1572674213180
DubboProtocol.export(Invoker<T> invoker)
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// export service.
// 獲取服務標識,理解成服務座標也行。由服務組名,服務名,服務版本號以及端口組成。比如:
// demoGroup/com.alibaba.dubbo.demo.DemoService:1.0.1:20880
String key = serviceKey(url);
// 創建dubbo的exporter
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;
}
構造DubboExporter,放入緩存;存根服務處理(這裏可以略過);啓動服務器;優化序列化(非重點關心)
openServer(url);
private void openServer(URL url) {
// 獲取 host:port,並將其作爲服務器實例的 key,用於標識當前的服務器實例
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) {
//獲取address對應的server,
ExchangeServer server = serverMap.get(key);
if (server == null) {
//創建服務器實例並放入緩存中
serverMap.put(key, createServer(url));
} else {
// server supports reset, use together with override
// 服務器已創建,則根據 url 中的配置重置服務器
server.reset(url);
}
}
}
如上,在同一臺機器上(單網卡),同一個端口上僅允許啓動一個服務器實例。若某個端口上已有服務器實例,此時則調用 reset 方法重置服務器的一些配置,讓我們先來分析下創建服務器實例方法。
createServer(URL 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 = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
// 獲取server參數
String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);
// 通過 SPI 檢測是否存在 server 參數所代表的 Transporter 拓展,不存在則拋出異常
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 {
//創建ExchangeServer
server = Exchangers.bind(url, requestHandler);
} catch (RemotingException e) {
throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
}
/**
* 獲取client代表的Transporter是否存在,如果不存在拋出異常好了
*/
str = url.getParameter(Constants.CLIENT_KEY);
if (str != null && str.length() > 0) {
// 獲取所有的 Transporter 實現類名稱集合,比如 supportedTypes = [netty, mina]
Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
// 檢測當前 Dubbo 所支持的 Transporter 實現類名稱列表中,
// 是否包含 client 所表示的 Transporter,若不包含,則拋出異常
if (!supportedTypes.contains(str)) {
throw new RpcException("Unsupported client type: " + str);
}
}
return server;
}
添加默認參數,然後檢測指定的server類型(netty,minal,gizzly)的Transproter是否存在,使用Exchangers創建的bind方法創建server實例,檢測是否支持 client 參數所表示的 Transporter 拓展,不存在也是拋出異常。
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");
return getExchanger(url).bind(url, handler);
}
public static Exchanger getExchanger(URL url) {
//獲取url中的exchanger屬性,沒有使用默認的header
String type = url.getParameter(Constants.EXCHANGER_KEY, Constants.DEFAULT_EXCHANGER);
return getExchanger(type);
}
public static Exchanger getExchanger(String type) {
//dubbo的spi機制
return ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type);
}
通過Exchanger命名理解設計他的初衷,交換層,輸入url和一個handler,輸出server(比如類型爲HeaderExchangeServer),然後dubbo本身支持spi擴展,所以exchanger也支持擴展(dubbo默認實現了一個擴展名稱爲header的Exchager即下文的HeaderExchanger)。
HeaderExchanger.bind(URL url, ExchangeHandler handler)
@Override
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
調用Transporters.bind方法創建Server(包裝handler爲DecodeHandler),類型可以爲Netty、Mina、Grizzly,使用HeaderExchageServer裝飾創建出來的Server,HeaderExchangeServer爲Server提供了發送空閒channel檢測、channel重連的功能
Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))
public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handlers == null || handlers.length == 0) {
throw new IllegalArgumentException("handlers == null");
}
ChannelHandler handler;
if (handlers.length == 1) {
handler = handlers[0];
} else {
handler = new ChannelHandlerDispatcher(handlers);
}
return getTransporter().bind(url, handler);
}
public static Transporter getTransporter() {
return ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();
}
通過spi獲取獲取不同類型的Transporter,默認是NettyTransporter,至於爲什麼請看Transporter$Adaptive的bind方法
package com.alibaba.dubbo.remoting;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Transporter$Adaptive implements com.alibaba.dubbo.remoting.Transporter {
public com.alibaba.dubbo.remoting.Client connect(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
if (arg0 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([client, transporter])");
com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
return extension.connect(arg0, arg1);
}
public com.alibaba.dubbo.remoting.Server bind(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
if (arg0 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
String extName = url.getParameter("server", url.getParameter("transporter", "netty"));
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])");
com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
return extension.bind(arg0, arg1);
}
}
NettyTransporter.bind(URL url, ChannelHandler listener)
@Override
public Server bind(URL url, ChannelHandler listener) throws RemotingException {
return new NettyServer(url, listener);
}
創建一個Netty類型的服務實例!!!NettyServer的類繼承關係圖如下
- ChannelHandler:主要是處理channel的,比如channel關閉、連接,發送消息,接收消息等操作
- Resetable:該接口實現類可以實現例如Server相關屬性的重置
- AbstractPeer:主要是保存了服務提供這協議的url和通過委託成員變量ChannelHandler實現了ChannelHandler接口的方法和EndPoint接口的方法
/**
* channel事件處理器
*/
private final ChannelHandler handler;
/**
* 第一個服務提供者協議的url地址
*/
private volatile URL url;
- AbstractEndPoint:實現了Resetable接口reset方法,使得我們可以重置編碼器、超時時間、連接超時時間
/**
* 編碼解碼器
*/
private Codec2 codec;
/**
* 超時時間
*/
private int timeout;
/**
* 連接超時時間
*/
private int connectTimeout;
- AbstractServer:該類實現了Server接口,作爲Mina、Netty、Grizzly類型的服務端統一實現,並且該類重寫了AbstractEndPoint的reset方法
ExecutorService executor;
/**
* url host:port地址。
*/
private InetSocketAddress localAddress;
/**
* 如果是多網卡,並且指定了 bind.ip、bind.port,如果爲空,與localAddress相同。
*/
private InetSocketAddress bindAddress;
/**
* AbstractServer#accepts未使用到。
*/
private int accepts;
/**
* 空閒時間
*/
private int idleTimeout = 600; //600 seconds
- NettyServer:服務端實現類
/**
* < ip:port, channel> 所有通道。
*/
private Map<String, Channel> channels; // <ip:port, channel>
/**
* netty 服務端啓動器。
*/
private ServerBootstrap bootstrap;
/**
* 服務端監聽通道。
*/
private io.netty.channel.Channel channel;
/**
* Netty boss線程組(負責連接事件)
*/
private EventLoopGroup bossGroup;
/**
* work線程組(負責IO事件)
*/
private EventLoopGroup workerGroup;
- 回顧下上在DubboProtocol.openServer(URL url)代碼中的如下代碼片段可知其真正的實現在AbstractServer中。
AbstractServer.reset(URL url)
@Override
public void reset(URL url) {
if (url == null) {
return;
}
try {
//url中是否包含accepts屬性,存在並且大於0則重置Server的accepts
if (url.hasParameter(Constants.ACCEPTS_KEY)) {
int a = url.getParameter(Constants.ACCEPTS_KEY, 0);
if (a > 0) {
this.accepts = a;
}
}
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
try {
//url中是否包含idle.timeout屬性,存在並且大於0則重置Server的idle.timeout
if (url.hasParameter(Constants.IDLE_TIMEOUT_KEY)) {
int t = url.getParameter(Constants.IDLE_TIMEOUT_KEY, 0);
if (t > 0) {
this.idleTimeout = t;
}
}
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
try {
//url是否包含threads屬性,並且executor是ThreadPoolExecutor的實例並且線程池未被關閉
if (url.hasParameter(Constants.THREADS_KEY)
&& executor instanceof ThreadPoolExecutor && !executor.isShutdown()) {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
//url中的threads數量
int threads = url.getParameter(Constants.THREADS_KEY, 0);
//線程池最大容量
int max = threadPoolExecutor.getMaximumPoolSize();
//線程池核心線程數
int core = threadPoolExecutor.getCorePoolSize();
if (threads > 0 && (threads != max || threads != core)) {
if (threads < core) {
threadPoolExecutor.setCorePoolSize(threads);
if (core == max) {
threadPoolExecutor.setMaximumPoolSize(threads);
}
} else {
threadPoolExecutor.setMaximumPoolSize(threads);
if (core == max) {
threadPoolExecutor.setCorePoolSize(threads);
}
}
}
}
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
//合併url中的參數並設置給AbstractPeer的url屬性
super.setUrl(getUrl().addParameters(url.getParameters()));
}
/**
* Add parameters to a new url.
*
* @param parameters parameters in key-value pairs
* @return A new URL
*/
public URL addParameters(Map<String, String> parameters) {
if (parameters == null || parameters.size() == 0) {
return this;
}
//判斷當前URL實例的parameter是否與參數中的parameters
//是否相同,包括鍵和值,不全相同返回false
boolean hasAndEqual = true;
for (Map.Entry<String, String> entry : parameters.entrySet()) {
String value = getParameters().get(entry.getKey());
if (value == null) {
if (entry.getValue() != null) {
hasAndEqual = false;
break;
}
} else {
if (!value.equals(entry.getValue())) {
hasAndEqual = false;
break;
}
}
}
// return immediately if there's no change
//沒有變化直接返回當前url
if (hasAndEqual) return this;
//如果有變化合並並且返回新的map
Map<String, String> map = new HashMap<String, String>(getParameters());
map.putAll(parameters);
return new URL(protocol, username, password, host, port, path, map);
}
覆蓋NettyServer的accepts、idleTimeout、threadPoolExecutor的配置;通過調用AbstractPeer的setUrl方法設置新的Url成員變量的值。
HeaderExchangeServer.reset(URL url)
@Override
public void reset(URL url) {
server.reset(url);
try {
//url中是否包含heartbeat或者heartbeat.timeout屬性
if (url.hasParameter(Constants.HEARTBEAT_KEY)
|| url.hasParameter(Constants.HEARTBEAT_TIMEOUT_KEY)) {
int h = url.getParameter(Constants.HEARTBEAT_KEY, heartbeat);
int t = url.getParameter(Constants.HEARTBEAT_TIMEOUT_KEY, h * 3);
if (t < h * 2) {
throw new IllegalStateException("heartbeatTimeout < heartbeatInterval * 2");
}
if (h != heartbeat || t != heartbeatTimeout) {
heartbeat = h;
heartbeatTimeout = t;
startHeartbeatTimer();
}
}
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
}
//發送心跳
private void startHeartbeatTimer() {
stopHeartbeatTimer();
if (heartbeat > 0) {
heartbeatTimer = scheduled.scheduleWithFixedDelay(
new HeartBeatTask(new HeartBeatTask.ChannelProvider() {
@Override
public Collection<Channel> getChannels() {
return Collections.unmodifiableCollection(
HeaderExchangeServer.this.getChannels());
}
}, heartbeat, heartbeatTimeout),
heartbeat, heartbeat, TimeUnit.MILLISECONDS);
}
}
//停止心跳那啥
private void stopHeartbeatTimer() {
try {
ScheduledFuture<?> timer = heartbeatTimer;
if (timer != null && !timer.isCancelled()) {
timer.cancel(true);
}
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
} finally {
heartbeatTimer = null;
}
}
可以看到HeaderExchageServer其實是在NettyServer的基礎上增加了下面兩個功能見HeartBeatTask.java
- a. 對channel進行 空閒時間檢測,超過則關閉連接,節省資源。
- b. 如果server關閉,則發送消息給client端,不再發送請求到該server。
HeartBeatTask.run
@Override
public void run() {
try {
long now = System.currentTimeMillis();
for (Channel channel : channelProvider.getChannels()) {
if (channel.isClosed()) {
continue;
}
try {
//channel上次讀數據的時間戳
Long lastRead = (Long) channel.getAttribute(
HeaderExchangeHandler.KEY_READ_TIMESTAMP);
//上次寫數據的時間戳
Long lastWrite = (Long) channel.getAttribute(
HeaderExchangeHandler.KEY_WRITE_TIMESTAMP);
//讀的時間戳不爲null並且空閒讀時間大於心跳
if ((lastRead != null && now - lastRead > heartbeat)
//寫的時間戳不爲null並且空閒寫時間大於心跳
|| (lastWrite != null && now - lastWrite > heartbeat)) {
/**
* 發送心跳數據
*/
Request req = new Request();
req.setVersion(Version.getProtocolVersion());
req.setTwoWay(true);
req.setEvent(Request.HEARTBEAT_EVENT);
channel.send(req);
if (logger.isDebugEnabled()) {
logger.debug("Send heartbeat to remote channel " + channel.getRemoteAddress()
+ ", cause: The channel has no data-transmission exceeds a heartbeat period: " + heartbeat + "ms");
}
}
//空閒讀時間戳不爲null並且空閒讀大於心跳超時時間
if (lastRead != null && now - lastRead > heartbeatTimeout) {
logger.warn("Close channel " + channel
+ ", because heartbeat read idle time out: " + heartbeatTimeout + "ms");
//channel是Client則重新連接
if (channel instanceof Client) {
try {
((Client) channel).reconnect();
} catch (Exception e) {
//do nothing
}
//否則關閉channel
} else {
channel.close();
}
}
} catch (Throwable t) {
logger.warn("Exception when heartbeat to remote channel " + channel.getRemoteAddress(), t);
}
}
} catch (Throwable t) {
logger.warn("Unhandled exception when heartbeat, cause: " + t.getMessage(), t);
}
}
NettyServer.java
- 構造函數代碼如下:通過super關鍵字調用父類方法,解析url中的參數給AbstractServer、AbstractPeer、AbstractEndpoint屬性賦值,值得注意的是AbstractServer中的doOpen()方法,該方法是個模板方法,子類實現doOpen方法啓動服務器
public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
}
public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
super(url, handler);
localAddress = getUrl().toInetSocketAddress();
String bindIp = getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());
int bindPort = getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());
if (url.getParameter(Constants.ANYHOST_KEY, false) || NetUtils.isInvalidLocalHost(bindIp)) {
bindIp = NetUtils.ANYHOST;
}
bindAddress = new InetSocketAddress(bindIp, bindPort);
//設置可接受最大連接數
this.accepts = url.getParameter(Constants.ACCEPTS_KEY, Constants.DEFAULT_ACCEPTS);
//連接空閒時間
this.idleTimeout = url.getParameter(Constants.IDLE_TIMEOUT_KEY, Constants.DEFAULT_IDLE_TIMEOUT);
try {
doOpen();
if (logger.isInfoEnabled()) {
logger.info("Start " + getClass().getSimpleName() + " bind " + getBindAddress() + ", export " + getLocalAddress());
}
} catch (Throwable t) {
throw new RemotingException(url.toInetSocketAddress(), null, "Failed to bind " + getClass().getSimpleName()
+ " on " + getLocalAddress() + ", cause: " + t.getMessage(), t);
}
//fixme replace this with better method
DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
executor = (ExecutorService) dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY, Integer.toString(url.getPort()));
}
ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME))
public static ChannelHandler wrap(ChannelHandler handler, URL url) {
return ChannelHandlers.getInstance().wrapInternal(handler, url);
}
protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
return new MultiMessageHandler(new HeartbeatHandler(ExtensionLoader.getExtensionLoader(Dispatcher.class)
.getAdaptiveExtension().dispatch(handler, url)));
}
Dispatcher通過dubbo的spi機制最終爲AllDispatcher,我們來看下Dispatcher$Adaptive的代碼就能知曉原因
package com.alibaba.dubbo.remoting;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Dispatcher$Adaptive implements com.alibaba.dubbo.remoting.Dispatcher {
public com.alibaba.dubbo.remoting.ChannelHandler dispatch(com.alibaba.dubbo.remoting.ChannelHandler arg0, com.alibaba.dubbo.common.URL arg1) {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = url.getParameter("dispatcher", url.getParameter("dispather", url.getParameter("channel.handler", "all")));
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Dispatcher) name from url(" + url.toString() + ") use keys([dispatcher, dispather, channel.handler])");
com.alibaba.dubbo.remoting.Dispatcher extension = (com.alibaba.dubbo.remoting.Dispatcher) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Dispatcher.class).getExtension(extName);
return extension.dispatch(arg0, arg1);
}
}
AllDispatcher.dispatch(ChannelHandler handler,URL url)
/**
* default thread pool configure
*/
public class AllDispatcher implements Dispatcher {
public static final String NAME = "all";
@Override
public ChannelHandler dispatch(ChannelHandler handler, URL url) {
return new AllChannelHandler(handler, url);
}
}
handler的包裝流程如下
DubboProtocol.createServer
#此時requestHandler爲DubboProtocol中的匿名內部類實現ExchangeHandlerAdapter
->Exchangers.bind(url,requestHandler)
#該方法對handler做了兩次包裝ExchangeHandlerAdapter->HeaderExchangeHandler->DecodeHandler
->HeaderExchanger.bind(url,handler)
#到這裏handler已經爲DecodeHandler了
->NettyServer(url,handler)
#該方法首先通過dubbo的spi機制將handler包裝爲AllChannelHandler->HeartbeatHandler->MultiMessageHandler
->ChannelHandlers.wrapInternal(handler,url)
MultiMessageHandler -> HeartbeatHandler -> AllChannelHandler -> DecodeHandler -> HeaderExchangeHandler -> DubboProtocol.requestHandler
- MultiMessageHandler:檢查消息是否爲MutiMessage ,如果 是,分開單條調用後續handler
- HeartbeatHanlder:1.在每個channel動作,對channel標記時間屬性, 2. 檢查是否心跳請求,是則直接返回心跳,不繼續後續請求。
- AllChannelHandler : 1. 將後續handler 包裝成 ChannelEventRunnable,捕獲後續執行的異常,記錄日誌 。 2. 包裝的runnable 放到獨立線程池運行, 達到全流程異步化效果。
- DecodeHandler:判斷message的種類然後解碼
Netty.doOpen()
@Override
protected void doOpen() throws Throwable {
bootstrap = new ServerBootstrap();
bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("NettyServerBoss", true));
workerGroup = new NioEventLoopGroup(getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),
new DefaultThreadFactory("NettyServerWorker", true));
final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);
channels = nettyServerHandler.getChannels();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
.childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
ch.pipeline()//.addLast("logging",new LoggingHandler(LogLevel.INFO))//for debug
.addLast("decoder", adapter.getDecoder())
.addLast("encoder", adapter.getEncoder())
.addLast("handler", nettyServerHandler);
}
});
// bind
ChannelFuture channelFuture = bootstrap.bind(getBindAddress());
channelFuture.syncUninterruptibly();
channel = channelFuture.channel();
}
Netty服務端啓動的標準模板:主要關注的點就是添加的編碼器、解碼器、NettyServerHandler。
目前的handler包裝鏈如下:
NettyServerHandler->NettyServer-> MultiMessageHandler -> HeartbeatHandler -> AllChannelHandler -> DecodeHandler -> HeaderExchangeHandler -> DubboProtocol.requestHandler
NettyCodecAdapter.java
- 成員變量如下
//內部編碼
private final ChannelHandler encoder = new InternalEncoder();
//內部解碼器
private final ChannelHandler decoder = new InternalDecoder();
//AbstractEndpoint中的編解碼屬性codec怎麼獲取的參考AbstractEndpoit.getChannelCodec(URL url)
private final Codec2 codec;
InternalEncoder.java
private class InternalEncoder extends MessageToByteEncoder {
@Override
protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
com.alibaba.dubbo.remoting.buffer.ChannelBuffer buffer = new NettyBackedChannelBuffer(out);
Channel ch = ctx.channel();
NettyChannel channel = NettyChannel.getOrAddChannel(ch, url, handler);
try {
codec.encode(channel, buffer, msg);
} finally {
NettyChannel.removeChannelIfDisconnected(ch);
}
}
}
委託給編解碼器codec處理
InternalDecoder.java
private class InternalDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf input, List<Object> out) throws Exception {
ChannelBuffer message = new NettyBackedChannelBuffer(input);
NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);
Object msg;
int saveReaderIndex;
try {
// decode object.
do {
saveReaderIndex = message.readerIndex();
try {
msg = codec.decode(channel, message);
} catch (IOException e) {
throw e;
}
if (msg == Codec2.DecodeResult.NEED_MORE_INPUT) {
message.readerIndex(saveReaderIndex);
break;
} else {
//is it possible to go here ?
if (saveReaderIndex == message.readerIndex()) {
throw new IOException("Decode without read data.");
}
if (msg != null) {
out.add(msg);
}
}
} while (message.readable());
} finally {
NettyChannel.removeChannelIfDisconnected(ctx.channel());
}
}
}
委託給編碼器codec處理
codec是怎麼獲取到的?
在doOpen中有以下代碼getCodec()
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
getCodec();該方法通過父類AbstractEndpoint繼承而來,我們看下codec在AbstractEndpoint的初始化邏輯
protected static Codec2 getChannelCodec(URL url) {
//獲取url中的codec屬性默認值爲telnet
String codecName = url.getParameter(Constants.CODEC_KEY, "telnet");
//如果Codec2的SPI實現類包含名稱爲codecName編解碼器的擴展直接加載返回
if (ExtensionLoader.getExtensionLoader(Codec2.class).hasExtension(codecName)) {
return ExtensionLoader.getExtensionLoader(Codec2.class).getExtension(codecName);
} else {
//加載Spi擴展Codec的名稱爲codecName的編解碼器
return new CodecAdapter(ExtensionLoader.getExtensionLoader(Codec.class)
.getExtension(codecName));
}
}
未完待續....