golang:使用timingwheel進行大量ticker的優化

Ticker

最近的項目用go實現的服務器需要掛載大量的socket連接。如何判斷連接是否還存活就是我們需要考慮的一個問題了。

通常情況下面,socket如果被客戶端正常close,服務器是能檢測到的,但是如果客戶端突然拔掉網線,或者是斷電,那麼socket的狀態在服務器看來可能仍然是established。而實際上該socket已經不可用了。

爲了判斷連接是否可用,通常我們會用timer機制來定時檢測,在go裏面,這非常容易實現,如下:

ticker := time.NewTicker(60 * time.Second)

for {
    select {
        case <-ticker.C:
            if err := ping(); err != nil {
                close()
            }
    }
}

上面我們使用一個60s的ticker,定時去ping,如果ping失敗了,證明連接已經斷開了,這時候就需要close了。

這套機制比較簡單,也運行的很好,直到我們的服務器連上了10w+的連接。因爲每一個連接都有一個ticker,所以同時會有大量的ticker運行,cpu一直在30%左右徘徊,性能不能讓人接受。

其實,我們只需要的是一套高效的超時通知機制。

Close channel to broadcast

在go裏面,channel是一個很不錯的東西,我們可以通過close channel來進行broadcast。如下:

ch := make(bool)

for i := 0; i < 10; i++ {
    go func() {
        println("begin")
        <-ch
        println("end")
    }
}

time.Sleep(10 * time.Second)

close(ch)

上面,我們啓動了10個goroutine,它們都會因爲等待ch的數據而block,10s之後close這個channel,那麼所有等待該channel的goroutine就會繼續往下執行。

TimingWheel

通過channel這種close broadcast機制,我們可以非常方便的實現一個timer,timer有一個channel ch,所有需要在某一個時間 “T” 收到通知的goroutine都可以嘗試讀該ch,當T到達時候,close該ch,那麼所有的goroutine都能收到該事件了。

timingwheel的使用很簡單,首先我們創建一個wheel

//這裏我們創建了一個timingwheel,精度是1s,最大的超時等待時間爲3600s
w := timingwheel.NewTimingWheel(1 * time.Second, 3600)

//等待10s
<-w.After(10 * time.Second)

因爲timingwheel只有一個1s的ticker,並且只創建了3600個channel,系統開銷很小。當我們程序換上timingwheel之後,10w+連接cpu開銷在10%以下,達到了優化效果。

timingwheel的代碼在這裏

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章