下面是关于如何热重启Golang服务器的详细攻略:
简介
热重启指在运行中的程序重启时,不需要中断或停止该程序的服务,而是在后台保持其服务的情况下,重新加载代码和配置文件,并使新代码和文件生效。 Golang 提供了一些方便的库和工具,可以让我们实现 HTTP 服务器的热重启,使得服务的高可用性和无停机更新成为可能。
方式1:graceful
graceful 是 Golang 中实现热重启的一种方式。它通过信号(比如 SIGUSR2 信号)来通知当前进程,使其优雅地停止服务并在后台启动新代码。具体实现方法如下:
安装
go get -u github.com/nu7hatch/gouuid
go get -u github.com/stretchr/testify/assert
go get -u github.com/stretchr/testify/suite
使用步骤
- 给 HTTP(s) 服务设置信号监听器,对有指定信号到来时,进行优雅关闭。如:
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"runtime"
"syscall"
"time"
)
func main() {
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGUSR2)
server := &http.Server{
Addr: ":8000",
Handler: YourHandler,
}
go func() {
sig := <-signalChan
switch sig {
case syscall.SIGTERM, syscall.SIGINT:
// 关闭服务,并等待当前请求在 5 秒内处理完毕再退出
gracefulStop(server, 5*time.Second)
case syscall.SIGUSR2:
// 启动新服务
restartServer(server)
}
}()
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}
func YourHandler(w http.ResponseWriter, r *http.Request) {
// ...
}
// 优雅地关闭服务
func gracefulStop(server *http.Server, timeout time.Duration) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
log.Println("Server exiting")
}
// 重启服务
func restartServer(server *http.Server) {
log.Println("restarting server...")
pid := os.Getpid()
executablePath, _ := os.Executable()
args := []string{"-graceful"}
// 在后台新启动一个进程,启动新服务
if runtime.GOOS == "linux" {
cmd := exec.Command(executablePath, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Start()
cmd.Process.Release()
log.Printf("Master process (%d) spawned child process (%d).", pid, cmd.Process.Pid)
} else {
log.Fatal("Only linux is supported now.")
}
// 等待一段时间,等待新服务启动完成
select {
case <-time.After(5 * time.Second):
log.Println("Server restarted.")
return
}
}
- 在代码中设置一个 signalChan 的通道,用于接收外部信号。
- 在 main 函数中监听通道(signalChan),并根据信号类型执行对应操作。如果接收到 SIGTERM 或 SIGINT 信号,就执行优雅停止;如果接收到 SIGUSR2 信号,就执行重启服务。其中,优雅停止和重启服务的具体实现在 gracefulStop 和 restartServer 函数中,其中重启服务的实现在 Linux 中可通过创建子进程的方法实现(详见代码中的注释部分)。
示例
- 创建一个 HTTP 服务器:
package main
import (
"fmt"
"net/http"
"os"
"time"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello world!")
})
addr := ":8080"
if port := os.Getenv("PORT"); port != "" {
addr = ":" + port
}
srv := &http.Server{
Addr: addr,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
if err := srv.ListenAndServe(); err != nil {
panic(err)
}
}
- 在终端中启动该服务器:
go run main.go
- 在另一个终端中给服务器发送 SIGUSR2 信号:
kill -USR2 $(pidof main)
- 观察日志,可以看到服务器热重启成功。
方式2:GoReleaser
还有一种比较简单的方式是使用 Golang 的可执行文件发布工具——GoReleaser,在构建工具中集成热部署功能,实现热重启。具体使用方法如下:
安装
首先需要安装 GoReleaser:
curl -L -s https://git.io/goreleaser | bash
使用步骤
- 配置 GoReleaser:在项目根目录下创建
goreleaser.yml
配置文件,如下:
project_name: myproject
binaries:
- exec: myproject
environments:
- GOOS=linux
builds:
- main:
ldflags: [ -s -w ]
goos:
- linux
goarch:
- amd64
goarm:
- 6
path: ./
hooks:
pre:
- go mod tidy
release:
github:
owner: mygithubusername
name: myproject
- 执行构建命令:
goreleaser build --snapshot --skip-publish
- 通过编写 shell 脚本,在部署时进行热更新和重启。如:
#!/bin/sh
start_server() {
echo "Starting server"
exec /usr/local/bin/server
}
stop_server() {
echo "Stopping server"
killall server
}
restart_server() {
echo "Restarting server"
distPath="/path/to/your/project/dist"
tmpFile="$(mktemp -p "$distPath" XXXXXX)"
mv "$distPath/server" "$tmpFile"
mv "$distPath/myproject_linux_amd64" "$distPath/server"
chmod +x "$distPath/server"
killall -USR2 server
sleep 2
stop_server
start_server
rm "$tmpFile"
}
case "$1" in
start)
start_server
;;
stop)
stop_server
;;
restart)
restart_server
;;
*)
echo "Usage: $0 {start|stop|restart}" >&2
exit 1
;;
esac
exit 0
其中,stop_server() 函数用于终止当前服务,start_server() 函数用于启动服务,并调用 stop_server 函数等待服务注销完成。restart_server() 函数则分别执行服务重启前和重启后的操作,如将服务器原来的文件改名并临时存储到 dist 目录下的一个临时文件中,修改新文件权限为可执行文件,发送 SIGUSR2 信号等。修改后的服务可以通过 killall server 终止前台服务,并通过 start_server() 重新启动服务。
示例
- 创建一个 HTTP 服务器:
package main
import (
"fmt"
"net/http"
"os"
"time"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello world!")
})
addr := ":8080"
if port := os.Getenv("PORT"); port != "" {
addr = ":" + port
}
srv := &http.Server{
Addr: addr,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
if err := srv.ListenAndServe(); err != nil {
panic(err)
}
}
- 生成可执行文件:
goreleaser build --snapshot --skip-publish
- 编写 shell 脚本用于热更新和重启,在部署时执行重启脚本:
start_server() {
echo "Starting server"
exec /usr/local/bin/server
}
stop_server() {
echo "Stopping server"
killall server
}
restart_server() {
echo "Restarting server"
distPath="/path/to/your/project/dist"
tmpFile="$(mktemp -p "$distPath" XXXXXX)"
mv "$distPath/server" "$tmpFile"
mv "$distPath/myproject_linux_amd64" "$distPath/server"
chmod +x "$distPath/server"
killall -USR2 server
sleep 2
stop_server
start_server
rm "$tmpFile"
}
case "$1" in
start)
start_server
;;
stop)
stop_server
;;
restart)
restart_server
;;
*)
echo "Usage: $0 {start|stop|restart}" >&2
exit 1
;;
esac
exit 0
- 启动服务器:
./server start
-
发送信号给服务器,进行热重启:
killall -USR2 server
示。 -
观察服务器的日志,可以看到服务器热重启成功。
以上是关于如何热重启 Golang 服务器的完整攻略,希望对你有所帮助。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解如何热重启golang服务器 - Python技术站