如何優(yōu)雅的停掉Server

引言

俗話說的好,請(qǐng)神容易,送神難。同樣,一個(gè)Server啟動(dòng)起來也很容易,但怎么退出,直接kill,還是直接退出main函數(shù)?

優(yōu)雅的停掉Server

直接kill,或是直接退出main函數(shù),這種方式很粗暴,可能會(huì)導(dǎo)致業(yè)務(wù)數(shù)據(jù)的損壞,不完整,丟失。
那應(yīng)該怎么停掉Server。這里筆者以Thrift Serve為列子。看看Thrift 源碼里面的一段停掉Server代碼:


func (p *TSimpleServer) AcceptLoop() error {
    for {
        client, err := p.serverTransport.Accept()
        if err != nil {
            select {
            case <-p.quit:
                return nil
            default:
            }
            return err
        }
        if client != nil {
            p.Add(1)
            go func() {
                if err := p.processRequests(client); err != nil {
                    log.Println("error processing request:", err)
                }
            }()
        }
    }
}

var once sync.Once

func (p *TSimpleServer) Stop() error {
    q := func() {
        close(p.quit)
        p.serverTransport.Interrupt()
        p.Wait()
    }
    once.Do(q)
    return nil
}


.......
func (p *TServerSocket) Interrupt() error {
    p.mu.Lock()
    p.interrupted = true
    p.Close()
    p.mu.Unlock()

    return nil
}

func (p *TServerSocket) Close() error {
    defer func() {
        p.listener = nil
    }()
    if p.IsListening() {
        return p.listener.Close()
    }
    return nil
}


當(dāng)調(diào)用Stop 停掉服務(wù)的時(shí)候關(guān)閉 p.quit chan,p.serverTransport.Interrupt()會(huì)關(guān)閉端口監(jiān)聽,這時(shí)候 client, err := p.serverTransport.Accept() 會(huì)拋出錯(cuò)誤。這時(shí) AcceptLoop 會(huì)結(jié)束,進(jìn)程阻塞在p.Wait(),等待 processRequests goroutine 結(jié)束。
這種方式比直接kill,或是直接退出main函數(shù)優(yōu)雅多了。但還是存在一些問題。

  1. 如果Server goroutine 死鎖了,這時(shí)候服務(wù)都不能順利退出。

  2. 只是針對(duì)了Server goroutine 等待, 但 Server goroutine 可能會(huì)開啟一些 client goroutine ,而且可能還有一些manager goroutine。

  3. Server在Wait 過程中,Client 還會(huì)嘗試調(diào)用Server,這時(shí)候Client 會(huì)一直報(bào)錯(cuò)。

針對(duì)一中的問題,加入等待超時(shí)機(jī)制,防止這種問題。超時(shí)時(shí)間確保所有收到的請(qǐng)求能處理完。

// AcceptLoop loops and accepts connections.
func (p *TSimpleServer) AcceptLoop() error {
    for {
        client, err := p.serverTransport.Accept()
        if err != nil {
            select {
            case <-p.quit:
                return nil
            default:
            }
            return err
        }
        if client != nil {
            p.Add(1)
            go func() {
                if err := p.processRequests(client); err != nil {
                    log.Println("error processing request:", err)
                }
            }()
        }
    }
}

var once sync.Once

func (p *TSimpleServer) Stop() {
     q := func() {
          close(p.quit)
          p.serverTransport.Interrupt()
          timer := time.NewTimer(p.GracefulTimeout)
          waitCh := make(chan struct{})
           go func() {
             p.Wait()
             close(waitCh)
            }()
            select {
                     case <-waitCh:
                     case <-timer.C:
             }
          }
          once.Do(q)
          return nil
}

針對(duì)二中的問題,定義一個(gè) Observer interface,相應(yīng)的goroutine 里注冊(cè)監(jiān)聽,實(shí)現(xiàn)stop 處理。當(dāng)Server stop的時(shí)候發(fā)送Shutdown消息。

type ShutdownObserver interface {
    ShutdownNotify(evt interface{})
}

......
func (n *ShutdownNotifier) Notify(evt interface{}) {
    n.RLock()
    for o := range n.observers {
        o.ShutdownNotify(evt)
    }
    n.RUnlock()
}

func (p *TSimpleServer) Stop() {
     q := func() {
          close(p.quit)
          p.serverTransport.Interrupt()
          timer := time.NewTimer(p.GracefulTimeout)
          waitCh := make(chan struct{})
           go func() {
             p.shutdown.Notify(nil)
             p.Wait()
             close(waitCh)
            }()
            select {
                     case <-waitCh:
                     case <-timer.C:
             }
          }
          once.Do(q)
          return nil
}

針對(duì)三中的問題, 以soa 服務(wù)為例,當(dāng)stop 的時(shí)候,把當(dāng)前機(jī)器從服務(wù)注冊(cè)中心當(dāng)前所在集群中踢掉,client 會(huì)更新集群機(jī)器列表,不去訪問當(dāng)前機(jī)器。

總結(jié)

以上是對(duì)停掉Server的一點(diǎn)思考,如有不對(duì),歡迎指教。

?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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