Go框架三件套之Kitex| 青训营笔记

339 阅读3分钟

这是我参与「第五届青训营 」笔记创作活动的第3天

Kitex

关于Kitex

Kitex 是字节内部的Golang微服务RPC框架,具有高性能、强扩展的主要特点,支持多协议并且拥有非常丰富的开源扩展

一个RPC框架,底层需要两大功能:

  1. Serialization 序列化
  2. Transport 传输

序列化Kitex支持两种序列化协议 thriftprotobuf;传输上 Kitex 使用扩展的 thrift 作为底层的传输协议

IDL 全称是 Interface Definition Language,接口定义语言。

Thrift IDL 语法可参考:Thrift interface description language

proto3 语法可参考:Language Guide(proto3)

安装Kitex代码生成工具

Kitex目前对Windows的支持不完善,如果本地开发环境时Windows的同学建议使用虚拟机或者WSL2

安装代码生成工具

  1. 安装 kitex:go install github.com/cloudwego/kitex/tool/cmd/kitex@latest
  2. 安装 thriftgo:go install github.com/cloudwego/thriftgo@latest
~ » kitex -version
v0.4.4
~ » thriftgo -version                       
thriftgo 0.2.5

创建项目目录

~/Desktop/Go开发 » mkdir example
~/Desktop/Go开发 » cd example  
~/Desktop/Go开发/example » go mod init example

编写IDL

以thrift IDL 为例:

创建一个名为echo.thrift 的thrift IDL 文件

然后在里面定义我们的服务

namespace go api
​
struct Request {
  1: string message
}
​
struct Response {
  1: string message
}
​
service Echo {
    Response echo(1: Request req)
}
​

生成echo服务代码

~/Desktop/Go开发/example » kitex -module example -service example echo.thrift
~/Desktop/Go开发/example » go mod tidy

如果遇到类似如下报错:

github.com/apache/thrift/lib/go/thrift: ambiguous import: found package github.com/apache/thrift/lib/go/thrift in multiple modules

先执行一遍下述命令,再继续操作:

go mod edit -droprequire=github.com/apache/thrift/lib/go/thrift
go mod edit -replace=github.com/apache/thrift=github.com/apache/thrift@v0.13.0

上述命令中,-module 表示生成的该项目的 go module 名,-service 表明我们要生成一个服务端项目,后面紧跟的 example 为该服务的名字。最后一个参数则为该服务的 IDL 文件。

生成后的项目结构如下:

.
|-- build.sh //构建脚本,编译脚本
|-- echo.thrift
|-- handler.go //用户在该文件里实现IDL Service定义的代码
|-- kitex_gen //内容相关的生成代码,主要是基础的Server/Client代码
|   `-- api
|       |-- echo
|       |   |-- client.go
|       |   |-- echo.go
|       |   |-- invoker.go
|       |   `-- server.go
|       |-- echo.go
|       `-- k-echo.go
|-- main.go //程序入口
|-- go.mod 
`-- script
    |-- bootstrap.sh
    `-- settings.py

编写echo 服务逻辑

我们需要编写的服务端逻辑都在 handler.go 这个文件中,现在这个文件应该如下所示:

package main
​
import (
  "context"
  "example/kitex_gen/api"
)
​
// EchoImpl implements the last service interface defined in the IDL.
type EchoImpl struct{}
​
// Echo implements the EchoImpl interface.
func (s *EchoImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) {
  // TODO: Your code here...
  return
}

修改 Echo 函数为下述代码:

func (s *EchoImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) {
  return &api.Response{Message: req.Message}, nil
}

编译运行

kitex 工具已经帮我们生成好了编译和运行所需的脚本:

编译:

$ sh build.sh

执行上述命令后,会生成一个 output 目录,里面含有我们的编译产物。

运行:

$ sh output/bootstrap.sh

执行上述命令后,Echo 服务就开始运行啦!

编写客户端

$ mkdir client

进入目录:

$ cd client

创建一个 main.go 文件,然后就开始编写客户端代码了。

package main
​
import (
  "context"
  "example/kitex_gen/api"
  "example/kitex_gen/api/echo"
  "github.com/cloudwego/kitex/client"
  "github.com/cloudwego/kitex/client/callopt"
  "log"
  "time"
)
​
func main() {
  c, err := echo.NewClient("example", client.WithHostPorts("0.0.0.0:8888"))
  if err != nil {
    log.Fatal(err)
  }
​
  req := &api.Request{Message: "my request"}
  resp, err := c.Echo(context.Background(), req, callopt.WithRPCTimeout(3*time.Second))
  if err != nil {
    log.Fatal(err)
  }
  log.Println(resp)
}
​

发起调用

go run main.go

结果

~/Desktop/Go开发/example/client » go run main.go                                                                                         xueyeshang@xueyeshangdeMacBook-Pro
2023/01/20 19:15:36 Response({Message:my request})

治理特性:服务注册与发现

Kitex 已经通过社区开发者的支持,完成了 ETCD、ZooKeeper、Eureka、Consul、Nacos、Polaris 多种服务发现模式,当然也支持 DNS 解析以及 Static IP 直连访问模式,建立起了强大且完备的社区生态,供用户按需灵活选用。

比如 DNS Resolver, 适合使用 DNS 作为服务发现的场景, 常见的用于 Kubernetes 集群。

服务发现组件:registry-etcdregistry-nacosregistry-zookeeperpolarisregistry-eurekaregistry-consulregistry-servicecomb

registry-etcd

以registry-etcd为例:

~/Desktop/Go开发/example/client » go get -u "github.com/kitex-contrib/registry-etcd@latest" 

服务端

package main
​
import (
   api "example/kitex_gen/api/echo"
   "github.com/cloudwego/kitex/pkg/rpcinfo"
   "github.com/cloudwego/kitex/server"
   etcd "github.com/kitex-contrib/registry-etcd"
   "log"
)
​
func main() {
   //注册etcd
   r, err := etcd.NewEtcdRegistry([]string{"127.0.0.1:2379"})
   if err != nil {
      log.Fatal(err)
   }
​
   ser := api.NewServer(new(EchoImpl), server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{ServiceName: "example"}), server.WithRegistry(r))
​
   err = ser.Run()
   if err != nil {
      log.Println(err.Error())
   }
}

客户端

package main
​
import (
   "context"
   "example/kitex_gen/api"
   "example/kitex_gen/api/echo"
   "github.com/cloudwego/kitex/client"
   "github.com/cloudwego/kitex/client/callopt"
   etcd "github.com/kitex-contrib/registry-etcd"
   "log"
   "time"
)
​
func main() {
   r, err := etcd.NewEtcdResolver([]string{"127.0.0.1:2379"})
   if err != nil {
      log.Fatal(err)
   }
   c, err := echo.NewClient("example", client.WithResolver(r))
   if err != nil {
      log.Fatal(err)
   }
​
   req := &api.Request{Message: "my request"}
   resp, err := c.Echo(context.Background(), req, callopt.WithRPCTimeout(3*time.Second))
   if err != nil {
      log.Fatal(err)
   }
   log.Println(resp)
}