Skip to main content

gva Viper配置

在运行时修改配置文件中的端口号没有生效

如果在运行时修改配置文件中的端口号没有生效,可能是因为应用程序在启动时已经绑定了端口,之后并没有重新绑定新的端口。一般情况下,端口绑定是在应用程序启动时完成的,动态更新端口号需要额外的处理逻辑。

下面是一个示例,展示如何在运行时动态更新端口号:

示例代码

package main

import (
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)

var server *http.Server

func main() {
// 初始化 Viper
v := viper.New()
v.SetConfigFile("config.yaml")
v.SetConfigType("yaml")
err := v.ReadInConfig()
if err != nil {
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}

// 监听配置文件变化
v.WatchConfig()
v.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name)
restartServer(v)
})

// 启动服务器
startServer(v)

// 捕捉终止信号
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
fmt.Println("Shutting down server...")

// 关闭服务器
if err := server.Close(); err != nil {
fmt.Println("Server Close:", err)
}
}

func startServer(v *viper.Viper) {
port := v.GetString("app.port")
if port == "" {
port = "8080"
}

r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello, World!")
})

server = &http.Server{
Addr: fmt.Sprintf(":%s", port),
Handler: r,
}

go func() {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
fmt.Printf("listen: %s\n", err)
}
}()
fmt.Printf("Server running on port %s\n", port)
}

func restartServer(v *viper.Viper) {
if server != nil {
if err := server.Close(); err != nil {
fmt.Println("Server Close:", err)
}
}
time.Sleep(1 * time.Second)
startServer(v)
}

解释

  1. 初始化 Viper:加载配置文件并设置监听配置文件变化。
  2. 启动服务器:读取配置文件中的端口号并启动服务器。
  3. 监听配置文件变化:当配置文件发生变化时,调用 restartServer 函数重新启动服务器。
  4. 捕捉终止信号:捕捉系统终止信号,优雅地关闭服务器。

注意事项

  1. 端口绑定:服务器启动时绑定的端口在运行时是不能动态改变的,因此需要重新启动服务器。
  2. 优雅关闭:确保在重新启动服务器前,优雅地关闭当前运行的服务器,以释放端口。
  3. 延迟处理:在关闭服务器和重新启动服务器之间加入适当的延迟,以避免端口未完全释放导致的绑定失败。

配置文件示例

app:
port: '8080'

总结

通过监听配置文件的变化并重新启动服务器,可以实现运行时动态更新端口号的功能。需要注意的是,重新启动服务器会导致短暂的服务中断,因此在生产环境中需要谨慎处理。

为什么捕捉终止信号

// 捕捉终止信号
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
fmt.Println("Shutting down server...")

在服务器应用程序中捕捉终止信号是为了优雅地关闭服务器。这种做法可以确保在接收到系统终止信号(如 SIGINTSIGTERM)时,服务器能够执行一些清理操作,比如关闭打开的连接、释放资源等,从而避免数据丢失或其他潜在问题。

详细解释

  1. 创建信号通道

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

    创建一个信号通道,用于接收系统信号。

  2. 注册信号通知

    signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

    signal.Notify 函数将指定的系统信号(如 SIGINTSIGTERM)注册到信号通道 sigChanSIGINT 通常是由用户按下 Ctrl+C 产生的,而 SIGTERM 通常是由系统发送的终止信号。

  3. 等待信号

    <-sigChan

    通过读取信号通道,程序会阻塞在这里,直到接收到一个信号。

  4. 处理信号

    fmt.Println("Shutting down server...")

    当接收到信号时,程序会继续执行,打印出 "Shutting down server...",并执行后续的清理操作。

为什么要这么做

  1. 优雅关闭: 捕捉终止信号可以让你在程序退出前执行一些清理操作,比如关闭数据库连接、保存未处理的数据、释放资源等。

  2. 避免数据丢失: 直接终止程序可能会导致数据丢失或资源未释放,捕捉信号并优雅关闭可以最大限度地减少这些问题。

  3. 提高可靠性: 在服务器应用程序中,优雅关闭可以确保所有正在处理的请求被正确处理完毕,从而提高系统的可靠性和稳定性。

总结

通过捕捉终止信号并优雅地关闭服务器,可以确保在程序退出前完成必要的清理工作,从而提高应用程序的稳定性和可靠性。这种做法在生产环境中尤为重要,能够有效避免数据丢失和资源泄露。

<-sigChan 代码运行到这里,会阻塞掉,等信号?

是的,<-sigChan 这行代码会阻塞当前的 Goroutine,直到从 sigChan 通道接收到一个信号。这个机制允许程序在接收到系统终止信号(如 SIGINTSIGTERM)之前保持运行状态。

详细解释

  1. 阻塞等待信号

    <-sigChan

    这行代码会使当前的 Goroutine 阻塞,直到从 sigChan 通道接收到一个信号。当用户按下 Ctrl+C 或系统发送 SIGTERM 信号时,这个阻塞会被解除,程序继续执行后续代码。

  2. 保持服务器运行: 在服务器应用程序中,这种阻塞等待信号的机制通常用于保持服务器运行。服务器会在后台处理请求,而主 Goroutine 则等待终止信号以便执行优雅关闭操作。