GOLANG實現超時物件檢測的最好理解的方式

winlin發表於2017-05-16

依賴於心跳的系統,都需要超時檢測。比如 P2P 系統中客戶端每隔 120 秒向資料伺服器傳送一次資料彙總,伺服器就需要維護一個超時時間。比如一個 UDP 伺服器,在和客戶端之間建立 Session 之後,如果沒有資料包,一般會有 Ping 包,說明這個 Session 是存活的,伺服器在發現 Session 超時後也需要清理。

首先,伺服器一般需要維護一個列表,以 Peer 為例:

type Peer struct {
    id uint64
    heartbeat time.Time
}

type Server struct {
    peers map[uint64]*Peer
    lock sync.Mutex
}

建立 Peer,同時在收到 Ping 訊息後,更新 Peer 的心跳時間:

func (v *Server) Create(id uint64) *Peer {
    v.lock.Lock()
    defer v.lock.UnLock()

    p = &Peer { id:id, heartbeat: time.Now(), }
    v.peers[id] = p
    return p
}

func (v *Server) OnPing(id uint64) {
    v.lock.Lock()
    defer v.lock.UnLock()

    if p,ok := v.peers[id]; ok {
        p.heatbeat = time.Now()
    }
}

當然,需要起一個 goroutine 定期掃描這個列表, 假設 300 秒超時:

go func(v *Server) {
    for {
        func(){
            v.lock.Lock()
            defer v.lock.UnLock()

            now := time.Now()
            for id,p := range v.peers {
                if p.heartbeat.Add(300 * time.Second).Before(now) {
                    delete(v.peers, id)
                }
            }
        }()
        time.Sleep(30 * time.Second)
    }
}(server)

如果 Peers 的數目非常多,那麼掃描時每次都需要鎖定v.peers,會導致其他的業務都無法進行。特別是清理 Peer 這個過程如果比較複雜,譬如需要發起 io 請求,是一個費時的操作時,就會造成系統的等待。

一般來說,超時的 Peer 不會很多,因此可以用 chan 放一個超時的 peer,每個 peer 專門起一個 goroutine 來看什麼時候超時,這樣就可以在檢測超時時避免用鎖了:

timeout := make(chan *Peer)

func (v *Server) Create(id uint64) *Peer {
    v.lock.Lock()
    defer v.lock.UnLock()

    p = &Peer { id:id, heartbeat: time.Now(), }
    v.peers[id] = p
    return p

    go func(p *Peer) {
        for {
            tm := p.heartbeat
            <- time.After(300 * time.Second)
            if tm.Equal(p.heartbeat) {
                timeout <- p
                break
            }
        }
    }(p)
}

go func(v *Server){
    for gw := range timeout {
        func(){
            lgateways.Lock()
            defer lgateways.Unlock()

            delete(gateways, gw.port)
        }()

        // Do something cleanup about the gateway.
    }
}(server)

這樣就只有在有 Peer 超時時,才真正鎖住Server.peers

檢測超時時,除了用heartbeat心跳時間,還可以用keepAlive chan bool保活訊息來實現:

type Peer struct {
    keepAlive chan bool
}

func (v *Server) OnPing(id uint64) {
    if p,ok := v.peers[id]; ok {
        select {
        case p.keepAlive <- true:
        default:
        }
    }
}

這樣檢測超時也更簡單:

go func(p *Peer) {
    for {
        select {
        case <- time.After(300 * time.Second):
            timeout <- p
        case <- p.keepAlive:
        }
    }
}(p)

這樣就是兩個 chan 聯合完成這個任務:

keepAlive => timeout
更多原創文章乾貨分享,請關注公眾號
  • GOLANG實現超時物件檢測的最好理解的方式
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章