中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Golang怎么使用channel實現一個優雅退出功能

發布時間:2023-03-09 11:27:35 來源:億速云 閱讀:68 作者:iii 欄目:開發技術

這篇文章主要介紹了Golang怎么使用channel實現一個優雅退出功能的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇Golang怎么使用channel實現一個優雅退出功能文章都會有所收獲,下面我們一起來看看吧。

    實現思路

    通過一個 os.Signal 類型的 chan 接收退出信號,收到信號后進行對應的退出收尾工作,利用 context.WithTimeouttime.After 等方式設置退出超時時間防止收尾等待時間過長。

    讀源碼

    由于 Hertz 的 Hook 功能中的 ShutdownHook 是 graceful shutdown 的一環,并且 Hook 功能的實現也不是很難所以這里就一起分析了,如果不想看直接跳到后面的章節即可 :)

    Hook

    Hook 函數是一個通用的概念,表示某事件觸發時所伴隨的操作,Hertz 提供了 StartHook 和 ShutdownHook 用于在服務觸發啟動后和退出前注入用戶自己的處理邏輯。

    兩種 Hook 具體是作為兩種不同類型的 Hertz Engine 字段,用戶可以直接以 append 的方式添加自己的 Hooks,下面是作為 Hertz Engine 字段的代碼:

    type Engine struct {
        ...
        
        // Hook functions get triggered sequentially when engine start
    	OnRun []CtxErrCallback
    
    	// Hook functions get triggered simultaneously when engine shutdown
    	OnShutdown []CtxCallback
        
        ...
    }

    可以看到兩者都是函數數組的形式,并且是公開字段,所以可以直接 append,函數的簽名如下,OnShutdown 的函數不會返回 error 因為都退出了所以沒法對錯誤進行處理:

    // OnRun
    type CtxCallback func(ctx context.Context)
    
    // OnShutdown
    type CtxErrCallback func(ctx context.Context) error

    并且設置的 StartHook 會按照聲明順序依次調用,但是 ShutdownHook 會并發的進行調用,這里的實現后面會講。

    StartHook 的執行時機

    觸發 Server 啟動后,框架會按函數聲明順序依次調用所有的 StartHook 函數,完成調用之后,才會正式開始端口監聽,如果發生錯誤,則立刻終止服務。

    上面是官方文檔中描述的 StartHook 的執行時機,具體在源碼中就是下面的代碼:

    func (engine *Engine) Run() (err error) {
    	...
    
    	// trigger hooks if any
    	ctx := context.Background()
    	for i := range engine.OnRun {
    		if err = engine.OnRun[i](ctx); err != nil {
    			return err
    		}
    	}
    
    	return engine.listenAndServe()
    }

    熟悉或使用過 Hertz 的同學肯定知道 h.Spin() 方法調用后會正式啟動 Hertz 的 HTTP 服務,而上面的 engine.Run 方法則是被 h.Spin 異步調用的。可以看到在 engine.Run 方法里循環調用 engine.OnRun 數組中注冊的函數,最后執行完成完成并且沒有 error 的情況下才會執行 engine.listenAndServe() 正式開始端口監聽,和官方文檔中說的一致,并且這里是通過 for 循環調用的所以也正如文檔所說框架會按函數聲明順序依次調用。

    ShutdownHook 的執行時機

    Server 退出前,框架會并發地調用所有聲明的 ShutdownHook 函數,并且可以通過 server.WithExitWaitTime配置最大等待時長,默認為5秒,如果超時,則立刻終止服務。

    上面是官方文檔中描述的 ShutdownHook 的執行時機,具體在源碼中就是下面的代碼:

    func (engine *Engine) executeOnShutdownHooks(ctx context.Context, ch chan struct{}) {
    	wg := sync.WaitGroup{}
    	for i := range engine.OnShutdown {
    		wg.Add(1)
    		go func(index int) {
    			defer wg.Done()
    			engine.OnShutdown[index](ctx)
    		}(i)
    	}
    	wg.Wait()
    	ch <- struct{}{}
    }

    通過 sync.WaitGroup 保證每個 ShutdownHook 函數都執行完畢后給形參 ch 發送信號通知,注意這里每個 ShutdownHook 都起了一個協程,所以是并發執行,這也是官方文檔所說的并發的進行調用。

    服務注冊與下線的執行時機

    服務注冊

    Hertz 雖然是一個 HTTP 框架,但是 Hertz 的客戶端和服務端可以通過注冊中心進行服務發現并進行調用,并且 Hertz 也提供了大部分常用的注冊中心擴展,在下面的 initOnRunHooks 方法中,通過注冊一個 StartHook 調用 Registry 接口的 Register 方法對服務進行注冊。

    func (h *Hertz) initOnRunHooks(errChan chan error) {
    	// add register func to runHooks
    	opt := h.GetOptions()
    	h.OnRun = append(h.OnRun, func(ctx context.Context) error {
    		go func() {
    			// delay register 1s
    			time.Sleep(1 * time.Second)
    			if err := opt.Registry.Register(opt.RegistryInfo); err != nil {
    				hlog.SystemLogger().Errorf("Register error=%v", err)
    				// pass err to errChan
    				errChan <- err
    			}
    		}()
    		return nil
    	})
    }

    取消注冊

    Shutdown 方法中進行調用 Deregister 取消注冊,可以看到剛剛提到的 executeOnShutdownHooks 的方法在開始異步執行后就會進行取消注冊操作。

    func (engine *Engine) Shutdown(ctx context.Context) (err error) {
    	...
    
    	ch := make(chan struct{})
    	// trigger hooks if any
    	go engine.executeOnShutdownHooks(ctx, ch)
    
    	defer func() {
    		// ensure that the hook is executed until wait timeout or finish
    		select {
    		case <-ctx.Done():
    			hlog.SystemLogger().Infof("Execute OnShutdownHooks timeout: error=%v", ctx.Err())
    			return
    		case <-ch:
    			hlog.SystemLogger().Info("Execute OnShutdownHooks finish")
    			return
    		}
    	}()
    
    	if opt := engine.options; opt != nil && opt.Registry != nil {
    		if err = opt.Registry.Deregister(opt.RegistryInfo); err != nil {
    			hlog.SystemLogger().Errorf("Deregister error=%v", err)
    			return err
    		}
    	}
    
    	...
    }

    Engine Status

    講 graceful shutdown 之前最好了解一下 Hertz Engine 的 status 字段以獲得更好的閱讀體驗ww

    type Engine struct {
        ...
        
        // Indicates the engine status (Init/Running/Shutdown/Closed).
        status uint32
        
        ...
    }

    如上所示,status 是一個 uint32 類型的內部字段,用來表示 Hertz Engine 的狀態,具體具有四種狀態(Init 1, Running 2, Shutdown 3, Closed 4),由下面的常量定義。

    const (
    	_ uint32 = iota
    	statusInitialized
    	statusRunning
    	statusShutdown
    	statusClosed
    )

    下面列出了 Hertz Engine 狀態改變的時機:

    函數狀態改變前狀態改變后
    engine.Init0Init (1)
    engine.RunInit (1)Running (2)
    engine.ShutdownRunning (2)Shutdown (3)
    engine.Run defer?Closed (4)

    對狀態的改變都是通過 atomic 包下的函數進行更改的,保證了并發安全。

    優雅退出

    Hertz Graceful Shutdown 功能的核心方法如下,signalToNotify 數組包含了所有會觸發退出的信號,觸發了的信號會傳向 signals 這個 channel,并且 Hertz 會根據收到信號類型決定進行優雅退出還是強制退出。

    // Default implementation for signal waiter.
    // SIGTERM triggers immediately close.
    // SIGHUP|SIGINT triggers graceful shutdown.
    func waitSignal(errCh chan error) error {
    	signalToNotify := []os.Signal{syscall.SIGINT, syscall.SIGHUP, syscall.SIGTERM}
    	if signal.Ignored(syscall.SIGHUP) {
    		signalToNotify = []os.Signal{syscall.SIGINT, syscall.SIGTERM}
    	}
    
    	signals := make(chan os.Signal, 1)
    	signal.Notify(signals, signalToNotify...)
    
    	select {
    	case sig := <-signals:
    		switch sig {
    		case syscall.SIGTERM:
    			// force exit
    			return errors.New(sig.String()) // nolint
    		case syscall.SIGHUP, syscall.SIGINT:
    			hlog.SystemLogger().Infof("Received signal: %s\n", sig)
    			// graceful shutdown
    			return nil
    		}
    	case err := <-errCh:
    		// error occurs, exit immediately
    		return err
    	}
    
    	return nil
    }

    如果 engine.Run 方法返回了一個錯誤則會通過 errCh 傳入 waitSignal 函數然后觸發立刻退出。前面也提到 h.Spin() 是以異步的方式調用 engine.RunwaitSignal 則由 h.Spin() 直接調用,所以運行后 Hertz 會阻塞在 waitSignal 函數的 select 這里等待信號。

    三個會觸發 Shutdown 的信號區別如下:

    • syscall.SIGINT 表示中斷信號,通常由用戶在終端上按下 Ctrl+C 觸發,用于請求程序停止運行;

    • syscall.SIGHUP 表示掛起信號,通常是由系統發送給進程,用于通知進程它的終端或控制臺已經斷開連接或終止,進程需要做一些清理工作;

    • syscall.SIGTERM 表示終止信號,通常也是由系統發送給進程,用于請求進程正常地終止運行,進程需要做一些清理工作;

    如果 waitSignal 的返回值為 nilh.Spin() 會進行優雅退出:

    func (h *Hertz) Spin() {
    	errCh := make(chan error)
    	h.initOnRunHooks(errCh)
    	go func() {
    		errCh <- h.Run()
    	}()
    
    	signalWaiter := waitSignal
    	if h.signalWaiter != nil {
    		signalWaiter = h.signalWaiter
    	}
    
    	if err := signalWaiter(errCh); err != nil {
    		hlog.SystemLogger().Errorf("Receive close signal: error=%v", err)
    		if err := h.Engine.Close(); err != nil {
    			hlog.SystemLogger().Errorf("Close error=%v", err)
    		}
    		return
    	}
    
    	hlog.SystemLogger().Infof("Begin graceful shutdown, wait at most num=%d seconds...", h.GetOptions().ExitWaitTimeout/time.Second)
    
    	ctx, cancel := context.WithTimeout(context.Background(), h.GetOptions().ExitWaitTimeout)
    	defer cancel()
    
    	if err := h.Shutdown(ctx); err != nil {
    		hlog.SystemLogger().Errorf("Shutdown error=%v", err)
    	}
    }

    并且 Hertz 通過 context.WithTimeout 的方式設置了優雅退出的超時時長,默認為 5 秒,用戶可以通過 WithExitWaitTime 方法配置 server 的優雅退出超時時長。將設置了超時時間的 ctx 傳入 Shutdown 方法,如果 ShutdownHook 先執行完畢則 ch channel 收到信號后返回退出,否則 Context 超時收到信號強制返回退出。

    func (engine *Engine) Shutdown(ctx context.Context) (err error) {
    	...
    
    	ch := make(chan struct{})
    	// trigger hooks if any
    	go engine.executeOnShutdownHooks(ctx, ch)
    
    	defer func() {
    		// ensure that the hook is executed until wait timeout or finish
    		select {
    		case <-ctx.Done():
    			hlog.SystemLogger().Infof("Execute OnShutdownHooks timeout: error=%v", ctx.Err())
    			return
    		case <-ch:
    			hlog.SystemLogger().Info("Execute OnShutdownHooks finish")
    			return
    		}
    	}()
    
    	...
    	return
    }

    以上就是 Hertz 優雅退出部分的源碼分析,可以發現 Hertz 多次利用了協程,通過 channel 傳遞信號進行流程控制和信息傳遞,并通過 Context 的超時機制完成了整個優雅退出流程。

    自己實現

    說是自己實現實際上也就是代碼搬運工,把 Hertz 的 graceful shutdown 及其相關功能給 PIANO 進行適配罷了ww

    代碼實現都差不多,一些小細節根據我個人的習慣做了修改,完整修改參考這個 commit,對 PIANO 感興趣的話歡迎 Star !

    適配 Hook

    type Engine struct {
        ...
    
    	// hook
    	OnRun      []HookFuncWithErr
    	OnShutdown []HookFunc
    
    	...
    }
    
    type (
    	HookFunc        func(ctx context.Context)
    	HookFuncWithErr func(ctx context.Context) error
    )
    
    func (e *Engine) executeOnRunHooks(ctx context.Context) error {
    	for _, h := range e.OnRun {
    		if err := h(ctx); err != nil {
    			return err
    		}
    	}
    	return nil
    }
    
    func (e *Engine) executeOnShutdownHooks(ctx context.Context, ch chan struct{}) {
    	wg := sync.WaitGroup{}
    	for _, h := range e.OnShutdown {
    		wg.Add(1)
    		go func(hook HookFunc) {
    			defer wg.Done()
    			hook(ctx)
    		}(h)
    	}
    	wg.Wait()
    	ch <- struct{}{}
    }

    適配 Engine Status

    type Engine struct {
    	...
        
    	// initialized | running | shutdown | closed
    	status uint32
    
        ...
    }
    
    const (
    	_ uint32 = iota
    	statusInitialized
    	statusRunning
    	statusShutdown
    	statusClosed
    )

    適配 Graceful Shutdown

    // Play the PIANO now
    func (p *Piano) Play() {
    	errCh := make(chan error)
    	go func() {
    		errCh <- p.Run()
    	}()
    	waitSignal := func(errCh chan error) error {
    		signalToNotify := []os.Signal{syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM}
    		if signal.Ignored(syscall.SIGHUP) {
    			signalToNotify = signalToNotify[1:]
    		}
    		signalCh := make(chan os.Signal, 1)
    		signal.Notify(signalCh, signalToNotify...)
    		select {
    		case sig := <-signalCh:
    			switch sig {
    			case syscall.SIGTERM:
    				// force exit
    				return errors.New(sig.String())
    			case syscall.SIGHUP, syscall.SIGINT:
    				// graceful shutdown
    				log.Infof("---PIANO--- Receive signal: %v", sig)
    				return nil
    			}
    		case err := <-errCh:
    			return err
    		}
    		return nil
    	}
    	if err := waitSignal(errCh); err != nil {
    		log.Errorf("---PIANO--- Receive close signal error: %v", err)
    		return
    	}
    	log.Infof("---PIANO--- Begin graceful shutdown, wait up to %d seconds", p.Options().ShutdownTimeout/time.Second)
    	ctx, cancel := context.WithTimeout(context.Background(), p.Options().ShutdownTimeout)
    	defer cancel()
    	if err := p.Shutdown(ctx); err != nil {
    		log.Errorf("---PIANO--- Shutdown err: %v", err)
    	}
    }

    關于“Golang怎么使用channel實現一個優雅退出功能”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“Golang怎么使用channel實現一個優雅退出功能”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    科技| 新密市| 苗栗市| 房山区| 荣昌县| 楚雄市| 清丰县| 伊吾县| 沙洋县| 海兴县| 安泽县| 宁海县| 客服| 新邵县| 河南省| 新竹县| 葵青区| 吉水县| 万山特区| 曲麻莱县| 洛隆县| 侯马市| 赫章县| 永安市| 南木林县| 平舆县| 娱乐| 安多县| 天津市| 额济纳旗| 尼木县| 武强县| 东莞市| 资阳市| 烟台市| 唐山市| 齐齐哈尔市| 富顺县| 东乌珠穆沁旗| 西丰县| 临江市|