最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go標準庫http?server的優(yōu)雅關閉深入理解

 更新時間:2024年01月15日 10:56:36   作者:涼涼的知識庫  
這篇文章主要為大家介紹了Go標準庫http?server的優(yōu)雅有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪關閉深入理解

引言

本篇為【深入理解Go標準庫】系列第三篇

第一篇:http server的啟動

第二篇:ServeMux的使用與模式匹配

第三篇:http server的優(yōu)雅關閉??

本系列將持續(xù)更新,歡迎關注 ?? 獲取實時通知

還記得怎么啟動一個HTTP Server么?

package main

import (
 "net"
 "net/http"
)

func main() {
 // 方式1
 err := http.ListenAndServe(":8080", nil)
 if err != nil {
   panic(err)
 }
    
 // 方式2
 // server := &http.Server{Addr: ":8080"}
 // err := server.ListenAndServe()
 // if err != nil {
 //  panic(err)
 // }
}

ListenAndServe在不出錯的情況下,會一直阻塞在這個位置,如何停止這樣的一個HTTP Server呢?

CTRL+C是結束一個進程常用的方式,它和kill pid或者kill -l 15 pid命令本質上沒有任何區(qū)別,他們都是向進程發(fā)送了SIGTERM信號。因為程序沒有設置對SIGTERM信號的處理程序,所以系統(tǒng)默認的信號處理程序結束了我們的進程

這會帶來什么問題?

在服務器的進程被殺死時,我們的服務器可能正在處理請求并未完成。因此對于客戶端產生了一個預期外的錯誤

curl -v --max-time 4 127.0.0.1:8009/foo
* Connection #0 to host 127.0.0.1 left intact
*   Trying 127.0.0.1:8009...
* Connected to 127.0.0.1 (127.0.0.1) port 8009 (#0)
> GET /foo HTTP/1.1
> Host: 127.0.0.1:8009
> User-Agent: curl/7.86.0
> Accept: */*
> 
* Empty reply from server
* Closing connection 0
curl: (52) Empty reply from server

如果有nginx代理,因為upstream的中斷,nginx會產生502的響應

curl -v --max-time 11 127.0.0.1:8010/foo
*   Trying 127.0.0.1:8010...
* Connected to 127.0.0.1 (127.0.0.1) port 8010 (#0)
> GET /foo HTTP/1.1
> Host: 127.0.0.1:8010
> User-Agent: curl/7.86.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 502 Bad Gateway
< Server: nginx/1.25.3
< Date: Sat, 02 Dec 2023 10:14:33 GMT
< Content-Type: text/html
< Content-Length: 497
< Connection: keep-alive
< ETag: "6537cac7-1f1"

優(yōu)雅關閉的初步實現(xiàn)

優(yōu)雅關閉(graceful shutdown)指的是我們的HTTP Server關閉前既拒絕新來的請求,又正確的處理完正在進行中的請求,隨后進程退出。如何實現(xiàn)?

?? 異步啟動HTTP server

因為ListenAndServe會阻塞goroutine,如果還需要讓代碼繼續(xù)執(zhí)行,我們需要把它放到一個異步的goroutine中

go func() {
    if err := srv.ListenAndServe(); err != nil {
        panic(err)
    }
}()

?? 第二步:設置SIGTERM信號處理程序

操作系統(tǒng)默認的信號處理程序是直接結束進程,因此要實現(xiàn)graceful shutdown,要設置程序自己的信號處理程序。

Go中可以使用如下的方式來處理信號

  • signal.Notify來設置我們要監(jiān)聽的信號,一旦有程序設定的信號發(fā)生時,信號會被寫入channel中

  • signalCh chan os.Signal我們定義的是一個帶緩沖的channel,當channel中沒有數(shù)據時讀操作會阻塞

signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)

sig := &lt;-signalCh
log.Printf("Received signal: %v\n", sig)

?? 第三步:平滑的關閉HTTP Server

在自定義的信號處理程序中處理什么呢?

1、首先需要關閉端口的監(jiān)聽,此時新的請求就無法建立連接

2、對空閑的連接進行關閉

3、對進行中的連接等待處理完成,變成空閑連接后進行關閉

在Go 1.8以前實現(xiàn)上述操作需要編寫大量的代碼,也有一些第三方的庫(tylerstillwate/graceful、facebookarchive/grace等)可供使用。但Go1.8之后標準庫提供了 Shutdown()方法

?? 實現(xiàn):綜合上面三步有如下實現(xiàn)

func main() {
 mx := http.NewServeMux()
 mx.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
  time.Sleep(time.Duration(rand.Intn(10)) * time.Second)
  w.Write([]byte("Receive path foo\n"))
 })

 srv := http.Server{
  Addr:    ":8009",
  Handler: mx,
 }

 go func() {
  if err := srv.ListenAndServe(); err != nil {
   panic(err)
  }
 }()

 signalCh := make(chan os.Signal, 1)
 signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)

 sig := <-signalCh
 log.Printf("Received signal: %v\n", sig)

 if err := srv.Shutdown(context.Background()); err != nil {
  log.Fatalf("Server shutdown failed: %v\n", err)
 }

 log.Println("Server shutdown gracefully")
}

沒有收到SIGINT、SIGTERM信號前,main goroutine被signalCh的讀阻塞

一旦收到信號,signalCh的阻塞被解除會往下執(zhí)行server的Shutdown()Shutdown()函數(shù)會處理好活躍和非活躍的連接,并返回結果

上述代碼有什么問題么?

優(yōu)雅關閉實現(xiàn)的細節(jié)

?? 當Shutdown被調用時ListenAndServe會立刻返回http.ErrServerClosed的錯誤

go func() {
    if err := srv.ListenAndServe(); err != nil {
        panic(err)
    }
}()

對于上文的代碼,Shutdown()剛被調用,ListenAndServe所在的goroutine就拋出了panic,因而也導致main goroutine被退出,并沒有達到運行Shutdown()預期的效果

如果依舊想對ListenAndServe的錯誤拋出painc,需要忽略http.ErrServerClosed的錯誤

go func() {
    err := srv.ListenAndServe()
    if err != nil &amp;&amp; err != http.ErrServerClosed {
        panic(err)
    }
}()

?? 在有限的時間內關閉服務器

優(yōu)雅關閉過程中會等待進行中的請求完成。但請求處理的過程可能非常耗時,或者請求本身已經陷入了無法結束的狀態(tài),我們不可能無限的等待下去,因此設定一個關閉的上限時間會更穩(wěn)妥。

Shutdown()接受一個context.Context類型的參數(shù),我們可以用來設定超時時間

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

if err := srv.Shutdown(ctx); err != nil {
    log.Fatalf("Server shutdown failed: %v\n", err)
}

log.Println("Server shutdown gracefully")

通過ctx.Done()可以區(qū)分是否因為超時導致的服務器關閉,因而可以對不同的退出原因進行區(qū)分

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
    select {
        case <-ctx.Done():
        // 由于達到超時時間服務器關閉,未完成優(yōu)雅關閉
        log.Println("timeout of 5 seconds.")
        default:
        // 其他原因導致的服務關閉異常,未完成優(yōu)雅關閉
        log.Fatalf("Server shutdown failed: %v\n", err)
    }
    return
}
// 正確執(zhí)行優(yōu)雅關閉服務器
log.Println("Server shutdown gracefully")

?? 釋放其他資源

除了顯式的釋放資源,main goroutine也有必要通知其他goroutine進程即將退出,做必要的處理

例如,我們的服務在啟動后會向服務中心進行注冊,之后異步定時上報自身狀態(tài)。

為了讓注冊中心第一時間感知到服務已下線,需要主動注銷服務。在注銷服務前,需要先暫停異步的定時上報

context.Context讓我們可以很輕松的做到這件事

ctx, cancel := context.WithCancel(context.Background())
defer func() {
    cancel()
}()
// 需要在服務啟動后才在注冊中心注冊
go func() {
    tc := time.NewTicker(5 * time.Second)
    for {
        select {
            case <-tc.C:
            // 上報狀態(tài)
            log.Println("status update success")
            case <-ctx.Done():
            // server closed, return
            tc.Stop()
            log.Println("stop update success")
            return
        }
    }
}()

示例倉庫中還有一個更復雜的利用context.Context退出子goroutine的例子

?? 全貌

結合上面的所有的細節(jié),一個優(yōu)雅關閉的http server代碼如下

func registerService(ctx context.Context) {
 tc := time.NewTicker(5 * time.Second)
 for {
  select {
  case <-tc.C:
   // 上報狀態(tài)
   log.Println("status update success")
  case <-ctx.Done():
   tc.Stop()
   log.Println("stop update success")
   return
  }
 }
}
func destroyService() {
 log.Println("destroy success")
}
func gracefulShutdown() {
 mainCtx, mainCancel := context.WithCancel(context.Background())
 // 用ctx初始化資源,mysql,redis等
 // ...
 defer func() {
  mainCancel()
  // 主動注銷服務
  destroyService()
  // 清理資源,mysql,redis等
  // ...
 }()
 mx := http.NewServeMux()
 mx.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
  time.Sleep(time.Duration(rand.Intn(10)) * time.Second)
  w.Write([]byte("Receive path foo\n"))
 })
 srv := http.Server{
  Addr:    ":8009",
  Handler: mx,
 }
 // ListenAndServe也會阻塞,需要把它放到一個goroutine中
 go func() {
  if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
   panic(err)
  }
 }()
 // 需要在服務啟動后才在注冊中心注冊
 go registerService(mainCtx)
 signalCh := make(chan os.Signal, 1)
 signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)
 // 等待信號
 sig := <-signalCh
 log.Printf("Received signal: %v\n", sig)
 ctxTimeout, cancelTimeout := context.WithTimeout(context.Background(), 5*time.Second)
 defer cancelTimeout()
 if err := srv.Shutdown(ctxTimeout); err != nil {
  select {
  case <-ctxTimeout.Done():
   // 由于達到超時時間服務器關閉,未完成優(yōu)雅關閉
   log.Println("timeout of 5 seconds.")
  default:
   // 其他原因導致的服務關閉異常,未完成優(yōu)雅關閉
   log.Fatalf("Server shutdown failed: %v\n", err)
  }
  return
 }
 // 正確執(zhí)行優(yōu)雅關閉服務器
 log.Println("Server shutdown gracefully")
}

以上就是Go標準庫http server的優(yōu)雅關閉深入理解的詳細內容,更多關于Go標準庫http server關閉的資料請關注腳本之家其它相關文章!

相關文章

  • 在Go中使用JSON(附demo)

    在Go中使用JSON(附demo)

    Go開發(fā)人員經常需要處理JSON內容,本文主要介紹了在Go中使用JSON,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • Go?語言開發(fā)環(huán)境搭建過程

    Go?語言開發(fā)環(huán)境搭建過程

    這篇文章主要介紹了Go?語言開發(fā)環(huán)境搭建過程,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2024-03-03
  • golang解析html網頁的方法

    golang解析html網頁的方法

    今天小編就為大家分享一篇golang解析html網頁的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-08-08
  • Golang 按行讀取文件的三種方法小結

    Golang 按行讀取文件的三種方法小結

    本文主要介紹了Golang 按行讀取文件的三種方法小結,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-04-04
  • Golang中堆排序的實現(xiàn)

    Golang中堆排序的實現(xiàn)

    堆是一棵基于數(shù)組實現(xiàn)的特殊的完全二叉樹,本文主要介紹了Golang中堆排序的實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • 如何使用Goland IDE go mod 方式構建項目

    如何使用Goland IDE go mod 方式構建項目

    這篇文章主要介紹了如何使用Goland IDE go mod 方式構建項目,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-10-10
  • Go語言基礎學習之Context的使用詳解

    Go語言基礎學習之Context的使用詳解

    在Go語言中,Context是一個非常重要的概念,它用于在不同的?goroutine?之間傳遞請求域的相關數(shù)據,本文將深入探討Go語言中?Context特性和Context的高級使用方法,希望對大家有所幫助
    2023-05-05
  • Go語言網絡故障診斷與調試技巧

    Go語言網絡故障診斷與調試技巧

    在分布式系統(tǒng)和微服務架構的浪潮中,網絡編程成為系統(tǒng)性能和可靠性的核心支柱,從高并發(fā)的API服務到實時通信應用,網絡的穩(wěn)定性直接影響用戶體驗,本文面向熟悉Go基本語法和網絡編程概念,通過實際項目經驗、可運行的代碼示例和踩坑教訓,為你提供一套系統(tǒng)化的網絡診斷方法
    2025-08-08
  • 一文理解Go 中的可尋址和不可尋址

    一文理解Go 中的可尋址和不可尋址

    如果字典的元素不存在,則返回零值,而零值是不可變對象,如果能尋址問題就大了。而如果字典的元素存在,考慮到 Go 中 map 實現(xiàn)中元素的地址是變化的,這意味著尋址的結果也是無意義的。下面我們就圍繞這個話題寫一篇文章吧,需要的朋友可以參考一下
    2021-10-10
  • 詳解Go語言中泛型的實現(xiàn)原理與使用

    詳解Go語言中泛型的實現(xiàn)原理與使用

    本文是對泛型的基本思想及其在?Go?中的實現(xiàn)的一個比較容易理解的介紹,同時也是對圍繞泛型的各種性能討論的簡單總結,感興趣的可以學習一下
    2022-05-05

最新評論

惠东县| 黎平县| 宜丰县| 德江县| 苍南县| 怀仁县| 瓮安县| 娄烦县| 赫章县| 都兰县| 方城县| 昌乐县| 雅安市| 天门市| 仁寿县| 荔波县| 博白县| 大化| 库伦旗| 沭阳县| 龙陵县| 留坝县| 太保市| 北京市| 吉木萨尔县| 伊宁市| 苍溪县| 普定县| 乐清市| 宁南县| 建瓯市| 晋城| 宜黄县| 剑河县| 高安市| 乳源| 香格里拉县| 仁布县| 湖州市| 高雄县| 积石山|