- 作者:老汪软件技巧
- 发表时间: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 文件命名空间冲突的问题,作者还在研究中。有兴趣的小伙伴可以自行研究一下。