使用 tableflip 實現應用的優雅熱升級
推薦 tableflip 的背景
在日常研發過程中,我們負責的 web 應用常常會因釋出過程中的服務重啟而出現短時間的服務不可用或大量請求報錯。隨著網際網路行業研發模式的逐漸敏捷和迭代週期的不斷縮短,應用升級導致的服務抖動對系統穩定性的影響已不可忽視。在應用中整合 tableflip
或許可以緩解大家在新功能上線時的擔憂。
tableflip
是 Cloudflare 針對 golang 程式實現優雅重啟而設計的一套開源類庫,整合 tableflip
可以讓我們的 go 應用獲得與 nginx reload 一樣強大的熱更新能力。如果你的應用尚未接入負載均衡與滾動釋出,或者你的應用本身就是需要特殊處理的有狀態應用,趕快試試 tableflip
吧!
tableflip 簡介
tableflip
的設計宗旨就是實現類似 nginx 的優雅熱更新能力,包括:
- 新程式啟動成功後,老程式不會有資源殘留
- 優雅的新程式初始化(新程式啟動和初始化的過程中服務不會中斷)
- 容忍新程式初始化的失敗(如果新程式初始化失敗,老程式會繼續工作而不是退出)
- 同一時間只能有一個更新動作執行
tableflip
中的核心型別是 Upgrader
,呼叫 Upgrader.Upgrade
會產生一個繼承必要的 net.Listeners
的新程式,並等待新程式發出表明其已成功完成初始化、退出或超時的訊號。如果當前已有升級的任務在執行,則直接返回相應的錯誤。
當新程式啟動成功後,呼叫 Upgrader.Ready
會清除無效的 fd 並向父程式發出初始化成功完成的訊號,然後父程式就可以安心退出。至此,我們就完成了一次優雅的程式重啟。
注:
tableflip
目前只適用於 Linux 和 macOS
tableflip 應用舉例
接下來我們設計一個整合 tableflip
的簡單 http server,完整程式碼如下:
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/cloudflare/tableflip"
)
// 當前程式的版本
const version = "v0.0.1"
func main() {
upg, err := tableflip.New(tableflip.Options{})
if err != nil {
panic(err)
}
defer upg.Stop()
// 為了演示方便,為程式啟動強行加入 1s 的延時,並在日誌中附上程式 pid
time.Sleep(time.Second)
log.SetPrefix(fmt.Sprintf("[PID: %d] ", os.Getpid()))
// 監聽系統的 SIGHUP 訊號,以此訊號觸發程式重啟
go func() {
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGHUP)
for range sig {
// 核心的 Upgrade 呼叫
err := upg.Upgrade()
if err != nil {
log.Println("Upgrade failed:", err)
}
}
}()
// 注意必須使用 upg.Listen 對埠進行監聽
ln, err := upg.Listen("tcp", ":8080")
if err != nil {
log.Fatalln("Can't listen:", err)
}
// 建立一個簡單的 http server,/version 返回當前的程式版本
mux := http.NewServeMux()
mux.HandleFunc("/version", func(rw http.ResponseWriter, r *http.Request) {
log.Println(version)
rw.Write([]byte(version + "\n"))
})
server := http.Server{
Handler: mux,
}
// 照常啟動 http server
go func() {
err := server.Serve(ln)
if err != http.ErrServerClosed {
log.Println("HTTP server:", err)
}
}()
if err := upg.Ready(); err != nil {
panic(err)
}
<-upg.Exit()
// 給老程式的退出設定一個 30s 的超時時間,保證老程式的退出
time.AfterFunc(30*time.Second, func() {
log.Println("Graceful shutdown timed out")
os.Exit(1)
})
// 等待 http server 的優雅退出
server.Shutdown(context.Background())
}
上面的程式碼實現了一個返回當前 version 的 http server,我們還在啟動過程中插入了 1s 的延時來拉長程式的初始化時間,以觀察升級過程中服務是否依舊可用。
編譯並執行之:
go build -o demo main.go
./demo
使用 curl 模擬一些客戶端請求(10 qps):
while true; do curl http://localhost:8080/version; sleep 0.1; done
...
[PID: 18939] 2021/07/04 15:02:47 v0.0.1
[PID: 18939] 2021/07/04 15:02:47 v0.0.1
[PID: 18939] 2021/07/04 15:02:47 v0.0.1
[PID: 18939] 2021/07/04 15:02:48 v0.0.1
...
然後,我們對應用進行了一些升級,將版本號修改為 v0.0.2
,並重新編譯程式:
go build -o demo main.go
最後,來試試優雅的熱重啟是否奏效吧!
kill -s HUP 18939
...
[PID: 19306] 2021/07/04 15:04:57 v0.0.2
[PID: 19306] 2021/07/04 15:04:57 v0.0.2
[PID: 19306] 2021/07/04 15:04:57 v0.0.2
[PID: 19306] 2021/07/04 15:04:57 v0.0.2
...
可見,客戶端完全不會受服務端的升級和重啟的影響,我們的應用實現了優雅升級!
...
v0.0.1
v0.0.1
v0.0.2
v0.0.2
v0.0.2
...
總結
tableflip
是實現 go 程式優雅重啟的優秀工具。因為其支援對連線進行保持和繫結,所以幾乎適用於所有的 web 框架(HTTP、gRPC 等)。通過簡單的配置,整合 tableflip
的程式也可以非常方便地被 systemd 等工具進行管控。
參考資料
- https://github.com/cloudflare/tableflip
- https://blog.cloudflare.com/graceful-upgrades-in-go/
- https://github.com/fvbock/endless
- https://pkg.go.dev/github.com/astaxie/beego/grace
- https://grisha.org/blog/2014/06/03/graceful-restart-in-golang/
歡迎加入 GOLANG 中國社群:https://gocn.vip
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- 如何優雅的升級到webpack4Web
- 如何優雅的將Flutter引入現有應用?Flutter
- 用proxy實現一個更優雅的vueVue
- 使用阿里 Druid 實現應用級waf阿里UI
- Python 使用 backoff 更優雅的實現輪詢Python
- Kubernetes 實戰——升級應用(Deployment)
- 優雅地使用TypeScript開發React Native應用TypeScriptReact Native
- EMQX 在 Kubernetes 中如何進行優雅升級MQ
- 使用require.context實現優雅的預載入UIContext
- 使用 Guava Retry 優雅的實現重試機制Guava
- 【深度思考】如何優雅告知使用者,網站正在升級維護?網站
- 如何實現優雅的重試?
- Laravel Telescope:優雅的應用除錯工具Laravel除錯
- PHP介面與性狀的優雅應用PHP
- 用WindowsAppSDK(WASDK)優雅的開發上位機應用WindowsAPP
- vue中點選空白處隱藏div的實現(用指令優雅的實現)Vue
- 如何在 Linux 下使用 TC 優雅的實現網路限流Linux
- 使用Jenkins優雅部署Java專案【超級詳細的實戰教程】JenkinsJava
- 優雅的使用UITableViewUIView
- 如何優雅地停止 Spring Boot 應用?Spring Boot
- OkHttp優雅的實現下載監聽HTTP
- Egg優雅的實現異常處理
- 如何優雅的實現訊息通訊?
- python 中 try...finally... 的優雅實現Python
- JavaFX桌面應用-版本升級Java
- Redis優雅實現分散式鎖Redis分散式
- 更優雅地實現策略模式模式
- 使用 Router 實現的模組化,如何優雅的回到主頁面
- 如何實現OpenHarmony的OTA升級
- Eclipse/tomcat 如何實現應用熱部署和熱啟動EclipseTomcat熱部署
- nginx實現平滑升級Nginx
- 應用升級SpringCloud版本時的注意事項(Dalston升級到Edgware)SpringGCCloud
- 你的Kubernetes Java應用優雅停機了嗎?Java
- 如何優雅的使用介面
- 如何優雅的使用MyBatis?MyBatis
- 如何優雅的使用切面和註解實現許可權驗證
- Redis刪除特定字首key的優雅實現Redis
- 安全優雅的RESTful API簽名實現方案RESTAPI