在php專案,如果遇到程式要修正,要重新發佈,那麼就直接把程式丟上去就好啦,因為是直譯式的語言,程式是執行到哪裡,才看哪裡。於是有更新的話,程式下次執行到有做更新的部分,看到的是新的內容,而不是舊的。
Golang就不一樣,編譯好的內容一但執行下去,它就把這些內容放到記憶體了,再怎麼改原始碼或重新編譯,都不會影響到目前正在執行的process。
有別於此,正在上線的Golang程式服務,若遇到緊急需要更新或者改版?要怎麼操作呢?總不能每次都掛維護,再來砍掉process,執行重新編譯好的檔案吧!如果process還有工作或服務正做到一半,你砍掉process等於強制中斷這些工作,可能影響使用者體驗,甚至資料正確性。
若等工作做完再砍掉目前執行的process呢?你的新問題會是:我要怎麼才能知道目前工作已做完,並且阻止新工作進來?
除了在load balance機器調整或設定,你有更好的做法。
對岸翻作『優雅關閉』,我個人是傾向專有名詞還是用原文來表示的好,但意思都一樣。
graceful shutdown 簡單來說,就是process收到關閉訊息時候,先把訊息擺在一邊,把目前手邊工作做完,再做關閉。
這是一種人為實作的機制,並不是程式就天生有附帶這種效果,你要說這是一種design pattern也可以。
粗暴一點的講法是:你寫一段code去攔截送到os的關閉signal,先立某個即將關閉的flag,程式每次執行到某個階段(結束),檢查到這個flag有立起,則進行os.exit等類似的關閉method。
如下面程式碼是我們的應用
{
// 開始排程
log.Println("INFO", `start`)
bg.Start()
// 等待結束訊號
<-gracefulShutdown()
log.Println("WARNIGN", `received singal`)
// 停止排程
bg.Stop()
// 等待背景結束
for _, job := range jobs {
job.Wait()
}
log.Println("INFO", `end`)
}
func gracefulShutdown() (sig chan os.Signal) {
sig = make(chan os.Signal)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
return
}
在gracefulShutdown() 未攔截到關閉的訊號茲前,排程job一直處理正常運作的狀況,一但gracefulShutdown()攔截到訊號後,channel就無法繼續卡住,而執行了bg.Stop(),這時排成job就會依序關閉,並不再啟動新工作。
對岸翻作『優雅重啟』,是更一步的進化版,通常你想要關閉運行中的程式,可能是為了換成新的程式編譯檔。graceful restart就是以graceful shutdown的方式殺掉process,再重新掛上的process的機制。
概念和做法可以參考下面這個facebook提供的開源套件,若仔細研究一下,你會發現好像不是什麼特別的東西。
概念是它找到目前process的環境參數,在啟動新的process的時候,把他帶過去,內容的關鍵流程大致如下。
kill 的時候帶入-USR2 讓程式知道你要優雅關閉
kill -USR2
尋找執行檔路徑(你要重新掛程式,可以在這之前先重新編譯過)
exec.LookPath()
取得環境變數
os.Getwd()
帶入就環境變數,啟動新的process
os.StartProcess
這篇短介是實務上我們遇到非常需要的知識重點,希望有幫助到類似需求的開發人員。