通过 layout 探索 kratos 运行原理
创建项目
首先需要安装好对应的依赖环境,以及工具:
- go
 - protoc
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
 - protoc-gen-go
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
 
# 创建项目模板
kratos new helloworld
cd helloworld
# 拉取项目依赖
go mod download
# 生成proto模板
kratos proto add api/helloworld/helloworld.proto
# 生成proto源码
kratos proto client api/helloworld/helloworld.proto
# 生成server模板
kratos proto server api/helloworld/helloworld.proto -t internal/service
执行命令后,会在当前目录下生成一个 service 工程,工程骨架如下,具体的工程骨架说明可以访问 layout

运行项目
# 生成所有proto源码、wire等等
go generate ./...
# 编译成可执行文件
go build -o ./bin/ ./...
# 运行项目
./bin/helloworld -conf ./configs
看到如下输出则证明项目启动正常
level=INFO module=app service_id=7114ad8a-b3bf-11eb-a1b9-f0189850d2cb service_name=  version=
level=INFO module=transport/grpc msg=[gRPC] server listening on: [::]:9000
level=INFO module=transport/http msg=[HTTP] server listening on: [::]:8000
测试接口
curl 'http://127.0.0.1:8000/helloworld/krtaos'
输出:
{
  "message": "Hello kratos"
}
应用是如何跑起来的?

通过上面的图例,我们可以直观观察到应用的调用链,简化来说如下图流程所示

1. 注入依赖并调用 newApp() 方法
// helloword/cmd/main.go
func main() {
    flag.Parse()
    logger := log.NewStdLogger(os.Stdout)
    // 调用 go-kratos/kratos/v2/config,创建 config 实例,并指定了来源和配置解析方法
    c := config.New(
    config.WithSource(
        file.NewSource(flagconf),
    ),
    config.WithDecoder(func(kv *config.KeyValue, v map[string]interface{}) error {
        return yaml.Unmarshal(kv.Value, v)
    }),
    )
    if err := c.Load(); err != nil {
        panic(err)
    }
    // 将配置扫描到,通过 proto 声明的 conf struct 上
    var bc conf.Bootstrap
    if err := c.Scan(&bc); err != nil {
        panic(err)
    }
    // 通过 wire 将依赖注入,并调用 newApp 方法
    app, cleanup, err := initApp(bc.Server, bc.Data, logger)
    if err != nil {
        panic(err)
    }
    // 省略代码...
}
2. 创建 kratos 实例
项目 main.go 的 newApp() 方法中,调用了 go-kratos/kratos/v2/app.go 中的 kratos.New() 方法
// helloword/cmd/main.go
func newApp(logger log.Logger, hs *http.Server, gs *grpc.Server) *kratos.App {
    return kratos.New(
        // 配置应用
        kratos.Name(Name),
        kratos.Version(Version),
        kratos.Metadata(map[string]string{}),
        kratos.Logger(logger),
        // kratos.Server() 传入的 http/grpc 服务会通过 buildInstance() 转换成registry.ServiceInstance struct*
        kratos.Server(
            hs,
            gs,
        ),
    )
}
该方法会返回一个 App struct,包含 Run() 和 Stop() 方法
// go-kratos/kratos/v2/app.go
type App struct {
    opts     options //配置
    ctx      context.Context // 上下文
    cancel   func() // context 的取消方法
    instance *registry.ServiceInstance //通过 kratos.Server()声明的实例,并通过 buildInstance() 转换后的 *registry.ServiceInstance struct
    log      *log.Helper // 日志
}
// Run executes all OnStart hooks registered with the application's Lifecycle.
func (a *App) Run() error {
    // 省略代码...
}
// Stop gracefully stops the application.
func (a *App) Stop() error {
    // 省略代码...
}
3. 调用 Run() 方法#
项目在 main 方法中调用了 kratos.App struct 的 Run() 方法.
// helloword/cmd/main.go
// 省略代码...
// 启动 Kratos
if err := app.Run(); err != nil {
    panic(err)
}
Run() 方法的实现细节
// go-kratos/kratos/v2/app.go
func (a *App) Run() error {
    a.log.Infow(
        "service_id", a.opts.id,
        "service_name", a.opts.name,
        "version", a.opts.version,
    )
    g, ctx := errgroup.WithContext(a.ctx)
        // 遍历通过 kratos.Server() 声明的服务实例
    for _, srv := range a.opts.servers {
        srv := srv
                // 执行两个goroutine, 用于处理服务启动和退出
        g.Go(func() error {
            <-ctx.Done() // 阻塞,等待调用 cancel 方法
            return srv.Stop() // 协程退出后,调用实例的停止方法
        })
        g.Go(func() error {
            return srv.Start() // 调用实例的运行方法
        })
    }
        // 判断是否调用 kratos.Registrar() 配置了注册发现中心
    if a.opts.registrar != nil {
        // 将实例注册到注册中心
        if err := a.opts.registrar.Register(a.opts.ctx, a.instance); err != nil
            return err
        }
    }
        // 监听进程退出信号
    c := make(chan os.Signal, 1)
    signal.Notify(c, a.opts.sigs...)
        // 处理进程退出和 context 退出
    g.Go(func() error {
        for {
            select {
            case <-ctx.Done():
                return ctx.Err()
            case <-c:
                        // 调用 kratos.App 的停止方法
                a.Stop()
            }
        }
    })
    if err := g.Wait(); err != nil && !errors.Is(err, context.Canceled) {
        return err
    }
    return nil
}
4. 应用退出
Kratos 实例在启动时,监听了系统的进程退出信号,当收到退出信号时,kratos 会调用 App struct 的 Stop() 方法
// go-kratos/kratos/v2/app.go
func (a *App) Stop() error {
    // 判断是否有注册中心配置
    if a.opts.registrar != nil {
        // 在注册中心中将实例注销
        if err := a.opts.registrar.Deregister(a.opts.ctx, a.instance); err != nil {
            return err
        }
    }
    // 控制 goroutine 的退出,当调用 a.cancel()时,Run()方法中 监听的 <-ctx.Done() 收到消息后,没有阻塞后,方法会调用 server 的 Stop()方法,停止服务
    if a.cancel != nil {
        a.cancel()
    }
    return nil
}
文章转自:


通过 layout 探索 kratos 运行原理的更多相关文章
- Asp.net WebPages框架运行原理浅析(转)
		
在Asp.net4和4.5中,新增了WebPages Framework,编写页面代码使用了新的Razor语法,代码更加的简洁和符合Web标准,编写方式更接近于PHP和以前的Asp,和使用 WebFo ...
 - ASP.NET的运行原理与运行机制
		
在Asp.net4和4.5中,新增了WebPages Framework,编写页面代码使用了新的Razor语法,代码更加的简洁和符合Web标准,编写方式更接近于PHP和以前的Asp,和使用WebFor ...
 - ASP.NET Core 运行原理解剖[1]:Hosting
		
ASP.NET Core 是新一代的 ASP.NET,第一次出现时代号为 ASP.NET vNext,后来命名为ASP.NET 5,随着它的完善与成熟,最终命名为 ASP.NET Core,表明它不是 ...
 - ASP.NET Core 运行原理解剖[2]:Hosting补充之配置介绍
		
在上一章中,我们介绍了 ASP.NET Core 的启动过程,主要是对 WebHost 源码的探索.而本文则是对上文的一个补充,更加偏向于实战,详细的介绍一下我们在实际开发中需要对 Hosting 做 ...
 - ASP.NET Core 运行原理解剖[3]:Middleware-请求管道的构成
		
在 ASP.NET 中,我们知道,它有一个面向切面的请求管道,有19个主要的事件构成,能够让我们进行灵活的扩展.通常是在 web.config 中通过注册 HttpModule 来实现对请求管道事件监 ...
 - ASP.NET Core 运行原理解剖[4]:进入HttpContext的世界
		
HttpContext是ASP.NET中的核心对象,每一个请求都会创建一个对应的HttpContext对象,我们的应用程序便是通过HttpContext对象来获取请求信息,最终生成响应,写回到Http ...
 - ASP.NET Core 运行原理解剖[5]:Authentication
		
在现代应用程序中,认证已不再是简单的将用户凭证保存在浏览器中,而要适应多种场景,如App,WebAPI,第三方登录等等.在 ASP.NET 4.x 时代的Windows认证和Forms认证已无法满足现 ...
 - iBatis.Net的基本情况和运行原理
		
转载http://www.cnblogs.com/13590/archive/2013/02/27/2934580.html 摘要:介绍iBatis.Net的基本情况和运行原理,运行环境中各参数的配置 ...
 - Android开发学习笔记(二)——编译和运行原理(1)
		
http://www.cnblogs.com/Pickuper/archive/2011/06/14/2078969.html 接着上一篇的内容,继续从全局了解Android.在清楚了Android的 ...
 
随机推荐
- C++第三十三篇 -- 研究一下Windows驱动开发(一)内部构造介绍
			
因为工作原因,需要做一些与网卡有关的测试,其中涉及到了驱动这一块的知识,虽然程序可以运行,但是不搞清楚,心里总是不安,觉得没理解清楚.因此想看一下驱动开发.查了很多资料,看到有人推荐Windows驱动 ...
 - 谷粒商城--分布式高级篇P102~P128
			
谷粒商城--分布式高级篇P102~P128 由于学习的时间也比较少,只有周六周末才有时间出来学习总结,所以一篇一篇慢慢更新吧,本次总结内容为Elasticsearch(相关内容:kibana,es,n ...
 - maven 工程构建 之_____<dependencyManagement>标签
			
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://mave ...
 - noip模拟29[简单的板子题](虽然我不会)
			
\(noip模拟29\;solutions\) 这次考试给我最大的伤害,让我意识到了差距 这场考试可以说是非常的简单,就是简单到,看两眼,打个表就有结果了 但是呢?我考得非常的完蛋,只有30pts 据 ...
 - Arp欺骗和DNS投毒
			
中间人攻击 ARP缓存攻击 ARP(Address Resolution Protocol,地址解析协议)是一个位于TCP/IP协议栈中的网络层,负责将某个IP地址解析成对应的MAC地址.简单来说,就 ...
 - C中的内置函数
			
1 //#include <stdio.h> 2 //#include <ctype.h> 3 //#include <math.h> 4 //#include & ...
 - 获取元素在页面中位置 getBoundingClientRect()
			
DOM 原生方法getBoundingClientRect()获取元素相对视口位置 DOMRect 对象包含了一组用于描述边框的只读属性--left.top.right和bottom,单位为像素.除了 ...
 - Linux命令(三)vim编辑器的常用命令
			
.subTitle { background: rgba(51, 153, 0, 0.53); border-bottom: 1px solid rgba(0, 102, 0, 1); border- ...
 - [数据结构-平衡树]普通 FHQ_Treap从入门到精通(注释比代码多系列)
			
普通 FHQ_Treap从入门到精通(注释比代码多系列) 前提说明,作者写注释太累了,文章里的部分讲解来源于Oi-wiki,并根据代码,有部分增改.本文仅仅发布于博客园,其他地方出现本文,均是未经许可 ...
 - RPM包方式安装Oracle21c的方法
			
RPM包方式安装Oracle21c的方法 前言 北京时间2021.8.14 Oracle发布了最新的数据库版本Oracle21c, Oracle规划不再发布Oracle20c和Oracle22c, 直 ...