2026-04-25 01:18:30 +08:00

87 lines
3.4 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 main 是 im-service 的进程入口,只做配置读取、依赖装配和生命周期托管。
package main
import (
"context"
"flag"
"log"
"os"
"os/signal"
"syscall"
"time"
"hyapp/services/im-service/internal/app"
"hyapp/services/im-service/internal/config"
)
// main 只负责装配并启动 im-service。
func main() {
// config 参数只决定当前进程读取哪份 YAML真实服务边界由 internal/app 统一装配。
configPath := flag.String("config", "services/im-service/configs/config.yaml", "path to im-service config")
flag.Parse()
// 配置加载失败直接终止进程避免以不完整端口、JWT secret 或 room-service 地址启动。
cfg, err := config.Load(*configPath)
if err != nil {
log.Fatal(err)
}
// App.New 会建立 room-service gRPC 连接、HTTP/WS listener、内部 gRPC listener 和健康检查状态。
application, err := app.New(cfg)
if err != nil {
log.Fatal(err)
}
// 使用带缓冲 signal channel第二次信号可以在优雅下线未完成时触发强制收敛。
signals := make(chan os.Signal, 2)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
defer signal.Stop(signals)
// Run 阻塞在 HTTP 与 gRPC 两个 listener 上;这里用错误通道把运行期失败反馈到主 goroutine。
errCh := make(chan error, 1)
go func() {
errCh <- application.Run()
}()
select {
case sig := <-signals:
// 第一次信号进入 drainingready 会立即失败,随后按配置的总预算执行优雅下线。
start := time.Now()
log.Printf("service=im-service node_id=%s signal=%s state=draining ready=false active_ws_connections=%d shutdown_deadline_ms=%d", cfg.NodeID, sig, application.ActiveWSConnections(), cfg.ShutdownTimeout.Milliseconds())
ctx, cancel := context.WithTimeout(context.Background(), cfg.ShutdownTimeout)
defer cancel()
shutdownDone := make(chan error, 1)
go func() {
shutdownDone <- application.Shutdown(ctx)
}()
select {
case err := <-shutdownDone:
if err != nil {
// 优雅下线失败也要带上耗时和结果,方便 supervisor 日志定位截断点。
log.Printf("service=im-service node_id=%s state=stopped result=shutdown_error duration_ms=%d error=%q", cfg.NodeID, time.Since(start).Milliseconds(), err.Error())
log.Fatal(err)
}
case sig := <-signals:
// 第二次信号允许加速退出,但必须先打印仍未完成的 WS 连接数量。
active := application.ActiveWSConnections()
log.Printf("service=im-service node_id=%s signal=%s state=draining result=second_signal active_ws_connections=%d", cfg.NodeID, sig, active)
forced := application.ForceClose()
log.Printf("service=im-service node_id=%s state=stopped result=forced active_ws_connections=%d duration_ms=%d", cfg.NodeID, forced, time.Since(start).Milliseconds())
case <-ctx.Done():
// shutdown_timeout 到期后强制关闭剩余连接,并记录被截断的资源数量。
active := application.ActiveWSConnections()
log.Printf("service=im-service node_id=%s state=draining result=shutdown_timeout active_ws_connections=%d", cfg.NodeID, active)
forced := application.ForceClose()
log.Printf("service=im-service node_id=%s state=stopped result=forced active_ws_connections=%d duration_ms=%d", cfg.NodeID, forced, time.Since(start).Milliseconds())
}
case err := <-errCh:
// 任一入口异常退出都视为进程级失败,避免节点处于半工作状态。
if err != nil {
log.Fatal(err)
}
}
}