golang:使用timingwheel进行大量ticker的优化

Ticker

最近的项目用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%左右徘徊,性能不能让人接受。性能

其实,咱们只须要的是一套高效的超时通知机制。优化

Close channel to broadcast

在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就会继续往下执行。事件

TimingWheel

经过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的代码在这里

相关文章
相关标签/搜索