创建项目

首先需要安装好对应的依赖环境,以及工具:

  1. go

  2. protoc
    • go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
  3. 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 structRun() 方法.

// 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 structStop() 方法

// 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 运行原理的更多相关文章

  1. Asp.net WebPages框架运行原理浅析(转)

    在Asp.net4和4.5中,新增了WebPages Framework,编写页面代码使用了新的Razor语法,代码更加的简洁和符合Web标准,编写方式更接近于PHP和以前的Asp,和使用 WebFo ...

  2. ASP.NET的运行原理与运行机制

    在Asp.net4和4.5中,新增了WebPages Framework,编写页面代码使用了新的Razor语法,代码更加的简洁和符合Web标准,编写方式更接近于PHP和以前的Asp,和使用WebFor ...

  3. ASP.NET Core 运行原理解剖[1]:Hosting

    ASP.NET Core 是新一代的 ASP.NET,第一次出现时代号为 ASP.NET vNext,后来命名为ASP.NET 5,随着它的完善与成熟,最终命名为 ASP.NET Core,表明它不是 ...

  4. ASP.NET Core 运行原理解剖[2]:Hosting补充之配置介绍

    在上一章中,我们介绍了 ASP.NET Core 的启动过程,主要是对 WebHost 源码的探索.而本文则是对上文的一个补充,更加偏向于实战,详细的介绍一下我们在实际开发中需要对 Hosting 做 ...

  5. ASP.NET Core 运行原理解剖[3]:Middleware-请求管道的构成

    在 ASP.NET 中,我们知道,它有一个面向切面的请求管道,有19个主要的事件构成,能够让我们进行灵活的扩展.通常是在 web.config 中通过注册 HttpModule 来实现对请求管道事件监 ...

  6. ASP.NET Core 运行原理解剖[4]:进入HttpContext的世界

    HttpContext是ASP.NET中的核心对象,每一个请求都会创建一个对应的HttpContext对象,我们的应用程序便是通过HttpContext对象来获取请求信息,最终生成响应,写回到Http ...

  7. ASP.NET Core 运行原理解剖[5]:Authentication

    在现代应用程序中,认证已不再是简单的将用户凭证保存在浏览器中,而要适应多种场景,如App,WebAPI,第三方登录等等.在 ASP.NET 4.x 时代的Windows认证和Forms认证已无法满足现 ...

  8. iBatis.Net的基本情况和运行原理

    转载http://www.cnblogs.com/13590/archive/2013/02/27/2934580.html 摘要:介绍iBatis.Net的基本情况和运行原理,运行环境中各参数的配置 ...

  9. Android开发学习笔记(二)——编译和运行原理(1)

    http://www.cnblogs.com/Pickuper/archive/2011/06/14/2078969.html 接着上一篇的内容,继续从全局了解Android.在清楚了Android的 ...

随机推荐

  1. spring IOC体系图

  2. 论文笔记:(2021CVPR)PAConv: Position Adaptive Convolution with Dynamic Kernel Assembling on Point Clouds

    目录 摘要 1.引言 2.相关工作 将点云映射到常规二维或三维栅格(体素) 基于MLPs的点表示学习 基于点卷积的点表示学习 动态卷积和条件卷积 3.方法 3.1 回顾 3.2 动态内核组装 Weig ...

  3. 自建简易FaaS平台

    近些年来,传统的 IaaS.PaaS 已经无法满足人们对资源调度的需求了.各大云厂商相继开始推出自家的 Serverless 服务.Serverless 顾名思义,它是"无服务器" ...

  4. Mybatis学习笔记-CRUD

    namespace namesapce中的包名需与Dao/Mapper接口的包名一致 SELCET 选择,查询语句 id:对应namespace中的方法: resultType:Sql语句执行的返回值 ...

  5. 黑盒渗透测试【转自HACK学习-FoxRoot】

    因 搜到一篇写渗透测试步骤比较详细的文章,转过来学习,方便时常看看. 内容如下: 一.信息搜集 主动/被动搜集 信息搜集分为主动信息搜集和被动信息搜集. 主动信息搜集就是通过直接访问和扫描信息的方式进 ...

  6. 05.表达式目录树Expression

    参考文章 https://www.cnblogs.com/xyh9039/p/12748983.html 1. 基本了解 1.1 Lambda表达式 演变过程 using System; namesp ...

  7. [TensorFow2.0]-MNIST手写数字识别

    本人人工智能初学者,现在在学习TensorFlow2.0,对一些学习内容做一下笔记.笔记中,有些内容理解可能较为肤浅.有偏差等,各位在阅读时如有发现问题,请评论或者邮箱(右侧边栏有邮箱地址)提醒. 若 ...

  8. 关于Feign、Jackson、RabbitMQ、Jrebel插件的开发中遇到的问题

    在工作实际开发中需要开发一个消息模块对外提供统一的接口feign调用提供消息加载到MQ队列的服务,采用泛型的形式. 刚开始搭建好之后,正好需要做一个全局的日志添加到zuul网关中,通过网关feign ...

  9. 【笔记】使用PCA对数据进行降噪(理解)

    使用PCA对数据进行降噪(使用手写数字实例) (在notebook中) 加载库并制作虚拟的数据并进行绘制 import numpy as np import matplotlib.pyplot as ...

  10. Redis实现分布式锁那件事

    今天我们来聊一聊分布式锁的那些事. 相信大家对锁已经不陌生了,我们在多线程环境中,如果需要对同一个资源进行操作,为了避免数据不一致,我们需要在操作共享资源之前进行加锁操作.在计算机科学中,锁(lock ...