欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Go語(yǔ)言如何熱重載和優(yōu)雅地關(guān)閉程序

 更新時(shí)間:2023年07月16日 09:14:42   作者:242030  
我們有時(shí)會(huì)因不同的目的去關(guān)閉服務(wù),一種關(guān)閉服務(wù)是終止操作系統(tǒng),一種關(guān)閉服務(wù)是用來(lái)更新配置,本文就來(lái)和大家簡(jiǎn)單講講這兩種方法的實(shí)現(xiàn)吧

我們有時(shí)會(huì)因不同的目的去關(guān)閉服務(wù),一種關(guān)閉服務(wù)是終止操作系統(tǒng),一種關(guān)閉服務(wù)是用來(lái)更新配置。

我們希望優(yōu)雅地關(guān)閉服務(wù)和通過(guò)熱重載重新加載配置,而這兩種方式可以通過(guò)信號(hào)包來(lái)完成。

1、代碼實(shí)現(xiàn)

package main
import (
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
)
type Config struct {
	Message string
}
var conf = &Config{Message: "Before hot reload"}
func router() {
	log.Println("starting up....")
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		_, _ = w.Write([]byte(conf.Message))
	})
	go func() {
		log.Fatal(http.ListenAndServe(":8080", nil))
	}()
}
func main() {
	router()
	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
	for {
		multiSignalHandler(<-sigCh)
	}
}
func multiSignalHandler(signal os.Signal) {
	switch signal {
	case syscall.SIGHUP:
		log.Println("Signal:", signal.String())
		log.Println("After hot reload")
		conf.Message = "Hot reload has been finished."
	case syscall.SIGINT:
		log.Println("Signal:", signal.String())
		log.Println("Interrupt by Ctrl+C")
		os.Exit(0)
	case syscall.SIGTERM:
		log.Println("Signal:", signal.String())
		log.Println("Process is killed.")
		os.Exit(0)
	default:
		log.Println("Unhandled/unknown signal")
	}
}

首先,定義了一個(gè) Config 結(jié)構(gòu)并聲明了一個(gè) conf 變量。

type Config struct {
	Message string
}
var conf = &Config{Message: "Before hot reload"}

這里的代碼只是一個(gè)簡(jiǎn)單的配置樣本,你可以根據(jù)自己的需要定義一個(gè)復(fù)雜的結(jié)構(gòu)。

其次,定義一個(gè)路由器函數(shù),用來(lái)綁定和監(jiān)聽(tīng) 8080 端口。在熱重載配置完成后,它也被用來(lái)顯示結(jié)果。

func router() {
    log.Println("starting up....")
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        _, _ = w.Write([]byte(conf.Message))
    })
    go func() {
        log.Fatal(http.ListenAndServe(":8080", nil))
    }()
}

下一步是服務(wù)器關(guān)機(jī)和熱重載配置,當(dāng)一個(gè)服務(wù)器關(guān)閉時(shí),它應(yīng)該停止接收新的請(qǐng)求,同時(shí)完成正在進(jìn)行的請(qǐng)求,

返回其響應(yīng),然后關(guān)閉,在這里使用信號(hào)包實(shí)現(xiàn)。

sigCh := make(chan os.Signal, 1)

之后,使用 signal.Notify() 一起發(fā)送更多的信號(hào)。

signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)

當(dāng)程序被中斷時(shí),signal.Notify 將向 sigCh 通道發(fā)送一個(gè)信號(hào)。

syscall.SIGHUP、syscall.SIGINT 和 syscall.SIGTERM 是什么意思?

  • syscall.SIGINT 是用來(lái)在 Ctrl+C 時(shí)優(yōu)雅地關(guān)閉的,它也相當(dāng)于 os.Interrupt。
  • syscall.SIGTERM 是常用的終止信號(hào),也是 docker 容器的默認(rèn)信號(hào),Kubernetes 也使用它。
  • syscall.SIGHUP 用于熱重載配置。

如何優(yōu)雅地關(guān)閉,multiSignalHandler(<-sigCh) 被用來(lái)接收 chan 值,然后它將決定運(yùn)行代碼的哪一部分。

func multiSignalHandler(signal os.Signal) {
    switch signal {
    case syscall.SIGHUP:
        log.Println("Signal:", signal.String())
        log.Println("After hot reload")
        conf.Message = "Hot reload has been finished."
    case syscall.SIGINT:
        log.Println("Signal:", signal.String())
        log.Println("Interrupt by Ctrl+C")
        os.Exit(0)
    case syscall.SIGTERM:
        log.Println("Signal:", signal.String())
        log.Println("Process is killed.")
        os.Exit(0)
    default:
        log.Println("Unhandled/unknown signal")
    }
}

2、測(cè)試優(yōu)雅關(guān)閉

首先,運(yùn)行服務(wù)器。

$ go run main.go
2023/06/25 09:57:05 starting up....

發(fā)送一個(gè)curl請(qǐng)求。

$ curl localhost:8080
Before hot reload

先用Ctrl+C測(cè)試一下中斷。

$ go run main.go
2023/06/25 09:57:05 starting up....
2023/06/25 09:59:45 Signal: interrupt
2023/06/25 09:59:45 Interrupt by Ctrl+C

3、熱重載

首先,運(yùn)行服務(wù)器。

$ go run main.go
2023/06/24 22:03:17 starting up....

然后,發(fā)送curl請(qǐng)求。

$ curl localhost:8080
Before hot reload

查看進(jìn)程:

$ lsof -i tcp:8080
COMMAND   PID USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
main    85193 root    3u  IPv6 13642377      0t0  TCP *:webcache (LISTEN)

使用 kill 殺死進(jìn)程:

$ kill -SIGHUP 85193

$ go run main.go
2023/06/24 22:03:17 starting up....
2023/06/24 22:06:05 Signal: hangup
2023/06/24 22:06:05 After hot reload

如果直接使用 kill 命令殺死程序:

$ go run main.go
2023/06/24 22:14:11 starting up....

$ lsof -i tcp:8080
COMMAND   PID USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
main    89619 root    3u  IPv6 13669401      0t0  TCP *:webcache (LISTEN)

$ kill -9 89619

$ go run main.go
2023/06/24 22:14:11 starting up....
2023/06/24 22:14:50 Signal: terminated
2023/06/24 22:14:50 Process is killed.

4、Go信號(hào)庫(kù)os/signal

在官方介紹中,這個(gè)庫(kù)主要封裝信號(hào)實(shí)現(xiàn)對(duì)輸入信號(hào)的訪(fǎng)問(wèn),信號(hào)主要用于類(lèi) Unix 系統(tǒng)。

信號(hào)是事件發(fā)生時(shí)對(duì)進(jìn)程的通知機(jī)制,有時(shí)也稱(chēng)之為軟件中斷。信號(hào)與硬件中斷的相似之處在于打斷了程序執(zhí)行的正常流程,大多數(shù)情況下,無(wú)法預(yù)測(cè)信號(hào)到達(dá)的精確時(shí)間。

因?yàn)橐粋€(gè)具有合適權(quán)限的進(jìn)程可以向另一個(gè)進(jìn)程發(fā)送信號(hào),這可以稱(chēng)為進(jìn)程間的一種同步技術(shù)。當(dāng)然,進(jìn)程也可以向自身發(fā)送信號(hào)。然而,發(fā)往進(jìn)程的諸多信號(hào),通常都是源于內(nèi)核。引發(fā)內(nèi)核為進(jìn)程產(chǎn)生信號(hào)的各類(lèi)事件如下:

硬件發(fā)生異常,即硬件檢測(cè)到一個(gè)錯(cuò)誤條件并通知內(nèi)核,隨即再由內(nèi)核發(fā)送相應(yīng)信號(hào)給相關(guān)進(jìn)程。比如執(zhí)行一條異常的機(jī)器語(yǔ)言指令(除0,引用無(wú)法訪(fǎng)問(wèn)的內(nèi)存區(qū)域)。

用戶(hù)鍵入了能夠產(chǎn)生信號(hào)的終端特殊字符。如中斷字符 (通常是 Control-C)、暫停字符(通常是 Control-Z)。

發(fā)生了軟件事件。如調(diào)整了終端窗口大小,定時(shí)器到期等。

4.1 舉例說(shuō)明

package main
import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
)
func main() {
	// os.Signal是一個(gè)系統(tǒng)信號(hào)接收channel
	c := make(chan os.Signal, 1)
	// syscall都是一些系統(tǒng)信號(hào)
	signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
	for {
		s := <-c
		fmt.Printf("get a signal %s", s.String())
		switch s {
		case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
			fmt.Printf("exit")
			os.Exit(0)
		case syscall.SIGHUP:
			fmt.Printf("reload")
		default:
			fmt.Printf("nothing")
		}
	}
}

1、首先初始化一個(gè) os.Signal 類(lèi)型的 channel,我們必須使用緩沖通道,否則在信號(hào)發(fā)送時(shí)如果還沒(méi)有準(zhǔn)備好接收信號(hào),就有丟失信號(hào)的風(fēng)險(xiǎn)。

2、signal.notify 用于監(jiān)聽(tīng)信號(hào),參數(shù)1表示接收信號(hào)的 channel,參數(shù)2及后面的表示要監(jiān)聽(tīng)的信號(hào):

  • syscall.SIGHUP 表示終端控制進(jìn)程結(jié)束
  • syscall.SIGQUIT 表示用戶(hù)發(fā)送QUIT字符 (Ctrl+/) 觸發(fā)
  • syscall.SIGTERM 表示結(jié)束進(jìn)程
  • syscall.SIGINT 表示用戶(hù)發(fā)送INTR字符 (Ctrl+C) 觸發(fā)

3、<-c 一直阻塞直到接收到信號(hào)退出。

對(duì)于上面的的程序是優(yōu)雅的退出守護(hù)進(jìn)程,接下來(lái)就是一些釋放資源或dump進(jìn)程當(dāng)前狀態(tài)或記錄日志的動(dòng)作,完成這些后,主進(jìn)程退出。

4.2 GO的信號(hào)類(lèi)型

4.2.1 POSIX.1-1990標(biāo)準(zhǔn)中定義的信號(hào)列表

4.2.2 在SUSv2和POSIX.1-2001標(biāo)準(zhǔn)中的信號(hào)列表

信號(hào) SIGKILL 和 SIGSTOP 可能不會(huì)被程序捕獲,因此不會(huì)受此軟件包影響。

同步信號(hào)是由程序執(zhí)行中的錯(cuò)誤觸發(fā)的信號(hào):SIGBUS,SIGFPE 和 SIGSEGV。這些只在程序執(zhí)行時(shí)才被認(rèn)為是同步的,而不是在使用 os.Process.Kill 或 kill 程序或類(lèi)似的機(jī)制發(fā)送時(shí)。一般來(lái)說(shuō),除了如下所述,Go 程序會(huì)將同步信號(hào)轉(zhuǎn)換為運(yùn)行時(shí)異常。其余信號(hào)是異步信號(hào),它們不是由程序錯(cuò)誤觸發(fā)的,而是從內(nèi)核或其他程序發(fā)送的。

在異步信號(hào)中,SIGHUP 信號(hào)在程序失去其控制終端時(shí)發(fā)送。當(dāng)控制終端的用戶(hù)按下中斷字符(默認(rèn)為^ C

(Control-C))時(shí),發(fā)送 SIGINT 信號(hào)。當(dāng)控制終端的用戶(hù)按下退出字符時(shí)發(fā)送 SIGQUIT 信號(hào),默認(rèn)為^ \

(Control-Backslash)。一般情況下,您可以通過(guò)按^ C來(lái)使程序簡(jiǎn)單地退出,并且可以通過(guò)按^使堆棧轉(zhuǎn)儲(chǔ)退出。

4.3 Kill命令的原理

我們平時(shí)在 Linux 系統(tǒng)會(huì) kill 命令來(lái)殺死進(jìn)程,那其中的原理是什么呢。

4.3.1 kill pid

kill pid 的作用是向進(jìn)程號(hào)為 pid 的進(jìn)程發(fā)送 SIGTERM (這是 kill 默認(rèn)發(fā)送的信號(hào)),該信號(hào)是一個(gè)結(jié)束進(jìn)程的信號(hào)且可以被應(yīng)用程序捕獲。若應(yīng)用程序沒(méi)有捕獲并響應(yīng)該信號(hào)的邏輯代碼,則該信號(hào)的默認(rèn)動(dòng)作是 kill 掉進(jìn)程。這是終止指定進(jìn)程的推薦做法。

4.3.2 kill -9 pid

kill -9 pid 則是向進(jìn)程號(hào)為 pid 的進(jìn)程發(fā)送 SIGKILL (該信號(hào)的編號(hào)為9),從本文上面的說(shuō)明可知,SIGKILL 既不能被應(yīng)用程序捕獲,也不能被阻塞或忽略,其動(dòng)作是立即結(jié)束指定進(jìn)程。通俗地說(shuō),應(yīng)用程序根本無(wú)法感知 SIGKILL信號(hào),它在完全無(wú)準(zhǔn)備的情況下,就被收到 SIGKILL 信號(hào)的操作系統(tǒng)給干掉了,顯然,在這種暴力情況下,應(yīng)用程序完全沒(méi)有釋放當(dāng)前占用資源的機(jī)會(huì)。事實(shí)上,SIGKILL 信號(hào)是直接發(fā)給 init 進(jìn)程的,它收到該信號(hào)后,負(fù)責(zé)終止 pid 指定的進(jìn)程。在某些情況下(如進(jìn)程已經(jīng) hang 死,無(wú)響應(yīng)正常信號(hào)),就可以使用 kill -9 來(lái)結(jié)束進(jìn)程。

到此這篇關(guān)于詳解Go語(yǔ)言如何熱重載和優(yōu)雅地關(guān)閉程序的文章就介紹到這了,更多相關(guān)Go語(yǔ)言?xún)?yōu)雅關(guān)閉程序內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 如何用golang運(yùn)行第一個(gè)項(xiàng)目

    如何用golang運(yùn)行第一個(gè)項(xiàng)目

    這篇文章主要介紹了如何用golang運(yùn)行第一個(gè)項(xiàng)目,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • golang?鏈路追蹤的實(shí)現(xiàn)示例

    golang?鏈路追蹤的實(shí)現(xiàn)示例

    本文主要介紹了golang?鏈路追蹤的實(shí)現(xiàn)示例,包括調(diào)用鏈過(guò)長(zhǎng)和接口響應(yīng)慢的問(wèn)題,具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-03-03
  • 最新評(píng)論