從nsq中學習如何優雅的退出go 網路程式

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

退出運行中的程式,可以粗暴的kill -9 $PID,但這樣會破壞業務的完整性,有可能一個正在在執行的邏輯半途而費,從而產生不正常的垃圾資料。

本文總結在go語言中,如何能優雅的退出網路應用,涉及的知識包括:signal,channel,WaitGroup等。

從這裡:https://gobyexample.com/channel-synchronization 可以簡單瞭解到,在go中如何使用channel實現goroutines同步。

在nsq中,也使用了相同的機制,不過封裝更複雜了些。我們以nsqadmin中的實現為例進行簡單的分析。

程式碼片段1(來自:https://github.com/bitly/nsq/blob/master/nsqadmin/main.go):

 

exitChan := make(chan int)

signalChan := make(chan os.Signal, 1)

go func() {

<-signalChan

exitChan <- 1

}()

signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)

//....

nsqadmin.Main()

<-exitChan

nsqadmin.Exit()

 

 

上面的代碼正常執行後,會卡到倒數第二句的<-exitChan中,直到exitChan中有資料進入。

當我們通過命令列執行:kill -s SIGINT $PID時,signalChan中收到一個資訊,上面第四行代碼中的goroutines會停止阻塞,繼續向下執行,exitChann中加入一條資料。當exitChann中有了資料,倒數第二句也會停止阻塞,執行nsqadmin.Exit()實現優雅退出。

當然上面的代碼,也可以簡化:

 

signalChan := make(chan os.Signal, 1)

signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)

//....

nsqadmin.Main()

<-signalChan

nsqadmin.Exit()

 

至於nsq為什麼沒有這麼做,還不太清楚。能力有限,體會不到其深意所在。

上面的例子只適用於兩個goroutines之間,一個處理完業務後給exitChan寫資料,主goroutines卡在exitChan上等資料。(主goroutines必須比處理業務的goroutines後退出)。

如果一個主線程下開了多個子goroutines,使用channel的方式就不夠優雅了。可以使用WaitGroup,關於WaitGroup的介紹可以參考:http://www.baiyuxiong.com/?p=913

在nsq中同樣使用了WaitGroup實現退出。

程式碼片段2(來自:https://github.com/bitly/nsq/blob/master/util/wait_group_wrapper.go)

 

type WaitGroupWrapper struct {

sync.WaitGroup

}

 

func (w *WaitGroupWrapper) Wrap(cb func()) {

w.Add(1)

go func() {

cb()

w.Done()

}()

}

 

程式碼片段3(來自:<a href="https://github.com/bitly/nsq/blob/master/nsqadmin/nsqadmin.go">https://github.com/bitly/nsq/blob/master/nsqadmin/nsqadmin.go</a>)

 

func (n *NSQAdmin) Main() {

httpListener, err := net.Listen("tcp", n.httpAddr.String())

if err != nil {

n.logf("FATAL: listen (%s) failed - %s", n.httpAddr, err)

os.Exit(1)

}

n.httpListener = httpListener

httpServer := NewHTTPServer(&Context{n})

n.waitGroup.Wrap(func() {

util.HTTPServer(n.httpListener, httpServer, n.opts.Logger, "HTTP")

})

n.waitGroup.Wrap(func() { n.handleAdminActions() })

}

 

func (n *NSQAdmin) Exit() {

n.httpListener.Close()

close(n.notifications)

n.waitGroup.Wait()

}

 

在程式碼片段2中,對waitGroup進行了簡單封裝,開啟goroutines前計數加1,執行完計數減1。

程式碼片段3中,Main()方法裡,調用了兩次waitGroup.Wrap()方法,參考程式碼片段2可以知道,這會啟動兩個子goroutines,並使waitGroup計數加2.而子goroutines中使用了http包監聽網路服務,阻塞goroutines,使得計數減1的操作不能被調用。

在我們的程式碼片段1中可以知道,命令列發送了kill以後,會執行程式碼片段3的Exit()方法,當方法裡的n.httpListener.Close()被調用後,網路服務中斷,程式碼片段2中的阻塞就會停止,進而執行計數減1的,當兩個子goroutines中計數各減1以後。Exit()方法中的n.waitGroup.Wait()就會繼續執行,主線程結束,程式退出。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.