欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解如何在Go中實(shí)現(xiàn)優(yōu)雅停止

 更新時(shí)間:2024年04月15日 08:23:29   作者:波羅學(xué)  
和其他語(yǔ)言相比,Go 中有相同也有不同,相同的是實(shí)現(xiàn)思路上和其他語(yǔ)言沒(méi)啥差異,不同在于 Go 采用的是 goroutine + channel 的并發(fā)模型,與傳統(tǒng)的進(jìn)程線程相比,實(shí)現(xiàn)細(xì)節(jié)上存在差異,本文將從實(shí)際場(chǎng)景和它的一般實(shí)現(xiàn)方式展開(kāi),逐步討論這個(gè)話題,需要的朋友可以參考下

簡(jiǎn)介

什么是優(yōu)雅停止?在談優(yōu)雅停止前,我們可以說(shuō)說(shuō)什么是優(yōu)雅重啟,或者說(shuō)熱重啟。

簡(jiǎn)言之,優(yōu)雅重啟就是在服務(wù)升級(jí)、配置更新時(shí),要重新啟動(dòng)服務(wù),優(yōu)雅重啟就是在服務(wù)不中斷或連接不丟失的情況下,重啟服務(wù)。優(yōu)雅重啟的整個(gè)流程中,新的進(jìn)程將在舊的進(jìn)程停止前啟動(dòng),舊進(jìn)程會(huì)完成活動(dòng)中的請(qǐng)求后優(yōu)雅地關(guān)閉進(jìn)程。

優(yōu)雅重啟是服務(wù)開(kāi)發(fā)中一個(gè)非常重要的概念,它讓我們?cè)诓恢袛喾?wù)的情況下,更新代碼和修復(fù)問(wèn)題。它在維持高可用性的生產(chǎn)環(huán)境中尤其關(guān)鍵。

從上面的這段可知,優(yōu)雅重啟是由兩個(gè)部分組成,分別是優(yōu)雅停止和啟動(dòng)。

本文重點(diǎn)介紹優(yōu)雅停止,而優(yōu)雅啟動(dòng)的整個(gè)流程要借助于外部工具控制,如 k8s 的容器編排。

優(yōu)雅停止

優(yōu)雅停止,即要在停止服務(wù)的同時(shí),保證業(yè)務(wù)的完整性。從目標(biāo)上看,優(yōu)雅停止經(jīng)歷三個(gè)步驟:通知服務(wù)停止、服務(wù)啟動(dòng)清理,等待清理確認(rèn)退出。

要停止一個(gè)服務(wù),首先是通過(guò)一些機(jī)制告知服務(wù)要執(zhí)行退出前的工作,最常見(jiàn)的就是基于操作系統(tǒng)信號(hào),我們慣例監(jiān)聽(tīng)的信號(hào)主要是兩個(gè),分別是由 kill PID 發(fā)出的 SIGTERM 和 CTRL+C 發(fā)出的 SIGINT。 其他信號(hào)還有,CTRL+/ 發(fā)出的 SIGQUIT。

當(dāng)接收到指定信號(hào),服務(wù)就要停止接受新的請(qǐng)求,且等待當(dāng)前活動(dòng)中的請(qǐng)求全部完成后再完全停止服務(wù)。

接下來(lái),開(kāi)始具體的代碼實(shí)現(xiàn)部分吧。

從 HTTP 服務(wù)開(kāi)始

談優(yōu)雅重啟,最常被引用的案例就是 HTTP 服務(wù),我將通過(guò)代碼逐步演示這個(gè)過(guò)程。如下是一個(gè)常規(guī) HTTP 服務(wù):

func hello(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "Hello World\n")
}

func main() {
  http.HandleFunc("/", hello)
  log.Println("Starting server on :8080")
  if err := http.ListenAndServe(":8080", nil); err != nil {
      log.Fatal("ListenAndServe: ", err)
  }
}

我們通過(guò) time.Sleep 增加 hello 的耗時(shí),以便于調(diào)試。

func hello(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "Hello World\n")
  time.Sleep(10 * time.Second)
}

運(yùn)行:

$ go run main.go

通過(guò) curl 請(qǐng)求訪問(wèn) http://localhost:8080/ ,它進(jìn)入到 10 秒的處理階段。假設(shè)這時(shí),我們 CTRL+C 請(qǐng)求退出,HTTP 服務(wù)會(huì)直接退出,我們的 curl 請(qǐng)求被直接中斷。

我們可以使用 Go 標(biāo)準(zhǔn)庫(kù)提供的 http.Server 有一個(gè) Shutdown 方法,可以安全地關(guān)閉服務(wù)器而不中斷任何活動(dòng)的連接。而我們要做的,只需在收到停止信號(hào)后,執(zhí)行 Shutdown 即可。

信號(hào)方面,我們通過(guò) Go 標(biāo)準(zhǔn)庫(kù) signal 實(shí)現(xiàn),它提供了一個(gè) Notify 函數(shù),可與 chan nnel 配合傳遞信號(hào)消息。我們監(jiān)聽(tīng)的目標(biāo)信號(hào)是 SIGINTSIGTERM。

重新修改 HTTP 服務(wù)入口,使用 http.ServerShutdown 函數(shù)關(guān)閉 Server。

func main() {
  mux := http.NewServeMux()
  mux.HandleFunc("/", hello)

  server := http.Server{Addr: ":8080", Handler: mux}
  go server.ListenAndServe()
  
  quit := make(chan os.Signal, 1)
  // 注冊(cè)接收信號(hào)的 channel
  signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 
  
  <-quit // 等待停止信號(hào)
  
  if err := server.Shutdown(context.Background()); err != nil {
    log.Fatal("Shutdown: ", err)
  }
}

我們將 server.ListenAndServe 運(yùn)行于另一個(gè) goroutine 中同時(shí)忽略了它的返回錯(cuò)誤。

通過(guò) signal.Notify 注冊(cè)信號(hào)。當(dāng)收到如 CTRL+C 或 kill PID 發(fā)出的中斷信號(hào),執(zhí)行 serve.Shutdown,它會(huì)通知到 server 停止接收新的請(qǐng)求,并等待活動(dòng)中的連接處理完成。

現(xiàn)在運(yùn)行 go run main.go 啟動(dòng)服務(wù),執(zhí)行 curl 命令測(cè)試接口,在請(qǐng)求還沒(méi)有返回之時(shí),我們可以通過(guò) CTRL+C 停止服務(wù),它會(huì)有一段時(shí)間等待,我們可以在這個(gè)過(guò)程中嘗試 curl 請(qǐng)求,看它是否還接收新的請(qǐng)求。

如果希望防止程序假死,或者其他問(wèn)題導(dǎo)致服務(wù)長(zhǎng)時(shí)間無(wú)法退出,可通過(guò) context.WithTimeout 方法包裝下傳遞給 Shutdown 方法的 ctx 變量。

ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()

if err := server.Shutdown(ctx); err != nil {
  log.Fatal("Shutdown: ", err)
}

到這里,我們就介紹完了 Go 標(biāo)準(zhǔn)庫(kù) net/http 的優(yōu)雅停止的使用方案。

抽象出一個(gè)常規(guī)方案

如果開(kāi)發(fā)一個(gè)非 HTTP 的服務(wù),如何讓它支持優(yōu)雅停止呢?畢竟不是所有項(xiàng)目都是 HTTP 服務(wù),不是所有項(xiàng)目都有現(xiàn)成的框架。

本文開(kāi)頭提到的的三步驟,net/http 包的 Shutdown 把最核心的服務(wù)停止前的清理和等待都已經(jīng)在內(nèi)部實(shí)現(xiàn)了。我們可解讀下它的實(shí)現(xiàn)。

進(jìn)入到 Shutdown 的源碼中,重點(diǎn)是開(kāi)頭的第一句代碼,如下所示:

// future calls to methods such as Serve will return ErrServerClosed.
func (srv *Server) Shutdown(ctx context.Context) error {
  srv.inShutdown.Store(true)
  // ...其他清理代碼
  // ...等待活動(dòng)請(qǐng)求完成并將其關(guān)閉
}

inShutdown 是一個(gè)標(biāo)志位,用于標(biāo)識(shí)程序是否已停止。為了解決并發(fā)數(shù)據(jù)競(jìng)爭(zhēng),它的底層類型是 atomic.bool,。

在 server.go 中的 Server.Serve 方法中,通過(guò)判斷 inShutdown 決定是否繼續(xù)接受新的請(qǐng)求。

func (srv *Server) Serve(l net.Listener)  error {
  // ...
  for {
    rw, err := l.Accept()
    if err != nil {
      if srv.shuttingDown() {
        return ErrServerClosed
      }
  // ...
}

我們可以從如上的分析中得知,要讓 HTTP 服務(wù)支持優(yōu)雅停止要啟動(dòng)兩個(gè) goroutine,Shutdown 運(yùn)行與 main goroutine 中,當(dāng)接收中停止信號(hào),通過(guò) inShutdown 標(biāo)志位通知運(yùn)行中的 goroutine。

用簡(jiǎn)化的代碼表示這個(gè)一般模式。

var inShutdown bool

func Start() {
  for !inShutdown {
    // running
    time.Sleep(10 * time.Second)
  }
}

func Shutdown() {
  inShutdown = true
}

func main() {
  go Start()

  quit = make(chan os.Signal, 1)
  signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
  <- quit

  Shutdown()
}

大概看起來(lái)是那么回事,但這里的代碼少了一個(gè)步驟,即 Shutdown 沒(méi)有等待 Start 完成。

標(biāo)準(zhǔn)庫(kù) net/http 是通過(guò) for 循環(huán)不斷檢查是否有活動(dòng)中的連接,如果連接沒(méi)有進(jìn)行中請(qǐng)求會(huì)將其關(guān)閉,直到將所有連接關(guān)閉,便會(huì)退出 Shutdown。

核心代碼如下:

func (srv *Server) Shutdown(ctx context.Context) {
  // ...之前的代碼

  timer := time.NewTimer(nextPollInterval())
  defer timer.Stop()
  for {
    if srv.closeIdleConns() {
      return lnerr
    }
    select {
    case <-ctx.Done():
      return ctx.Err()
    case <-timer.C:
      timer.Reset(nextPollInterval())
    }
  }
}

重點(diǎn)就是那句 closeIdleConns,它負(fù)責(zé)檢查是否還有執(zhí)行中的請(qǐng)求。我就不把這部分的源代碼貼出來(lái)了。而檢查頻率是通過(guò) timer 控制的。

現(xiàn)在讓簡(jiǎn)化版等待 Start 完成后才退出。我們引入一個(gè)名為 isStop 的標(biāo)志位以監(jiān)控停止?fàn)顟B(tài)。

var inShutdown bool
var isStop bool

func Start() {
  for !inShutdown {
    // running
    time.Sleep(10 * time.Second)
  }
  isStop = true
}

func Shutdown() {
  inShutdown = true

  timer := time.NewTimer(time.Millisecond)
  defer timer.Stop()
  for {
    if isStop {
      return
    }
    <- timer.C
    timer.Reset(time.Millisecond))
  }
}

如上的代碼中,Start 函數(shù)退出時(shí)會(huì)執(zhí)行 isStop = true 表明已退出,在 Shutdown 中,通過(guò)定期檢查 isStop 等待 Start 退出完成。

此外,net/httpShutdown 方法還接收了一個(gè) context.Context 參數(shù),允許實(shí)現(xiàn)超時(shí)控制,從而防止程序假死或強(qiáng)制關(guān)閉。

需要特別指出的是,示例中用的 isStopinShutdown 標(biāo)志位為非原子類型,在正式場(chǎng)景中,為避免數(shù)據(jù)競(jìng)爭(zhēng),要使用原子操作或其他同步機(jī)制。

除了可用共享內(nèi)存標(biāo)志位在不同協(xié)程間傳遞狀態(tài),也可以通過(guò) channel 實(shí)現(xiàn),或你看到過(guò)類似如下的形式。

var inShutdown bool

func Start(stop chan struct{}) {
	for !inShutdown {
		// running
		time.Sleep(10 * time.Second)
	}
	stop <- struct{}{}
}

func Shutdown() {
	inShutdown = true
}

func main() {
	stop := make(chan struct{})
	defer close(stop)

	go Start(stop)

	go func() {
		quit := make(chan os.Signal, 1)
		signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
		<-quit
		Shutdown()
	}()

	<-stop
}

如上的代碼中,Start 通過(guò) channel 通知主 goroutine,當(dāng)觸發(fā)停止信號(hào),isShutdown 通知 Start 要停止退出,它成功退出后,通過(guò) stop <- struct{} 通知主函數(shù),結(jié)束等待。

總的來(lái)說(shuō),channel 的優(yōu)勢(shì)很明顯,避免了單獨(dú)管理一個(gè) isStop 標(biāo)志位來(lái)標(biāo)識(shí)服務(wù)狀態(tài),并且免去了基于定時(shí)器的定期輪詢檢查的過(guò)程,還更加實(shí)時(shí)和高效。當(dāng)然,net/http 使用輪詢檢查機(jī)制,是它的場(chǎng)景所決定,和我們這里不完全一樣。

一點(diǎn)思考

Go 語(yǔ)言支持多種方式在 Goroutine 間傳遞信息,這催生了多樣的優(yōu)雅停止實(shí)現(xiàn)方式。如果是在涉及多個(gè)嵌套 Goroutine 的場(chǎng)景中,我們可以引入 context 來(lái)實(shí)現(xiàn)多層級(jí)的狀態(tài)和信息傳遞,確保操作的連貫性和安全性。

然盡管實(shí)現(xiàn)方式眾多,但其核心思路是一致的,而底層目標(biāo)始終是我們要保證處理邏輯的完整性。

另外,通過(guò)將優(yōu)雅停止與容器編排技術(shù)結(jié)合,并為服務(wù)添加健康檢查,我們能夠確保服務(wù)總有實(shí)例在活躍狀態(tài),實(shí)現(xiàn)真正意義上的優(yōu)雅重啟。這不僅提高了服務(wù)的可靠性,也優(yōu)化了資源的利用效率。

總結(jié)

本文探索了 Go 語(yǔ)言中優(yōu)雅重啟的實(shí)現(xiàn)方法,展示了如何通過(guò) http.Server 的 Shutdown 方法安全地重啟服務(wù),以及使用 context 控制超時(shí)?;诖耍覀兂橄蟪隽艘话惴?wù)優(yōu)雅停止的核心思路。

以上就是詳解如何在Go中實(shí)現(xiàn)優(yōu)雅停止的詳細(xì)內(nèi)容,更多關(guān)于在Go中實(shí)現(xiàn)停止的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • golang 生成對(duì)應(yīng)的數(shù)據(jù)表struct定義操作

    golang 生成對(duì)應(yīng)的數(shù)據(jù)表struct定義操作

    這篇文章主要介紹了golang 生成對(duì)應(yīng)的數(shù)據(jù)表struct定義操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-04-04
  • GoLang channel底層代碼實(shí)現(xiàn)詳解

    GoLang channel底層代碼實(shí)現(xiàn)詳解

    Channel和goroutine的結(jié)合是Go并發(fā)編程的大殺器。而Channel的實(shí)際應(yīng)用也經(jīng)常讓人眼前一亮,通過(guò)與select,cancel,timer等結(jié)合,它能實(shí)現(xiàn)各種各樣的功能。接下來(lái),我們就要梳理一下GoLang channel底層代碼實(shí)現(xiàn)
    2022-10-10
  • Mac下Vs code配置Go語(yǔ)言環(huán)境的詳細(xì)過(guò)程

    Mac下Vs code配置Go語(yǔ)言環(huán)境的詳細(xì)過(guò)程

    這篇文章給大家介紹Mac下Vs code配置Go語(yǔ)言環(huán)境的詳細(xì)過(guò)程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2021-07-07
  • golang?pprof?監(jiān)控系列?go?trace統(tǒng)計(jì)原理與使用解析

    golang?pprof?監(jiān)控系列?go?trace統(tǒng)計(jì)原理與使用解析

    這篇文章主要為大家介紹了golang?pprof?監(jiān)控系列?go?trace統(tǒng)計(jì)原理與使用解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • Golang使用反射的動(dòng)態(tài)方法調(diào)用詳解

    Golang使用反射的動(dòng)態(tài)方法調(diào)用詳解

    Go是一種靜態(tài)類型的語(yǔ)言,提供了大量的安全性和性能。這篇文章主要和大家介紹一下Golang使用反射的動(dòng)態(tài)方法調(diào)用,感興趣的小伙伴可以了解一下
    2023-03-03
  • Golang實(shí)現(xiàn)超時(shí)機(jī)制讀取文件的方法示例

    Golang實(shí)現(xiàn)超時(shí)機(jī)制讀取文件的方法示例

    讀寫(xiě)文件是Go程序的基本任務(wù),包括使用程序查看文件內(nèi)容、創(chuàng)建或修改文件,Go提供了os,ioutil,io以及bufio包實(shí)現(xiàn)文件操作,本文介紹如果在讀文件過(guò)程中增加超時(shí)機(jī)制,避免文件太大一直占用資源,需要的朋友可以參考下
    2025-01-01
  • Go?gRPC服務(wù)客戶端流式RPC教程

    Go?gRPC服務(wù)客戶端流式RPC教程

    這篇文章主要為大家介紹了Go?gRPC服務(wù)客戶端流式RPC教程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • 用Go語(yǔ)言編寫(xiě)一個(gè)簡(jiǎn)單的分布式系統(tǒng)

    用Go語(yǔ)言編寫(xiě)一個(gè)簡(jiǎn)單的分布式系統(tǒng)

    這篇文章主要介紹了用Go語(yǔ)言編寫(xiě)一個(gè)簡(jiǎn)單的分布式系統(tǒng),文中的代碼示例講解的非常詳細(xì),對(duì)我們的學(xué)習(xí)或工作有一定的幫助,感興趣的小伙伴跟著小編一起來(lái)看看吧
    2023-08-08
  • Go語(yǔ)言kube-scheduler深度剖析與開(kāi)發(fā)之pod調(diào)度

    Go語(yǔ)言kube-scheduler深度剖析與開(kāi)發(fā)之pod調(diào)度

    這篇文章主要為大家介紹了Go語(yǔ)言kube-scheduler深度剖析與開(kāi)發(fā),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • 詳解Go語(yǔ)言中new和make關(guān)鍵字的區(qū)別

    詳解Go語(yǔ)言中new和make關(guān)鍵字的區(qū)別

    本篇文章來(lái)介紹一道非常常見(jiàn)的面試題,到底有多常見(jiàn)呢?可能很多面試的開(kāi)場(chǎng)白就是由此開(kāi)始的。那就是 new 和 make 這兩個(gè)內(nèi)置函數(shù)的區(qū)別,希望對(duì)大家有所幫助
    2023-03-03

最新評(píng)論