亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Go標(biāo)準(zhǔn)庫http?server的優(yōu)雅關(guān)閉深入理解

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

引言

本篇為【深入理解Go標(biāo)準(zhǔn)庫】系列第三篇

第一篇:http server的啟動(dòng)

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

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

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

還記得怎么啟動(dòng)一個(gè)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在不出錯(cuò)的情況下,會(huì)一直阻塞在這個(gè)位置,如何停止這樣的一個(gè)HTTP Server呢?

CTRL+C是結(jié)束一個(gè)進(jìn)程常用的方式,它和kill pid或者kill -l 15 pid命令本質(zhì)上沒有任何區(qū)別,他們都是向進(jìn)程發(fā)送了SIGTERM信號(hào)。因?yàn)槌绦驔]有設(shè)置對(duì)SIGTERM信號(hào)的處理程序,所以系統(tǒng)默認(rèn)的信號(hào)處理程序結(jié)束了我們的進(jìn)程

這會(huì)帶來什么問題?

在服務(wù)器的進(jìn)程被殺死時(shí),我們的服務(wù)器可能正在處理請(qǐng)求并未完成。因此對(duì)于客戶端產(chǎn)生了一個(gè)預(yù)期外的錯(cuò)誤

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代理,因?yàn)閡pstream的中斷,nginx會(huì)產(chǎn)生502的響應(yīng)

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)雅關(guān)閉的初步實(shí)現(xiàn)

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

?? 異步啟動(dòng)HTTP server

因?yàn)?code>ListenAndServe會(huì)阻塞goroutine,如果還需要讓代碼繼續(xù)執(zhí)行,我們需要把它放到一個(gè)異步的goroutine中

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

?? 第二步:設(shè)置SIGTERM信號(hào)處理程序

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

Go中可以使用如下的方式來處理信號(hào)

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

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

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

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

?? 第三步:平滑的關(guān)閉HTTP Server

在自定義的信號(hào)處理程序中處理什么呢?

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

2、對(duì)空閑的連接進(jìn)行關(guān)閉

3、對(duì)進(jìn)行中的連接等待處理完成,變成空閑連接后進(jìn)行關(guān)閉

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

?? 實(shí)現(xiàn):綜合上面三步有如下實(shí)現(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")
}

沒有收到SIGINTSIGTERM信號(hào)前,main goroutine被signalCh的讀阻塞

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

上述代碼有什么問題么?

優(yōu)雅關(guān)閉實(shí)現(xiàn)的細(xì)節(jié)

?? 當(dāng)Shutdown被調(diào)用時(shí)ListenAndServe會(huì)立刻返回http.ErrServerClosed的錯(cuò)誤

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

對(duì)于上文的代碼,Shutdown()剛被調(diào)用,ListenAndServe所在的goroutine就拋出了panic,因而也導(dǎo)致main goroutine被退出,并沒有達(dá)到運(yùn)行Shutdown()預(yù)期的效果

如果依舊想對(duì)ListenAndServe的錯(cuò)誤拋出painc,需要忽略http.ErrServerClosed的錯(cuò)誤

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

?? 在有限的時(shí)間內(nèi)關(guān)閉服務(wù)器

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

Shutdown()接受一個(gè)context.Context類型的參數(shù),我們可以用來設(shè)定超時(shí)時(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ū)分是否因?yàn)槌瑫r(shí)導(dǎo)致的服務(wù)器關(guān)閉,因而可以對(duì)不同的退出原因進(jìn)行區(qū)分

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

?? 釋放其他資源

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

例如,我們的服務(wù)在啟動(dòng)后會(huì)向服務(wù)中心進(jìn)行注冊(cè),之后異步定時(shí)上報(bào)自身狀態(tài)。

為了讓注冊(cè)中心第一時(shí)間感知到服務(wù)已下線,需要主動(dòng)注銷服務(wù)。在注銷服務(wù)前,需要先暫停異步的定時(shí)上報(bào)

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

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

示例倉庫中還有一個(gè)更復(fù)雜的利用context.Context退出子goroutine的例子

?? 全貌

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

func registerService(ctx context.Context) {
 tc := time.NewTicker(5 * time.Second)
 for {
  select {
  case <-tc.C:
   // 上報(bào)狀態(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()
  // 主動(dòng)注銷服務(wù)
  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也會(huì)阻塞,需要把它放到一個(gè)goroutine中
 go func() {
  if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
   panic(err)
  }
 }()
 // 需要在服務(wù)啟動(dòng)后才在注冊(cè)中心注冊(cè)
 go registerService(mainCtx)
 signalCh := make(chan os.Signal, 1)
 signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)
 // 等待信號(hào)
 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():
   // 由于達(dá)到超時(shí)時(shí)間服務(wù)器關(guān)閉,未完成優(yōu)雅關(guān)閉
   log.Println("timeout of 5 seconds.")
  default:
   // 其他原因?qū)е碌姆?wù)關(guān)閉異常,未完成優(yōu)雅關(guān)閉
   log.Fatalf("Server shutdown failed: %v\n", err)
  }
  return
 }
 // 正確執(zhí)行優(yōu)雅關(guān)閉服務(wù)器
 log.Println("Server shutdown gracefully")
}

以上就是Go標(biāo)準(zhǔn)庫http server的優(yōu)雅關(guān)閉深入理解的詳細(xì)內(nèi)容,更多關(guān)于Go標(biāo)準(zhǔn)庫http server關(guān)閉的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Goland的設(shè)置與配置全過程

    Goland的設(shè)置與配置全過程

    這篇文章主要介紹了Goland的設(shè)置與配置全過程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • Go?Java?算法之字符串解碼示例詳解

    Go?Java?算法之字符串解碼示例詳解

    這篇文章主要為大家介紹了Go?Java?算法之字符串解碼示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • golang bufio包中Write方法的深入講解

    golang bufio包中Write方法的深入講解

    這篇文章主要給大家介紹了關(guān)于golang bufio包中Write方法的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-02-02
  • Go語言CSP并發(fā)模型實(shí)現(xiàn)MPG

    Go語言CSP并發(fā)模型實(shí)現(xiàn)MPG

    這篇文章主要為大家介紹了Go語言CSP并發(fā)模型實(shí)現(xiàn)MPG圖文詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • Go java 算法之括號(hào)生成示例詳解

    Go java 算法之括號(hào)生成示例詳解

    這篇文章主要為大家介紹了Go java 算法之括號(hào)生成示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • Go string轉(zhuǎn)int,int64,int32及注意事項(xiàng)說明

    Go string轉(zhuǎn)int,int64,int32及注意事項(xiàng)說明

    這篇文章主要介紹了Go string轉(zhuǎn)int,int64,int32及注意事項(xiàng)說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • go語言阻塞函數(shù)和非阻塞函數(shù)實(shí)現(xiàn)

    go語言阻塞函數(shù)和非阻塞函數(shù)實(shí)現(xiàn)

    本文主要介紹了go語言阻塞函數(shù)和非阻塞函數(shù)實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • 重學(xué)Go語言之如何使用Context

    重學(xué)Go語言之如何使用Context

    Context,中文也叫做上下文,Go語言在1.7版本中新增的context包中定義了Context,下面我們就來一起看看如何在Go語言中使用Context吧
    2023-07-07
  • Golang中tinyrpc框架的源碼解讀詳解

    Golang中tinyrpc框架的源碼解讀詳解

    tinyrpc是一個(gè)高性能的基于protocol?buffer的rpc框架。項(xiàng)目代碼非常少,很適合初學(xué)者進(jìn)行g(shù)olang的學(xué)習(xí)。本文將從源碼的角度帶大家了解tinyrpc框架的使用,需要的可以參考一下
    2023-01-01
  • Go語言學(xué)習(xí)之JSON編碼解析與使用

    Go語言學(xué)習(xí)之JSON編碼解析與使用

    這篇文章主要為大家詳細(xì)介紹了Go語言中JSON編碼的解析與使用已經(jīng)JSON與Map、結(jié)構(gòu)體的互相轉(zhuǎn)化,文中的示例代碼講解詳細(xì),需要的可以參考一下
    2023-02-02

最新評(píng)論