最近的項目用go實現的服務器須要掛載大量的socket鏈接。如何判斷鏈接是否還存活就是咱們須要考慮的一個問題了。git
一般狀況下面,socket若是被客戶端正常close,服務器是能檢測到的,可是若是客戶端忽然拔掉網線,或者是斷電,那麼socket的狀態在服務器看來可能仍然是established。而實際上該socket已經不可用了。github
爲了判斷鏈接是否可用,一般咱們會用timer機制來定時檢測,在go裏面,這很是容易實現,以下:服務器
ticker := time.NewTicker(60 * time.Second) for { select { case <-ticker.C: if err := ping(); err != nil { close() } } }
上面咱們使用一個60s的ticker,定時去ping,若是ping失敗了,證實鏈接已經斷開了,這時候就須要close了。socket
這套機制比較簡單,也運行的很好,直到咱們的服務器連上了10w+的鏈接。由於每個鏈接都有一個ticker,因此同時會有大量的ticker運行,cpu一直在30%左右徘徊,性能不能讓人接受。性能
其實,咱們只須要的是一套高效的超時通知機制。優化
在go裏面,channel是一個很不錯的東西,咱們能夠經過close channel來進行broadcast。以下:code
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就會繼續往下執行。事件
經過channel這種close broadcast機制,咱們能夠很是方便的實現一個timer,timer有一個channel ch,全部須要在某一個時間 「T」 收到通知的goroutine均可以嘗試讀該ch,當T到達時候,close該ch,那麼全部的goroutine都能收到該事件了。get
timingwheel的使用很簡單,首先咱們建立一個wheelit
//這裏咱們建立了一個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的代碼在這裏。