• 作者:老汪软件技巧
  • 发表时间:2024-08-27 11:02
  • 浏览量:

概述

本篇文章给大家介绍一款为 Go 后端开发设计的微服务框架——go-kratos(下面简称 Kratos)。Kratos 是一套由 Bilibili 开源的轻量级 Go 微服务框架,包含大量微服务相关框架及工具。Kratos(奎托斯)是希腊神话中的战神,其主要经历是由凡人成为战神并展开弑神屠杀。

Kratos Framework 有如下特性:

Kratos 提供了很多通用模块的统一接口,这样和第三方组件对接起来就十分方便。其实开发微服务你不用微服务框架都可以进行开发,比如单纯使用 Gin 框架开发,但是和第三方微服务组件对接需要自行封装逻辑。

环境准备

要想使用 Kratos 框架进行开发,那么首先要准备以下环境:

Go SDK 的安装我就不多赘述了,默认读者都会安装。

protoc

点击上面的链接下载安装完成之后,把目录放到合适的位置,然后配置环境变量:

# Windows PowerShell
setx PATH "$ENV:PATH;/path/to/protoc/bin" /m

# Linux Bash
vim /etc/profile
# 添加
export PATH="/path/to/protoc/bin:$PATH"
source /etc/profile

# Macos brew 一键安装,环境变量会自动配置
brew install protobuf

检查一下是否安装成功:

protoc --version

protoc-gen-go

这个是生成 Go 程序源代码的 protoc 的插件,使用 go install 命令安装:

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# 测试一下
protoc-gen-go --version
protoc-gen-go-grpc -version

kratos

kratos 是与 Kratos 框架配套的脚手架工具。kratos 工具支持以下功能:

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

# 测试一下
kratos -v

初始化一个工程

首先创建一个工作目录,然后在这个目录下创建多个 Kratos 微服务模块。这里我们使用多模块开发项目,Kratos 代码生成策略也是一个服务一个 Go 模块,而不是像一些项目使用的是“大仓模式”——多个子模块共用一个 go.mod 文件。

创建一个名为 kratos-first 目录,然后进入这个目录。使用 kratos new 命令创建 provider 服务:

kratos new provider-srv

然后紧接着创建 consumer 服务:

kratos new consumer-srv

分别进入这两个服务目录下安装一下依赖:

go mod tidy

修改配置文件

在每个服务的工程内部有一个 configs 目录,有一个 config.yaml 文件,配置如下:

server:
  http:
    addr: 0.0.0.0:8000
    timeout: 1s
  grpc:
    addr: 0.0.0.0:9000
    timeout: 1s
data:
  database:
    driver: mysql
    source: root:root@tcp(127.0.0.1:3306)/test?parseTime=True&loc=Local
  redis:
    addr: 127.0.0.1:6379
    read_timeout: 0.2s
    write_timeout: 0.2s

provider-srv 的 http/gRPC 端口分别设置为 8000/9000。consumer-srv http/gRPC 端口分别设置为 8100/9100。修改上面的端口号即可。

配置启动项

修改完毕之后,点击 main 函数运行发现运行不起来:

程序无法识别 ../../configs 路径,解决方案就是添加命令行参数:-conf ./configs

抽离接口有关结构体

为了让微服务的接口用到的接口体类型可以达到复用的目的,这就需要把微服务用到的结构体类型都抽离到一个公共模块中去。一个微服务对应一个对外暴露的模块,或者所有微服务都共用一个公共模块(其实这样并不好,随着项目发展,模块会变得十分臃肿)。

在根目录下分别创建 provider-srv-interface 和 consumer-srv-interface 模块,这两个模块是 Go Module。然后我们再在根目录下创建一个 go.work 文件,这个是 Go 1.18 引入的新特性,可以实现在本地导入模块(但是在实际打包部署的时候引用仓库的包)。

go.work 编写如下代码:

go 1.20
use (  
    consumer-srv
    consumer-srv-interface
    provider-srv
    provider-srv-interface
)

最后目录结构如下:

最后把 provider-srv 的 api 目录抽离到 provider-srv-interface 模块中:

这样假如别的微服务要调用 provider-srv 时候就可以导入这个模块,使用里面的结构体类型。

同理,consumer-srv 也是如法炮制。

引入注册中心

微服务开发那必然离不开注册中心,这里我们使用的是 consul。consul 是一个 HashiCorp 公司开源的网络工具,承诺永久免费,可提供功能齐全的服务网格和服务发现。

使用一下 docker-compose 配置:

version: '3.8'
services:
  consul:
    container_name: consul
    image: hashicorp/consul
    ports:
      - "8500:8500"
    command: agent -dev -client=0.0.0.0

然后使用如下命令启动:

# 这是新版 docker 的命令,不带 - 符号
docker compose up -d

分别给两个微服务引入 consul 客户端依赖:

go get github.com/hashicorp/consul/api
go get "github.com/go-kratos/kratos/contrib/registry/consul/v2"

修改 main 文件中的配置,其他微服务同理:

// go build -ldflags "-X main.Version=x.y.z"  
var (  
    // Name is the name of the compiled software.  
    Name = "consumer-service"  
    // Version is the version of the compiled software.  
    Version string  
    // flagconf is the config flag.  
    flagconf string  
    id = Name + "-" + uuid.NewString()  
)  
  
func init() {  
    flag.StringVar(&flagconf, "conf", "../../configs", "config path, eg: -conf config.yaml")
}  
  
func newApp(logger log.Logger, gs *grpc.Server, hs *http.Server) *kratos.App {
    // 获取 consul 客户端
    consulConfig := api.DefaultConfig()  
    consulConfig.Address = "http://localhost:8500"  
    consulClient, err := api.NewClient(consulConfig)  
    if err != nil {  
        log.Fatal(err)  
    }  
  
    // 获取 consul 注册管理器  
    reg := consul.New(consulClient)
  
    // 设置元数据  
    meta := map[string]string{  
        "weight": "999",  
    }
  
    return kratos.New(  
        kratos.ID(id),  
        kratos.Name(Name),  
        kratos.Version(Version),  
        kratos.Metadata(meta),  
        kratos.Logger(logger),  
        kratos.Server(  
            gs,  
            hs,  
        ),
        // 创建服务时,指定服务器注册
        kratos.Registrar(reg),  
    )  
}

重新启动服务之后可以看到服务成功地注册到了 consul。

微服务调用

下面来让 consumer-srv 来调用 provider-srv,通过注册中心。

修改 service/greeter.go 文件:

package service
import (
    "context"
    "github.com/go-kratos/kratos/contrib/registry/consul/v2"
    "github.com/go-kratos/kratos/v2/log"
    "github.com/go-kratos/kratos/v2/transport/grpc"
    "github.com/hashicorp/consul/api"
    "consumer-srv/internal/biz"
    v1 "provider-srv-interface/api/helloworld/v1"
)
// GreeterService is a greeter service.
type GreeterService struct {
    v1.UnimplementedGreeterServer
    uc *biz.GreeterUsecase
}
// NewGreeterService new a greeter service.
func NewGreeterService(uc *biz.GreeterUsecase) *GreeterService {
    return &GreeterService{uc: uc}
}
// SayHello implements helloworld.GreeterServer.
func (s *GreeterService) SayHello(ctx context.Context, in *v1.HelloRequest) (*v1.HelloReply, error) {
    consulConfig := api.DefaultConfig()
    consulConfig.Address = "localhost:8500"
    consulClient, err := api.NewClient(consulConfig)
    // 获取服务发现管理器
    dis := consul.New(consulClient)
    if err != nil {
        log.Fatal(err)
    }
    // 连接目标 grpc 服务器
    endpoint := "discovery:///provider-service"
    conn, err := grpc.DialInsecure(
        ctx,
        grpc.WithEndpoint(endpoint),
        grpc.WithDiscovery(dis),
    )
    if err != nil {
        return &v1.HelloReply{
            Message: "provider-service is down",
        }, nil
    }
    client := v1.NewGreeterClient(conn)
    resp, err := client.SayHello(ctx, &v1.HelloRequest{
        Name: "Jerris",
    })
    if err != nil {
        return &v1.HelloReply{
            Message: "grpc call of provider service failed",
        }, nil
    }
	return &v1.HelloReply{Message: "grpc call from consumer to provider: " + resp.Message}, nil
}

使用浏览器访问一下 :8100/hello/666 地址:

成功实现了调用。这里可能会出现一些 proto 文件命名空间冲突的问题,作者还在研究中。有兴趣的小伙伴可以自行研究一下。


上一条查看详情 +1.为什么是需要gzip压缩?
下一条 查看详情 +没有了