精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Golang 優雅關閉 gRPC 實踐

開發 后端
本文主要討論了在 Go 語言中實現gRPC服務優雅關閉的技術和方法,從而確保所有連接都得到正確處理,防止數據丟失或損壞。

問題

我在上次做技術支持的時候,遇到了一個有趣的錯誤。我們的服務在 Kubernetes 上運行,有一個容器在重啟時不斷出現以下錯誤信息--"Error bind: address already in use"。對于大多數程序員來說,這是一個非常熟悉的錯誤信息,表明一個進程正試圖綁定到另一個進程正在使用的端口上。

一、背景

我的團隊維護一個 Go 服務,啟動時會在各自的 goroutine 中生成大量不同的 gRPC 服務。

Goroutine[2] - Go 運行時管理的輕量級線程,運行時只需要幾 KB 內存,是 Go 并發性的基礎。

以下是我們服務架構的簡化版本,以及以前啟動和停止服務器時所執行的任務。

package main

type GrpcServerInterface interface{
  Run(stopChan chan <-struct{})
}

type Server struct {
  ServerA GrpcServerIface
  ServerB GrpcServerIface
}

func NewServer() *Server {
  return &NewServer{
    ServerA: NewServerA,
    ServerB: NewServerB,
  }
}

// Start runs each of the grpc servers
func (s *Server) Start(stopChan <-chan struct{}){
  go ServerA.Run(stopChan)
  go ServerB.Run(stopChan)
  <- stopChan
}

func main() {
  stopChan := make(chan struct{})
  server := NewServer()
  server.Start(stopChan)
 
  // Wait for program to terminate and then signal servers to stop
  ch := make(chan os.Signal, 1)
  signal.Notify(c, os.Interrupt, syscall.SIGTERM)
  <-ch
  close(stopChan)
}
package internal

type ServerA struct {
  stopChan <-chan struct{}
}

// Start runs each of the grpc servers
func (s *ServerA) Run(stopChan <-chan struct{}){
  grpcServer := grpc.NewServer()
  
  var listener net.Listener
  ln, err := net.Listen("tcp", ":8080")
  if err != nil {
   // handle error
  }
  
  for {
   err := grpcServer.Serve(listener)
   if err != nil {
     return 
   }
  }
  
  <- stopChan
  grpcServer.Stop() // Gracefully terminate connections and close listener
}

我首先想到這可能是 Docker 或 Kubernetes 運行時的某種偶發性錯誤。這個錯誤讓我覺得很奇怪,原因如下:1.)查看代碼,我們似乎確實在主程序退出時關閉了所有監聽,端口怎么可能在重啟時仍在使用?2.)錯誤信息持續出現了幾個小時,以至于需要人工干預。我原以為在最壞情況下,操作系統會在嘗試重啟容器之前為我們清理資源。或許是清理速度不夠快?

團隊成員建議我們再深入調查一下。

二、解決方案

經過仔細研究,發現我們的代碼實際上存在一些問題...

1. 通道(Channel)與上下文(Context)

通道用于在程序之間發送信號,通常以一對一的方式使用,當一個值被發送到某個通道時,只能從該通道讀取一次。在我們的代碼中,使用的是一對多模式。我們將在 main 中創建的通道傳遞給多個不同的 goroutine,每個 goroutine 都在等待 main 關閉通道,以便知道何時運行清理函數。

從 Go 1.7 開始,上下文被認為是向多個 goroutine 廣播信號的標準方式。雖然這可能不是我們遇到問題的根本原因(我們是在等待通道關閉,而不是試圖讓每個 goroutine 從通道中讀取相同的值),但考慮到這是最佳實踐,還是希望采用這種模式。

以下是從通道切換到上下文后更新的代碼。

package internal

type ServerA struct {}

func (s *ServerA) Run(ctx context.Context){
  grpcServer := grpc.NewServer()
  var listener net.Listener
  ln, err := net.Listen("tcp", ":8080")
  if err != nil {
   log.Fatal("ServerA - Failed to create listener")
  }
  
  for {
   err := grpcServer.Serve(listener)
   if err != nil {
     log.Fatal("ServerA - Failed to start server") 
   }
  }
  
  <- ctx.Done()
  // Clean up logic 
  grpcServer.Stop() // Gracefully terminate connections and close listener
}
package main

type GrpcServerInterface interface{
 Run(stopChan chan <-struct{})
}

type Server struct {
 ServerA GrpcServerIface
 ServerB GrpcServerIface
 stopServer context.CancelFunc
 serverCtx context.Context
}

func NewServer() *Server {
 return &NewServer{
    ServerA: NewServerA,
    ServerB: NewServerB,
  }
}

// Start runs each of the grpc servers
func (s *Server) Start(ctx context.Context){
  // create new context from parent context
  s.serverCtx, stopServer := context.WithCancel(ctx) 
  go ServerA.Run(s.serverCtx)
  go ServerB.Run(s.serverCtx)
}

func (s *Server) Stop() {
  s.stopServer() // close server context to signal spawned goroutines to stop
}

func main() {
 ctx, cancel := context.withCancel()
 server := NewServer()
 server.Start(ctx)
 // Wait for program to terminate and then signal servers to stop
 ch := make(chan os.Signal, 1)
 signal.Notify(c, os.Interrupt, syscall.SIGTERM)
 
 <-ch
 cancel() // close main context on terminate signal
 server.Stop() // clean up server resources
}

2. 基于等待組(WaitGroup)的優雅停機

雖然我們通過取消主上下文向 goroutine 發出了退出信號,但并沒有等待它們完成工作。當主程序收到退出信號時,即使我們發送了取消信號,也不能保證它會等待生成的 goroutine 完成工作。因此我們必須明確等待每個 goroutine 完成工作,以避免任何泄漏,為此我們使用了 WaitGroup。

WaitGroup[3] 是一種計數器,用于阻止函數(或者說是 goroutine)的執行,直到其內部計數器變為 0。

package internal

type ServerA struct {}

func (s *ServerA) Run(ctx context.Context, wg *sync.WaitGroup){
  wg.Add(1) // Add the current function to the parent's wait group
  defer wg.Done() // Send "done" signal upon function exit
  
  grpcServer := grpc.NewServer()
  var listener net.Listener
  ln, err := net.Listen("tcp", ":8080")
  if err != nil {
   log.Fatal("ServerA - Failed to create listener")
  }
  
  for {
   err := grpcServer.Serve(listener)
   if err != nil {
     log.Fatal("ServerA - Failed to start server") 
   }
  }
  
  <- ctx.Done()
  // Clean up logic 
  grpcServer.Stop() // Gracefully terminate connections and close listener
  fmt.Println("ServerA has stopped")
}
package main

type GrpcServerInterface interface{
 Run(stopChan chan <-struct{})
}

type Server struct {
 ServerA GrpcServerIface
 ServerB GrpcServerIface
 wg sync.WaitGroup
 stopServer context.CancelFunc
 serverCtx context.Context
}

func NewServer() *Server {
 return &NewServer{
    ServerA: NewServerA,
    ServerB: NewServerB,
  }
}

// Start runs each of the grpc servers
func (s *Server) Start(ctx context.Context){
  s.serverCtx, stopServer := context.WithCancel(ctx)
  go ServerA.Run(s.serverCtx, &s.wg)
  go ServerB.Run(s.serverCtx, &s.wg)
}

func (s *Server) Stop() {
  s.stopServer() // close server context to signal spawned goroutines to stop
  s.wg.Wait()  // wait for all goroutines to exit before returning
  fmt.Println("Main Server has stopped")
}

func main() {
 ctx, cancel := context.withCancel()
 server := NewServer()
 server.Start(ctx)
 // Wait for program to terminate and then signal servers to stop
 ch := make(chan os.Signal, 1)
 signal.Notify(c, os.Interrupt, syscall.SIGTERM)
 
 <-ch
 cancel() // close main context on terminate signal
 server.Stop() // clean up server resources
}

3. 基于通道的啟動信號

在測試過程中,又發現了一個隱藏錯誤。我們未能在接受流量之前等待所有服務端啟動,而這在測試中造成了一些誤報,即流量被發送到服務端,但沒有實際工作。為了向主服務發送所有附屬服務都已準備就緒的信號,我們使用了通道。

package internal

type ServerA struct {
  startChan  
}

func (s *ServerA) Run(ctx context.Context, wg *sync.WaitGroup){
  wg.Add(1) // Add the current function to the parent's wait group
  defer wg.Done() // Send "done" signal upon function exit
   
  go func(){
    grpcServer := grpc.NewServer()
    
    var listener net.Listener
    ln, err := net.Listen("tcp", ":8080")
    if err != nil {
     log.Fatal("ServerA - Failed to create listener")
    }
    
    for {
     err := grpcServer.Serve(listener)
     if err != nil {
       log.Fatal("ServerA - Failed to start server") 
     }
    }
    close(s.startChan) // Signal that we are done starting server to exit function
    // Wait in the background for mina program to exit
    <- ctx.Done()
    // Clean up logic 
    grpcServer.Stop() // Gracefully terminate connections and close listener
    fmt.Println("ServerA has stopped")
  }()
  <- s.StartChan // Wait for signal before exiting function
  fmt.Println("ServerA has started")
}
package main

type GrpcServerInterface interface{
 Run(stopChan chan <-struct{})
}

type Server struct {
 ServerA GrpcServerIface
 ServerB GrpcServerIface
 wg sync.WaitGroup
 stopServer context.CancelFunc
 serverCtx context.Context
 startChan chan <-struct{}
}

func NewServer() *Server {
 return &NewServer{
    ServerA: NewServerA,
    ServerB: NewServerB,
    startChan: make(chan <-struct{}),
  }
}

// Start runs each of the grpc servers
func (s *Server) Start(ctx context.Context){
  s.serverCtx, stopServer := context.WithCancel(ctx)
  ServerA.Run(s.serverCtx, &s.wg)
  ServerB.Run(s.serverCtx, &s.wg)
  close(s.startChan)
  <- s.startChan // wait for each server to Start before returning
  fmt.Println("Main Server has started")
}

func (s *Server) Stop() {
  s.stopServer() // close server context to signal spawned goroutines to stop
  s.wg.Wait()  // wait for all goroutines to exit before returning
  fmt.Println("Main Server has stopped")
}

func main() {
 ctx, cancel := context.withCancel()
 server := NewServer()
 server.Start(ctx)
 // Wait for program to terminate and then signal servers to stop
 ch := make(chan os.Signal, 1)
 signal.Notify(c, os.Interrupt, syscall.SIGTERM)
 
 <-ch
 cancel() // close main context on terminate signal
 server.Stop() // clean up server resources
}

三、結論

不瞞你說,剛開始學習 Go 時,并發會讓你頭疼不已。調試這個問題讓我有機會看到這些概念的實際用途,并強化了之前不確定的主題,建議你自己嘗試簡單的示例!

參考資料:

  • [1]Go Concurrency — Graceful Shutdown: https:/medium.com/@goldengirlgeeks/go-graceful-shutdown-0c46e67ab9c9
  • [2]Goroutine: https://go.dev/tour/concurrency/1
  • [3]WaitGroup: https://www.geeksforgeeks.org/using-waitgroup-in-golang/amp
責任編輯:趙寧寧 來源: DeepNoMind
相關推薦

2021-09-13 05:02:49

GogRPC語言

2021-09-26 10:20:06

開發Golang代碼

2021-04-20 08:00:31

Redisson關閉訂單支付系統

2021-01-19 10:35:49

JVM場景函數

2021-09-01 23:29:37

Golang語言gRPC

2021-06-04 10:52:51

kubernetes場景容器

2023-12-05 07:26:21

Golang項目結構

2024-04-02 09:55:36

GolangColly開發者

2017-12-19 10:03:44

JavaLinux代碼

2024-11-13 16:37:00

Java線程池

2022-04-11 08:17:07

JVMJava進程

2024-04-28 18:24:05

2020-11-23 14:16:42

Golang

2022-02-20 23:15:46

gRPCGolang語言

2022-04-29 11:52:02

API代碼HTTP

2021-03-28 09:17:18

JVM場景鉤子函數

2024-10-21 15:39:24

2024-01-07 12:47:35

Golang流水線設計模式

2024-05-28 00:00:30

Golang數據庫

2018-12-17 16:39:20

Golang微服務
點贊
收藏

51CTO技術棧公眾號

亚洲精品成人自拍| 五月综合激情| 老司机免费视频久久| 欧美精品三级日韩久久| 国内精品久久久久伊人av| 亚洲乱码国产一区三区| 亚洲av无码乱码在线观看性色| 美女久久久久| 亚洲午夜免费电影| 国产剧情久久久久久| 国产精品无码毛片| 欧美性videos| 亚洲精品456| 亚洲综合激情另类小说区| 国产精品高清免费在线观看| 日本黄色网址大全| 91福利在线免费| 国产精品亚洲专一区二区三区| 国产香蕉97碰碰久久人人| 精品少妇人欧美激情在线观看| 97超碰人人草| 日韩国产欧美一区二区| 午夜精品福利一区二区三区蜜桃| av免费观看久久| 日本免费网站视频| 丁香婷婷久久| 中文字幕的久久| 国产精品爱久久久久久久| 国产精品免费人成网站酒店| 韩国理伦片久久电影网| 久久久久99精品国产片| 欧美一性一乱一交一视频| 成人在线视频免费播放| 国产精品13p| www.视频一区| 欧美最近摘花xxxx摘花| 香蕉视频久久久| 欧美电影免费观看| 久久久99精品免费观看不卡| 99久久自偷自偷国产精品不卡| 中文字幕乱码人妻二区三区| 成人羞羞网站入口免费| 在线观看区一区二| 亚洲v欧美v另类v综合v日韩v| 99re国产在线| 成人激情诱惑| 日韩av在线网页| 青青在线视频观看| www.在线播放| 精品一区中文字幕| 欧美成人免费一级人片100| 黄色小视频免费网站| av毛片在线免费| 波多野结衣在线aⅴ中文字幕不卡| 欧美性受xxxx黑人猛交| 日韩污视频在线观看| 在线亚洲a色| 亚洲精品资源美女情侣酒店| 色啦啦av综合| 青娱乐极品盛宴一区二区| 亚洲欧美激情视频在线观看一区二区三区| 国产成人精品免费视频大全最热| 中文字字幕在线中文| 国产欧美日韩精品一区二区三区 | 久久婷婷色综合| 国产精品青草久久久久福利99| 在线免费看av网站| 天天操综合网| 九九热99久久久国产盗摄| 久久一区二区电影| 91精品国产一区二区在线观看| 亚洲综合激情网| 蜜臀精品一区二区| 老司机深夜福利在线观看| 国产精品久久看| 国产一区高清视频| 岳乳丰满一区二区三区| 99热免费精品在线观看| 中文字幕精品网| 捆绑裸体绳奴bdsm亚洲| 偷拍视屏一区| 日韩欧美一区二区免费| 艹b视频在线观看| 国产亚洲成av人片在线观看| 欧美日韩一区二区免费在线观看| 一区二区三区四区免费观看| 久久国产精品高清一区二区三区| 国产高清精品久久久久| 国产精品久久激情| 国产精品久久婷婷| 老司机精品视频网站| 成人h猎奇视频网站| 日本在线播放视频| 免费久久99精品国产| 69久久夜色精品国产69乱青草| 国产精品成人在线视频| 亚洲三级精品| 久久精品国产欧美激情| 白白色免费视频| 亚洲女同另类| 自拍视频国产精品| 久久一区二区三| 欧美日本一区| 另类视频在线观看| 精品无码久久久久成人漫画| 亚洲青涩在线| 久久久久久久久久久国产| 538精品在线视频| 婷婷激情综合| 2019精品视频| jizz中国少妇| 国产精品系列在线观看| 国产欧美日韩综合精品二区| 无遮挡的视频在线观看 | 国模精品娜娜一二三区| 永久免费av在线| 欧美日韩精品国产| 又黄又爽又色的视频| 9999精品免费视频| 亚洲欧洲美洲在线综合| 国产一二三四五区| 国内自拍一区| 性欧美办公室18xxxxhd| 免费毛片一区二区三区| 亚洲人成毛片在线播放女女| 成人妇女淫片aaaa视频| 国产中文字幕在线观看| 午夜视频一区在线观看| 熟妇女人妻丰满少妇中文字幕| 日韩精品视频在线看| 欧美一二三区在线| 欧洲熟妇的性久久久久久| 久久午夜影院| 亚洲香蕉在线观看| 国产黄色片免费看| 免费一级片91| 欧美极品日韩| 日本在线免费看| 日本韩国欧美国产| 粉色视频免费看| 国内精品视频在线观看| 91wwwcom在线观看| 性猛交xxxx| 久久伊人蜜桃av一区二区| 精品无码av无码免费专区| 欧美v亚洲v| 亚洲影视资源网| 中文字幕55页| 永久91嫩草亚洲精品人人| 成人黄色av网站| 青青青青在线| 欧美精品亚洲二区| 少妇被躁爽到高潮无码文| 麻豆91小视频| 国产精华一区| 国产三级伦理在线| 欧美综合视频在线观看| 国产交换配乱淫视频免费| 久久国产中文字幕| 成人激情视频网| 亚洲七七久久综合桃花剧情介绍| 疯狂欧美牲乱大交777| 亚洲国产精品无码久久久久高潮 | 一本大道熟女人妻中文字幕在线| 日韩中文在线播放| 精品久久久久久久久久久久久久久久久 | 国产午夜精品一区二区| 亚洲一区bb| bl视频在线免费观看| 亚洲第一区中文字幕| 国产wwwwxxxx| 日韩福利电影在线观看| 成人午夜电影免费在线观看| 国模私拍视频在线播放| 日韩成人激情视频| 国产成人精品亚洲| 不卡视频一二三| 久久久一本二本三本| 精品中文在线| 中文字幕久热精品在线视频| 91精品国产乱码久久久久| 亚洲精品日韩综合观看成人91| 一边摸一边做爽的视频17国产| 日韩欧美一区二区三区在线视频| 成人久久一区二区三区| 久草在线资源站资源站| 亚洲免费人成在线视频观看| 久久久久无码国产精品| 91视视频在线观看入口直接观看www | av在线不卡观看免费观看| 日韩少妇内射免费播放18禁裸乳| 综合综合综合综合综合网| 成人黄色免费网站在线观看| 日本在线啊啊| 日韩在线播放视频| 日本a级c片免费看三区| 99综合电影在线视频| 别急慢慢来1978如如2| 欧美激情视频一区二区三区免费| 国产欧美一区二区三区在线看| 男女污视频在线观看| 亚洲第一搞黄网站| 国产chinesehd精品露脸| 91综合网人人| 国产在线精品一区免费香蕉| 成人福利电影| 中文字幕在线视频日韩| 香蕉视频网站在线| 欧美一区二区在线免费播放| 久久成人小视频| 久久精品72免费观看| www插插插无码视频网站| www.久久东京| 国内精品视频久久| av大片在线播放| 精品污污网站免费看| 欧美一区二区三区粗大| 国产.欧美.日韩| 无码人妻少妇伦在线电影| 精品欧美久久| 国产日产久久高清欧美一区| 草草在线视频| 欧美风情在线观看| 熟妇人妻av无码一区二区三区| 亚洲午夜视频在线| 精品亚洲乱码一区二区| 久久精品亚洲乱码伦伦中文| 99re这里只有| 成人午夜激情在线| 能在线观看的av| 黑丝一区二区| 中国一级大黄大黄大色毛片| 久久激情电影| 日韩资源av在线| 成人在线免费av| 欧美中文字幕视频在线观看| av美女在线观看| 欧美黑人视频一区| 日本在线视频www鲁啊鲁| 久久成人18免费网站| 精品欧美色视频网站在线观看| 制服丝袜成人动漫| 国产午夜小视频| 亚洲永久精品国产| 久久中文字幕无码| 亚洲一区二区三区爽爽爽爽爽| 精品欧美一区二区久久久久| 99久久er热在这里只有精品15| 欧美伦理片在线看| 三级久久三级久久| 国产福利影院在线观看| 国产精品多人| 成人免费在线网| 国产日韩欧美三区| 在线免费一区| 日韩精品社区| 你懂的网址一区二区三区| 亚洲欧美专区| 亚洲自拍欧美另类| 欧美aaa视频| 国产+人+亚洲| 91精品久久| 久久久久久久久91| 中国色在线日|韩| 欧美日本亚洲视频| 精精国产xxxx视频在线中文版| 色综合久久中文字幕综合网小说| 色呦呦视频在线观看| 91国产美女视频| 亚洲人免费短视频| 成人午夜黄色影院| 都市激情亚洲欧美| 成人在线播放av| 99久久香蕉| 日本一区免费观看| 亚洲视频国产| 91深夜福利视频| 精品无人乱码一区二区三区 | xx视频.9999.com| 亚洲妇熟xxxx妇色黄| 欧美亚洲激情视频| 日本成人一区二区| 国产精品久久久久久久久婷婷| 成人国产精品一区二区免费麻豆| 国产在线拍揄自揄视频不卡99| 亚洲天堂av资源在线观看| 欧美在线视频二区| 亚洲一区 二区 三区| 国产精品一区二区免费在线观看| 热久久免费视频| 欧美xxxxx在线视频| 激情综合激情| www.国产亚洲| 久久aⅴ乱码一区二区三区| 激情五月婷婷六月| 中出一区二区| 岳毛多又紧做起爽| 国产精品自在在线| 谁有免费的黄色网址| 亚洲综合偷拍欧美一区色| 99re这里只有精品在线| 欧美成人午夜电影| 18视频免费网址在线观看| 97在线精品视频| 国产精品xnxxcom| 国产综合福利在线| 欧洲vs亚洲vs国产| 久久久久高清| 免费欧美视频| 国产在线视频综合| 免费日本视频一区| 成人免费无码大片a毛片| 亚洲欧洲韩国日本视频| 少妇视频一区二区| 欧美性生交xxxxxdddd| 午夜影院免费在线观看| 日韩三级在线观看| 午夜视频在线观看网站| 欧日韩在线观看| 精品欧美午夜寂寞影院| 免费观看中文字幕| 免费人成精品欧美精品| 亚洲专区区免费| 亚洲va欧美va人人爽| 国产情侣在线播放| 日韩午夜在线观看| 日本成a人片在线观看| 国产精品激情av电影在线观看 | 免费在线小视频| 电影午夜精品一区二区三区| 一本到12不卡视频在线dvd| 一区二区三区国产免费| 久久婷婷国产综合国色天香 | 欧美三级一区二区| 免费看男男www网站入口在线| 国内精品久久久久久中文字幕| 视频精品二区| 日本a在线天堂| 国产高清视频一区| 精国产品一区二区三区a片| 精品magnet| 夜夜躁日日躁狠狠久久av| 精品网站999www| 蜜桃av.网站在线观看| 国产视频一区二区不卡| 欧美日韩精品| 午夜影院福利社| 性做久久久久久免费观看欧美| 丰满岳乱妇国产精品一区| 亚洲欧美在线磁力| 激情视频网站在线播放色 | 真实原创一区二区影院| 色综合av综合无码综合网站| 久久婷婷久久一区二区三区| 免费黄色小视频在线观看| 91精品国产福利| 黄色av电影在线观看| 亚洲自拍小视频| 亚洲香蕉网站| 性欧美丰满熟妇xxxx性久久久| 欧美日韩中国免费专区在线看| 精品无人乱码| 国产精品自产拍在线观看中文| 久久久久蜜桃| 国产综合免费视频| 欧美激情综合在线| 日本一级黄色录像| 亚洲精品视频久久| 日韩欧美2区| 中文字幕色呦呦| 麻豆成人免费电影| 欧美黑人猛猛猛| 亚洲国产高清高潮精品美女| 亚洲免费福利| 亚洲视频导航| 国产不卡免费视频| 日韩一区二区视频在线| 正在播放国产一区| 日韩欧美中文字幕在线视频| 久久精品视频16| 国产一区二区三区在线看麻豆| 美女久久久久久久久久| 欧美日本一道本| 久久综合九色综合久| 国产啪精品视频| 亚洲美女黄色| 国产又粗又黄又猛| 欧美va亚洲va香蕉在线| 成人做爰视频www网站小优视频| 国产又大又长又粗又黄| 免费不卡在线视频| 久久久精品一区二区涩爱| 亚洲区在线播放| 亚洲精品午夜| 黑森林精品导航| 亚洲国产精品久久久久婷婷884 | 国产伦精品一区二区三区在线播放| www.四虎成人| 亚洲永久免费视频| 2017亚洲天堂1024|