本篇幅主要分析DubboProtocol.refer方法創建invoker
DubboProtocol.refer(Class<T> serviceType, URL url)
@Override
public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
//序列化優化
optimizeSerialization(url);
//創建invoker
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
上述方法創建了一個DubboInvoker,Invoker 是 Dubbo 的核心模型,代表一個可執行體。在服務提供方,Invoker 用於調用服務提供類。在服務消費方,Invoker 用於執行遠程調用。這裏有個方法的調用getClients(url)方法節目組金瓶梅掛牌,創建ExchangeClient實例(單個或者多個),ExchangeClient基於NettyClient與NettyServer進行通信。
getClients(URL url)
private ExchangeClient[] getClients(URL url) {
// 是否共享連接
boolean service_share_connect = false;
// 獲取連接數,默認爲0
int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
// 如果未配置,則共享連接,否則,一項服務的一個連接
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) {
//獲取共享的ExchangeClient
clients[i] = getSharedClient(url);
} else {
//初始化新的客戶端
clients[i] = initClient(url);
}
}
return clients;
}
根據url中的sessions數量判斷是否創建共享連接,如果需要創建共享連接則通過getSharedClient獲取共享連接,如果客戶端未建立,則創建客戶端。
getSharedClient(URL url)
private ExchangeClient getSharedClient(URL url) {
String key = url.getAddress();
//獲取帶有引用計數功能的ExchangeClient
ReferenceCountExchangeClient client = referenceClientMap.get(key);
//獲取到了並且client未被關閉增加引用計數
if (client != null) {
if (!client.isClosed()) {
client.incrementAndGetCount();
return client;
} else {
//客戶端被關閉了,根據key刪除掉吧
referenceClientMap.remove(key);
}
}
//放入該key對應的鎖對象
locks.putIfAbsent(key, new Object());
//加鎖重新獲取
synchronized (locks.get(key)) {
if (referenceClientMap.containsKey(key)) {
return referenceClientMap.get(key);
}
//通過url初始化exchangeClient
ExchangeClient exchangeClient = initClient(url);
//包裝exchangeClient爲帶有引用計數功能的
client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap);
//加入緩存
referenceClientMap.put(key, client);
ghostClientMap.remove(key);
//鎖刪除
locks.remove(key);
return client;
}
}
該方法首先從referenceClientMap緩存中獲取ReferenceCountExchangeClient即帶有引用計數功能的ExchangeClient實例,否則通過調用initClient方法初始化ExchangeClient,然後通過裝飾器將創建的exchangeClient包裝爲ReferenceCountExchangeClient對象加入緩存中,並返回ReferenceCountExchangeClient。
initClient(URL url)
private ExchangeClient initClient(URL url) {
// 獲取客戶端的類型默認值爲netty
String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));
// 添加編解碼器與心跳配置
url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
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 {
// 獲取 lazy 配置,並根據配置值決定創建的客戶端類型
if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
client = new LazyConnectExchangeClient(url, requestHandler);
} else {
// 創建普通 ExchangeClient 實例
client = Exchangers.connect(url, requestHandler);
}
} catch (RemotingException e) {
throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e);
}
return client;
}
initClient 方法首先獲取用戶配置的客戶端類型,默認爲 netty。然後檢測用戶配置的客戶端類型是否存在,不存在則拋出異常。最後根據 lazy 配置決定創建什麼類型的客戶端。
Exchangers.connect(URL url, ExchangeHandler handler)
public static ExchangeClient connect(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");
//獲取到HeaderExchageClient
return getExchanger(url).connect(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);
}
getExchanger 會通過 SPI 加載 HeaderExchangeClient 實例
HeaderExchanger.connect(URL url,ExchangeHandler handler)
@Override
public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
}
handler的包裝處理:DubboProtocol.ExchangeHandler->HeaderExchangeHandler->DecodeHandler,通過Transporters.connect方法創建ExchangeClient對象實例,包裝ExchangeClient對象爲HeaderExchangeClient對象實例
Transporters.connect(URL url,ChannelHandler... handlers)
public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
ChannelHandler handler;
if (handlers == null || handlers.length == 0) {
handler = new ChannelHandlerAdapter();
} else if (handlers.length == 1) {
handler = handlers[0];
} else {
handler = new ChannelHandlerDispatcher(handlers);
}
return getTransporter().connect(url, handler);
}
public static Transporter getTransporter() {
return ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();
}
通過dubbo的spi機制獲取到NettyTransporter,然後調用connect方法,創建NettyClient對象
NettyTransporter.connect(URL url, ChannelHandler listener)
@Override
public Client connect(URL url, ChannelHandler listener) throws RemotingException {
return new NettyClient(url, listener);
}
NettyClient.java
- ChannelHandler:主要是處理channel的,比如channel關閉、連接,發送消息,接收消息等操作
- Resetable:該接口實現類可以實現例如Client相關屬性的重置
- AbstractPeer:主要是保存了服務提供這協議的url和通過委託成員變量ChannelHandler實現了ChannelHandler接口的方法和EndPoint接口的方法
/**
* channel事件處理器
*/
private final ChannelHandler handler;
/**
* 第一個服務提供者協議的url地址
*/
private volatile URL url;
- Channel:Netty爲了統一實現不同類型NIO框架的框架對Channel處理而做出的抽象接口
- Client:約定客戶端必須要實現Client接口的reconnect方法
- AbstractEndpoint:繼承AbstractPeer並實現了Resetable接口reset方法,使得我們可以AbstractPeer的Url屬性重置編碼器、超時時間、連接超時時間
/**
* 編碼解碼器
*/
private Codec2 codec;
/**
* 超時時間
*/
private int timeout;
/**
* 連接超時時間
*/
private int connectTimeout;
- AbstractClient:該類實現了Client接口和Channel接口的方法,Mina、Netty、Grizzly類型的客戶端統一實現,並且該類重寫了AbstractEndPoint的reset方法
/**
* 客戶端線程池ID自增器
*/
private static final AtomicInteger CLIENT_THREAD_POOL_ID = new AtomicInteger();
/**
* 客戶端連接重連線程池
*/
private static final ScheduledThreadPoolExecutor reconnectExecutorService = new ScheduledThreadPoolExecutor(2, new NamedThreadFactory("DubboClientReconnectTimer", true));
/**
* 客戶端連接服務端獨佔鎖,保證一個客戶端同時只會一個線程在執行連接動作
*/
private final Lock connectLock = new ReentrantLock();
/**
* 消息發送時,如果當前客戶端未連接,是否發起重連操作
*/
private final boolean send_reconnect;
/**
* 記錄重連的次數
*/
private final AtomicInteger reconnect_count = new AtomicInteger(0);
// Reconnection error log has been called before?
/**
* 連接出錯後是否打印過ERROR日誌
*/
private final AtomicBoolean reconnect_error_log_flag = new AtomicBoolean(false);
// reconnect warning period. Reconnect warning interval (log warning after how many times) //for test
/**
* 對連接異常,以WARN級別日誌輸出的頻率,默認第一次是以Error日誌,然後每出現reconnect_warning_period次後,就打印一次warn級別日誌
*/
private final int reconnect_warning_period;
/**
* 關閉服務的超時時間
*/
private final long shutdown_timeout;
/**
* 客戶端線程池
*/
protected volatile ExecutorService executor;
/**
* 重連的Future
*/
private volatile ScheduledFuture<?> reconnectExecutorFuture = null;
// the last successed connected time
/**
* 上一次重連時間戳
*/
private long lastConnectedTime = System.currentTimeMillis();
public AbstractClient(URL url, ChannelHandler handler) throws RemotingException {
//調用父類方法初始化url、handler
super(url, handler);
/**
* 初始化send_reconnect 、shutdown_timeout、reconnect_warning_period(默認1小時打印一次日誌)
*/
send_reconnect = url.getParameter(Constants.SEND_RECONNECT_KEY, false);
shutdown_timeout = url.getParameter(Constants.SHUTDOWN_TIMEOUT_KEY, Constants.DEFAULT_SHUTDOWN_TIMEOUT);
reconnect_warning_period = url.getParameter("reconnect.waring.period", 1800);
try {
//模板方法,委託子類實現,此方法還未真正的連接到服務端
doOpen();
} catch (Throwable t) {
close();
throw new RemotingException(url.toInetSocketAddress(), null,
"Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress()
+ " connect to the server " + getRemoteAddress() + ", cause: " + t.getMessage(), t);
}
try {
// 這裏會連接nettyServer服務器
connect();
if (logger.isInfoEnabled()) {
logger.info("Start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress() + " connect to the server " + getRemoteAddress());
}
} catch (RemotingException t) {
if (url.getParameter(Constants.CHECK_KEY, true)) {
close();
throw t;
} else {
logger.warn("Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress()
+ " connect to the server " + getRemoteAddress() + " (check == false, ignore and retry later!), cause: " + t.getMessage(), t);
}
} catch (Throwable t) {
close();
throw new RemotingException(url.toInetSocketAddress(), null,
"Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress()
+ " connect to the server " + getRemoteAddress() + ", cause: " + t.getMessage(), t);
}
//從DataStore中獲取線程池
executor = (ExecutorService) ExtensionLoader.getExtensionLoader(DataStore.class)
.getDefaultExtension().get(Constants.CONSUMER_SIDE, Integer.toString(url.getPort()));
//從DataStore中移除線程池
ExtensionLoader.getExtensionLoader(DataStore.class)
.getDefaultExtension().remove(Constants.CONSUMER_SIDE, Integer.toString(url.getPort()));
}
- a.調用父類構造函數初始化handler、和url
- b.初始化send_reconnect 、shutdown_timeout、reconnect_warning_period(默認1小時打印一次日誌)
- c.委託子類實現doOpen方法初始化客戶端
- d.connect()方法,真正建立TCP連接,其主要邏輯是開啓重連任務,然後委託不同的客戶端實現類型實現doConnect()方法打開TCP連接,設置重連次數和重連標識。
protected void connect() throws RemotingException {
//加鎖
connectLock.lock();
try {
//已經連接成功返回
if (isConnected()) {
return;
}
//重連任務調度開啓
initConnectStatusCheckCommand();
//委託子類實現真正的連接邏輯
doConnect();
//還未連接到服務器
if (!isConnected()) {
throw new RemotingException(this, "Failed connect to server " + getRemoteAddress() + " from " + getClass().getSimpleName() + " "
+ NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion()
+ ", cause: Connect wait timeout: " + getConnectTimeout() + "ms.");
} else {
if (logger.isInfoEnabled()) {
logger.info("Successed connect to server " + getRemoteAddress() + " from " + getClass().getSimpleName() + " "
+ NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion()
+ ", channel is " + this.getChannel());
}
}
//設置重連次數爲0
reconnect_count.set(0);
//設置重連error log標識爲false
reconnect_error_log_flag.set(false);
} catch (RemotingException e) {
throw e;
} catch (Throwable e) {
throw new RemotingException(this, "Failed connect to server " + getRemoteAddress() + " from " + getClass().getSimpleName() + " "
+ NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion()
+ ", cause: " + e.getMessage(), e);
} finally {
connectLock.unlock();
}
}
- e.從DataStore中獲取線程池,然後從DataStore中移除該線程池
- NettyClient:繼承AbstractClient,真正的客戶端類,主要負責與服務端通信。
/**
* IO線程組,同一個JVM中所有的客戶端公用一個IO線程組,且線程數固定爲(32與CPU核數+1的最小值)。
*/
private static final NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup(Constants.DEFAULT_IO_THREADS, new DefaultThreadFactory("NettyClientWorker", true));
/**
* Netty客戶端啓動實例
*/
private Bootstrap bootstrap;
/**
* 客戶端連接,請copy其引用使用。
*/
private volatile Channel channel;
public NettyClient(final URL url, final ChannelHandler handler) throws RemotingException {
super(url, wrapChannelHandler(url, handler));
}
不難看出,NettyClient的構造函數主要是調用父類AbstractClient的構造函數進行屬性初始化
AbstractClient.wrapChannelHandler(URL url,ChannelHandler handler)
protected static ChannelHandler wrapChannelHandler(URL url, ChannelHandler handler) {
//在url添加threadname屬性
url = ExecutorUtil.setThreadName(url, CLIENT_THREAD_POOL_NAME);
//在url添加threadpool屬性值爲cached
url = url.addParameterIfAbsent(Constants.THREADPOOL_KEY, Constants.DEFAULT_CLIENT_THREADPOOL);
return ChannelHandlers.wrap(handler, url);
}
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)));
}
由Dubbo源碼學習07可知我們的chandler的整體包裝流程: MultiMessageHandler -> HeartbeatHandler -> AllChannelHandler -> DecodeHandler -> HeaderExchangeHandler -> DubboProtocol.requestHandler
- MultiMessageHandler:檢查消息是否爲MutiMessage ,如果 是,分開單條調用後續handler
- HeartbeatHanlder:1.在每個channel動作,對channel標記時間屬性, 2. 檢查是否心跳請求,是則直接返回心跳,不繼續後續請求。
- AllChannelHandler : 1. 將後續handler 包裝成 ChannelEventRunnable,捕獲後續執行的異常,記錄日誌 。 2. 包裝的runnable 放到獨立線程池運行, 達到全流程異步化效果。
- DecodeHandler:判斷message的種類然後解碼
NettyClient.doOpen()
@Override
protected void doOpen() throws Throwable {
final NettyClientHandler nettyClientHandler = new NettyClientHandler(getUrl(), this);
bootstrap = new Bootstrap();
bootstrap.group(nioEventLoopGroup)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
//.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getTimeout())
.channel(NioSocketChannel.class);
if (getConnectTimeout() < 3000) {
bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000);
} else {
bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getConnectTimeout());
}
bootstrap.handler(new ChannelInitializer() {
@Override
protected void initChannel(Channel ch) throws Exception {
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);
ch.pipeline()//.addLast("logging",new LoggingHandler(LogLevel.INFO))//for debug
.addLast("decoder", adapter.getDecoder())
.addLast("encoder", adapter.getEncoder())
.addLast("handler", nettyClientHandler);
}
});
}
標準的NettyClient的啓動模板,創建NettyClientHandler,配置客戶端啓動實例的屬性,設置連接超時時間,最小連接超時時間爲3s,初始化ChannelInitializer添加編解碼器,NettyClientHandler
NettyClient.doConnect()
@Override
protected void doConnect() throws Throwable {
long start = System.currentTimeMillis();
//連接到遠程服務
ChannelFuture future = bootstrap.connect(getConnectAddress());
try {
boolean ret = future.awaitUninterruptibly(getConnectTimeout(), TimeUnit.MILLISECONDS);
if (ret && future.isSuccess()) {
Channel newChannel = future.channel();
try {
// Close old channel
//關閉oldchannel
Channel oldChannel = NettyClient.this.channel; // copy reference
if (oldChannel != null) {
try {
if (logger.isInfoEnabled()) {
logger.info("Close old netty channel " + oldChannel + " on create new netty channel " + newChannel);
}
oldChannel.close();
} finally {
NettyChannel.removeChannelIfDisconnected(oldChannel);
}
}
} finally {
//如果當前客戶端被關閉了,關閉newChannel
if (NettyClient.this.isClosed()) {
try {
if (logger.isInfoEnabled()) {
logger.info("Close new netty channel " + newChannel + ", because the client closed.");
}
newChannel.close();
} finally {
NettyClient.this.channel = null;
NettyChannel.removeChannelIfDisconnected(newChannel);
}
} else {
//將newChannel賦值給client的channel
NettyClient.this.channel = newChannel;
}
}
} else if (future.cause() != null) {
throw new RemotingException(this, "client(url: " + getUrl() + ") failed to connect to server "
+ getRemoteAddress() + ", error message is:" + future.cause().getMessage(), future.cause());
} else {
throw new RemotingException(this, "client(url: " + getUrl() + ") failed to connect to server "
+ getRemoteAddress() + " client-side timeout "
+ getConnectTimeout() + "ms (elapsed: " + (System.currentTimeMillis() - start) + "ms) from netty client "
+ NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion());
}
} finally {
if (!isConnected()) {
//future.cancel(true);
}
}
}
調用doOpen方法創建的bootstrap實例發起連接,等待個connecTimeout超時時間去連接到服務端,如果連接成功:關閉以前的channel,判斷客戶端是否已經關閉,如果已經關閉,調用channel.close()方法關閉channel。