讀 "優雅關閉的 Go Web 伺服器"
GitHub 倉庫:https://github.com/stayfoo/stayfoo-hub
文章《優雅的關閉 Go Web
伺服器》( Go 語言中文網 ) 寫到:可以通過開啟一個單獨的 goroutine
攔截關閉訊號,這樣,當伺服器真正關閉之前,可以做一些任務,做完任務再發出執行完畢的關閉號。
一些任務比如:清理某些資源;完成資料庫事務;一些其他長時間的操作;退出服務的那一刻,剛好收到一個響應,為了保證所有請求完成,就可以在這裡,在最大超時時間內去處理這個響應;dump 程式當前狀態;記錄日誌的動作。
啟動應用,Ctrl + C
中斷程式,中斷訊號被攔截,do something.....
go run main.go -listen-addr=5001
http: 2019/08/31 11:34:30 Server is ready to handle requests at :5001
^Chttp: 2019/08/31 11:34:32 Server is shutting down...
do something start ..... 2019-08-31 11:34:32.594668 +0800 CST m=+2.337451148
do something end ..... 2019-08-31 11:34:37.598248 +0800 CST m=+7.340881516
http: 2019/08/31 11:34:37 Server stopped
對文中程式碼做了改造,程式碼如下:
var listenAddr string
func init() {
//接收埠號,預設埠號:5000
flag.StringVar(&listenAddr, "listen-addr", ":5000", "server listen address")
}
func main() {
flag.Parse() //外部引數解析
listenAddr = fmt.Sprintf(":%s",listenAddr)
logger := log.New(os.Stdout, "http: ", log.LstdFlags)
//建立 server:
server := newWebServer(logger)
done := make(chan struct{}, 1)
quit := make(chan os.Signal, 1)
//os.Interrupt: syscall.SIGINT
signal.Notify(quit, os.Interrupt)
//啟動另一個 goroutine ,監聽將要關閉訊號:
go shutdown(server, logger, quit, done)
//啟動 server:
logger.Println("Server is ready to handle requests at",listenAddr)
err := server.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
logger.Fatalf("Could not listen on %s: %v \n", listenAddr, err)
}
//等待已經關閉的訊號:
<-done
logger.Println("Server stopped")
}
//初始化 server
func newWebServer(logger *log.Logger) *http.Server {
//路由:
router := http.NewServeMux()
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
//http 服務配置:
server := &http.Server{
Addr: listenAddr,
Handler: router,
ErrorLog: logger,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 15 * time.Second,
}
return server
}
//關閉 server
//quit: 接收關閉訊號
//done: 發出已經關閉訊號
func shutdown(server *http.Server, logger *log.Logger, quit <-chan os.Signal, done chan<- struct{}) {
//等待接收到退出訊號:
<-quit
logger.Println("Server is shutting down...")
ctx, cancel := context.WithTimeout(context.Background(), 30 * time.Second)
defer cancel()
server.SetKeepAlivesEnabled(false)
err := server.Shutdown(ctx)
if err != nil {
logger.Fatalf("Could not gracefully shutdown the server: %v \n", err)
}
//do Something :
fmt.Println("do something start ..... ", time.Now())
time.Sleep(5 * time.Second)
fmt.Println("do something end ..... ", time.Now())
close(done)
}
改造的地方
原文程式碼:
done := make(chan bool, 1)
改造後程式碼:
done := make(chan struct{}, 1)
可以看到,把關閉訊號從 chan bool
改成了 chan struct{}
。
好處:空 struct{}
不佔用空間,chan bool
佔 1 位元組。空 struct{}
更節省資源。
由下面測試程式碼可以知道:
s := struct {}{}
b := true
fmt.Println("struct: ", unsafe.Sizeof(s), " bool: ", unsafe.Sizeof(b))
//列印結果:struct: 0 bool: 1
一些其他
Go 中的訊號
os/signal
包對訊號處理。
- 監聽收到的訊號:
func Notify(c chan<- os.Signal, sig ...os.Signal)
- 取消監聽:
func Stop(c chan<- os.Signal)
- 監聽 Interrupt 訊號,使用者
Ctrl+C
觸發:
quit := make(chan os.Signal, 1)
//os.Interrupt: syscall.SIGINT
signal.Notify(quit, os.Interrupt)
- 監聽所有訊號:
c := make(chan os.Signal)
signal.Notify(c)
- 監聽多個指定訊號:
c := make(chan os.Signal)
signal.Notify(c,os.Interrupt, os.Kill, syscall.SIGQUIT)
- 一些訊號:
//作業系統收到訊號後的動作:
//Term: 表明預設動作為終止程式
//Ign: 表明預設動作為忽略該訊號
//Core: 表明預設動作為終止程式同時輸出core dump
//Stop: 表明預設動作為停止程式
// Signals
const (
SIGABRT = Signal(0x6) //呼叫abort函式觸發,十進位制值:6, Core
SIGALRM = Signal(0xe) //時鐘定時訊號,十進位制值:14, Term
SIGBUS = Signal(0xa) //非法地址(記憶體地址對齊錯誤),十進位制值:10 Core
SIGCHLD = Signal(0x14)//子程式結束(由父程式接收),十進位制值:20 Ign
SIGCONT = Signal(0x13)//繼續執行已經停止的程式(不能被阻塞),十進位制:19 Cont
SIGEMT = Signal(0x7)
SIGFPE = Signal(0x8)//算術執行錯誤(浮點運算錯誤、除數為零等),十進位制:8 Core
SIGHUP = Signal(0x1)//終端控制程式結束(終端連線斷開),十進位制:1 Term
SIGILL = Signal(0x4)//非法指令(程式錯誤、試圖執行資料段、棧溢位等) Core
SIGINFO = Signal(0x1d)
SIGINT = Signal(0x2)//使用者傳送INTR字元(Ctrl+C)觸發,十進位制值:2
SIGIO = Signal(0x17)
SIGIOT = Signal(0x6)
SIGKILL = Signal(0x9)//無條件結束程式(不能被捕獲、阻塞或忽略)十進位制:9
SIGPIPE = Signal(0xd)//訊息管道損壞(FIFO/Socket通訊時,管道未開啟而進行寫操作)
SIGPROF = Signal(0x1b)
SIGQUIT = Signal(0x3)//使用者傳送QUIT字元(Ctrl+/)觸發,十進位制值:3
SIGSEGV = Signal(0xb)//無效記憶體引用(試圖訪問不屬於自己的記憶體空間、對只讀記憶體空間進行寫操作)
SIGSTOP = Signal(0x11)//停止程式(不能被捕獲、阻塞或忽略)
SIGSYS = Signal(0xc)
SIGTERM = Signal(0xf)
SIGTRAP = Signal(0x5)
SIGTSTP = Signal(0x12)//停止程式(可以被捕獲、阻塞或忽略)
SIGTTIN = Signal(0x15)//後臺程式從終端中讀取資料時觸發
SIGTTOU = Signal(0x16)//後臺程式向終端中寫資料時觸發
SIGURG = Signal(0x10)
SIGUSR1 = Signal(0x1e)
SIGUSR2 = Signal(0x1f)
SIGVTALRM = Signal(0x1a)
SIGWINCH = Signal(0x1c)
SIGXCPU = Signal(0x18)//超過CPU時間資源限制(4.2BSD)
SIGXFSZ = Signal(0x19)//超過檔案大小資源限制(4.2BSD)
)
更多原創文章乾貨分享,請關注公眾號
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- 讀 "優雅關閉的 Go Web 伺服器"GoWeb伺服器
- Go 呼叫 elasticsearch 批量操作介面 “/_bulk” ,提示"\n"錯誤GoElasticsearch
- 如何優雅的關閉Go Channel「譯」Go
- Tomcat 優雅關閉之路Tomcat
- 優雅關閉執行緒池的方案執行緒
- golang程式優雅關閉與重啟Golang
- Java優雅關閉執行緒池Java執行緒
- 如何優雅得關閉協程呢
- 優雅關閉maven-default-http-blocker?MavenHTTPBloC
- 如何優雅的關閉Java執行緒池Java執行緒
- Go優雅重啟Web server示例-講解版GoWebServer
- 【剖析 | SOFARPC 框架】系列之 SOFARPC 優雅關閉剖析RPC框架
- JS 獲取到的 JSON 資料字串中的雙引號變成了" 怎麼解決呢?JSON字串
- 如何優雅的搞垮伺服器,再優雅的救活伺服器
- 【Go】優雅的讀取 http 請求或響應的資料GoHTTP
- 【Go】優雅的讀取http請求或響應的資料GoHTTP
- SpringBoot如何優雅關閉(SpringBoot2.3&Spring Boot2.2)Spring Boot
- Go的優雅終止姿勢Go
- 【Go】優雅的讀取http請求或響應的資料-續GoHTTP
- 【Go】優雅的讀取 http 請求或響應的資料-續GoHTTP
- 我們們從頭到尾說一次優雅關閉
- 優雅的使用 Brew 切換 Go 版本Go
- NLP新秀 : BERT的優雅解讀
- 利用 trap 在 docker 容器優雅關閉前執行環境清理Docker
- Go程式設計技巧–Goroutine的優雅控制Go程式設計
- [譯]Go如何優雅的處理異常Go
- C#如何優雅的多表讀取C#
- Spring Boot使用@Async實現非同步呼叫:ThreadPoolTaskScheduler執行緒池的優雅關閉Spring Boot非同步thread執行緒
- 如何優雅引入神策Web JS SDKWebJS
- Go 關閉chanel & chanel的range迴圈Go
- go 如何優雅的判斷變數是否為 nilGo變數
- 超越 Nginx!號稱下一代 Web 伺服器,用起來夠優雅!NginxWeb伺服器
- Linux伺服器---關閉selinuxLinux伺服器
- 【Python標準庫:fileinput】優雅的讀取檔案Python
- SOFAJRaft原始碼閱讀-ShutdownHook如何優雅的停機Raft原始碼Hook
- go : channel , queue , 程式管理 , 關閉channel ?Go
- [ gev ] Go 語言優雅處理 TCP “粘包”GoTCP
- 使用go優雅地撰寫單元測試Go