使用 Go 語言實現優雅的服務器重啟
英文原文:Graceful server restart with Go
Go被設計為一種后臺語言,它通常也被用于后端程序中。服務端程序是GO語言最常見的軟件產品。在這我要解決的問題是:如何干凈利落地升級正在運行的服務端程序。 目標:
|
![]() pseudo
|
原理在基于Unix的操作系統中,signal(信號)是與長時間運行的進程交互的常用方法.
如果收到SIGHUP信號,優雅地重啟進程需要以下幾個步驟:
|
![]() pseudo
|
停止接受連接請求服務器程序的共同點:持有一個死循環來接受連接請求:
跳出這個循環的最簡單方式是在socket監聽器上設置一個超時,當調用listener.SetTimeout(time.Now())后,listener.Accept()會立即返回一個timeout err,你可以捕獲并處理:
注意這個操作與關閉listener有所不同。這樣進程仍在監聽服務器端口,但連接請求會被操作系統的網絡棧排隊,等待一個進程接受它們。 |
![]() pseudo
|
啟動新進程Go提供了一個原始類型ForkExec來產生新進程.你可以與這個新進程共享某些消息,例如文件描述符或環境參數。
你會發現這個進程使用完全相同的參數os.Args啟動了一個新進程。 |
![]() pseudo
|
發送socket到子進程并恢復它
正如你先前看到的,你可以將文件描述符傳遞到新進程,這需要一些UNIX魔法(一切都是文件),我們可以把socket發送到新進程中,這樣新進程就能夠使用它并接收及等待新的連接。 但fork-execed進程需要知道它必須從文件中得到socket而不是新建一個(有些興許已經在使用了,因為我們還沒斷開已有的監聽)。你可以按任何你希望的方法來,最常見的是通過環境變量或命令行標志。
然后在程序的開始處:
文件描述沒有被隨機的選擇為3,這是因為uintptr的切片已經發送了fork,監聽獲取了索引3。留意隱式聲明問題。 |
![]() Garfielt
|
最后一步,等待舊服務連接停止到此為止,就這樣,我們已經將其傳到另一個正在正確運行的進程,對于舊服務器的最后操作是等其連接關閉。由于標準庫里提供了sync.WaitGroup結構體,用go實現這個功能很簡單。 每次接收一個連接,在WaitGroup上加1,然后,我們在它完成時將計數器減一:
至于等待連接的結束,你僅需要wg.Wait(),因為沒有新的連接,我們等待wg.Done()已經被所有正在運行的handler調用。 |
![]() Ley
|
Bonus: 不要無限制等待,給定限量的時間
有time.Timer,實現很簡單:
完整的示例這篇文章中的代碼片段都是從這個完整的示例中提取的:https://github.com/Scalingo/go-graceful-restart-example 結論socket傳遞配合ForkExec使用確實是一種無干擾更新進程的有效方式,在最大時間上,新的連接會等待幾毫秒——用于服務的啟動和恢復socket,但這個時間很短。 這篇文章是我#周五技術系列的一部分,下這個周不會有新的更新了,大家圣誕節快樂。 鏈接:
— Léo Unbekandt CTO @ Appsdeck |
![]() Garfielt
|
我們的翻譯工作遵照 CC 協議,如果我們的工作有侵犯到您的權益,請及時聯系我們