快速啟動一個 api
目錄
├── api \\ 服務入口
├── cmd \\ 服務啟動入口
├── config
├── doc
└── chaper1.md├── go.mod
├── internal \\ 業務邏輯
├── main.go
├── pkg \\ 三方包初始化
└── router \\ Api 路由
GIN && Cobra
一般 WEB 服務,都會包含多個模組: API 介面、定時任務、消費 MQ 常駐程式等等,在這種情況下,很顯然直接使用 GIN 來啟動就只能開啟 API 模組,十分不便。
我們用 Cobra 來管理專案的啟動,現在不用關心 Cobra 如何使用,現在要的是滿足我們需求。
很多時候人會陷入到細節裡,就無法巨集觀的把控全域性設計。無論是做需求還是設計框架,都應該梳理整個流程,每個流程需要什麼樣的技術,而技術細節反而不是最需要關心的。網際網路發展到今天,你遇到的問題一定有人遇到過要把關注點放到你的設計上。
初始化一個 rootCmd 來管理專案中所有的模組
// main.go
func main() {
cmd.Execute()}
// cmd/root.go
var rootCmd = &cobra.Command{
Use: "提供WebApi服務", Short: "webApi",}
func init() {
rootCmd.AddCommand(apiCmd)}
func Execute() {
if err := rootCmd.Execute(); err != nil { fmt.Println("[錯誤]啟動失敗:", err) }}
// cmd/api.go
var httpServer *http.Server
var apiCmd = &cobra.Command {
Use: "api",
Short: "apiCmd",
Long: `apiCmd 提供api介面服務`,
Run: func(cmd *cobra.Command, args []string) {
address := fmt.Sprintf("%v:%v", "0.0.0.0", 8080)
engine := gin.New()
httpServer = &http.Server{
Addr: address,
Handler: engine,
IdleTimeout: time.Minute,
}
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
fmt.Println("啟動失敗", err)
}},
}
}
這個時候啟動一下, 可以看到需要傳一個命令列引數:
➜ gin-web-layout git:(master) ✗ go run main.go
webApi
Usage:
提供WebApi服務 [command]
Available Commands:
api apiCmd completion Generate the autocompletion script for the specified shell help Help about any command
使用 go run main.go api
就可以啟動服務了
➜ gin-web-layout git:(master) ✗ go run main.go api
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode)
首先開始接入路由, 所見即所得,能快速的看到自己寫的成果
router/router.go
func InitRouter(engine *gin.Engine) {
engine.GET("/", func(c *gin.Context) { c.JSON(200, "ok") })}
在 cmd/api.go 增加以下程式碼
engine := gin.New()
router.InitRouter(engine)
這樣一個 hello world 就完成了,這個也是 gin 快速開始的內容。 啟動後訪問一下:
➜ gin-web-layout git:(master) ✗ curl http://0.0.0.0:8080
"ok"%
這個時候我們來完善一下啟動模組的程式碼,加上平滑重啟,設定 5 秒後退出
// cmd/api.go
// 只展示核心程式碼,完整程式碼可以在 github 檢視
// 等待 5 秒 timeout = 5 *time.sencond
func OnServiceStop(timeout time.Duration) {
quit := make(chan os.Signal)
signal.Notify(quit, signals.Get()...)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
err := httpServer.Shutdown(ctx)
if err != nil {
log.Fatalf("service stop error:%v", err)
}
log.Println("service is stopping")
}
這樣就支援了熱啟動,然後就編譯啟動,ctrl+c, 他為什麼沒阻塞 5 秒,直接就退出了?
因為 ctrl+c 的時候,會檢查是否有待處理的請求,如沒有就會直接退出。我們可以模擬一個耗時請求:
// router/router.go
engine.GET("/", func(c *gin.Context) {
time.Sleep(5 * time.Second)
c.JSON(200, "ok")
})
重新啟動後,再次 ctrl+c 會發現 5 秒後專案才退出完成。
題外話,線上的服務是如何釋出部署的?
一般線上服務都會用閘道器排程流量,當我們一個服務接受到 kill(重啟指令碼一般用 kill,殺掉 pid) 訊號,就不再接受新請求。
這一點可以用我們剛配置的熱重啟驗證一下,把 timeout 設定 10s, 偽造一個耗時 10s 的請求,啟動後執行退出(用 ctrl+c 或者 kill, 本質都是傳送一個訊號), 然後再訪問服務,
會得到
➜ gin-web-layout git:(master) ✗ curl http://0.0.0.0:8080
curl: (7) Failed to connect to 0.0.0.0 port 8080: Connection refused
閘道器和服務會有心跳監測,無法提供服務後,閘道器自動踢掉服務,不再發流量,待恢復後再重新發流量。但是實際部署部署是另有方案,因為心跳是有間隔的,這個間隔期間服務退出了,就會造成大量的 502
實際線上操作為,當一臺服務要退出的時候,會先到閘道器摘流量,再執行平滑退出,啟動新服務,到閘道器掛上流量。 閘道器一般用的是阿里的 slb,也有人用 kong,都是一樣的套路。
本作品採用《CC 協議》,轉載必須註明作者和本文連結