摘要:和斷開,處理措施不一樣,會分別做出重連和關閉通道的操作。取消定時器取消大量已排隊任務,用于回收空間該方法是停止現有心跳,也就是停止定時器,釋放空間。做到異步處理返回結果時能給準確的返回給對應的請求。
遠程通訊——Exchange層
目標:介紹Exchange層的相關設計和邏輯、介紹dubbo-remoting-api中的exchange包內的源碼解析。前言
上一篇文章我講的是dubbo框架設計中Transport層,這篇文章我要講的是它的上一層Exchange層,也就是信息交換層。官方文檔對這一層的解釋是封裝請求響應模式,同步轉異步,以 Request, Response為中心,擴展接口為 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer。
這一層的設計意圖是什么?它應該算是在信息傳輸層上又做了部分裝飾,為了適應rpc調用的一些需求,比如rpc調用中一次請求只關心它所對應的響應,這個時候只是一個message消息傳輸過來,是無法區(qū)分這是新的請求還是上一個請求的響應,這種類似于冪等性的問題以及rpc異步處理返回結果、內置事件等特性都是在Transport層無法解決滿足的,所有在Exchange層講message分成了request和response兩種類型,并且在這兩個模型上增加一些系統(tǒng)字段來處理問題。具體我會在下面講到。而dubbo把一條消息分為了協(xié)議頭和內容兩部分:協(xié)議頭包括系統(tǒng)字段,例如編號等,內容包括具體請求的參數和響應的結果等。在exchange層中大量邏輯都是基于協(xié)議頭的。
現在對這一層的設計意圖大致應該有所了解了吧,現在來看看exchange的類圖:
我講解的順序還是按照類圖從上而下,分塊講解,忽略綠色的test類。
源碼解析 (一)ExchangeChannelpublic interface ExchangeChannel extends Channel {
ResponseFuture request(Object request) throws RemotingException;
ResponseFuture request(Object request, int timeout) throws RemotingException;
ExchangeHandler getExchangeHandler();
@Override
void close(int timeout);
}
該接口是信息交換通道接口,有四個方法,前兩個是發(fā)送請求消息,區(qū)別就是第二個發(fā)送請求有超時的參數,getExchangeHandler方法就是返回一個信息交換處理器,第四個是需要覆寫父類的方法。
(二)HeaderExchangeChannel該類實現了ExchangeChannel,是基于協(xié)議頭的信息交換通道。
1.屬性private static final Logger logger = LoggerFactory.getLogger(HeaderExchangeChannel.class); /** * 通道的key值 */ private static final String CHANNEL_KEY = HeaderExchangeChannel.class.getName() + ".CHANNEL"; /** * 通道 */ private final Channel channel; /** * 是否關閉 */ private volatile boolean closed = false;
上述屬性比較簡單,還是放一下這個類的屬性是因為該類中有channel屬性,也就是說HeaderExchangeChannel是Channel的裝飾器,每個實現方法都會調用channel的方法。
2.靜態(tài)方法static HeaderExchangeChannel getOrAddChannel(Channel ch) {
if (ch == null) {
return null;
}
// 獲得通道中的HeaderExchangeChannel
HeaderExchangeChannel ret = (HeaderExchangeChannel) ch.getAttribute(CHANNEL_KEY);
if (ret == null) {
// 創(chuàng)建一個HeaderExchangeChannel實例
ret = new HeaderExchangeChannel(ch);
// 如果通道連接
if (ch.isConnected()) {
// 加入屬性值
ch.setAttribute(CHANNEL_KEY, ret);
}
}
return ret;
}
static void removeChannelIfDisconnected(Channel ch) {
// 如果通道斷開連接
if (ch != null && !ch.isConnected()) {
// 移除屬性值
ch.removeAttribute(CHANNEL_KEY);
}
}
該靜態(tài)方法做了HeaderExchangeChannel的創(chuàng)建和銷毀,并且生命周期隨channel銷毀而銷毀。
3.send@Override
public void send(Object message) throws RemotingException {
send(message, getUrl().getParameter(Constants.SENT_KEY, false));
}
@Override
public void send(Object message, boolean sent) throws RemotingException {
// 如果通道關閉,拋出異常
if (closed) {
throw new RemotingException(this.getLocalAddress(), null, "Failed to send message " + message + ", cause: The channel " + this + " is closed!");
}
// 判斷消息的類型
if (message instanceof Request
|| message instanceof Response
|| message instanceof String) {
// 發(fā)送消息
channel.send(message, sent);
} else {
// 新建一個request實例
Request request = new Request();
// 設置信息的版本
request.setVersion(Version.getProtocolVersion());
// 該請求不需要響應
request.setTwoWay(false);
// 把消息傳入
request.setData(message);
// 發(fā)送消息
channel.send(request, sent);
}
}
該方法是在channel的send方法上加上了request和response模型,最后再調用channel.send,起到了裝飾器的作用。
4.request@Override
public ResponseFuture request(Object request) throws RemotingException {
return request(request, channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT));
}
@Override
public ResponseFuture request(Object request, int timeout) throws RemotingException {
// 如果通道關閉,則拋出異常
if (closed) {
throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!");
}
// create request.創(chuàng)建請求
Request req = new Request();
// 設置版本號
req.setVersion(Version.getProtocolVersion());
// 設置需要響應
req.setTwoWay(true);
// 把請求數據傳入
req.setData(request);
// 創(chuàng)建DefaultFuture對象,可以從future中主動獲得請求對應的響應信息
DefaultFuture future = new DefaultFuture(channel, req, timeout);
try {
// 發(fā)送請求消息
channel.send(req);
} catch (RemotingException e) {
future.cancel();
throw e;
}
return future;
}
該方法是請求方法,用Request模型把請求內容裝飾起來,然后發(fā)送一個Request類型的消息,并且返回DefaultFuture實例,DefaultFuture我會在后面講到。
cloes方法也重寫了,我就不再多說,因為比較簡單,沒有重點,其他方法都是直接調用channel屬性的方法。
(三)ExchangeClient該接口繼承了Client和ExchangeChannel,是信息交換客戶端接口,其中沒有定義多余的方法。
(四)HeaderExchangeClient該類實現了ExchangeClient接口,是基于協(xié)議頭的信息交互客戶端類,同樣它是Client、Channel的適配器。在該類的源碼中可以看到所有的實現方法都是調用了client和channel屬性的方法。該類主要的作用就是增加了心跳功能,為什么要增加心跳功能呢,對于長連接,一些拔網線等物理層的斷開,會導致TCP的FIN消息來不及發(fā)送,對方收不到斷開事件,那么就需要用到發(fā)送心跳包來檢測連接是否斷開。consumer和provider斷開,處理措施不一樣,會分別做出重連和關閉通道的操作。
1.屬性private static final Logger logger = LoggerFactory.getLogger(HeaderExchangeClient.class);
/**
* 定時器線程池
*/
private static final ScheduledThreadPoolExecutor scheduled = new ScheduledThreadPoolExecutor(2, new NamedThreadFactory("dubbo-remoting-client-heartbeat", true));
/**
* 客戶端
*/
private final Client client;
/**
* 信息交換通道
*/
private final ExchangeChannel channel;
// heartbeat timer
/**
* 心跳定時器
*/
private ScheduledFuture> heartbeatTimer;
// heartbeat(ms), default value is 0 , won"t execute a heartbeat.
/**
* 心跳周期,間隔多久發(fā)送心跳消息檢測一次
*/
private int heartbeat;
/**
* 心跳超時時間
*/
private int heartbeatTimeout;
該類的屬性除了需要適配的屬性外,其他都是跟心跳相關屬性。
2.構造函數public HeaderExchangeClient(Client client, boolean needHeartbeat) {
if (client == null) {
throw new IllegalArgumentException("client == null");
}
this.client = client;
// 創(chuàng)建信息交換通道
this.channel = new HeaderExchangeChannel(client);
// 獲得dubbo版本
String dubbo = client.getUrl().getParameter(Constants.DUBBO_VERSION_KEY);
//獲得心跳周期配置,如果沒有配置,并且dubbo是1.0版本的,則這只為1分鐘,否則設置為0
this.heartbeat = client.getUrl().getParameter(Constants.HEARTBEAT_KEY, dubbo != null && dubbo.startsWith("1.0.") ? Constants.DEFAULT_HEARTBEAT : 0);
// 獲得心跳超時配置,默認是心跳周期的三倍
this.heartbeatTimeout = client.getUrl().getParameter(Constants.HEARTBEAT_TIMEOUT_KEY, heartbeat * 3);
// 如果心跳超時時間小于心跳周期的兩倍,則拋出異常
if (heartbeatTimeout < heartbeat * 2) {
throw new IllegalStateException("heartbeatTimeout < heartbeatInterval * 2");
}
if (needHeartbeat) {
// 開啟心跳
startHeartbeatTimer();
}
}
構造函數就是對一些屬性初始化設置,優(yōu)先從url中獲取。心跳超時時間小于心跳周期的兩倍就拋出異常,意思就是至少重試兩次心跳檢測。
3.startHeartbeatTimerprivate void startHeartbeatTimer() {
// 停止現有的心跳線程
stopHeartbeatTimer();
// 如果需要心跳
if (heartbeat > 0) {
// 創(chuàng)建心跳定時器
heartbeatTimer = scheduled.scheduleWithFixedDelay(
// 新建一個心跳線程
new HeartBeatTask(new HeartBeatTask.ChannelProvider() {
@Override
public Collection getChannels() {
// 返回一個只包含HeaderExchangeClient對象的不可變列表
return Collections.singletonList(HeaderExchangeClient.this);
}
}, heartbeat, heartbeatTimeout),
heartbeat, heartbeat, TimeUnit.MILLISECONDS);
}
}
該方法就是開啟心跳。利用心跳定時器來做到定時檢測心跳。因為這是信息交換客戶端類,所有這里的只是返回包含HeaderExchangeClient對象的不可變列表,因為客戶端跟channel是一一對應的,只有這一個該客戶端本身的channel需要心跳。
4.stopHeartbeatTimerprivate void stopHeartbeatTimer() {
if (heartbeatTimer != null && !heartbeatTimer.isCancelled()) {
try {
// 取消定時器
heartbeatTimer.cancel(true);
// 取消大量已排隊任務,用于回收空間
scheduled.purge();
} catch (Throwable e) {
if (logger.isWarnEnabled()) {
logger.warn(e.getMessage(), e);
}
}
}
heartbeatTimer = null;
}
該方法是停止現有心跳,也就是停止定時器,釋放空間。
其他方法都是調用channel和client屬性的方法。
(五)HeartBeatTask該類實現了Runnable接口,實現的是心跳任務,里面包含了核心的心跳策略。
1.屬性/** * 通道管理 */ private ChannelProvider channelProvider; /** * 心跳間隔 單位:ms */ private int heartbeat; /** * 心跳超時時間 單位:ms */ private int heartbeatTimeout;
后兩個屬性跟HeaderExchangeClient中的屬性含義一樣,第一個是該類自己內部的一個接口:
interface ChannelProvider {
// 獲得所有的通道集合,需要心跳的通道數組
Collection getChannels();
}
該接口就定義了一個方法,獲得需要心跳的通道集合??上攵?,會對集合內的通道都做心跳檢測。
2.run@Override
public void run() {
try {
long now = System.currentTimeMillis();
// 遍歷所有通道
for (Channel channel : channelProvider.getChannels()) {
// 如果通道關閉了,則跳過
if (channel.isClosed()) {
continue;
}
try {
// 最后一次接收到消息的時間戳
Long lastRead = (Long) channel.getAttribute(
HeaderExchangeHandler.KEY_READ_TIMESTAMP);
// 最后一次發(fā)送消息的時間戳
Long lastWrite = (Long) channel.getAttribute(
HeaderExchangeHandler.KEY_WRITE_TIMESTAMP);
// 如果最后一次接收或者發(fā)送消息到時間到現在的時間間隔超過了心跳間隔時間
if ((lastRead != null && now - lastRead > heartbeat)
|| (lastWrite != null && now - lastWrite > heartbeat)) {
// 創(chuàng)建一個request
Request req = new Request();
// 設置版本號
req.setVersion(Version.getProtocolVersion());
// 設置需要得到響應
req.setTwoWay(true);
// 設置事件類型,為心跳事件
req.setEvent(Request.HEARTBEAT_EVENT);
// 發(fā)送心跳請求
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");
}
}
// 如果最后一次接收消息的時間到現在已經超過了超時時間
if (lastRead != null && now - lastRead > heartbeatTimeout) {
logger.warn("Close channel " + channel
+ ", because heartbeat read idle time out: " + heartbeatTimeout + "ms");
// 如果該通道是客戶端,也就是請求的服務器掛掉了,客戶端嘗試重連服務器
if (channel instanceof Client) {
try {
// 重新連接服務器
((Client) channel).reconnect();
} catch (Exception e) {
//do nothing
}
} 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);
}
}
該方法中是心跳機制的核心邏輯。注意以下幾個點:
如果需要心跳的通道本身如果關閉了,那么跳過,不添加心跳機制。
無論是接收消息還是發(fā)送消息,只要超過了設置的心跳間隔,就發(fā)送心跳消息來測試是否斷開
如果最后一次接收到消息到到現在已經超過了心跳超時時間,那就認定對方的確斷開,分兩種情況來處理對方斷開的情況。分別是服務端斷開,客戶端重連以及客戶端斷開,服務端斷開這個客戶端的連接。,這里要好好品味一下誰是發(fā)送方,誰在等誰的響應,苦苦沒有等到。
(六)ResponseFuturepublic interface ResponseFuture {
Object get() throws RemotingException;
Object get(int timeoutInMillis) throws RemotingException;
void setCallback(ResponseCallback callback);
boolean isDone();
}
該接口是響應future接口,該接口的設計意圖跟java.util.concurrent.Future很類似。發(fā)送出去的消息,潑出去的水,只有等到對方主動響應才能得到結果,但是請求方需要去主動回去該請求的結果,就顯得有些艱難,所有產生了這樣一個接口,它能夠獲取任務執(zhí)行結果、可以核對請求消息是否被響應,還能設置回調來支持異步。
(七)DefaultFuture該類實現了ResponseFuture接口,其中封裝了處理響應的邏輯。你可以把DefaultFuture看成是一個中介,買房和賣房都通過這個中介進行溝通,中介擁有著買房者的信息request和賣房者的信息response,并且促成他們之間的買賣。
1.屬性private static final Logger logger = LoggerFactory.getLogger(DefaultFuture.class); /** * 通道集合 */ private static final MapCHANNELS = new ConcurrentHashMap (); /** * Future集合,key為請求編號 */ private static final Map FUTURES = new ConcurrentHashMap (); // invoke id. /** * 請求編號 */ private final long id; /** * 通道 */ private final Channel channel; /** * 請求 */ private final Request request; /** * 超時 */ private final int timeout; /** * 鎖 */ private final Lock lock = new ReentrantLock(); /** * 完成情況,控制多線程的休眠與喚醒 */ private final Condition done = lock.newCondition(); /** * 創(chuàng)建開始時間 */ private final long start = System.currentTimeMillis(); /** * 發(fā)送請求時間 */ private volatile long sent; /** * 響應 */ private volatile Response response; /** * 回調 */ private volatile ResponseCallback callback;
可以看到,該類的屬性包含了request、response、channel三個實例,在該類中,把請求和響應通過唯一的id一一對應起來。做到異步處理返回結果時能給準確的返回給對應的請求??梢钥吹綄傩灾杏袃蓚€集合,分別是通道集合和future集合,也就是該類本身也是所有 DefaultFuture 的管理容器。
2.構造函數public DefaultFuture(Channel channel, Request request, int timeout) {
this.channel = channel;
this.request = request;
// 設置請求編號
this.id = request.getId();
this.timeout = timeout > 0 ? timeout : channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
// put into waiting map.,加入到等待集合中
FUTURES.put(id, this);
CHANNELS.put(id, channel);
}
構造函數比較簡單,每一個DefaultFuture實例都跟每一個請求一一對應,被存入到集合中管理起來。
3.closeChannelpublic static void closeChannel(Channel channel) {
// 遍歷通道集合
for (long id : CHANNELS.keySet()) {
if (channel.equals(CHANNELS.get(id))) {
// 通過請求id獲得future
DefaultFuture future = getFuture(id);
if (future != null && !future.isDone()) {
// 創(chuàng)建一個關閉通道的響應
Response disconnectResponse = new Response(future.getId());
disconnectResponse.setStatus(Response.CHANNEL_INACTIVE);
disconnectResponse.setErrorMessage("Channel " +
channel +
" is inactive. Directly return the unFinished request : " +
future.getRequest());
// 接收該關閉通道并且請求未完成的響應
DefaultFuture.received(channel, disconnectResponse);
}
}
}
}
該方法是關閉不活躍的通道,并且返回請求未完成。也就是關閉指定channel的請求,返回的是請求未完成。
4.receivedpublic static void received(Channel channel, Response response) {
try {
// future集合中移除該請求的future,(響應id和請求id一一對應的)
DefaultFuture future = FUTURES.remove(response.getId());
if (future != null) {
// 接收響應結果
future.doReceived(response);
} else {
logger.warn("The timeout response finally returned at "
+ (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()))
+ ", response " + response
+ (channel == null ? "" : ", channel: " + channel.getLocalAddress()
+ " -> " + channel.getRemoteAddress()));
}
} finally {
// 通道集合移除該請求對應的通道,代表著這一次請求結束
CHANNELS.remove(response.getId());
}
}
該方法是接收響應,也就是某個請求得到了響應,那么代表這次請求任務完成,所有需要把future從集合中移除。具體的接收響應結果在doReceived方法中實現。
5.doReceivedprivate void doReceived(Response res) {
// 獲得鎖
lock.lock();
try {
// 設置響應
response = res;
if (done != null) {
// 喚醒等待
done.signal();
}
} finally {
// 釋放鎖
lock.unlock();
}
if (callback != null) {
// 執(zhí)行回調
invokeCallback(callback);
}
}
可以看到,當接收到響應后,會把等待的線程喚醒,然后執(zhí)行回調來處理該響應結果。
6.invokeCallbackprivate void invokeCallback(ResponseCallback c) {
ResponseCallback callbackCopy = c;
if (callbackCopy == null) {
throw new NullPointerException("callback cannot be null.");
}
c = null;
Response res = response;
if (res == null) {
throw new IllegalStateException("response cannot be null. url:" + channel.getUrl());
}
// 如果響應成功,返回碼是20
if (res.getStatus() == Response.OK) {
try {
// 使用響應結果執(zhí)行 完成 后的邏輯
callbackCopy.done(res.getResult());
} catch (Exception e) {
logger.error("callback invoke error .reasult:" + res.getResult() + ",url:" + channel.getUrl(), e);
}
//超時,回調處理成超時異常
} else if (res.getStatus() == Response.CLIENT_TIMEOUT || res.getStatus() == Response.SERVER_TIMEOUT) {
try {
TimeoutException te = new TimeoutException(res.getStatus() == Response.SERVER_TIMEOUT, channel, res.getErrorMessage());
// 回調處理異常
callbackCopy.caught(te);
} catch (Exception e) {
logger.error("callback invoke error ,url:" + channel.getUrl(), e);
}
// 其他情況處理成RemotingException異常
} else {
try {
RuntimeException re = new RuntimeException(res.getErrorMessage());
callbackCopy.caught(re);
} catch (Exception e) {
logger.error("callback invoke error ,url:" + channel.getUrl(), e);
}
}
}
該方法是執(zhí)行回調來處理響應結果。分為了三種情況:
響應成功,那么執(zhí)行完成后的邏輯。
超時,會按照超時異常來處理
其他,按照RuntimeException異常來處理
具體的處理都在ResponseCallback接口的實現類里執(zhí)行,后面我會講到。
7.get@Override
public Object get() throws RemotingException {
return get(timeout);
}
@Override
public Object get(int timeout) throws RemotingException {
// 超時時間默認為1s
if (timeout <= 0) {
timeout = Constants.DEFAULT_TIMEOUT;
}
// 如果請求沒有完成,也就是還沒有響應返回
if (!isDone()) {
long start = System.currentTimeMillis();
// 獲得鎖
lock.lock();
try {
// 輪詢 等待請求是否完成
while (!isDone()) {
// 線程阻塞等待
done.await(timeout, TimeUnit.MILLISECONDS);
// 如果請求完成或者超時,則結束
if (isDone() || System.currentTimeMillis() - start > timeout) {
break;
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
// 釋放鎖
lock.unlock();
}
// 如果沒有收到響應,則拋出超時的異常
if (!isDone()) {
throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false));
}
}
// 返回響應
return returnFromResponse();
}
該方法是實現了ResponseFuture定義的方法,是獲得該future對應的請求對應的響應結果,其實future、請求、響應都是一一對應的。其中如果還沒得到響應,則會線程阻塞等待,等到有響應結果或者超時,才返回。返回的邏輯在returnFromResponse中實現。
8.returnFromResponseprivate Object returnFromResponse() throws RemotingException {
Response res = response;
if (res == null) {
throw new IllegalStateException("response cannot be null");
}
// 如果正常返回,則返回響應結果
if (res.getStatus() == Response.OK) {
return res.getResult();
}
// 如果超時,則拋出超時異常
if (res.getStatus() == Response.CLIENT_TIMEOUT || res.getStatus() == Response.SERVER_TIMEOUT) {
throw new TimeoutException(res.getStatus() == Response.SERVER_TIMEOUT, channel, res.getErrorMessage());
}
// 其他 拋出RemotingException異常
throw new RemotingException(channel, res.getErrorMessage());
}
這代碼跟invokeCallback方法中差不多,都是把響應分了三種情況。
9.cancelpublic void cancel() {
// 創(chuàng)建一個取消請求的響應
Response errorResult = new Response(id);
errorResult.setErrorMessage("request future has been canceled.");
response = errorResult;
// 從集合中刪除該請求
FUTURES.remove(id);
CHANNELS.remove(id);
}
該方法是取消一個請求,可以直接關閉一個請求,也就是值創(chuàng)建一個響應來回應該請求,把response值設置到該請求對于到future中,做到了中斷請求的作用。該方法跟closeChannel的區(qū)別是closeChannel中對response的狀態(tài)設置了CHANNEL_INACTIVE,而cancel方法是中途被主動取消的,雖然有response值,但是并沒有一個響應狀態(tài)。
10.RemotingInvocationTimeoutScanprivate static class RemotingInvocationTimeoutScan implements Runnable {
@Override
public void run() {
while (true) {
try {
for (DefaultFuture future : FUTURES.values()) {
// 已經完成,跳過掃描
if (future == null || future.isDone()) {
continue;
}
// 超時
if (System.currentTimeMillis() - future.getStartTimestamp() > future.getTimeout()) {
// create exception response.,創(chuàng)建一個超時的響應
Response timeoutResponse = new Response(future.getId());
// set timeout status.,設置超時狀態(tài),是服務端側超時還是客戶端側超時
timeoutResponse.setStatus(future.isSent() ? Response.SERVER_TIMEOUT : Response.CLIENT_TIMEOUT);
// 設置錯誤信息
timeoutResponse.setErrorMessage(future.getTimeoutMessage(true));
// handle response.,接收創(chuàng)建的超時響應
DefaultFuture.received(future.getChannel(), timeoutResponse);
}
}
// 睡眠
Thread.sleep(30);
} catch (Throwable e) {
logger.error("Exception when scan the timeout invocation of remoting.", e);
}
}
}
}
該方法是掃描調用超時任務的線程,每次都會遍歷future集合,檢測請求是否超時了,如果超時則創(chuàng)建一個超時響應來回應該請求。
static {
// 開啟一個后臺掃描調用超時任務
Thread th = new Thread(new RemotingInvocationTimeoutScan(), "DubboResponseTimeoutScanTimer");
th.setDaemon(true);
th.start();
}
開啟一個后臺線程進行掃描的邏輯寫在了靜態(tài)代碼塊里面,只開啟一次。
(八)SimpleFuture該類實現了ResponseFuture,目前沒有用到,很簡單的實現,我就不多說了。
(九)ExchangeHandler該接口繼承了ChannelHandler, TelnetHandler接口,是信息交換處理器接口。
public interface ExchangeHandler extends ChannelHandler, TelnetHandler {
/**
* reply.
* 回復請求結果
* @param channel
* @param request
* @return response
* @throws RemotingException
*/
Object reply(ExchangeChannel channel, Object request) throws RemotingException;
}
該接口只定義了一個回復請求結果的方法,返回的是請求結果。
(十)ExchangeHandlerDispatcher該類實現了ExchangeHandler接口, 是信息交換處理器調度器類,也就是對應不同的事件,選擇不同的處理器去處理。該類中有三個屬性,分別對應了三種事件:
/** * 回復者調度器 */ private final ReplierDispatcher replierDispatcher; /** * 通道處理器調度器 */ private final ChannelHandlerDispatcher handlerDispatcher; /** * Telnet 命令處理器 */ private final TelnetHandler telnetHandler;
如果事件是跟通道處理器有關的,就調用通道處理器來處理,比如:
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Object reply(ExchangeChannel channel, Object request) throws RemotingException {
return ((Replier) replierDispatcher).reply(channel, request);
}
@Override
public void connected(Channel channel) {
handlerDispatcher.connected(channel);
}
@Override
public String telnet(Channel channel, String message) throws RemotingException {
return telnetHandler.telnet(channel, message);
}
可以看到以上三種事件,回復請求結果需要回復者調度器來處理,連接需要通道處理器調度器來處理,telnet消息需要Telnet命令處理器來處理。
(十一)ExchangeHandlerAdapter該類繼承了TelnetHandlerAdapter,實現了ExchangeHandler,是信息交換處理器的適配器類。
public abstract class ExchangeHandlerAdapter extends TelnetHandlerAdapter implements ExchangeHandler {
@Override
public Object reply(ExchangeChannel channel, Object msg) throws RemotingException {
// 直接返回null
return null;
}
}
該類直接讓ExchangeHandler定義的方法reply返回null,交由它的子類選擇性的去實現具體的回復請求結果。
(十二)ExchangeServer該接口繼承了Server接口,定義了兩個方法:
public interface ExchangeServer extends Server {
/**
* get channels.
* 獲得通道集合
* @return channels
*/
Collection getExchangeChannels();
/**
* get channel.
* 根據遠程地址獲得對應的信息通道
* @param remoteAddress
* @return channel
*/
ExchangeChannel getExchangeChannel(InetSocketAddress remoteAddress);
}
該接口比較好理解,并且在Server接口基礎上新定義了兩個方法。直接來看看它的實現類吧。
(十三)HeaderExchangeServer該類實現了ExchangeServer接口,是基于協(xié)議頭的信息交換服務器實現類,HeaderExchangeServer是Server的裝飾器,每個實現方法都會調用server的方法。
1.屬性protected final Logger logger = LoggerFactory.getLogger(getClass());
/**
* 線程池
*/
private final ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(1,
new NamedThreadFactory(
"dubbo-remoting-server-heartbeat",
true));
/**
* 服務器
*/
private final Server server;
// heartbeat timer
/**
* 心跳定時器
*/
private ScheduledFuture> heartbeatTimer;
// heartbeat timeout (ms), default value is 0 , won"t execute a heartbeat.
/**
* 心跳周期
*/
private int heartbeat;
/**
* 心跳超時時間
*/
private int heartbeatTimeout;
/**
* 信息交換服務器是否關閉
*/
private AtomicBoolean closed = new AtomicBoolean(false);
該類里面的很多實現跟HeaderExchangeClient差不多,包括心跳檢測等邏輯??吹枚鲜鑫抑v的HeaderExchangeClient的屬性,想必這里的屬性應該也很簡單了。
2.構造函數public HeaderExchangeServer(Server server) {
if (server == null) {
throw new IllegalArgumentException("server == null");
}
this.server = server;
//獲得心跳周期配置,如果沒有配置,默認設置為0
this.heartbeat = server.getUrl().getParameter(Constants.HEARTBEAT_KEY, 0);
// 獲得心跳超時配置,默認是心跳周期的三倍
this.heartbeatTimeout = server.getUrl().getParameter(Constants.HEARTBEAT_TIMEOUT_KEY, heartbeat * 3);
// 如果心跳超時時間小于心跳周期的兩倍,則拋出異常
if (heartbeatTimeout < heartbeat * 2) {
throw new IllegalStateException("heartbeatTimeout < heartbeatInterval * 2");
}
// 開始心跳
startHeartbeatTimer();
}
public Server getServer() {
return server;
}
構造函數就是對屬性的設置,心跳的機制以及默認值都跟HeaderExchangeClient中的一模一樣。
3.isRunningprivate boolean isRunning() {
Collection channels = getChannels();
// 遍歷所有連接該服務器的通道
for (Channel channel : channels) {
/**
* If there are any client connections,
* our server should be running.
*/
// 只要有任何一個客戶端連接,則服務器還運行著
if (channel.isConnected()) {
return true;
}
}
return false;
}
該方法是檢測服務器是否還運行,只要有一個客戶端連接著,就算服務器運行著。
4.close@Override
public void close() {
// 關閉線程池和心跳檢測
doClose();
// 關閉服務器
server.close();
}
@Override
public void close(final int timeout) {
// 開始關閉
startClose();
if (timeout > 0) {
final long max = (long) timeout;
final long start = System.currentTimeMillis();
if (getUrl().getParameter(Constants.CHANNEL_SEND_READONLYEVENT_KEY, true)) {
// 發(fā)送 READONLY_EVENT事件給所有連接該服務器的客戶端,表示 Server 不可讀了。
sendChannelReadOnlyEvent();
}
// 當服務器還在運行,并且沒有超時,睡眠,也就是等待timeout左右時間在進行關閉
while (HeaderExchangeServer.this.isRunning()
&& System.currentTimeMillis() - start < max) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
logger.warn(e.getMessage(), e);
}
}
}
// 關閉線程池和心跳檢測
doClose();
// 延遲關閉
server.close(timeout);
}
兩個close方法,第二個close方法是優(yōu)雅的關閉,有一定的延時來讓一些響應或者操作做完。關閉分兩個步驟,第一個就是關閉信息交換服務器中的線程池和心跳檢測,然后才是關閉服務器。
5.sendChannelReadOnlyEventprivate void sendChannelReadOnlyEvent() {
// 創(chuàng)建一個READONLY_EVENT事件的請求
Request request = new Request();
request.setEvent(Request.READONLY_EVENT);
// 不需要響應
request.setTwoWay(false);
// 設置版本
request.setVersion(Version.getProtocolVersion());
Collection channels = getChannels();
// 遍歷連接的通道,進行通知
for (Channel channel : channels) {
try {
// 通過通道還連接著,則發(fā)送通知
if (channel.isConnected())
channel.send(request, getUrl().getParameter(Constants.CHANNEL_READONLYEVENT_SENT_KEY, true));
} catch (RemotingException e) {
logger.warn("send cannot write message error.", e);
}
}
}
在關閉服務器中有一個操作就是發(fā)送事件READONLY_EVENT,告訴客戶端該服務器不可讀了,就是該方法實現的,逐個通知連接的客戶端該事件。
6.doCloseprivate void doClose() {
if (!closed.compareAndSet(false, true)) {
return;
}
// 停止心跳檢測
stopHeartbeatTimer();
try {
// 關閉線程池
scheduled.shutdown();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
該方法就是close方法調用到的停止心跳檢測和關閉線程池。
7.getExchangeChannels@Override public CollectiongetExchangeChannels() { Collection exchangeChannels = new ArrayList (); // 獲得連接該服務器通道集合 Collection channels = server.getChannels(); if (channels != null && !channels.isEmpty()) { // 遍歷通道集合,為每個通道都創(chuàng)建信息交換通道,并且加入信息交換通道集合 for (Channel channel : channels) { exchangeChannels.add(HeaderExchangeChannel.getOrAddChannel(channel)); } } return exchangeChannels; }
該方法是返回連接該服務器信息交換通道集合。邏輯就是先獲得通道集合,在根據通道來創(chuàng)建信息交換通道,然后返回信息通道集合。
8.reset@Override
public void reset(URL url) {
// 重置屬性
server.reset(url);
try {
// 重置的邏輯跟構造函數一樣設置
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);
}
}
該方法就是重置屬性,重置后,重新開始心跳,設置心跳屬性的機制跟構造函數一樣。
9.startHeartbeatTimerprivate void startHeartbeatTimer() {
// 先停止現有的心跳檢測
stopHeartbeatTimer();
if (heartbeat > 0) {
// 創(chuàng)建心跳定時器
heartbeatTimer = scheduled.scheduleWithFixedDelay(
new HeartBeatTask(new HeartBeatTask.ChannelProvider() {
@Override
public Collection getChannels() {
// 返回一個不可修改的連接該服務器的信息交換通道集合
return Collections.unmodifiableCollection(
HeaderExchangeServer.this.getChannels());
}
}, heartbeat, heartbeatTimeout),
heartbeat, heartbeat, TimeUnit.MILLISECONDS);
}
}
該方法是開始心跳,跟HeaderExchangeClient類中的開始心跳方法唯一區(qū)別是獲得的通道不一樣,客戶端跟通道是一一對應的,所有只要對一個通道進行心跳檢測,而服務端跟通道是一對多的關系,所有需要對該服務器連接的所有通道進行心跳檢測。
10.stopHeartbeatTimerprivate void stopHeartbeatTimer() {
if (heartbeatTimer != null && !heartbeatTimer.isCancelled()) {
try {
// 取消定時器
heartbeatTimer.cancel(true);
// 取消大量已排隊任務,用于回收空間
scheduled.purge();
} catch (Throwable e) {
if (logger.isWarnEnabled()) {
logger.warn(e.getMessage(), e);
}
}
}
heartbeatTimer = null;
}
該方法是停止當前的心跳檢測。
(十四)ExchangeServerDelegate該類實現了ExchangeServer接口,是信息交換服務器裝飾者,是ExchangeServer的裝飾器。該類就一個屬性ExchangeServer server,所有實現方法都調用了server屬性的方法。目前只有在p2p中被用到,代碼為就不貼了,很簡單。
(十五)Exchanger@SPI(HeaderExchanger.NAME)
public interface Exchanger {
/**
* bind.
* 綁定一個服務器
* @param url 服務器url
* @param handler 數據交換處理器
* @return message server 數據交換服務器
*/
@Adaptive({Constants.EXCHANGER_KEY})
ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException;
/**
* connect.
* 連接一個服務器,也就是創(chuàng)建一個客戶端
* @param url 服務器url
* @param handler 數據交換處理器
* @return message channel 返回數據交換客戶端
*/
@Adaptive({Constants.EXCHANGER_KEY})
ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException;
}
該接口是數據交換者接口,該接口是一個可擴展接口默認實現的是HeaderExchanger類,并且用到了dubbo SPI的Adaptive機制,優(yōu)先實現url攜帶的配置。如果不了解dubbo SPI機制的可以看《dubbo源碼解析(二)Dubbo擴展機制SPI》。那么回到該接口定義的方法,定義了綁定和連接兩個方法,分別返回信息交互服務器和客戶端實例。
(十六)HeaderExchangerpublic class HeaderExchanger implements Exchanger {
public static final String NAME = "header";
@Override
public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
// 用傳輸層連接返回的client 創(chuàng)建對應的信息交換客戶端,默認開啟心跳檢測
return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
}
@Override
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
// 用傳輸層綁定返回的server 創(chuàng)建對應的信息交換服務端
return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
}
該類繼承了Exchanger接口,是Exchanger接口的默認實現,實現了Exchanger接口定義的兩個方法,分別調用的是Transporters的連接和綁定方法,再利用這這兩個方法返回的客戶端和服務端實例來創(chuàng)建信息交換的客戶端和服務端。
(十七)Replier我們知道Request對應的是ExchangeHandler接口實現對象來處理,但有些時候我們需要不同數據類型對應不同的處理器,該類就是為了支持這一需求所設計的。
public interface Replier{ /** * reply. * 回復請求結果 * @param channel * @param request * @return response * @throws RemotingException */ Object reply(ExchangeChannel channel, T request) throws RemotingException; }
可以看到該接口跟ExchangeHandler定義的方法也一一,只有請求的類型改為了范型。
(十八)ReplierDispatcher該類實現了Replier接口,是回復者調度器實現類。
/** * 默認回復者 */ private final Replier> defaultReplier; /** * 回復者集合 */ private final Map, Replier>> repliers = new ConcurrentHashMap , Replier>>();
這是該類的兩個屬性,緩存了回復者集合和默認的回復者。
/**
* 從回復者集合中找到該類型的回復者,并且返回
* @param type
* @return
*/
private Replier> getReplier(Class> type) {
for (Map.Entry, Replier>> entry : repliers.entrySet()) {
if (entry.getKey().isAssignableFrom(type)) {
return entry.getValue();
}
}
if (defaultReplier != null) {
return defaultReplier;
}
throw new IllegalStateException("Replier not found, Unsupported message object: " + type);
}
/**
* 回復請求
* @param channel
* @param request
* @return
* @throws RemotingException
*/
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Object reply(ExchangeChannel channel, Object request) throws RemotingException {
return ((Replier) getReplier(request.getClass())).reply(channel, request);
}
上述是該類中關鍵的兩個方法,reply還是調用實現類的reply。根據請求的數據類型來使用指定的回復者進行回復。
(十九)MultiMessage該類實現了實現 Iterable 接口,是多消息的封裝,我們直接看它的屬性:
/** * 消息集合 */ private final List messages = new ArrayList();
該類要和《dubbo源碼解析(九)遠程通信——Transport層》的(八)MultiMessageHandler聯(lián)合著看。
(二十)HeartbeatHandler該類繼承了AbstractChannelHandlerDelegate類,是心跳處理器。是用來處理心跳事件的,也接收消息上增加了對心跳消息的處理。該類是
@Override
public void received(Channel channel, Object message) throws RemotingException {
// 設置接收時間的時間戳屬性值
setReadTimestamp(channel);
// 如果是心跳請求
if (isHeartbeatRequest(message)) {
Request req = (Request) message;
// 如果需要響應
if (req.isTwoWay()) {
// 創(chuàng)建一個響應
Response res = new Response(req.getId(), req.getVersion());
// 設置為心跳事件的響應
res.setEvent(Response.HEARTBEAT_EVENT);
// 發(fā)送消息,也就是返回響應
channel.send(res);
if (logger.isInfoEnabled()) {
int heartbeat = channel.getUrl().getParameter(Constants.HEARTBEAT_KEY, 0);
if (logger.isDebugEnabled()) {
logger.debug("Received heartbeat from remote channel " + channel.getRemoteAddress()
+ ", cause: The channel has no data-transmission exceeds a heartbeat period"
+ (heartbeat > 0 ? ": " + heartbeat + "ms" : ""));
}
}
}
return;
}
// 如果是心跳響應,則直接return
if (isHeartbeatResponse(message)) {
if (logger.isDebugEnabled()) {
logger.debug("Receive heartbeat response in thread " + Thread.currentThread().getName());
}
return;
}
handler.received(channel, message);
}
該方法是就是在handler處理消息上增加了處理心跳消息的功能,做到了功能增強。
(二十一)Exchangers該類跟Transporters的設計意圖是一樣的,Transporters我在《dubbo源碼解析(八)遠程通信——開篇》的(十)Transporters已經講到了。Exchangers也用到了外觀模式。代碼為就不貼了,可以對照著Transporters來看,很簡單。
(二十二)Request請求模型類,最重要的肯定是模型的屬性,我們來看看屬性:
/** * 心跳事件 */ public static final String HEARTBEAT_EVENT = null; /** * 只讀事件 */ public static final String READONLY_EVENT = "R"; /** * 請求編號自增序列 */ private static final AtomicLong INVOKE_ID = new AtomicLong(0); /** * 請求編號 */ private final long mId; /** * dubbo版本 */ private String mVersion; /** * 是否需要響應 */ private boolean mTwoWay = true; /** * 是否是事件 */ private boolean mEvent = false; /** * 是否是異常的請求 */ private boolean mBroken = false; /** * 請求數據 */ private Object mData;
由于心跳事件比較常用,所有設置為null。
請求編號使用INVOKE_ID生成,是JVM 進程內唯一的。
其他屬性比較簡單
(二十三)Response響應模型,來看看它的屬性:
/** * 心跳事件 */ public static final String HEARTBEAT_EVENT = null; /** * 只讀事件 */ public static final String READONLY_EVENT = "R"; /** * ok. * 成功狀態(tài)碼 */ public static final byte OK = 20; /** * clien side timeout. * 客戶端側的超時狀態(tài)碼 */ public static final byte CLIENT_TIMEOUT = 30; /** * server side timeout. * 服務端側超時的狀態(tài)碼 */ public static final byte SERVER_TIMEOUT = 31; /** * channel inactive, directly return the unfinished requests. * 通道不活躍,返回未完成請求的狀態(tài)碼 */ public static final byte CHANNEL_INACTIVE = 35; /** * request format error. * 請求格式錯誤狀態(tài)碼 */ public static final byte BAD_REQUEST = 40; /** * response format error. * 響應格式錯誤狀態(tài)碼 */ public static final byte BAD_RESPONSE = 50; /** * service not found. * 服務找不到狀態(tài)碼 */ public static final byte SERVICE_NOT_FOUND = 60; /** * service error. * 服務錯誤狀態(tài)碼 */ public static final byte SERVICE_ERROR = 70; /** * internal server error. * 內部服務器錯誤狀態(tài)碼 */ public static final byte SERVER_ERROR = 80; /** * internal server error. * 客戶端錯誤狀態(tài)碼 */ public static final byte CLIENT_ERROR = 90; /** * server side threadpool exhausted and quick return. * 服務器端線程池耗盡并快速返回狀態(tài)碼 */ public static final byte SERVER_THREADPOOL_EXHAUSTED_ERROR = 100; /** * 響應編號 */ private long mId = 0; /** * dubbo 版本 */ private String mVersion; /** * 狀態(tài) */ private byte mStatus = OK; /** * 是否是事件 */ private boolean mEvent = false; /** * 錯誤信息 */ private String mErrorMsg; /** * 返回結果 */ private Object mResult;
很多屬性跟Request模型的屬性一樣,并且含義也一樣,不過該模型多了很多的狀態(tài)碼。關鍵的是id跟請求一一對應。
(二十四)ResponseCallbackpublic interface ResponseCallback {
/**
* done.
* 處理請求
* @param response
*/
void done(Object response);
/**
* caught exception.
* 處理異常
* @param exception
*/
void caught(Throwable exception);
}
該接口是回調的接口,定義了兩個方法,分別是處理正常的響應結果和處理異常。
(二十五)ExchangeCodec該類繼承了TelnetCodec,是信息交換編解碼器。在本文的開頭,我就寫到,dubbo將一條消息分成了協(xié)議頭和協(xié)議體,用來解決粘包拆包問題,但是頭跟體在編解碼上有區(qū)別,我們先來看看dubbo 的協(xié)議頭的配置:
上圖是官方文檔的圖片,能夠清晰的看出協(xié)議中各個數據所占的位數:
0-7位和8-15位:Magic High和Magic Low,類似java字節(jié)碼文件里的魔數,用來判斷是不是dubbo協(xié)議的數據包,就是一個固定的數字
16位:Req/Res:請求還是響應標識。
17位:2way:單向還是雙向
18位:Event:是否是事件
19-23位:Serialization 編號
24-31位:status狀態(tài)
32-95位:id編號
96-127位:body數據
128-…位:上圖表格內的數據
可以看到一個該協(xié)議中前65位是協(xié)議頭,后面的都是協(xié)議體數據。那么在編解碼中,協(xié)議頭是通過 Codec 編解碼,而body部分是用Serialization序列化和反序列化的。下面我們就來看看該類對協(xié)議頭的編解碼。
1.屬性// header length. /** * 協(xié)議頭長度:16字節(jié) = 128Bits */ protected static final int HEADER_LENGTH = 16; // magic header. /** * MAGIC二進制:1101101010111011,十進制:55995 */ protected static final short MAGIC = (short) 0xdabb; /** * Magic High,也就是0-7位:11011010 */ protected static final byte MAGIC_HIGH = Bytes.short2bytes(MAGIC)[0]; /** * Magic Low 8-15位 :10111011 */ protected static final byte MAGIC_LOW = Bytes.short2bytes(MAGIC)[1]; // message flag. /** * 128 二進制:10000000 */ protected static final byte FLAG_REQUEST = (byte) 0x80; /** * 64 二進制:1000000 */ protected static final byte FLAG_TWOWAY = (byte) 0x40; /** * 32 二進制:100000 */ protected static final byte FLAG_EVENT = (byte) 0x20; /** * 31 二進制:11111 */ protected static final int SERIALIZATION_MASK = 0x1f;
可以看到 MAGIC是個固定的值,用來判斷是不是dubbo協(xié)議的數據包,并且MAGIC_LOW和MAGIC_HIGH分別是MAGIC的低位和高位。其他的屬性用來干嘛后面會講到。
2.encode@Override
public void encode(Channel channel, ChannelBuffer buffer, Object msg) throws IOException {
if (msg instanceof Request) {
// 如果消息是Request類型,對請求消息編碼
encodeRequest(channel, buffer, (Request) msg);
} else if (msg instanceof Response) {
// 如果消息是Response類型,對響應消息編碼
encodeResponse(channel, buffer, (Response) msg);
} else {
// 直接讓父類( Telnet ) 處理,目前是 Telnet 命令的結果。
super.encode(channel, buffer, msg);
}
}
該方法是根據消息的類型來分別進行編碼,分為三種情況:Request類型、Response類型以及其他
3.encodeRequestprotected void encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException {
Serialization serialization = getSerialization(channel);
// header.
// 創(chuàng)建16字節(jié)的字節(jié)數組
byte[] header = new byte[HEADER_LENGTH];
// set magic number.
// 設置前16位數據,也就是設置header[0]和header[1]的數據為Magic High和Magic Low
Bytes.short2bytes(MAGIC, header);
// set request and serialization flag.
// 16-23位為serialization編號,用到或運算10000000|serialization編號,例如serialization編號為11111,則為00011111
header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId());
// 繼續(xù)上面的例子,00011111|1000000 = 01011111
if (req.isTwoWay()) header[2] |= FLAG_TWOWAY;
// 繼續(xù)上面的例子,01011111|100000 = 011 11111 可以看到011代表請求標記、雙向、是事件,這樣就設置了16、17、18位,后面19-23位是Serialization 編號
if (req.isEvent()) header[2] |= FLAG_EVENT;
// set request id.
// 設置32-95位請求id
Bytes.long2bytes(req.getId(), header, 4);
// encode request data.
// // 編碼 `Request.data` 到 Body ,并寫入到 Buffer
int savedWriteIndex = buffer.writerIndex();
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
// 對body數據序列化
ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
// 如果該請求是事件
if (req.isEvent()) {
// 特殊事件編碼
encodeEventData(channel, out, req.getData());
} else {
// 正常請求編碼
encodeRequestData(channel, out, req.getData(), req.getVersion());
}
// 釋放資源
out.flushBuffer();
if (out instanceof Cleanable) {
((Cleanable) out).cleanup();
}
bos.flush();
bos.close();
int len = bos.writtenBytes();
//檢驗消息長度
checkPayload(channel, len);
// 設置96-127位:Body值
Bytes.int2bytes(len, header, 12);
// write
// 把header寫入到buffer
buffer.writerIndex(savedWriteIndex);
buffer.writeBytes(header); // write header.
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
}
該方法是對Request類型的消息進行編碼,仔細閱讀上述我寫的注解,結合協(xié)議頭各個位數的含義,好好品味我舉的例子。享受二進制位運算帶來的快樂,也可以看到前半部分邏輯是對協(xié)議頭的編碼,后面還有對body值的序列化。
4.encodeResponseprotected void encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException {
Serialization serialization = getSerialization(channel);
// header.
// 創(chuàng)建16字節(jié)的字節(jié)數組
byte[] header = new byte[HEADER_LENGTH];
// set magic number.
// 設置前16位數據,也就是設置header[0]和header[1]的數據為Magic High和Magic Low
Bytes.short2bytes(MAGIC, header);
// set request and serialization flag.
// 16-23位為serialization編號,用到或運算10000000|serialization編號,例如serialization編號為11111,則為00011111
header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId());
// 繼續(xù)上面的例子,00011111|1000000 = 01011111
if (req.isTwoWay()) header[2] |= FLAG_TWOWAY;
// 繼續(xù)上面的例子,01011111|100000 = 011 11111 可以看到011代表請求標記、雙向、是事件,這樣就設置了16、17、18位,后面19-23位是Serialization 編號
if (req.isEvent()) header[2] |= FLAG_EVENT;
// set request id.
// 設置32-95位請求id
Bytes.long2bytes(req.getId(), header, 4);
// encode request data.
// // 編碼 `Request.data` 到 Body ,并寫入到 Buffer
int savedWriteIndex = buffer.writerIndex();
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
// 對body數據序列化
ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
// 如果該請求是事件
if (req.isEvent()) {
// 特殊事件編碼
encodeEventData(channel, out, req.getData());
} else {
// 正常請求編碼
encodeRequestData(channel, out, req.getData(), req.getVersion());
}
// 釋放資源
out.flushBuffer();
if (out instanceof Cleanable) {
((Cleanable) out).cleanup();
}
bos.flush();
bos.close();
int len = bos.writtenBytes();
//檢驗消息長度
checkPayload(channel, len);
// 設置96-127位:Body值
Bytes.int2bytes(len, header, 12);
// write
// 把header寫入到buffer
buffer.writerIndex(savedWriteIndex);
buffer.writeBytes(header); // write header.
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
}
protected void encodeResponse(Channel channel, ChannelBuffer buffer, Response res) throws IOException {
int savedWriteIndex = buffer.writerIndex();
try {
Serialization serialization = getSerialization(channel);
// header.
// 創(chuàng)建16字節(jié)大小的字節(jié)數組
byte[] header = new byte[HEADER_LENGTH];
// set magic number.
// 設置前0-15位為魔數
Bytes.short2bytes(MAGIC, header);
// set request and serialization flag.
// 設置響應標志和序列化id
header[2] = serialization.getContentTypeId();
// 如果是心跳事件,則設置第18位為事件
if (res.isHeartbeat()) header[2] |= FLAG_EVENT;
// set response status.
// 設置24-31位為狀態(tài)碼
byte status = res.getStatus();
header[3] = status;
// set request id.
// 設置32-95位為請求id
Bytes.long2bytes(res.getId(), header, 4);
// 寫入數據
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
// 對body進行序列化
ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
// encode response data or error message.
if (status == Response.OK) {
if (res.isHeartbeat()) {
// 對心跳事件編碼
encodeHeartbeatData(channel, out, res.getResult());
} else {
// 對普通響應編碼
encodeResponseData(channel, out, res.getResult(), res.getVersion());
}
} else out.writeUTF(res.getErrorMessage());
// 釋放
out.flushBuffer();
if (out instanceof Cleanable) {
((Cleanable) out).cleanup();
}
bos.flush();
bos.close();
int len = bos.writtenBytes();
checkPayload(channel, len);
Bytes.int2bytes(len, header, 12);
// write
buffer.writerIndex(savedWriteIndex);
buffer.writeBytes(header); // write header.
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
} catch (Throwable t) {
// clear buffer
buffer.writerIndex(savedWriteIndex);
// send error message to Consumer, otherwise, Consumer will wait till timeout.
//如果在寫入數據失敗,則返回響應格式錯誤的返回碼
if (!res.isEvent() && res.getStatus() != Response.BAD_RESPONSE) {
Response r = new Response(res.getId(), res.getVersion());
r.setStatus(Response.BAD_RESPONSE);
if (t instanceof ExceedPayloadLimitException) {
logger.warn(t.getMessage(), t);
try {
r.setErrorMessage(t.getMessage());
// 發(fā)送響應
channel.send(r);
return;
} catch (RemotingException e) {
logger.warn("Failed to send bad_response info back: " + t.getMessage() + ", cause: " + e.getMessage(), e);
}
} else {
// FIXME log error message in Codec and handle in caught() of IoHanndler?
logger.warn("Fail to encode response: " + res + ", send bad_response info instead, cause: " + t.getMessage(), t);
try {
r.setErrorMessage("Failed to send response: " + res + ", cause: " + StringUtils.toString(t));
channel.send(r);
return;
} catch (RemotingException e) {
logger.warn("Failed to send bad_response info back: " + res + ", cause: " + e.getMessage(), e);
}
}
}
// Rethrow exception
if (t instanceof IOException) {
throw (IOException) t;
} else if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else if (t instanceof Error) {
throw (Error) t;
} else {
throw new RuntimeException(t.getMessage(), t);
}
}
}
該方法是對Response類型的消息進行編碼,該方法里面我沒有舉例子演示如何進行編碼,不過過程跟encodeRequest類似。
5.decode@Override
public Object decode(Channel channel, ChannelBuffer buffer) throws IOException {
int readable = buffer.readableBytes();
// 讀取前16字節(jié)的協(xié)議頭數據,如果數據不滿16字節(jié),則讀取全部
byte[] header = new byte[Math.min(readable, HEADER_LENGTH)];
buffer.readBytes(header);
// 解碼
return decode(channel, buffer, readable, header);
}
@Override
protected Object decode(Channel channel, ChannelBuffer buffer, int readable, byte[] header) throws IOException {
// check magic number.
// 核對魔數(該數字固定)
if (readable > 0 && header[0] != MAGIC_HIGH
|| readable > 1 && header[1] != MAGIC_LOW) {
int length = header.length;
// 將 buffer 完全復制到 `header` 數組中
if (header.length < readable) {
header = Bytes.copyOf(header, readable);
buffer.readBytes(header, length, readable - length)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://www.hztianpu.com/yun/72797.html
摘要:可以參考源碼解析二十四遠程調用協(xié)議的八。十六的該類也是用了適配器模式,該類主要的作用就是增加了心跳功能,可以參考源碼解析十遠程通信層的四。二十的可以參考源碼解析十七遠程通信的一。 2.7大揭秘——消費端發(fā)送請求過程 目標:從源碼的角度分析一個服務方法調用經歷怎么樣的磨難以后到達服務端。 前言 前一篇文章講到的是引用服務的過程,引用服務無非就是創(chuàng)建出一個代理。供消費者調用服務的相關方法。...
摘要:而存在的意義就是保證請求或響應對象可在線程池中被解碼,解碼完成后,就會分發(fā)到的。 2.7大揭秘——服務端處理請求過程 目標:從源碼的角度分析服務端接收到請求后的一系列操作,最終把客戶端需要的值返回。 前言 上一篇講到了消費端發(fā)送請求的過程,該篇就要將服務端處理請求的過程。也就是當服務端收到請求數據包后的一系列處理以及如何返回最終結果。我們也知道消費端在發(fā)送請求的時候已經做了編碼,所以我...
摘要:而編碼器是講應用程序的數據轉化為網絡格式,解碼器則是講網絡格式轉化為應用程序,同時具備這兩種功能的單一組件就叫編解碼器。在中是老的編解碼器接口,而是新的編解碼器接口,并且已經用把適配成了。 遠程通訊——開篇 目標:介紹之后解讀遠程通訊模塊的內容如何編排、介紹dubbo-remoting-api中的包結構設計以及最外層的的源碼解析。 前言 服務治理框架中可以大致分為服務通信和服務管理兩個...
摘要:大揭秘異步化改造目標從源碼的角度分析的新特性中對于異步化的改造原理。看源碼解析四十六消費端發(fā)送請求過程講到的十四的,在以前的邏輯會直接在方法中根據配置區(qū)分同步異步單向調用。改為關于可以參考源碼解析十遠程通信層的六。 2.7大揭秘——異步化改造 目標:從源碼的角度分析2.7的新特性中對于異步化的改造原理。 前言 dubbo中提供了很多類型的協(xié)議,關于協(xié)議的系列可以查看下面的文章: du...
摘要:服務暴露過程目標從源碼的角度分析服務暴露過程。導出服務,包含暴露服務到本地,和暴露服務到遠程兩個過程。其中服務暴露的第八步已經沒有了。將泛化調用版本號或者等信息加入獲得服務暴露地址和端口號,利用內數據組裝成。 dubbo服務暴露過程 目標:從源碼的角度分析服務暴露過程。 前言 本來這一篇一個寫異步化改造的內容,但是最近我一直在想,某一部分的優(yōu)化改造該怎么去撰寫才能更加的讓讀者理解。我覺...
閱讀 2660·2023-04-26 00:56
閱讀 2080·2021-10-25 09:46
閱讀 1317·2019-10-29 15:13
閱讀 887·2019-08-30 15:54
閱讀 2273·2019-08-29 17:10
閱讀 2682·2019-08-29 15:43
閱讀 548·2019-08-29 15:28
閱讀 3103·2019-08-29 13:24