php框架 svc dao,Bilibili Kratos 框架源码分析(1) -- 启动流程

kratos.png

这里先吐槽一下 kratos 官方 wiki 写的实在不咋地, 一些很基本的使用方法, 一些很好的功能都没有体现出现, 同时也建议多去 github issue 里去找找答案, 那里面比 wiki 详细很多.

这个系列的文章我会基于 v0.4.2 这个版本的源码进行. 现在正式进入这个系列源码的第一篇: Kratos 启动流程

安装 kratos

至于如何安装 kratos, 请参考 官方wiki,

Kratos 官方推荐方式: GO111MODULE=on && go get -u github.com/go-kratos/kratos/tool/kratos, 不过有可能你依然无法安装成功.

特别是 bilibili/kratos 迁移到 go-kratos/kratos 之后, 如果你之前安装过 kratos, 使用上面的方式基本就 gg 了, 会遇到下面问题 module declares its path as: github.com/go-kratos/kratos but was required as: github.com/bilibili/kratos

解决方案:

方案1: kratos 维护人员推荐的方案, 将整个代码拷贝下来, cd kratos/tool && go install ./...

方案2: 彻底清理以前安装的 kratos 相关的组件(kratos, kratos-gen-bts, kratos-gen-mc, kratos-gen-project, kratos-protoc, testgen, swagger, wire, testcli), 然后GO111MODULE=on && go get -u github.com/go-kratos/kratos/tool/kratos 应该就能安装成功了, 如果还不成功, 多试几次或者尝试 方案1

初始化demo

使用 kratos new kratos-demo 生成一个demo项目

123

cd kratos-demo/cmd

go build

./cmd -conf ../configs

打开浏览器访问:http://localhost:8000/kratos-demo/start,你会看到输出了Golang 大法好 !!!

整体流程

接下来看下程序的启动流程, 主函数在 cmd/main.go 中

123456789101112131415161718192021222324252627

func main() {

flag.Parse()

log.Init(nil) // debug flag: log.dir={path} ---> 启动logdefer log.Close()

log.Info("kratos-demo start")

paladin.Init() // ----> 启动配置文件_, closeFunc, err := di.InitApp() // ----> 利用依赖注入, 初始化各个组件if err != nil {

panic(err)

}

c := make(chan os.Signal, 1) // ---> 优雅重启signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)

for {

s :=

log.Info("get a signal %s", s.String())

switch s {

case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:

closeFunc()

log.Info("kratos-demo exit")

time.Sleep(time.Second)

return

case syscall.SIGHUP:

default:

return

}

}

}启动 log

启动启动配置文件 [config]

利用依赖注入(wire), 初始化各个组件(redis, mc, db, dao, service, http server, grpc server), 并将整体串联起来

优雅重启

1. 启动 log

这里先不对 log 做详细的分析, 后面连载章节介绍

官方介绍:

基于zap的field方式实现的高性能log库,提供Info、Warn、Error日志级别;

并提供了context支持,方便打印环境信息以及日志的链路追踪,在框架中都通过field方式实现,避免format日志带来的性能消耗。

2. 启动启动配置文件

这里先不对 配置文件 做详细的分析, 后面连载章节介绍

官方介绍

初看起来,配置管理可能很简单,但是这其实是不稳定的一个重要来源。

即变更管理导致的故障,我们目前基于配置中心(config-service)的部署方式,二进制文件的发布与配置文件的修改是异步进行的,每次变更配置,需要重新构建发布版。

由此,我们整体对配置文件进行梳理,对配置进行模块化,以及方便易用的paladin config sdk

3. 利用依赖注入初始化各个组件

想要了解这个过程, 需要先简单了解 wire 的运作流程. 关于 wire 库是如何使用的, 我这里就不做 demo 过多的介绍, 推荐查看公众号: GoUpUp Go 每日一库之 wire

下面直接进入 kratos 的依赖注入过程

Provider(构造器)

internal/dao/dao.go 有下面一段代码:

1

var Provider = wire.NewSet(New, NewDB, NewRedis, NewMC)

internal/service/service.go 也有差不多的一段代码:

1

var Provider = wire.NewSet(New, wire.Bind(new(pb.DemoServer), new(*Service)))

Injector(注入器)

123456789101112131415161718

// +build wireinject ----> 尤其要注意这里// The build tag makes sure the stub is not built in the final build.

package di

import (

"kratos-demo/internal/dao"

"kratos-demo/internal/service"

"kratos-demo/internal/server/grpc"

"kratos-demo/internal/server/http"

"github.com/google/wire"

)

//go:generate kratos t wirefunc InitApp() (*App, func(), error) {

panic(wire.Build(dao.Provider, service.Provider, http.New, grpc.New, NewApp))

}

特别要注意: // +build wireinject , +build不是一个注释这么简单, 其实是 Go 语言的一个特性。类似 C/C++ 的条件编译,在执行go build时可传入一些选项,根据这个选项决定某些文件是否编译(引用Go 每日一库之 wire), 还要注意

几个非常重要的 New

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657

// NewApp 串联整个kraots的组件func NewApp(svc *service.Service, h *bm.Engine, g *warden.Server) (app *App, closeFunc func(), err error){

app = &App{

svc: svc,

http: h,

grpc: g,

}

closeFunc = func() {

ctx, cancel := context.WithTimeout(context.Background(), 35*time.Second)

if err := g.Shutdown(ctx); err != nil {

log.Error("grpcSrv.Shutdown error(%v)", err)

}

if err := h.Shutdown(ctx); err != nil {

log.Error("httpSrv.Shutdown error(%v)", err)

}

cancel()

}

return

}

// http.New 初始化和启动 Http Server(gin)func New(s pb.DemoServer) (engine *bm.Engine, err error) {

var (

cfg bm.ServerConfig

ct paladin.TOML

)

if err = paladin.Get("http.toml").Unmarshal(&ct); err != nil {

return

}

if err = ct.Get("Server").UnmarshalTOML(&cfg); err != nil {

return

}

svc = s

engine = bm.DefaultServer(&cfg)

pb.RegisterDemoBMServer(engine, s)

initRouter(engine)

err = engine.Start()

return

}

// grpc.New 初试化和启动 grpc serverfunc New(svc pb.DemoServer) (ws *warden.Server, err error) {

var (

cfg warden.ServerConfig

ct paladin.TOML

)

if err = paladin.Get("grpc.toml").Unmarshal(&ct); err != nil {

return

}

if err = ct.Get("Server").UnmarshalTOML(&cfg); err != nil {

return

}

ws = warden.NewServer(&cfg)

pb.RegisterDemoServer(ws.Server(), svc)

ws, err = ws.Start()

return

}

直接在 internal/di 下运行 wire 或者执行 kratos tool wire, 最终会在 internal/di下生成 wire_gen.go, 其生成的具体启动步骤:

dao.NewRedis(): 生成 redis handler 及 cleanup 清理函数(给优雅重启使用)

dao.NewMC(): 生成 memcache handler 及 cleanup 清理函数(给优雅重启使用)

dao.NewDB(): 生成 db handler 及 cleanup 清理函数(给优雅重启使用)

dao.New(redis, memcache, db): 将 redis, mc, db handler 传给 dao 的初始化函数, 初始化Dao

service.New(daoDao): 将 dao 传给 service, 将 service 初始化

http.New(serviceService): 将 service 传给 http, 启动 http server

grpc.New(serviceService): 将 service 传给 grpc, 启动 grpc server

NewApp(serviceService, engine, server): 将 http 和 grpc server 整体跟框架串联起来

4. 优雅重启

12345678910111213141516

c := make(chan os.Signal, 1)

signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)

for {

s :=

log.Info("get a signal %s", s.String())

switch s {

case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:

closeFunc()

log.Info("kratos-demo exit")

time.Sleep(time.Second)

return

case syscall.SIGHUP:

default:

return

}

}

这个就比较简单了, 主要利用各个组件的初始化函数返回的 cleanup 函数, 在程序收到 syscall.SIGQUIT(3), syscall.SIGTERM(15), syscall.SIGINT(2) 优雅关闭各个组件

总结

至此, 输出 Golang 大法好 !!! 的整个流程大概介绍了一遍. 下面一篇会写一个简单的 demo, postman 通过 /check_role 接口 访问 biz 项目, biz 通过 grpc 和 http 两种方式访问 up 项目

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值