Kratos 微服务框架入门与实践

一. Kratos 简介

Kratos 是 B 站基于 Golang 实现的一个开源的面向微服务的框架. 使用 Kratos 可以很方便地构建一个规范的服务.

开源 GitHub 地址: https://github.com/go-kratos/kratos

文档地址: https://go-kratos.dev/docs/

二. Kratos 特点

  • 简单易用
  • 高效
  • 稳定
  • 高性能
  • 可扩展
  • 容错性好
  • 丰富的工具链

以上特点, 还都是从 Readme 文档翻译出来的, 后面将逐一验证和体验.

三. Kratos 实现一个 Helloworld

安装

go get github.com/go-kratos/kratos/cmd/kratos/v2@latest

根据文档

# create project template
kratos new helloworld

cd helloworld
# download modules
go mod download

# generate Proto template
kratos proto add api/helloworld/helloworld.proto
# generate Proto source code
kratos proto client api/helloworld/helloworld.proto
# generate server template
kratos proto server api/helloworld/helloworld.proto -t internal/service

# generate all proto source code, wire, etc.
go generate ./...

编译并执行

mkdir bin
# compile
go build -o ./bin/ ./...
# run
./bin/helloworld -conf ./configs

就以上这么一顿操作, 就一起完成一个服务的构建.

四. 目录结构分析

tree -d 查看目录结构

.
├── api
│   └── helloworld
│       └── v1
├── cmd
│   └── helloworld
├── configs
└── internal
    ├── biz
    ├── conf
    ├── data
    ├── server
    └── service

1. /api

这个文件夹下面, 包含 proto 文件和编译 proto 文件生成的 go 文件.

之所以把目录命名为 api, 是因为可以把 proto 当作 api 文档, 生成的 go 文件就相当于 api.

2. /cmd

这个文件夹下的代码, 主要负责程序的启动、关闭、配置初始化等.

cmd 大概率是 command 的缩写, 下面的文件夹是一个 helloworld 应用并且包含 main.go文件.

在执行 go build 的时候, 就会以文件夹的名称来命名应用.

一般不建议在 /cmd 这个文件夹下放置过多的代码和文件夹.

如果代码不是可重用的, 或者你不希望其他人重用它, 请将该代码放到 /internal 目录中.

3. /configs

放置配置文件模板, 或默认配置, 一般是 yaml 文件.

比如注册的服务的地址, 各种数据库的连接配置等.

4. /internal

这个文件夹下面, 一般都放置不希望作为第三包给他人使用的, 即是当作私有库.

  • /biz 目录: 业务逻辑的组装层, 类似于 DDD (领域驱动设计) 的 domain 层.
  • /conf 目录: 这里会有 proto 文件和编译 proto 得到的 go 代码, 而且 proto 会与上面的 /configs 下的 yaml 文件对应.
  • /data 目录: 业务数据访问, 包含 cache、db 等封装, 实现了 biz 的 repo 接口. 类似于 DDD 的 repo.
  • /server 目录: gRPC 和 HTTP 服务的注册.
  • /service 目录: 这里是实现上层的 /api 的 interface. 类似于 DDD 的 application 层.

更多参考: https://github.com/golang-standards/project-layout/blob/master/README_zh.md

五. 关键代码分析

helloworld/cmd/helloworld/main.go

func main() {
    flag.Parse()
    logger := log.NewStdLogger(os.Stdout)

    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)
    }

    var bc conf.Bootstrap
    if err := c.Scan(&bc); err != nil {
        panic(err)
    }

    app, cleanup, err := initApp(bc.Server, bc.Data, logger)
    if err != nil {
        panic(err)
    }
    defer cleanup()

    // start and wait for stop signal
    if err := app.Run(); err != nil {
        panic(err)
    }
}

这里使用 "github.com/go-kratos/kratos/v2/config" 这个包, 它的作用是将解析 /configs/config.yaml 文件解析为 protobuf, 内部隐藏的操作是, 将 yaml 文件转为 json, 然后再转为 protobuf.

六. 结合 Kratos 实现 gRPC 客户端

想法: 前面学习了 gRPC, 这里学习了 Kratos, 而 Kratos 里也有 gRPC, 已经实现了 gRPC 的服务端. 所以, 不如结合 Kratos 实现一个 gRPC 客户端, 以作 Kratos 实战.

根据上面的目录结构分析, 客户端的实现逻辑代码, 应当放在 /internal/client 文件夹下.

另外 main.go 文件, 应该放在 /cmd/helloworld_client 文件夹下.

如果先前没有的文件夹则创建.

以下主要结合了 Kratos 的 log 和 config 两个工具, 以及 wire (github.com/google/wire).

1./internal/client/grpc.go

为了使用 wire , 在 NewGrpcClient 函数的返回值的第二个参数, 必须是 func() 类型, 用于 cleanup.

NewGrpcClient 函数主要有两步, 第一步是建立 gRPC 服务连接并连接 greeter RPC, 第二步是 cleanup 处理.

GrpcClient 结构体主要是实现 RPC 的通信.

package client

import (
    "context"
    "time"

    "helloworld/internal/conf"

    "google.golang.org/grpc"

    "github.com/go-kratos/kratos/v2/log"

    v1 "helloworld/api/helloworld/v1"

    "github.com/pkg/errors"
)

type GrpcClient struct {
    logger *log.Helper
    cli    v1.GreeterClient
}

func (gc *GrpcClient) DoSay(name string) error {
    ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
    defer cancel()
    reply, err := gc.cli.SayHello(ctx, &v1.HelloRequest{Name: name})
    if err != nil {
        return errors.Wrap(err, "could not greet")
    }
    gc.logger.Infof("Greeting: %s", reply.GetMessage())
    return nil
}

func NewGrpcClient(confServer *conf.Server, logger log.Logger) (*GrpcClient, func(), error) {
    addr := confServer.Grpc.Addr
    conn, err := grpc.Dial(addr, grpc.WithInsecure(), grpc.WithBlock())
    if err != nil {
        return nil, nil, errors.Wrap(err, "did not dail gRPC server")
    }
    gc := &GrpcClient{
        logger: log.NewHelper("client/greeter", logger),
        cli:    v1.NewGreeterClient(conn),
    }
    cleanup := func() {
        if err := conn.Close(); err != nil {
            gc.logger.Errorf("hanppend a little wrongs when close gRPC server:\n %v", err)
        }
    }
    return gc, cleanup, nil
}

2./internal/client/client.go

wire.NewSet 可以放多个函数, 不分顺序.

package client

import "github.com/google/wire"

var ProviderSet = wire.NewSet(NewGrpcClient)

3./cmd/helloworld_client/wire.go

这个文件主要是通过 wire 命令来生成 wire_gen.go, 关键函数是 wire.Build , 同时不要删掉这行代码 //+build wireinject .

//+build wireinject

package main

import (
    "helloworld/internal/client"
    "helloworld/internal/conf"

    "github.com/go-kratos/kratos/v2/log"
    "github.com/google/wire"
)

func initClient(confServer *conf.Server, logger log.Logger) (*client.GrpcClient, func(), error) {
    panic(wire.Build(client.ProviderSet))
}

4./cmd/helloworld_client/main.go

这里也主要学习了 helloworld 应用中的 conf 的使用, 以及发了 100 次的 SayHello.

package main

import (
    "flag"
    "os"
    "strconv"

    "helloworld/internal/conf"

    "github.com/go-kratos/kratos/v2/config"
    "github.com/go-kratos/kratos/v2/config/file"
    "github.com/go-kratos/kratos/v2/log"
    "gopkg.in/yaml.v2"
)

var (
    flagconf string
    logger   log.Logger
)

func init() {
    flag.StringVar(&flagconf, "conf", "../../configs", "config path, eg: -conf config.yaml")
    logger = log.NewStdLogger(os.Stdout)
}

func main() {
    flag.Parse()

    // yaml 先转 json,
    // json 再转 protobuf
    cfg := 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 := cfg.Load(); err != nil {
        panic(err)
    }

    var bc conf.Bootstrap
    if err := cfg.Scan(&bc); err != nil {
        panic(err)
    }

    gcli, cleanup, err := initClient(bc.Server, logger)
    if err != nil {
        panic(err)
    }
    defer cleanup()

    lg := log.NewHelper("client/main", logger)

    for i := 0; i < 100; i++ {
        if err := gcli.DoSay("fango" + strconv.Itoa(i+1)); err != nil {
            lg.Error(err)
        }
    }
    lg.Info("finished")
}

5.编译 helloworld_client

go build -o ./bin/ ./cmd/helloworld_client

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容