应用生命周期管理
概述
应用生命周期管理定义了 Go 应用从启动到关闭的完整流程。核心目标是确保资源正确初始化和释放,支持优雅关闭。
生命周期阶段
完整生命周期流程
App 容器模式
App 结构定义
使用 App 容器管理所有依赖:
go
// app/app.go
package app
import (
"context"
"fmt"
"your-project/clients/storage"
"your-project/config"
"your-project/services"
"gorm.io/gorm"
)
// App 应用容器
type App struct {
// 配置
config *config.Config
// 基础设施
db *gorm.DB
// Clients
storageClient *storage.Client
emailClient *email.Client
// Services
userService *services.UserService
productService *services.ProductService
orderService *services.OrderService
}
// NewApp 创建应用实例
func NewApp() *App {
return &App{}
}应用初始化
go
// Init 初始化应用
func (a *App) Init(ctx context.Context, cfg *config.Config) error {
a.config = cfg
// 1. 初始化数据库
if err := a.initDatabase(ctx); err != nil {
return fmt.Errorf("failed to init database: %w", err)
}
// 2. 初始化 Clients
if err := a.initClients(); err != nil {
return fmt.Errorf("failed to init clients: %w", err)
}
// 3. 初始化 Services
a.initServices()
return nil
}
// initDatabase 初始化数据库连接
func (a *App) initDatabase(ctx context.Context) error {
db, err := gorm.Open(mysql.Open(a.config.Database.DSN()), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
return err
}
// 配置连接池
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
sqlDB.SetConnMaxLifetime(time.Hour)
a.db = db
return nil
}
// initClients 初始化 Clients
func (a *App) initClients() error {
// 对象存储 Client
storageClient, err := storage.NewClient(&a.config.Storage)
if err != nil {
return fmt.Errorf("failed to init storage client: %w", err)
}
a.storageClient = storageClient
// 邮件 Client
emailClient, err := email.NewClient(&a.config.Email)
if err != nil {
return fmt.Errorf("failed to init email client: %w", err)
}
a.emailClient = emailClient
return nil
}
// initServices 初始化 Services
func (a *App) initServices() {
// 注入依赖创建 Services
a.userService = services.NewUserService(a.db, a.emailClient)
a.productService = services.NewProductService(a.db, a.storageClient)
a.orderService = services.NewOrderService(a.db, a.userService, a.productService)
}
// GetUserService 获取 UserService
func (a *App) GetUserService() *services.UserService {
return a.userService
}
// GetProductService 获取 ProductService
func (a *App) GetProductService() *services.ProductService {
return a.productService
}
// GetOrderService 获取 OrderService
func (a *App) GetOrderService() *services.OrderService {
return a.orderService
}主程序入口
main.go 实现
go
// main.go
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"your-project/app"
"your-project/config"
"your-project/httpapi/routes"
)
func main() {
// 1. 加载配置
cfg, err := config.Load("config.toml")
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
// 2. 创建 App 容器
application := app.NewApp()
// 3. 初始化应用
ctx := context.Background()
if err := application.Init(ctx, cfg); err != nil {
log.Fatalf("Failed to init app: %v", err)
}
// 4. 创建 HTTP 服务器
e := echo.New()
// 5. 配置中间件
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.CORS())
// 6. 注册路由
routes.Register(e, application)
// 7. 启动服务器(非阻塞)
go func() {
addr := fmt.Sprintf(":%d", cfg.Server.Port)
if err := e.Start(addr); err != nil && err != http.ErrServerClosed {
log.Fatalf("Failed to start server: %v", err)
}
}()
log.Printf("Server started on port %d", cfg.Server.Port)
// 8. 等待中断信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
// 9. 优雅关闭
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := e.Shutdown(shutdownCtx); err != nil {
log.Printf("Server shutdown error: %v", err)
}
// 10. 清理资源
if err := application.Close(ctx); err != nil {
log.Printf("App cleanup error: %v", err)
}
log.Println("Server stopped")
}路由注册
go
// httpapi/routes/routes.go
package routes
import (
"github.com/labstack/echo/v4"
"your-project/app"
"your-project/httpapi/handlers"
)
// Register 注册所有路由
func Register(e *echo.Echo, app *app.App) {
// API 分组
api := e.Group("/api/v1")
// 用户路由
userHandler := handlers.NewUserHandler(app)
api.POST("/users", userHandler.Create)
api.GET("/users", userHandler.List)
api.GET("/users/:id", userHandler.GetByID)
api.PATCH("/users/:id", userHandler.Update)
api.DELETE("/users/:id", userHandler.Delete)
// 商品路由
productHandler := handlers.NewProductHandler(app)
api.POST("/products", productHandler.Create)
api.GET("/products", productHandler.List)
api.GET("/products/:id", productHandler.GetByID)
// 订单路由
orderHandler := handlers.NewOrderHandler(app)
api.POST("/orders", orderHandler.Create)
api.GET("/orders", orderHandler.List)
api.GET("/orders/:id", orderHandler.GetByID)
}优雅关闭
关闭流程
go
// app/app.go
// Close 优雅关闭应用
func (a *App) Close(ctx context.Context) error {
log.Println("Closing application resources...")
// 1. 关闭 Clients
if a.storageClient != nil {
if err := a.storageClient.Close(); err != nil {
log.Printf("Failed to close storage client: %v", err)
}
}
if a.emailClient != nil {
if err := a.emailClient.Close(); err != nil {
log.Printf("Failed to close email client: %v", err)
}
}
// 2. 关闭数据库连接
if a.db != nil {
sqlDB, _ := a.db.DB()
if err := sqlDB.Close(); err != nil {
log.Printf("Failed to close database: %v", err)
}
}
log.Println("Application resources closed")
return nil
}信号处理
go
// ✅ 正确 - 处理多个信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
select {
case <-quit:
log.Println("Received shutdown signal")
case <-ctx.Done():
log.Println("Context canceled")
}
// 优雅关闭,等待正在处理的请求完成
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := e.Shutdown(shutdownCtx); err != nil {
log.Fatalf("Server forced to shutdown: %v", err)
}健康检查
健康检查端点
go
// httpapi/handlers/health.go
package handlers
import (
"net/http"
"github.com/labstack/echo/v4"
"your-project/app"
)
type HealthHandler struct {
app *app.App
}
func NewHealthHandler(app *app.App) *HealthHandler {
return &HealthHandler{app: app}
}
// Health 健康检查
func (h *HealthHandler) Health(c echo.Context) error {
// 检查数据库连接
sqlDB, _ := h.app.GetDB().DB()
if err := sqlDB.Ping(); err != nil {
return c.JSON(http.StatusServiceUnavailable, map[string]interface{}{
"status": "unhealthy",
"error": "database connection failed",
})
}
return c.JSON(http.StatusOK, map[string]interface{}{
"status": "healthy",
})
}
// Readiness 就绪检查
func (h *HealthHandler) Readiness(c echo.Context) error {
// 检查应用是否完全初始化
if h.app.GetUserService() == nil {
return c.JSON(http.StatusServiceUnavailable, map[string]interface{}{
"status": "not ready",
})
}
return c.JSON(http.StatusOK, map[string]interface{}{
"status": "ready",
})
}注册健康检查路由
go
// routes/routes.go
func Register(e *echo.Echo, app *app.App) {
// 健康检查(不需要认证)
healthHandler := handlers.NewHealthHandler(app)
e.GET("/health", healthHandler.Health)
e.GET("/ready", healthHandler.Readiness)
// API 路由...
}配置热重载
监听配置变化
go
// ✅ 正确 - 使用 fsnotify 监听配置文件
import "github.com/fsnotify/fsnotify"
func watchConfig(configPath string, reloadFunc func()) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
go func() {
for {
select {
case event := <-watcher.Events:
if event.Op&fsnotify.Write == fsnotify.Write {
log.Println("Config file changed, reloading...")
reloadFunc()
}
case err := <-watcher.Errors:
log.Println("Watcher error:", err)
}
}
}()
watcher.Add(configPath)
<-make(chan struct{}) // 阻塞
}启动检查清单
应用启动时必须验证:
- [ ] 配置文件加载成功
- [ ] 所有必需的环境变量已设置
- [ ] 数据库连接成功
- [ ] 所有 Client 初始化成功
- [ ] HTTP 服务器启动成功
- [ ] 健康检查端点可访问
错误处理原则
启动时错误
go
// ✅ 正确 - 启动失败立即退出
if err := application.Init(ctx, cfg); err != nil {
log.Fatalf("Failed to init app: %v", err) // 使用 Fatalf 退出
}
// ❌ 错误 - 启动失败继续运行
if err := application.Init(ctx, cfg); err != nil {
log.Printf("Failed to init app: %v", err) // 仅记录日志
}运行时错误
go
// ✅ 正确 - 运行时错误记录日志并返回错误响应
func (h *UserHandler) Create(c echo.Context) error {
user, err := h.app.GetUserService().Create(ctx, req)
if err != nil {
log.Printf("Failed to create user: %v", err)
return c.JSON(500, response.Fail(500, "创建失败"))
}
return c.JSON(201, response.Success(user))
}