<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ### 1 前言 在前一篇文章**《聊聊 TCP 長連接和心跳那些事》**中,我們已經聊過了 TCP 中的 KeepAlive,以及在應用層設計心跳的意義,但卻對長連接心跳的設計方案沒有做詳細地介紹。事實上,設計一個好的心跳機制并不是一件容易的事,就我所熟知的幾個 RPC 框架,它們的心跳機制可以說大相徑庭,這篇文章我將探討一下**如何設計一個優雅的心跳機制,主要從 Dubbo 的現有方案以及一個改進方案來做分析**。 ### [](https://www.cnkirito.moe/heartbeat-design/#2-%E9%A2%84%E5%A4%87%E7%9F%A5%E8%AF%86 "2 預備知識")2 預備知識 因為后續我們將從源碼層面來進行介紹,所以一些服務治理框架的細節還需要提前交代一下,方便大家理解。 #### [](https://www.cnkirito.moe/heartbeat-design/#2-1-%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%A6%82%E4%BD%95%E5%BE%97%E7%9F%A5%E8%AF%B7%E6%B1%82%E5%A4%B1%E8%B4%A5%E4%BA%86%EF%BC%9F "2.1 客戶端如何得知請求失敗了?")2.1 客戶端如何得知請求失敗了? 高性能的 RPC 框架幾乎都會選擇使用 Netty 來作為通信層的組件,非阻塞式通信的高效不需要我做過多的介紹。但也由于非阻塞的特性,導致其發送數據和接收數據是一個異步的過程,所以當存在服務端異常、網絡問題時,客戶端接是接收不到響應的,那我們如何判斷一次 RPC 調用是失敗的呢? 誤區一:Dubbo 調用不是默認同步的嗎? Dubbo 在通信層是異步的,呈現給使用者同步的錯覺是因為內部做了阻塞等待,實現了異步轉同步。 誤區二:`Channel.writeAndFlush`會返回一個`channelFuture`,我只需要判斷`channelFuture.isSuccess`就可以判斷請求是否成功了。 注意,writeAndFlush 成功并不代表對端接受到了請求,返回值為 true 只能保證寫入網絡緩沖區成功,并不代表發送成功。 避開上述兩個誤區,我們再來回到本小節的標題:客戶端如何得知請求失敗?**正確的邏輯應當是以客戶端接收到失敗響應為判斷依據**。等等,前面不還在說在失敗的場景中,服務端是不會返回響應的嗎?沒錯,既然服務端不會返回,那就只能客戶端自己造了。 一個常見的設計是:客戶端發起一個 RPC 請求,會設置一個超時時間`client_timeout`,發起調用的同時,客戶端會開啟一個延遲`client_timeout`的定時器 * 接收到正常響應時,移除該定時器。 * 定時器倒計時完畢,還沒有被移除,則認為請求超時,構造一個失敗的響應傳遞給客戶端。 Dubbo 中的超時判定邏輯: ~~~ public static DefaultFuture newFuture(Channel channel, Request request, int timeout) { final DefaultFuture future = new DefaultFuture(channel, request, timeout); // timeout check timeoutCheck(future); return future; } private static void timeoutCheck(DefaultFuture future) { TimeoutCheckTask task = new TimeoutCheckTask(future); TIME_OUT_TIMER.newTimeout(task, future.getTimeout(), TimeUnit.MILLISECONDS); } private static class TimeoutCheckTask implements TimerTask { private DefaultFuture future; TimeoutCheckTask(DefaultFuture future) { this.future = future; } @Override public void run(Timeout timeout) { if (future == null || future.isDone()) { return; } // create exception response. Response timeoutResponse = new Response(future.getId()); // set timeout status. timeoutResponse.setStatus(future.isSent() ? Response.SERVER_TIMEOUT : Response.CLIENT_TIMEOUT); timeoutResponse.setErrorMessage(future.getTimeoutMessage(true)); // handle response. DefaultFuture.received(future.getChannel(), timeoutResponse); } } ~~~ 主要邏輯涉及的類:`DubboInvoker`,`HeaderExchangeChannel`,`DefaultFuture`,通過上述代碼,我們可以得知一個細節,無論是何種調用,都會經過這個定時器的檢測,**超時即調用失敗,一次 RPC 調用的失敗,必須以客戶端收到失敗響應為準**。 #### [](https://www.cnkirito.moe/heartbeat-design/#2-2-%E5%BF%83%E8%B7%B3%E6%A3%80%E6%B5%8B%E9%9C%80%E8%A6%81%E5%AE%B9%E9%94%99 "2.2 心跳檢測需要容錯")2.2 心跳檢測需要容錯 網絡通信永遠要考慮到最壞的情況,一次心跳失敗,不能認定為連接不通,多次心跳失敗,才能采取相應的措施。 #### [](https://www.cnkirito.moe/heartbeat-design/#2-3-%E5%BF%83%E8%B7%B3%E6%A3%80%E6%B5%8B%E4%B8%8D%E9%9C%80%E8%A6%81%E5%BF%99%E6%A3%80%E6%B5%8B "2.3 心跳檢測不需要忙檢測")2.3 心跳檢測不需要忙檢測 忙檢測的對立面是空閑檢測,我們做心跳的初衷,是為了保證連接的可用性,以保證及時采取斷連,重連等措施。如果一條通道上有頻繁的 RPC 調用正在進行,我們不應該為通道增加負擔去發送心跳包。**心跳扮演的角色應當是晴天收傘,雨天送傘。** ### [](https://www.cnkirito.moe/heartbeat-design/#3-Dubbo-%E7%8E%B0%E6%9C%89%E6%96%B9%E6%A1%88 "3 Dubbo 現有方案")3 Dubbo 現有方案 > 本文的源碼對應 Dubbo 2.7.x 版本,在 apache 孵化的該版本中,心跳機制得到了增強。 介紹完了一些基礎的概念,我們再來看看 Dubbo 是如何設計應用層心跳的。Dubbo 的心跳是雙向心跳,客戶端會給服務端發送心跳,反之,服務端也會向客戶端發送心跳。 #### [](https://www.cnkirito.moe/heartbeat-design/#3-1-%E8%BF%9E%E6%8E%A5%E5%BB%BA%E7%AB%8B%E6%97%B6%E5%88%9B%E5%BB%BA%E5%AE%9A%E6%97%B6%E5%99%A8 "3.1 連接建立時創建定時器")3.1 連接建立時創建定時器 ~~~ public class HeaderExchangeClient implements ExchangeClient { private int heartbeat; private int heartbeatTimeout; private HashedWheelTimer heartbeatTimer; public HeaderExchangeClient(Client client, boolean needHeartbeat) { this.client = client; this.channel = new HeaderExchangeChannel(client); 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 (needHeartbeat) { <1> long tickDuration = calculateLeastDuration(heartbeat); heartbeatTimer = new HashedWheelTimer(new NamedThreadFactory("dubbo-client-heartbeat", true), tickDuration, TimeUnit.MILLISECONDS, Constants.TICKS_PER_WHEEL); <2> startHeartbeatTimer(); } } } ~~~ **默認開啟心跳檢測的定時器** **創建了一個`HashedWheelTimer`開啟心跳檢測**,這是 Netty 所提供的一個經典的時間輪定時器實現,至于它和 jdk 的實現有何不同,不了解的同學也可以關注下,我就不拓展了。 不僅`HeaderExchangeClient`客戶端開起了定時器,`HeaderExchangeServer`服務端同樣開起了定時器,由于服務端的邏輯和客戶端幾乎一致,所以后續我并不會重復粘貼服務端的代碼。 > Dubbo 在早期版本版本中使用的是 schedule 方案,在 2.7.x 中替換成了 HashWheelTimer。 #### [](https://www.cnkirito.moe/heartbeat-design/#3-2-%E5%BC%80%E5%90%AF%E4%B8%A4%E4%B8%AA%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1 "3.2 開啟兩個定時任務")3.2 開啟兩個定時任務 ~~~ private void startHeartbeatTimer() { long heartbeatTick = calculateLeastDuration(heartbeat); long heartbeatTimeoutTick = calculateLeastDuration(heartbeatTimeout); HeartbeatTimerTask heartBeatTimerTask = new HeartbeatTimerTask(cp, heartbeatTick, heartbeat); <1> ReconnectTimerTask reconnectTimerTask = new ReconnectTimerTask(cp, heartbeatTimeoutTick, heartbeatTimeout); <2> heartbeatTimer.newTimeout(heartBeatTimerTask, heartbeatTick, TimeUnit.MILLISECONDS); heartbeatTimer.newTimeout(reconnectTimerTask, heartbeatTimeoutTick, TimeUnit.MILLISECONDS); } ~~~ Dubbo 在`startHeartbeatTimer`方法中主要開啟了兩個定時器:`HeartbeatTimerTask`,`ReconnectTimerTask` `HeartbeatTimerTask`主要用于定時發送心跳請求 `ReconnectTimerTask`主要用于心跳失敗之后處理重連,斷連的邏輯 至于方法中的其他代碼,其實也是本文的重要分析內容,先容我賣個關子,后面再來看追溯。 #### [](https://www.cnkirito.moe/heartbeat-design/#3-3-%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1%E4%B8%80%EF%BC%9A%E5%8F%91%E9%80%81%E5%BF%83%E8%B7%B3%E8%AF%B7%E6%B1%82 "3.3 定時任務一:發送心跳請求")3.3 定時任務一:發送心跳請求 詳細解析下心跳檢測定時任務的邏輯`HeartbeatTimerTask#doTask`: ~~~ protected void doTask(Channel channel) { Long lastRead = lastRead(channel); Long lastWrite = lastWrite(channel); if ((lastRead != null && now() - lastRead > heartbeat) || (lastWrite != null && now() - lastWrite > heartbeat)) { Request req = new Request(); req.setVersion(Version.getProtocolVersion()); req.setTwoWay(true); req.setEvent(Request.HEARTBEAT_EVENT); channel.send(req); } } } ~~~ 前面已經介紹過,**Dubbo 采取的是是雙向心跳設計**,即服務端會向客戶端發送心跳,客戶端也會向服務端發送心跳,接收的一方更新 lastRead 字段,發送的一方更新 lastWrite 字段,超過心跳間隙的時間,便發送心跳請求給對端。這里的 lastRead/lastWrite 同樣會被同一個通道上的普通調用更新,通過更新這兩個字段,實現了只在連接空閑時才會真正發送空閑報文的機制,符合我們一開始科普的做法。 > 注意:不僅僅心跳請求會更新 lastRead 和 lastWrite,普通請求也會。這對應了我們預備知識中的空閑檢測機制。 #### [](https://www.cnkirito.moe/heartbeat-design/#3-4-%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1%E4%BA%8C%EF%BC%9A%E5%A4%84%E7%90%86%E9%87%8D%E8%BF%9E%E5%92%8C%E6%96%AD%E8%BF%9E "3.4 定時任務二:處理重連和斷連")3.4 定時任務二:處理重連和斷連 繼續研究下重連和斷連定時器都實現了什么`ReconnectTimerTask#doTask`。 ~~~ protected void doTask(Channel channel) { Long lastRead = lastRead(channel); Long now = now(); if (lastRead != null && now - lastRead > heartbeatTimeout) { if (channel instanceof Client) { ((Client) channel).reconnect(); } else { channel.close(); } } } ~~~ 第二個定時器則負責根據客戶端、服務端類型來對連接做不同的處理,當超過設置的心跳總時間之后,客戶端選擇的是重新連接,服務端則是選擇直接斷開連接。這樣的考慮是合理的,客戶端調用是強依賴可用連接的,而服務端可以等待客戶端重新建立連接。 > 細心的朋友會發現,這個類被命名為 ReconnectTimerTask 是不太準確的,因為它處理的是重連和斷連兩個邏輯。 #### [](https://www.cnkirito.moe/heartbeat-design/#3-5-%E5%AE%9A%E6%97%B6%E4%B8%8D%E7%B2%BE%E7%A1%AE%E7%9A%84%E9%97%AE%E9%A2%98 "3.5 定時不精確的問題")3.5 定時不精確的問題 在 Dubbo 的 issue 中曾經有人反饋過定時不精確的問題,我們來看看是怎么一回事。 Dubbo 中默認的心跳周期是 60s,設想如下的時序: * 第 0 秒,心跳檢測發現連接活躍 * 第 1 秒,連接實際斷開 * 第 60 秒,心跳檢測發現連接不活躍 由于**時間窗口的問題,死鏈不能夠被及時檢測出來,最壞情況為一個心跳周期**。 為了解決上述問題,我們再倒回去看一下上面的`startHeartbeatTimer()`方法 ``` long heartbeatTick = calculateLeastDuration(heartbeat); long heartbeatTimeoutTick = calculateLeastDuration(heartbeatTimeout); heartbeatTimer.newTimeout(reconnectTimerTask, heartbeatTimeoutTick, TimeUnit.MILLISECONDS); ``` 其中`calculateLeastDuration`根據心跳時間和超時時間分別計算出了一個 tick 時間,實際上就是將兩個變量除以了 3,使得他們的值縮小,并傳入了`HashedWheelTimer`的第二個參數之中 ``` heartbeatTimer.newTimeout(heartBeatTimerTask, heartbeatTick, TimeUnit.MILLISECONDS); heartbeatTimer.newTimeout(reconnectTimerTask, heartbeatTimeoutTick, TimeUnit.MILLISECONDS); ``` tick 的含義便是定時任務執行的頻率。這樣,通過減少檢測間隔時間,增大了及時發現死鏈的概率,原先的最壞情況是 60s,如今變成了 20s。這個頻率依舊可以加快,但需要考慮資源消耗的問題。 > 定時不準確的問題出現在 Dubbo 的兩個定時任務之中,所以都做了 tick 操作。事實上,所有的定時檢測的邏輯都存在類似的問題。 #### [](https://www.cnkirito.moe/heartbeat-design/#3-6-Dubbo-%E5%BF%83%E8%B7%B3%E6%80%BB%E7%BB%93 "3.6 Dubbo 心跳總結")3.6 Dubbo 心跳總結 Dubbo 對于建立的每一個連接,同時在客戶端和服務端開啟了 2 個定時器,一個用于定時發送心跳,一個用于定時重連、斷連,執行的頻率均為各自檢測周期的 1/3。定時發送心跳的任務負責在連接空閑時,向對端發送心跳包。定時重連、斷連的任務負責檢測 lastRead 是否在超時周期內仍未被更新,如果判定為超時,客戶端處理的邏輯是重連,服務端則采取斷連的措施。 先不急著判斷這個方案好不好,再來看看改進方案是怎么設計的。 ### [](https://www.cnkirito.moe/heartbeat-design/#4-Dubbo-%E6%94%B9%E8%BF%9B%E6%96%B9%E6%A1%88 "4 Dubbo 改進方案")4 Dubbo 改進方案 實際上我們可以更優雅地實現心跳機制,本小節開始,我將介紹一個新的心跳機制。 #### [](https://www.cnkirito.moe/heartbeat-design/#4-1-IdleStateHandler-%E4%BB%8B%E7%BB%8D "4.1 IdleStateHandler 介紹")4.1 IdleStateHandler 介紹 Netty 對空閑連接的檢測提供了天然的支持,使用`IdleStateHandler`可以很方便的實現空閑檢測邏輯。 ``` public IdleStateHandler( long readerIdleTime, long writerIdleTime, long allIdleTime, TimeUnit unit){} ``` * readerIdleTime:讀超時時間 * writerIdleTime:寫超時時間 * allIdleTime:所有類型的超時時間 `IdleStateHandler`這個類會根據設置的超時參數,循環檢測 channelRead 和 write 方法多久沒有被調用。當在 pipeline 中加入`IdleSateHandler`之后,可以在此 pipeline 的任意 Handler 的`userEventTriggered`方法之中檢測`IdleStateEvent`事件, ``` @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof IdleStateEvent) { //do something } ctx.fireUserEventTriggered(evt); } ``` 為什么需要介紹`IdleStateHandler`呢?其實提到它的空閑檢測 + 定時的時候,大家應該能夠想到了,這不天然是給心跳機制服務的嗎?很多服務治理框架都選擇了借助`IdleStateHandler`來實現心跳。 > IdleStateHandler 內部使用了 eventLoop.schedule(task) 的方式來實現定時任務,使用 eventLoop 線程的好處是還同時保證了**線程安全**,這里是一個小細節。 #### [](https://www.cnkirito.moe/heartbeat-design/#4-2-%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%92%8C%E6%9C%8D%E5%8A%A1%E7%AB%AF%E9%85%8D%E7%BD%AE "4.2 客戶端和服務端配置")4.2 客戶端和服務端配置 首先是將`IdleStateHandler`加入 pipeline 中。 **客戶端:** ``` bootstrap.handler(new ChannelInitializer&lt;NioSocketChannel&gt;() { @Override protected void initChannel(NioSocketChannel ch) throws Exception { ch.pipeline().addLast("clientIdleHandler", new IdleStateHandler(60, 0, 0)); } }); ``` **服務端:** ``` serverBootstrap.childHandler(new ChannelInitializer&lt;NioSocketChannel&gt;() { @Override protected void initChannel(NioSocketChannel ch) throws Exception { ch.pipeline().addLast("serverIdleHandler",new IdleStateHandler(0, 0, 200)); } } ``` 客戶端配置了 read 超時為 60s,服務端配置了 write/read 超時為 200s,先在此埋下兩個伏筆: 1. 為什么客戶端和服務端配置的超時時間不一致? 2. 為什么客戶端檢測的是讀超時,而服務端檢測的是讀寫超時? #### [](https://www.cnkirito.moe/heartbeat-design/#4-3-%E7%A9%BA%E9%97%B2%E8%B6%85%E6%97%B6%E9%80%BB%E8%BE%91-%E2%80%94-%E5%AE%A2%E6%88%B7%E7%AB%AF "4.3 空閑超時邏輯 — 客戶端")4.3 空閑超時邏輯 — 客戶端 對于空閑超時的處理邏輯,客戶端和服務端是不同的。首先來看客戶端 ``` @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof IdleStateEvent) { // send heartbeat sendHeartBeat(); } else { super.userEventTriggered(ctx, evt); } } ``` 檢測到空閑超時之后,采取的行為是向服務端發送心跳包,具體是如何發送,以及處理響應的呢?偽代碼如下 ``` public void sendHeartBeat() { Invocation invocation = new Invocation(); invocation.setInvocationType(InvocationType.HEART_BEAT); channel.writeAndFlush(invocation).addListener(new CallbackFuture() { @Override public void callback(Future future) { RPCResult result = future.get(); // 超時 或者 寫失敗 if (result.isError()) { channel.addFailedHeartBeatTimes(); if (channel.getFailedHeartBeatTimes() &gt;= channel.getMaxHeartBeatFailedTimes()) { channel.reconnect(); } } else { channel.clearHeartBeatFailedTimes(); } } }); } ``` 行為并不復雜,構造一個心跳包發送到服務端,接受響應結果 * 響應成功,清空請求失敗標記 * 響應失敗,心跳失敗標記 +1,如果超過配置的失敗次數,則重新連接 > 不僅僅是心跳,普通請求返回成功響應時也會清空標記 #### [](https://www.cnkirito.moe/heartbeat-design/#4-4-%E7%A9%BA%E9%97%B2%E8%B6%85%E6%97%B6%E9%80%BB%E8%BE%91-%E2%80%94-%E6%9C%8D%E5%8A%A1%E7%AB%AF "4.4 空閑超時邏輯 — 服務端")4.4 空閑超時邏輯 — 服務端 ``` @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof IdleStateEvent) { channel.close(); } else { super.userEventTriggered(ctx, evt); } } ``` 服務端處理空閑連接的方式非常簡單粗暴,直接關閉連接。 #### [](https://www.cnkirito.moe/heartbeat-design/#4-5-%E6%94%B9%E8%BF%9B%E6%96%B9%E6%A1%88%E5%BF%83%E8%B7%B3%E6%80%BB%E7%BB%93 "4.5 改進方案心跳總結")4.5 改進方案心跳總結 1. 為什么客戶端和服務端配置的超時時間不一致? 因為客戶端有重試邏輯,不斷發送心跳失敗 n 次之后,才認為是連接斷開;而服務端是直接斷開,留給服務端時間得長一點。60 \* 3 < 200 還說明了一個問題,雙方都擁有斷開連接的能力,但連接的創建是由客戶端主動發起的,那么客戶端也更有權利去主動斷開連接。 2. 為什么客戶端檢測的是讀超時,而服務端檢測的是讀寫超時? 這其實是一個心跳的共識了,仔細思考一下,定時邏輯是由客戶端發起的,所以整個鏈路中不通的情況只有可能是:服務端接收,服務端發送,客戶端接收。也就是說,只有客戶端的 pong,服務端的 ping,pong 的檢測是有意義的。 > 主動追求別人的是你,主動說分手的也是你。 利用`IdleStateHandler`實現心跳機制可以說是十分優雅的,借助 Netty 提供的空閑檢測機制,利用客戶端維護單向心跳,在收到 3 次心跳失敗響應之后,客戶端斷開連接,交由異步線程重連,本質還是表現為客戶端重連。服務端在連接空閑較長時間后,主動斷開連接,以避免無謂的資源浪費。 ### [](https://www.cnkirito.moe/heartbeat-design/#5-%E5%BF%83%E8%B7%B3%E8%AE%BE%E8%AE%A1%E6%96%B9%E6%A1%88%E5%AF%B9%E6%AF%94 "5 心跳設計方案對比")5 心跳設計方案對比 | | Dubbo 現有方案 | Dubbo 改進方案 | | --- | --- | --- | | **主體設計** | 開啟兩個定時器 | 借助 IdleStateHandler,底層使用 schedule | | **心跳方向** | 雙向 | 單向(客戶端 -> 服務端) | | **心跳失敗判定方式** | 心跳成功更新標記,借助定時器定時掃描標記,如果超過心跳超時周期未更新標記,認為心跳失敗。 | 通過判斷心跳響應是否失敗,超過失敗次數,認為心跳失敗 | | **擴展性** | Dubbo 存在 mina,grizzy 等其他通信層實現,自定義定時器很容易適配多種擴展 | 多通信層各自實現心跳,不做心跳的抽象 | | **設計性** | 編碼復雜度高,代碼量大,方案復雜,不易維護 | 編碼量小,可維護性強 | 私下請教過**美團點評的長連接負責人:俞超(閃電俠)**,美點使用的心跳方案和 Dubbo 改進方案幾乎一致,可以說該方案是標準實現了。 ### [](https://www.cnkirito.moe/heartbeat-design/#6-Dubbo-%E5%AE%9E%E9%99%85%E6%94%B9%E5%8A%A8%E7%82%B9%E5%BB%BA%E8%AE%AE "6 Dubbo 實際改動點建議")6 Dubbo 實際改動點建議 鑒于 Dubbo 存在一些其他通信層的實現,所以可以保留現有的定時發送心跳的邏輯。 * **建議改動點一:** 雙向心跳的設計是不必要的,兼容現有的邏輯,可以讓客戶端在連接空閑時發送單向心跳,服務端定時檢測連接可用性。定時時間盡量保證:客戶端超時時間 \* 3 ≈ 服務端超時時間 * **建議改動點二:** 去除處理重連和斷連的定時任務,Dubbo 可以判斷心跳請求是否響應失敗,可以借鑒改進方案的設計,在連接級別維護一個心跳失敗次數的標記,任意響應成功,清除標記;連續心跳失敗 n 次,客戶端發起重連。這樣可以減少一個不必要的定時器,任何輪詢的方式,都是不優雅的。 最后再聊聊可擴展性這個話題。其實我是建議把定時器交給更加底層的 Netty 去做,也就是完全使用`IdleStateHandler`,其他通信層組件各自實現自己的空閑檢測邏輯,但是 Dubbo 中 mina,grizzy 的兼容問題囿住了我的拳腳,但試問一下,如今的 2019 年,又有多少人在使用 mina 和 grizzy?因為一些不太可能用的特性,而限制了主流用法的優化,這肯定不是什么好事。抽象,功能,可擴展性并不是越多越好,開源產品的人力資源是有限的,框架使用者的理解能力也是有限的,能解決大多數人問題的設計,才是好的設計。哎,誰讓我不會 mina,grizzy,還懶得去學呢 \[攤手\]。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看