溫馨提示×

溫馨提示×

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

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

怎么優(yōu)雅管理Go?Project生命周期

發(fā)布時間:2023-03-21 14:30:04 來源:億速云 閱讀:274 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹了怎么優(yōu)雅管理Go Project生命周期的相關(guān)知識,內(nèi)容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇怎么優(yōu)雅管理Go Project生命周期文章都會有所收獲,下面我們一起來看看吧。

    一、什么時候要注意管理應(yīng)用的生命周期?

    先來看一段代碼:(假設(shè)無 err 值)

    func main() {
        // 1、啟動HTTP服務(wù)
    	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    		fmt.Fprintln(w, "Hello, World!")
    	})
    	http.ListenAndServe(":8080", nil)
        // 2、啟動GRPC服務(wù)
        server := grpc.NewServer()
        listener, _ := net.Listen("tcp", ":1234")
    	server.Serve(listener)    
    }

    這一段代碼,相信你一眼就能看出問題,因為在啟動HTTP后,進程會堵塞住,下面啟動GRPC服務(wù)的代碼,壓根就不會執(zhí)行。

    但是,如果想要同時啟動GRPC服務(wù)呢?該怎么做呢?

    自己沒有時間,那么就請一個幫手咯,讓它來為我們啟動GRPC服務(wù),而這個幫手,就是go的攜程。

    • 來看一段偽代碼,也就是調(diào)整成這樣,

    func main() {
        // 1、將HTTP服務(wù)放在后臺啟動
        go start http
        // 2、將GRPC服務(wù)放在前臺啟動
        start grpc  
    }

    但是調(diào)整成這樣之后,理想的情況就是,HTTP成功啟動后、GRPC也要啟動成功。HTTP意外退出后,GRPC也需要退出服務(wù),他們倆需要共存亡。

    但若出現(xiàn)了 HTTP 意外退出、GRPC還未退出,那么就會浪費資源。還可能出現(xiàn)其他的問題。比如接口異常。這樣會很危險。那我們該利用什么方式,讓同一服務(wù)內(nèi),啟動多個線程。并且讓他們共同存亡的呢?

    了解了上面的問題,我們再來重新描述總結(jié)一下出現(xiàn)的問題。

    一個服務(wù),可能會啟動多個進程,比如說 HTTP API、GRPC API、服務(wù)的注冊,這些模塊都是獨立的,都是需要在程序啟動的時候進行啟動。

    而且如果需要關(guān)閉掉這個應(yīng)用,還需要處理很多關(guān)閉的問題。比如說

    • HTTP、GRPC 的優(yōu)雅關(guān)閉

    • 關(guān)閉數(shù)據(jù)庫鏈接

    • 完成注冊中心的注銷操作

    • ...

    而且,啟動的多個進程間,該如何通信呢? 某些服務(wù)意外退出了,按理來說要關(guān)閉整個應(yīng)用,該如何監(jiān)聽到呢?

    二、我們是如何做的

    (1)利用面向?qū)ο蟮姆绞絹砉芾響?yīng)用的生命周期

    定義一個管理者對象,來管理我們應(yīng)用所需要啟動的所有服務(wù),比如這里需要被我們啟動的服務(wù)有:HTTP、GRPC

    這個管理者核心有兩個方法:start、stop

    // 用于管理服務(wù)的開啟、和關(guān)閉
    type manager struct {
    	http *protocol.HttpService // HTTP生命周期的結(jié)構(gòu)體[自定義]
    	grpc *protocol.GRPCService // GRPC生命周期的結(jié)構(gòu)體[自定義]
    	l    logger.Logger		   // 日志對象
    }

    不用關(guān)心這里依賴的 http、grpc結(jié)構(gòu)體是什么,我們在后面的章節(jié),會詳細解釋。只需要知道,我們用manager這個結(jié)構(gòu)體,用于管理http、grpc服務(wù)即可。

    (2)處理start

    start這個函數(shù),核心只做了兩件事,分別啟動HTTP、GRPC服務(wù)。

    func (m *manager) start() error {
    	// 打印加載好的服務(wù)
    	m.l.Infof("已加載的 [Internal] 服務(wù): %s", ioc.ExistingInternalDependencies())
    	m.l.Infof("已加載的 [GRPC] 服務(wù): %s", ioc.ExistingGrpcDependencies())
    	m.l.Infof("已加載的 [HTTP] 服務(wù): %s", ioc.ExistingGinDependencies())
    	// 如果不需要啟動HTTP服務(wù),需要才啟動HTTP服務(wù)
    	if m.http != nil {
    		// 將HTTP放在后臺跑
    		go func() {
    			// 注:這屬于正常關(guān)閉:"http: Server closed"
    			if err := m.http.Start(); err != nil && err.Error() != "http: Server closed" {
    				return
    			}
    		}()
    	}
        // 將GRPC放入前臺啟動
    	m.grpc.Start()
    	return nil
    }

    又因為開頭說過了,啟動這兩任一服務(wù),都會將進程堵塞住。

    所以我們找了一個幫手(攜程)來啟動HTTP服務(wù),然后將GRPC服務(wù)放在前臺運行。

    那為什么我要將GRPC服務(wù)放在前臺運行呢?其實理論上放誰都行,但由于我們的架構(gòu)原因。我們有的服務(wù)不需要啟動HTTP服務(wù),而每一個服務(wù)都會啟動GRPC服務(wù)。所以,將GRPC放置在前臺,會更合適。

    至于里面如何使用HTTP、GRPC的服務(wù)對象啟動它們的服務(wù)。在這一節(jié)就不多贅述了。在之后的章節(jié)會有詳細的介紹~

    看完了統(tǒng)一管理啟動的start方法,那我們來看看如何停止服務(wù)吧

    (3)處理stop

    1、什么時候才去Stop?

    我們開啟了多個服務(wù),并且有的還是放在后臺運行的。這就涉及到了多個攜程的間通信的問題了

    用什么來通信吶?我怎么知道HTTP服務(wù)掛沒掛?是意外掛的還是主動掛的?我們怎么能夠優(yōu)雅的統(tǒng)一關(guān)閉所有服務(wù)呢?

    其實這一切的問題,Go都為我們想好了:那就是使用Channels。一個channel是一個通信機制,它可以讓一個攜程通過它給另一個攜程發(fā)送值信息。每個channel都有一個特殊的類型,也就是channels可發(fā)送數(shù)據(jù)的類型。

    我們把一個go程當(dāng)作一個人的化,那么main 方法啟動的主go程就是你自己。在你的程序中使用到的其他go程,都是你的好幫手,你的好朋友,它們有給你去處理耗時邏輯的、有給你去執(zhí)行業(yè)務(wù)無關(guān)的切面邏輯的。而且是你的好幫手,按理來說最好是由你自己去決定,要不要請一個好幫手。

    當(dāng)你請來了一個好幫手后,它們會在你的背后為你做你讓他們做的事情。那么多個人之間的通信,比較現(xiàn)代的方法,那可以是:打個電話?發(fā)個消息?所以用到了一個溝通的信道:Channel

    好了,當(dāng)你了解了這些后,也就是接收到一些電話后,我們才需要去stop。我們再回到Dousheng使用的情景:

    2、Dousheng的應(yīng)用場景

    主攜程是GRPC服務(wù)這個人,我們請了一個幫手,給我啟動HTTP服務(wù)。這個時候,如果HTTP服務(wù)這個幫手意外出事了。既然是幫我么你做事,那我們肯定得對別人負責(zé)是吧。但是我們也不知道它出不出意外啊,怎么辦呢?這時候你想了兩個方法:

    • 跟你的幫手HTTP,發(fā)了如下消息

    怎么優(yōu)雅管理Go?Project生命周期

    這就需要HTTP自己告訴我們,按理來說,應(yīng)該是可以的。但是如果HTTP遇到了重大問題,根本來不及告訴我們呢?咱們又是一個負責(zé)的男人。為了避免這種情況發(fā)生,又請一個人,專門給我們看HTTP有沒有遇到重大問題。于是有了第二種方式:

    • 在請一個幫手signal.Notify,幫助我們監(jiān)聽HTTP可能會遇到的重大問題

    怎么優(yōu)雅管理Go?Project生命周期

    當(dāng)我們收到HTTP出事的信號后,那我們就可以統(tǒng)一的去優(yōu)雅關(guān)閉服務(wù)了。就這樣,我們做了一個負責(zé)的人~

    相信你已經(jīng)了解了核心的思想,我們來看看,用代碼該如何實現(xiàn)

    3、代碼實現(xiàn)
    • 啟動signal.Notify,用于監(jiān)聽系統(tǒng)信號

    我們已經(jīng)分析過了,我們需要再請一個幫手,來給我們處理HTTP可能會遇到的重大事故:(syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP, syscall.SIGINT)

    // WaitSign 等待退出的信號,實現(xiàn)優(yōu)雅退出
    func (m *manager) waitSign() {
       // 用于接收信號的信道
       ch := make(chan os.Signal, 1)
       // 接收這幾種信號
       signal.Notify(ch, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP, syscall.SIGINT)
       // 需要在后臺等待關(guān)閉
       go m.waitStop(ch)
    }

    當(dāng)signal.Notify收到上面所列舉的信號后,那么就可以去做關(guān)閉的事情了,那如何關(guān)閉呢?

    • 讀取信號,執(zhí)行優(yōu)雅關(guān)閉邏輯

    // WaitStop 中斷信號,比如Terminal [關(guān)閉服務(wù)的方法]
    func (m *manager) waitStop(ch <-chan os.Signal) {
       // 等待信號,若收到了,我們進行服務(wù)統(tǒng)一的關(guān)閉
       for v := range ch {
          switch v {
          default:
             m.l.Infof("接受到信號:%s", v)
             // 優(yōu)雅關(guān)閉HTTP服務(wù)
             if m.http != nil {
                if err := m.http.Stop(); err != nil {
                   m.l.Errorf("優(yōu)雅關(guān)閉 [HTTP] 服務(wù)出錯:%s", err.Error())
                }
             }
    		// 優(yōu)雅關(guān)閉GRPC服務(wù)
             if err := m.grpc.Stop(); err != nil {
                m.l.Errorf("優(yōu)雅關(guān)閉 [GRPC] 服務(wù)出錯:%s", err.Error())
             }
          }
       }
    }

    這里的邏輯比較簡單,就是當(dāng)接收到信號的時候,對HTTP、GRPC做優(yōu)雅關(guān)閉的邏輯。至于為什么要進行優(yōu)雅關(guān)閉,而不是直接os.Exit()?我們在下一節(jié)講~

    這里值得一提的是,我們從chanel里獲取數(shù)據(jù),因為我們這里只和單個攜程間進行通信了,使用的是 for range,并沒有使用for select

    好了,這樣我們應(yīng)用的生命周期算是被我們優(yōu)雅的拿捏了。我們一直在講優(yōu)雅關(guān)閉這個詞,我們來解釋一下什么是優(yōu)雅關(guān)閉?為什么需要優(yōu)雅關(guān)閉?

    三、什么是優(yōu)雅關(guān)閉

    既然HTTP服務(wù)和GRPC服務(wù)都需要優(yōu)雅關(guān)閉,我們這里用HTTP服務(wù)來舉例。

    先來看這張圖,假設(shè)有三個并行的請求至我們的HTTP服務(wù)。它們都期望得到服務(wù)器response。HTTP服務(wù)器正常運行的情況下,多半是沒問題的。

    怎么優(yōu)雅管理Go?Project生命周期

    請求已發(fā)出,若提供的HTTP服務(wù)突然異常關(guān)閉了呢?我們繼續(xù)來把HTTP服務(wù)比作一個人。看看它是否優(yōu)雅呢?

    (1)沒有優(yōu)雅關(guān)閉

    如果HTTP這個人不太優(yōu)雅,是一個做事不怎么負責(zé)的渣男。當(dāng)自己異常over了之后,也不解決完自己的事情,就讓別人(request),找不到資源了。真的很不負責(zé)啊。

    大致用一幅圖表示:

    怎么優(yōu)雅管理Go?Project生命周期

    這個不優(yōu)雅的HTTP服務(wù),當(dāng)有還未處理的請求時,自己就異常關(guān)閉了,那么它根本不會理會原先的請求是否完成了。它只管自己退出程序。

    (2)有了優(yōu)雅關(guān)閉

    看完了那個渣男HTTP(沒有優(yōu)雅關(guān)閉),我們簡直想罵它了。那我們來看,當(dāng)一個優(yōu)雅的謙謙君子(有優(yōu)雅關(guān)閉),又是如何看待這個問題的。

    怎么優(yōu)雅管理Go?Project生命周期

    這是一個負責(zé)人的人,為什么說他負責(zé)人、說它優(yōu)雅呢?因為當(dāng)它自己接收到異常關(guān)閉的信號后。它不會只顧自己關(guān)閉。它大概還會做兩件事:

    • 關(guān)閉建立連接的請求通道,防止還會接收到新的請求

    • 處理完以請求的,但是還未響應(yīng)的請求。保證資源得到響應(yīng),哪怕是錯誤的response。

    正是因為它主要做了這兩件事,我們才說此時的HTTP服務(wù),是一個優(yōu)雅的謙謙君子。

    而當(dāng)有很多個請求到時候,我們怎么知道是否會不會突然異常關(guān)閉呢?如果遇到了這種情況,我們應(yīng)該處理完未完成的響應(yīng),拒絕新的請求建立連接,因為我們是一個優(yōu)雅的人。

    關(guān)于“怎么優(yōu)雅管理Go Project生命周期”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對“怎么優(yōu)雅管理Go Project生命周期”知識都有一定的了解,大家如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道。

    向AI問一下細節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI