lolly/internal/app/app.go
xfy 9d24263918 feat(stream,server,handler): 实现 Phase 6 性能优化和热升级
新增功能:
- stream 模块: 流式传输支持,优化大文件和实时数据传输
- Goroutine 池: 限制并发数量,减少调度开销
- 优雅升级: 零停机热升级,继承父进程监听器
- sendfile: 零拷贝文件传输,大文件直接从内核传输

重构改进:
- App 结构体封装,支持热升级和信号处理
- 配置结构字段对齐和代码清理
- 完善错误处理和日志记录

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-03 10:39:22 +08:00

277 lines
6.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package app 提供应用程序的启动和运行逻辑。
package app
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
"rua.plus/lolly/internal/config"
"rua.plus/lolly/internal/logging"
"rua.plus/lolly/internal/server"
)
// 版本信息,通过 -ldflags 注入。
var (
Version = "dev"
GitCommit = "unknown"
GitBranch = "unknown"
BuildTime = "unknown"
GoVersion = "unknown"
BuildPlatform = "unknown"
)
// 应用状态。
var (
shutdownTimeout = 30 * time.Second // 优雅停止超时时间
)
// App 应用程序结构。
type App struct {
cfgPath string
cfg *config.Config
srv *server.Server
upgradeMgr *server.UpgradeManager
pidFile string
logFile string // 日志文件路径(用于重新打开)
}
// NewApp 创建应用程序。
func NewApp(cfgPath string) *App {
return &App{
cfgPath: cfgPath,
}
}
// SetPidFile 设置 PID 文件路径。
func (a *App) SetPidFile(path string) {
a.pidFile = path
}
// SetLogFile 设置日志文件路径。
func (a *App) SetLogFile(path string) {
a.logFile = path
}
// Run 应用程序入口。
func Run(cfgPath string, genConfig bool, outputPath string, showVersion bool) int {
if genConfig {
return generateConfig(outputPath)
}
if showVersion {
printVersion()
return 0
}
app := NewApp(cfgPath)
return app.Run()
}
// generateConfig 生成默认配置文件。
func generateConfig(outputPath string) int {
cfg := config.DefaultConfig()
yamlData, err := config.GenerateConfigYAML(cfg)
if err != nil {
fmt.Fprintf(os.Stderr, "生成配置失败: %v\n", err)
return 1
}
if outputPath == "" {
fmt.Print(string(yamlData))
} else {
if err := os.WriteFile(outputPath, yamlData, 0644); err != nil {
fmt.Fprintf(os.Stderr, "写入文件失败: %v\n", err)
return 1
}
fmt.Printf("配置已写入: %s\n", outputPath)
}
return 0
}
// printVersion 打印版本信息。
func printVersion() {
fmt.Printf("lolly version %s\n", Version)
fmt.Printf(" Git: %s (%s)\n", GitCommit, GitBranch)
fmt.Printf(" Built: %s\n", BuildTime)
fmt.Printf(" Go: %s\n", GoVersion)
fmt.Printf(" Platform: %s\n", BuildPlatform)
}
// Run 启动应用程序。
func (a *App) Run() int {
// 检查是否是子进程(热升级)
if os.Getenv("GRACEFUL_UPGRADE") == "1" {
fmt.Println("检测到热升级模式,继承父进程监听器")
}
cfg, err := config.Load(a.cfgPath)
if err != nil {
fmt.Fprintf(os.Stderr, "加载配置失败: %v\n", err)
return 1
}
a.cfg = cfg
fmt.Printf("配置加载成功: %s\n", a.cfgPath)
fmt.Printf("监听地址: %s\n", cfg.Server.Listen)
// 创建服务器
a.srv = server.New(cfg)
// 创建升级管理器
a.upgradeMgr = server.NewUpgradeManager(a.srv)
if a.pidFile != "" {
a.upgradeMgr.SetPidFile(a.pidFile)
a.upgradeMgr.WritePid()
}
// 启动信号处理
sigChan := make(chan os.Signal, 1)
a.setupSignalHandlers(sigChan)
// 启动服务器
errChan := make(chan error, 1)
go func() {
fmt.Println("服务器启动中...")
if err := a.srv.Start(); err != nil {
errChan <- err
}
}()
// 等待信号或启动错误
for {
select {
case err := <-errChan:
fmt.Fprintf(os.Stderr, "服务器启动失败: %v\n", err)
return 1
case sig := <-sigChan:
if !a.handleSignal(sig) {
// 返回 false 表示退出
fmt.Println("服务器已停止")
return 0
}
// 返回 true 表示继续运行(如重载配置)
}
}
}
// setupSignalHandlers 设置信号处理。
func (a *App) setupSignalHandlers(sigChan chan<- os.Signal) {
signal.Notify(sigChan,
syscall.SIGTERM, // 快速停止kill 或 systemd stop
syscall.SIGINT, // 快速停止Ctrl+C
syscall.SIGQUIT, // 优雅停止
syscall.SIGHUP, // 重载配置
syscall.SIGUSR1, // 重新打开日志
syscall.SIGUSR2, // 热升级
)
}
// handleSignal 处理信号,返回 false 表示退出。
func (a *App) handleSignal(sig os.Signal) bool {
switch sig {
case syscall.SIGQUIT:
// 优雅停止:等待请求完成
fmt.Printf("\n收到 SIGQUIT优雅停止等待 %v...\n", shutdownTimeout)
a.srv.GracefulStop(shutdownTimeout)
return false
case syscall.SIGTERM, syscall.SIGINT:
// 快速停止
fmt.Printf("\n收到 %v停止服务器...\n", sigName(sig.(syscall.Signal)))
a.srv.Stop()
return false
case syscall.SIGHUP:
// 重载配置
fmt.Println("\n收到 SIGHUP重载配置...")
a.reloadConfig()
return true
case syscall.SIGUSR1:
// 重新打开日志
fmt.Println("\n收到 SIGUSR1重新打开日志...")
a.reopenLogs()
return true
case syscall.SIGUSR2:
// 热升级
fmt.Println("\n收到 SIGUSR2执行热升级...")
a.gracefulUpgrade()
return true
default:
fmt.Printf("\n收到未知信号: %v\n", sig)
return true
}
}
// reloadConfig 重载配置。
func (a *App) reloadConfig() {
newCfg, err := config.Load(a.cfgPath)
if err != nil {
fmt.Fprintf(os.Stderr, "重载配置失败: %v\n", err)
return
}
// 更新配置
a.cfg = newCfg
fmt.Println("配置重载成功")
// 注意:当前实现不重启服务器,仅更新配置
// 如需应用新配置,需要重启服务器或实现热更新
fmt.Println("配置已重新加载")
}
// reopenLogs 重新打开日志文件。
func (a *App) reopenLogs() {
// 重新初始化日志系统
if a.cfg != nil {
logging.Init(a.cfg.Logging.Error.Level, false)
}
fmt.Println("日志已重新打开")
}
// gracefulUpgrade 执行热升级。
func (a *App) gracefulUpgrade() {
// 获取当前可执行文件路径
execPath, err := os.Executable()
if err != nil {
fmt.Fprintf(os.Stderr, "获取可执行文件路径失败: %v\n", err)
return
}
// 执行升级
if err := a.upgradeMgr.GracefulUpgrade(execPath); err != nil {
fmt.Fprintf(os.Stderr, "热升级失败: %v\n", err)
return
}
fmt.Println("热升级已启动,新进程正在接管")
// 当前进程优雅停止
a.srv.GracefulStop(shutdownTimeout)
}
// sigName 返回信号名称(用于日志输出)。
func sigName(sig syscall.Signal) string {
switch sig {
case syscall.SIGTERM:
return "SIGTERM"
case syscall.SIGINT:
return "SIGINT"
case syscall.SIGQUIT:
return "SIGQUIT"
case syscall.SIGHUP:
return "SIGHUP"
case syscall.SIGUSR1:
return "SIGUSR1"
case syscall.SIGUSR2:
return "SIGUSR2"
default:
return fmt.Sprintf("Signal(%d)", sig)
}
}