四、启动流程详解
本章将深入分析 Kratos 的启动流程,从 kratos.New() 到 app.Run() 的完整过程。
4.1 入口函数分析
让我们从一个典型的 Kratos 入口开始:
go
// main.go
package main
import (
"github.com/go-kratos/kratos/v2"
"github.com/go-kratos/kratos/v2/config"
"github.com/go-kratos/kratos/v2/config/file"
"github.com/go-kratos/kratos/v2/log"
"github.com/go-kratos/kratos/v2/transport/http"
)
func main() {
// 1. 加载配置
c := config.New(
config.WithSource(file.NewSource("configs/config.yaml")),
)
defer c.Close()
// 2. 创建应用
app := kratos.New(
kratos.Name("hello-kratos"),
kratos.Version("v1.0.0"),
kratos.Server(
http.NewServer(
http.Address(":8080"),
),
),
)
// 3. 启动应用
app.Run()
}4.2 kratos.New() 流程
4.2.1 New() 源码
go
// kratos/app.go
func New(opts ...Option) *App {
// 1. 创建带取消的上下文
ctx, cancel := context.WithCancel(context.Background())
// 2. 创建应用实例
app := &App{
ctx: ctx,
cancel: cancel,
opts: newOptions(opts),
}
// 3. 构建服务
app.build()
// 4. 返回应用
return app
}4.2.2 流程图解
kratos.New(opts...)
│
▼
┌───────────────────┐
│ 创建 context │ ctx, cancel := context.WithCancel(...)
└───────────────────┘
│
▼
┌───────────────────┐
│ 创建 App 实例 │ app := &App{...}
└───────────────────┘
│
▼
┌───────────────────┐
│ 构建服务 (build) │ app.build()
└───────────────────┘
│
▼
┌───────────────────┐
│ 返回 App │ return app
└───────────────────┘4.3 build() 内部实现
4.3.1 build() 源码
go
// kratos/app.go
func (app *App) build() {
// 1. 设置日志
if app.opts.logger != nil {
log.SetLogger(app.opts.logger)
}
// 2. 设置信号处理
app.handleSignal()
// 3. 构建所有服务
for _, srv := range app.opts.servers {
app.wg.Add(1)
go func(srv Server) {
// 在 goroutine 中启动服务
srv.Start()
// 服务停止后,减少 WaitGroup 计数
app.wg.Done()
}(srv)
}
}4.3.2 build 流程图解
app.build()
│
├── 1. 设置日志
│ │
│ ▼
│ log.SetLogger(opts.logger)
│
├── 2. 设置信号处理
│ │
│ ▼
│ app.handleSignal() ──→ 监听 SIGINT/SIGTERM
│
└── 3. 启动服务
│
▼
for srv in servers:
│
▼
┌─────────────────────┐
│ go srv.Start() │ ← 并发启动
└─────────────────────┘
│
▼
┌─────────────────────┐
│ app.wg.Add(1) │ ← 计数+1
└─────────────────────┘4.4 app.Run() 流程
4.4.1 Run() 源码
go
// kratos/app.go
func (app *App) Run() error {
// 1. 等待所有服务启动完成
// (WaitGroup 计数为 0 表示服务都启动了)
app.wg.Wait()
// 2. 等待退出信号
<-app.ctx.Done()
// 3. 优雅关闭
return app.shutdown()
}4.4.2 Run 流程图解
app.Run()
│
▼
┌───────────────────┐
│ app.wg.Wait() │ ← 等待所有服务启动完成
└───────────────────┘
│
▼
┌───────────────────┐
│ <-app.ctx.Done()│ ← 等待退出信号
└───────────────────┘
│
▼
┌───────────────────┐
│ app.shutdown() │ ← 优雅关闭
└───────────────────┘
│
▼
for srv in servers:
│
▼
┌─────────────────────┐
│ srv.GracefulStop() │ ← 优雅停止每个服务
└─────────────────────┘4.5 信号处理
4.5.1 handleSignal() 源码
go
// kratos/app.go
func (app *App) handleSignal() {
// 创建信号通道
app.opts.sigCh = make(chan os.Signal, 1)
// 监听信号
signal.Notify(app.opts.sigCh, syscall.SIGINT, syscall.SIGTERM)
// 启动信号处理协程
go func() {
select {
case sig := <-app.opts.sigCh:
// 收到信号,取消上下文
app.cancel()
// 记录日志
log.Infof("收到信号: %s", sig)
case <-app.ctx.Done():
// 上下文已取消
}
}()
}4.5.2 信号处理流程
┌─────────────────────────────────────────────────────────────┐
│ 信号处理流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 系统信号 (SIGINT/SIGTERM) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ signal.Notify(ch, SIGINT, SIGTERM) │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ go func() { │ │
│ │ select { │ │
│ │ case sig := <-ch: │ │
│ │ app.cancel() // 取消上下文 │ │
│ │ case <-app.ctx.Done(): │ │
│ │ // 退出 │ │
│ │ } │ │
│ │ }() │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ app.ctx.Done() 触发 ──► app.Run() 中的 <-app.ctx.Done() │
│ │
└─────────────────────────────────────────────────────────────┘4.6 优雅关闭
4.6.1 shutdown() 源码
go
// kratos/app.go
func (app *App) shutdown() error {
// 1. 记录日志
log.Info("开始关闭应用...")
// 2. 停止所有服务
for _, srv := range app.opts.servers {
// 优雅停止
if err := srv.GracefulStop(); err != nil {
log.Errorf("停止服务失败: %v", err)
}
}
// 3. 记录日志
log.Info("应用已关闭")
return nil
}4.6.2 优雅关闭流程
┌─────────────────────────────────────────────────────────────┐
│ 优雅关闭流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 停止接收新请求 │
│ └── Server 停止监听端口 │
│ │
│ 2. 等待正在处理的请求完成 │
│ └── 等待所有进行中的请求处理完成 │
│ │
│ 3. 关闭连接 │
│ └── 关闭所有客户端连接 │
│ │
│ 4. 释放资源 │
│ └── 关闭数据库连接、缓存连接等 │
│ │
└─────────────────────────────────────────────────────────────┘4.7 完整启动流程
┌─────────────────────────────────────────────────────────────┐
│ 完整启动流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 用户代码 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ kratos.New( │ │
│ │ kratos.Name("hello-kratos"), │ │
│ │ kratos.Version("v1.0.0"), │ │
│ │ kratos.Server(httpServer), │ │
│ │ ) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ app.build() │ │
│ │ │ │ │
│ │ ├── 设置日志 │ │
│ │ ├── 设置信号处理 │ │
│ │ └── 启动所有 Server (并发) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ HTTP Server 启动监听 :8080 │ │
│ │ gRPC Server 启动监听 :50051 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ app.Run() │ │
│ │ │ │ │
│ │ ├── app.wg.Wait() 等待服务启动完成 │ │
│ │ │ │ │
│ │ ├── <-app.ctx.Done() 等待退出信号 │ │
│ │ │ │ │
│ │ └── app.shutdown() 优雅关闭 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 停止所有 Server │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘4.8 本章小结
┌─────────────────────────────────────────────────────────────┐
│ 本章总结 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ✓ kratos.New() 创建应用并构建服务 │
│ │
│ ✓ app.build() 并发启动所有 Server │
│ │
│ ✓ app.Run() 等待信号并优雅关闭 │
│ │
│ ✓ 使用 WaitGroup 协调服务启动和关闭 │
│ │
│ ✓ 支持 SIGINT/SIGTERM 信号处理 │
│ │
└─────────────────────────────────────────────────────────────┘下章预告
下一章我们将学习 HTTP Server 详解,包括 Server 的创建、启动和配置。
敬请期待!