時(shí)鐘輪在 RPC 中的應(yīng)用

今天這篇文章介紹一下RPC中如何使用時(shí)鐘輪實(shí)現(xiàn)定時(shí)任務(wù),比如調(diào)用端的超時(shí)處理、定時(shí)心跳....

定時(shí)任務(wù)帶來(lái)了什么問(wèn)題?

在講解時(shí)鐘輪之前,我們先來(lái)聊聊定時(shí)任務(wù)。相信你在開(kāi)發(fā)的過(guò)程中,很多場(chǎng)景都會(huì)使用到定時(shí)任務(wù),在 RPC 框架中也有很多地方會(huì)使用到它。就以調(diào)用端請(qǐng)求超時(shí)的處理邏輯為例,下面我們看一下 RPC 框架是如果處理超時(shí)請(qǐng)求的。

在講解 Future 的時(shí)候說(shuō)過(guò):無(wú)論是同步調(diào)用還是異步調(diào)用,調(diào)用端內(nèi)部實(shí)行的都是異步,而調(diào)用端在向服務(wù)端發(fā)送消息之前會(huì)創(chuàng)建一個(gè) Future,并存儲(chǔ)這個(gè)消息標(biāo)識(shí)與這個(gè) Future 的映射,當(dāng)服務(wù)端收到消息并且處理完畢后向調(diào)用端發(fā)送響應(yīng)消息,調(diào)用端在接收到消息后會(huì)根據(jù)消息的唯一標(biāo)識(shí)找到這個(gè) Future,并將結(jié)果注入給這個(gè) Future。

那在這個(gè)過(guò)程中,如果服務(wù)端沒(méi)有及時(shí)響應(yīng)消息給調(diào)用端呢?調(diào)用端該如何處理超時(shí)的請(qǐng)求?

沒(méi)錯(cuò),就是可以利用定時(shí)任務(wù)。每次創(chuàng)建一個(gè) Future,我們都記錄這個(gè) Future 的創(chuàng)建時(shí)間與這個(gè) Future 的超時(shí)時(shí)間,并且有一個(gè)定時(shí)任務(wù)進(jìn)行檢測(cè),當(dāng)這個(gè) Future 到達(dá)超時(shí)時(shí)間并且沒(méi)有被處理時(shí),我們就對(duì)這個(gè) Future 執(zhí)行超時(shí)邏輯。

那定時(shí)任務(wù)該如何實(shí)現(xiàn)呢?

有種實(shí)現(xiàn)方式是這樣的,也是最簡(jiǎn)單的一種。每創(chuàng)建一個(gè) Future 我們都啟動(dòng)一個(gè)線(xiàn)程,之后 sleep,到達(dá)超時(shí)時(shí)間就觸發(fā)請(qǐng)求超時(shí)的處理邏輯。

這種方式吧,確實(shí)簡(jiǎn)單,在某些場(chǎng)景下也是可以使用的,但弊端也是顯而易見(jiàn)的。就像剛才我講的那個(gè) Future 超時(shí)處理的例子,如果我們面臨的是高并發(fā)的請(qǐng)求,單機(jī)每秒發(fā)送數(shù)萬(wàn)次請(qǐng)求,請(qǐng)求超時(shí)時(shí)間設(shè)置的是 5 秒,那我們要?jiǎng)?chuàng)建多少個(gè)線(xiàn)程用來(lái)執(zhí)行超時(shí)任務(wù)呢?超過(guò) 10 萬(wàn)個(gè)線(xiàn)程,這個(gè)數(shù)字真的夠嚇人了。

別急,我們還有另一種實(shí)現(xiàn)方式。我們可以用一個(gè)線(xiàn)程來(lái)處理所有的定時(shí)任務(wù),還以剛才那個(gè) Future 超時(shí)處理的例子為例。假設(shè)我們要啟動(dòng)一個(gè)線(xiàn)程,這個(gè)線(xiàn)程每隔 100 毫秒會(huì)掃描一遍所有的處理 Future 超時(shí)的任務(wù),當(dāng)發(fā)現(xiàn)一個(gè) Future 超時(shí)了,我們就執(zhí)行這個(gè)任務(wù),對(duì)這個(gè) Future 執(zhí)行超時(shí)邏輯。

這種方式我們用得最多,它也解決了第一種方式線(xiàn)程過(guò)多的問(wèn)題,但其實(shí)它也有明顯的弊端。

同樣是高并發(fā)的請(qǐng)求,那么掃描任務(wù)的線(xiàn)程每隔 100 毫秒要掃描多少個(gè)定時(shí)任務(wù)呢?如果調(diào)用端剛好在 1 秒內(nèi)發(fā)送了 1 萬(wàn)次請(qǐng)求,這 1 萬(wàn)次請(qǐng)求要在 5 秒后才會(huì)超時(shí),那么那個(gè)掃描的線(xiàn)程在這個(gè) 5 秒內(nèi)就會(huì)不停地對(duì)這 1 萬(wàn)個(gè)任務(wù)進(jìn)行掃描遍歷,要額外掃描 40 多次(每 100 毫秒掃描一次,5 秒內(nèi)要掃描近 50 次),很浪費(fèi) CPU。

在我們使用定時(shí)任務(wù)時(shí),它所帶來(lái)的問(wèn)題,就是讓 CPU 做了很多額外的輪詢(xún)遍歷操作,浪費(fèi)了 CPU,這種現(xiàn)象在定時(shí)任務(wù)非常多的情況下,尤其明顯。

什么是時(shí)鐘輪?

這個(gè)問(wèn)題也不難解決,我們只要找到一種方式,減少額外的掃描操作就行了。比如我的一批定時(shí)任務(wù)是 5 秒之后執(zhí)行,我在 4.9 秒之后才開(kāi)始掃描這批定時(shí)任務(wù),這樣就大大地節(jié)省了 CPU。這時(shí)我們就可以利用時(shí)鐘輪的機(jī)制了。

我們先來(lái)看下我們生活中用到的時(shí)鐘。

很熟悉了吧,時(shí)鐘有時(shí)針、分針和秒針,秒針跳動(dòng)一周之后,也就是跳動(dòng) 60 個(gè)刻度之后,分針跳動(dòng) 1 次,分針跳動(dòng) 60 個(gè)刻度,時(shí)針走動(dòng)一步。

而時(shí)鐘輪的實(shí)現(xiàn)原理就是參考了生活中的時(shí)鐘跳動(dòng)的原理。

在時(shí)鐘輪機(jī)制中,有時(shí)間槽和時(shí)鐘輪的概念,時(shí)間槽就相當(dāng)于時(shí)鐘的刻度,而時(shí)鐘輪就相當(dāng)于秒針與分針等跳動(dòng)的一個(gè)周期,我們會(huì)將每個(gè)任務(wù)放到對(duì)應(yīng)的時(shí)間槽位上。

時(shí)鐘輪的運(yùn)行機(jī)制和生活中的時(shí)鐘也是一樣的,每隔固定的單位時(shí)間,就會(huì)從一個(gè)時(shí)間槽位跳到下一個(gè)時(shí)間槽位,這就相當(dāng)于我們的秒針跳動(dòng)了一次;

時(shí)鐘輪可以分為多層,下一層時(shí)鐘輪中每個(gè)槽位的單位時(shí)間是當(dāng)前時(shí)間輪整個(gè)周期的時(shí)間,這就相當(dāng)于 1 分鐘等于 60 秒鐘;當(dāng)時(shí)鐘輪將一個(gè)周期的所有槽位都跳動(dòng)完之后,就會(huì)從下一層時(shí)鐘輪中取出一個(gè)槽位的任務(wù),重新分布到當(dāng)前的時(shí)鐘輪中,當(dāng)前時(shí)鐘輪則從第 0 槽位從新開(kāi)始跳動(dòng),這就相當(dāng)于下一分鐘的第 1 秒。

為了方便你了解時(shí)鐘輪的運(yùn)行機(jī)制,我們用一個(gè)場(chǎng)景例子來(lái)模擬下,一起看下這個(gè)場(chǎng)景。

假設(shè)我們的時(shí)鐘輪有 10 個(gè)槽位,而時(shí)鐘輪一輪的周期是 1 秒,那么我們每個(gè)槽位的單位時(shí)間就是 100 毫秒,而下一層時(shí)間輪的周期就是 10 秒,每個(gè)槽位的單位時(shí)間也就是 1 秒,并且當(dāng)前的時(shí)鐘輪剛初始化完成,也就是第 0 跳,當(dāng)前在第 0 個(gè)槽位。

好,現(xiàn)在我們有 3 個(gè)任務(wù),分別是任務(wù) A(90 毫秒之后執(zhí)行)、任務(wù) B(610 毫秒之后執(zhí)行)與任務(wù) C(1 秒 610 毫秒之后執(zhí)行),我們將這 3 個(gè)任務(wù)添加到時(shí)鐘輪中,任務(wù) A 被放到第 0 槽位,任務(wù) B 被放到第 6 槽位,任務(wù) C 被放到下一層時(shí)間輪的第 1 槽位,如下面這張圖所示。

當(dāng)任務(wù) A 剛被放到時(shí)鐘輪,就被即刻執(zhí)行了,因?yàn)樗环诺搅说?0 槽位,而當(dāng)前時(shí)間輪正好跳到第 0 槽位(實(shí)際上還沒(méi)開(kāi)始跳動(dòng),狀態(tài)為第 0 跳);600 毫秒之后,時(shí)間輪已經(jīng)進(jìn)行了 6 跳,當(dāng)前槽位是第 6 槽位,第 6 槽位所有的任務(wù)都被取出執(zhí)行;1 秒鐘之后,當(dāng)前時(shí)鐘輪的第 9 跳已經(jīng)跳完,從新開(kāi)始了第 0 跳,這時(shí)下一層時(shí)鐘輪從第 0 跳跳到了第 1 跳,將第 1 槽位的任務(wù)取出,分布到當(dāng)前的時(shí)鐘輪中,這時(shí)任務(wù) C 從下一層時(shí)鐘輪中取出并放到當(dāng)前時(shí)鐘輪的第 6 槽位;1 秒 600 毫秒之后,任務(wù) C 被執(zhí)行。

看完了這個(gè)場(chǎng)景,相信你對(duì)時(shí)鐘輪的機(jī)制已經(jīng)有所了解了。在這個(gè)例子中,時(shí)鐘輪的掃描周期仍是 100 毫秒,但是其中的任務(wù)并沒(méi)有被過(guò)多的重復(fù)掃描,它完美地解決了 CPU 浪費(fèi)的問(wèn)題。

這個(gè)機(jī)制其實(shí)不難理解,但實(shí)現(xiàn)起來(lái)還是很有難度的,其中要注意的問(wèn)題也很多.

時(shí)鐘輪在 RPC 中的應(yīng)用

通過(guò)剛才對(duì)時(shí)鐘輪的講解,相信你可以看出,它就是用來(lái)執(zhí)行定時(shí)任務(wù)的,可以說(shuō)在 RPC 框架中只要涉及到定時(shí)相關(guān)的操作,我們就可以使用時(shí)鐘輪。

那么 RPC 框架在哪些功能實(shí)現(xiàn)中會(huì)用到它呢?

剛才我舉例講到的調(diào)用端請(qǐng)求超時(shí)處理,這里我們就可以應(yīng)用到時(shí)鐘輪,我們每發(fā)一次請(qǐng)求,都創(chuàng)建一個(gè)處理請(qǐng)求超時(shí)的定時(shí)任務(wù)放到時(shí)鐘輪里,在高并發(fā)、高訪(fǎng)問(wèn)量的情況下,時(shí)鐘輪每次只輪詢(xún)一個(gè)時(shí)間槽位中的任務(wù),這樣會(huì)節(jié)省大量的 CPU。

調(diào)用端與服務(wù)端啟動(dòng)超時(shí)也可以應(yīng)用到時(shí)鐘輪,以調(diào)用端為例,假設(shè)我們想要讓?xiě)?yīng)用可以快速地部署,例如 1 分鐘內(nèi)啟動(dòng),如果超過(guò) 1 分鐘則啟動(dòng)失敗。我們可以在調(diào)用端啟動(dòng)時(shí)創(chuàng)建一個(gè)處理啟動(dòng)超時(shí)的定時(shí)任務(wù),放到時(shí)鐘輪里。

除此之外,你還能想到 RPC 框架在哪些地方可以應(yīng)用到時(shí)鐘輪嗎?還有定時(shí)心跳。RPC 框架調(diào)用端定時(shí)向服務(wù)端發(fā)送心跳,來(lái)維護(hù)連接狀態(tài),我們可以將心跳的邏輯封裝為一個(gè)心跳任務(wù),放到時(shí)鐘輪里。

這時(shí)你可能會(huì)有一個(gè)疑問(wèn),心跳是要定時(shí)重復(fù)執(zhí)行的,而時(shí)鐘輪中的任務(wù)執(zhí)行一遍就被移除了,對(duì)于這種需要重復(fù)執(zhí)行的定時(shí)任務(wù)我們?cè)撊绾翁幚砟??在定時(shí)任務(wù)的執(zhí)行邏輯的最后,我們可以重設(shè)這個(gè)任務(wù)的執(zhí)行時(shí)間,把它重新丟回到時(shí)鐘輪里。

總結(jié)

今天我們主要講解了時(shí)鐘輪的機(jī)制,以及時(shí)鐘輪在 RPC 框架中的應(yīng)用

這個(gè)機(jī)制很好地解決了定時(shí)任務(wù)中,因每個(gè)任務(wù)都創(chuàng)建一個(gè)線(xiàn)程,導(dǎo)致的創(chuàng)建過(guò)多線(xiàn)程的問(wèn)題,以及一個(gè)線(xiàn)程掃描所有的定時(shí)任務(wù),讓 CPU 做了很多額外的輪詢(xún)遍歷操作而浪費(fèi) CPU 的問(wèn)題。

時(shí)鐘輪的實(shí)現(xiàn)機(jī)制就是模擬現(xiàn)實(shí)生活中的時(shí)鐘,將每個(gè)定時(shí)任務(wù)放到對(duì)應(yīng)的時(shí)間槽位上,這樣可以減少掃描任務(wù)時(shí)對(duì)其它時(shí)間槽位定時(shí)任務(wù)的額外遍歷操作。

在時(shí)間輪的使用中,有些問(wèn)題需要你額外注意:

  • 時(shí)間槽位的單位時(shí)間越短,時(shí)間輪觸發(fā)任務(wù)的時(shí)間就越精確。例如時(shí)間槽位的單位時(shí)間是 10 毫秒,那么執(zhí)行定時(shí)任務(wù)的時(shí)間誤差就在 10 毫秒內(nèi),如果是 100 毫秒,那么誤差就在 100 毫秒內(nèi)
  • 時(shí)間輪的槽位越多,那么一個(gè)任務(wù)被重復(fù)掃描的概率就越小,因?yàn)橹挥性诙鄬訒r(shí)鐘輪中的任務(wù)才會(huì)被重復(fù)掃描。比如一個(gè)時(shí)間輪的槽位有 1000 個(gè),一個(gè)槽位的單位時(shí)間是 10 毫秒,那么下一層時(shí)間輪的一個(gè)槽位的單位時(shí)間就是 10 秒,超過(guò) 10 秒的定時(shí)任務(wù)會(huì)被放到下一層時(shí)間輪中,也就是只有超過(guò) 10 秒的定時(shí)任務(wù)會(huì)被掃描遍歷兩次,但如果槽位是 10 個(gè),那么超過(guò) 100 毫秒的任務(wù),就會(huì)被掃描遍歷兩次。

結(jié)合這些特點(diǎn),我們就可以視具體的業(yè)務(wù)場(chǎng)景而定,對(duì)時(shí)鐘輪的周期和時(shí)間槽數(shù)進(jìn)行設(shè)置。

在 RPC 框架中,只要涉及到定時(shí)任務(wù),我們都可以應(yīng)用時(shí)鐘輪,比較典型的就是調(diào)用端的超時(shí)處理、調(diào)用端與服務(wù)端的啟動(dòng)超時(shí)以及定時(shí)心跳等等。

本文由mdnice多平臺(tái)發(fā)布

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容