go #源码分析 #v1.0.0 #学习

概述

首先将代码切换到 v1.0.0 标签, 这里我从 cmd/iam-apiservice/apiservice.go 文件开始分析, 并跟随所有的 run 函数跟进学习如何运行。

入口

cmd/iam-apiservice/apiservice.go

入口文件是 cmd/iam-apiservice/apiservice.go 这是只包括一个 main 函数

里面的内容较少,主要功能函数只有 5 行左右

  1. func main() {
  2. rand.Seed(time.Now().UTC().UnixNano())
  3. if len(os.Getenv("GOMAXPROCS")) == 0 {
  4. runtime.GOMAXPROCS(runtime.NumCPU())
  5. }
  6. apiserver.NewApp("iam-apiserver").Run()
  7. }
  • 第 2 行 设置随机种子为当前启动时间, 没有啥别特的
  • 第 3 行 使用 os.Getenv 获取 GOMAXPROCS 环境变量的值, 若其为 0 则使用 runtime.GOMAXPROCS 设置程序的并发性, 当环境变量设置时会使用环境变量. 参考 Go语言GOMAXPROCS(调整并发的运行性能) 在 go 1.5 版本以上就默认执行这个操作了. 不过这里设置为 0 的时候是不是就是单线程运行??
  • 第 7 行 调用 internal 中的 apiserver.NewApp 构建 APP 并 运行. 从这里我们扩展去看 NewApp 的实现

    apiserver.NewApp

    internal/apiserver/apiserver.go

这个函数完成了一个通用 APP 对象的构建

  1. // NewApp creates a App object with default parameters.
  2. func NewApp(basename string) *app.App {
  3. opts := options.NewOptions()
  4. application := app.NewApp("IAM API Server",
  5. basename,
  6. app.WithOptions(opts),
  7. app.WithDescription(commandDesc),
  8. app.WithDefaultValidArgs(),
  9. app.WithRunFunc(run(opts)),
  10. )
  11. return application
  12. }
  • 第 1 行 它写的注释是说此函数是利用默认参数创建一个 app 对象.
  • 第 2 行 函数定义中的返回值 app.APP 应该是定义在 pkg 中的一个通用 APP 启动 SDK,后面详细分析.
  • 第 3 行 建立一个新的选项信息,所有的选项信息被定义在 options 包中.
  • 第 4 - 10 行 这里是利用 选项模式 实现的一个 app 构造函数. 后面将逐一对 WithOptions, WithDescription, WithDefaultValidArgs, WithRunFunc 进行分析.
  • 第 10 行 这里调用了本身的 run 函数,应该是启动 server 的关键函数。

下面基于我目前的开发需求, 首先对 run 函数进行分析. appoptions 包在以后时间多的情况下进行详细分析.

run

internal/apiserver/apiserver.go

这个函数返回了一个 app.RunFunc , 这是一个函数类型的定义 **type RunFunc func(basename string) error**.

  1. func run(opts *options.Options) app.RunFunc {
  2. return func(basename string) error {
  3. log.Init(opts.Log)
  4. defer log.Flush()
  5. cfg, err := config.CreateConfigFromOptions(opts)
  6. if err != nil {
  7. return err
  8. }
  9. return Run(cfg)
  10. }
  11. }
  • 第 3 行 初始化 log
  • 第 4 行 在整个程序被退出时, 执行 Flush 将数据刷入文件. 这里是一个巧妙的设计, 由于这个函数是一个返回值, 其本质是在 _main_ 函数中才被调用, 所以这里的 defer 一定是在程序退出之后执行。
  • 第 6 - 9 行 利用 option 生成 config.
  • 第 11 行 调用 Run 函数启动服务.

下面分析 Run 函数的实现

Run

internal/apiserver/run.go

这是一个永远运行不会退出的函数, 个人认为这个函数的好处是可以让 service 的启动运行变的简单, 隔离了 service 运行和命令行输入相关功能, 之间使用 config 做桥梁。

  1. // Run runs the specified APIServer. This should never exit.
  2. func Run(cfg *config.Config) error {
  3. server, err := createAPIServer(cfg)
  4. if err != nil {
  5. return err
  6. }
  7. return server.PrepareRun().Run()
  8. }
  • 第 3 行 创建 APIServer 实例
  • 第 8 行 Server 启动

下面紧跟 RunPrepareRun 函数研究执行过程

apiServer.PrepareRun

internal/apiserver/server.go

server 运行前的准备, 返回一个准备好的 service。为什么不直接返回 server 而是要再处理一步,是为了函数地职责单一吗? 这里对server的状态也做了划分, 目前看可以是 _**利用配置创建 -> server启动准备 -> server 启动**_

  1. func (s *apiServer) PrepareRun() preparedAPIServer {
  2. initRouter(s.genericAPIServer.Engine)
  3. s.initRedisStore()
  4. s.gs.AddShutdownCallback(shutdown.ShutdownFunc(func(string) error {
  5. mysqlStore, _ := mysql.GetMySQLFactoryOr(nil)
  6. if mysqlStore != nil {
  7. return mysqlStore.Close()
  8. }
  9. s.gRPCAPIServer.Close()
  10. s.genericAPIServer.Close()
  11. return nil
  12. }))
  13. return preparedAPIServer{s}
  14. }
  • 第 2 行 初始化路由
  • 第 4 行 初始化 Redis
  • 第 6 - 15 行 建立一个在程序被 shutdown 后的资源处理函数
  • 第 18 行 返回一个名为准备好的 APIServer 结构

    preparedAPIServer.Run

    internal/apiserver/server.go

  1. func (s preparedAPIServer) Run() error {
  2. go s.gRPCAPIServer.Run()
  3. // start shutdown managers
  4. if err := s.gs.Start(); err != nil {
  5. log.Fatalf("start shutdown manager failed: %s", err.Error())
  6. }
  7. return s.genericAPIServer.Run()
  8. }
  • 第 2 行 启动 GRPC 服务
  • 第 4 行 启动 shutdown 管理
  • 第 9 行 调用 通用的 APIserver 逻辑启动 Server

    总结

    至此 server 的启动过程被全部研究完毕。这里可以看到它使用了 app options 和 genericapiserver 对一些通用的功能进行封装处理。对一些通用逻辑操作进行提取,只将需要的不同数据安装到通用逻辑中实现操作。