Kitex 是一种用于构建高性能、可扩展和易于维护的分布式应用程序的开源框架,主要用于 Golang 编程语言。它旨在帮助开发人员构建基于微服务架构的应用程序,提供了一系列强大的功能和工具,包括网络通信、序列化、负载均衡、服务注册与发现等。
接下来简单介绍Kitex的使用方法。
1 定义服务接口和数据传输格式
Kitex 框架及命令行工具,默认支持 thrift 和 proto3 两种 IDL,对应的 Kitex 支持 thrift 和 protobuf 两种序列化协议。 传输上 Kitex 使用扩展的 thrift 作为底层的传输协议。此处以thrift为案例定义IDL。
1.1 初始化项目
为方便操作,此处在$GOPATH的src下创建项目
mkdir KitexLearn
cd KitexLearn
go mod init KitexLearn
1.2 编写 thrift IDL
1.2.1 什么是IDL?
DL(Interface Definition Language,接口定义语言)是一种用于描述计算机系统中组件之间接口的语言。它允许不同的软件模块、服务、组件或系统之间进行通信和交互,而无需考虑它们的实现细节和编程语言。
IDL的主要目的是提供一个统一的、独立于编程语言的方式来定义接口,使得不同的程序能够相互通信,即使它们是用不同的编程语言编写的。这对于分布式系统、跨平台应用、远程过程调用(RPC)等场景非常有用。
在IDL中,开发人员可以定义数据结构、接口方法、异常、服务等,以及它们之间的关系。然后,基于IDL的定义,可以使用特定的工具将其翻译成各种编程语言的代码,用于实际的开发和集成。
1.2.2 thrift IDL
Kitex 默认支持 thrift 和 proto3 两种 IDL, Kitex 使用扩展的 thrift 作为底层的传输协议。
此处以thrift IDL 进行案例讲解,在项目文件夹下新建myserver.thrift
namespace go api
struct Req1 {
1: required string req1,
}
struct Resp1 {
2: required string res1,
}
struct Req2 {
1: required i64 a,
2: required i64 b,
}
struct Resp2 {
2: required i64 res2,
}
service Myserver {
Resp1 echo(1: Req1 req1)
Resp2 add(1: Req2 req2)
}
这段代码定义了一个包含两个操作的服务:echo和add,分别视线回响 服务和返回两个整型数相加结果的服务。这些操作接受不同类型的请求并返回相应类型的响应。这种定义使得可以通过Thrift生成的代码在Go语言中实现客户端和服务器端的通信,并进行数据交换。注意,这里只是定义了数据结构和服务操作的接口,实际的操作逻辑需要在生成的代码中实现。
使用如下命令根据IDL生成代码:
kitex -service myserver ./myserver.thrift
其中-service是指定服务名
2 修改生成代码
生成代码后,项目结构如下:
├── build.sh
├── go.mod
├── go.sum
├── `handler.go`
├── kitex_gen
│ └── api
│ ├── k-consts.go
│ ├── k-myserver.go
│ ├── myserver
│ │ ├── client.go
│ │ ├── invoker.go
│ │ ├── myserver.go
│ │ └── server.go
│ └── myserver.go
├── kitex_info.yaml
├── main.go
├── myserver.thrift
└── script
└── bootstrap.sh
这个项目结构中,kitex_gen 目录是 Kitex 生成的代码的存放位置,包括服务接口、客户端、服务端等代码文件。myserver.thrift 文件是服务接口的定义,用于生成服务相关的代码。handler.go 和 main.go 文件包含了服务的处理逻辑和项目的入口代码。整个结构体现了一个典型的基于 Kitex 的微服务项目的组织方式。
我们需要进行修改的代码主要是handler.go,Kitex已经为我们生成了主要框架,我们只需要对TODO的部分进行修改以完成我们的业务逻辑,修改handler.go如下:
// Echo implements the MyserverImpl interface.
func (s *MyserverImpl) Echo(ctx context.Context, req1 *api.Req1) (resp *api.Resp1, err error) {
// TODO: Your code here...
resp = &api.Resp1{Res1: req1.Req1}
return
}
// Add implements the MyserverImpl interface.
func (s *MyserverImpl) Add(ctx context.Context, req2 *api.Req2) (resp *api.Resp2, err error) {
// TODO: Your code here...
resp = &api.Resp2{Res2: req2.A + req2.B}
return
}
3 自定义客户端代码
在项目目录下新建client代码:
mkdir client && cd client
新建client.go如下:
package main
import (...)
func main() {
client, err := myserver.NewClient("my-example", client.WithHostPorts("0.0.0.0:8888"))
if err != nil {
log.Fatal(err)
}
for {
req := &api.Req1{Req1: "hello"}
resp, err := client.Echo(context.Background(), req)
if err != nil {
log.Fatal(err)
}
log.Println(resp)
time.Sleep(time.Second)
req2 := &api.Req2{A: 1, B: 2}
resp2, err := client.Add(context.Background(), req2)
if err != nil {
log.Fatal(err)
}
log.Println(resp2)
time.Sleep(time.Second)
}
}
其中,myserver结构体来源于之前IDL文件中定义的服务名Myserver,Echo和Add方法名来源于之前在IDL文件中定义的方法名echo和add,Kitex会自动更改其大小写。
可以看出,在Kitex已经生成了相关代码后,可以直接使用 client.Echo 方法调用远程服务的 Echo 方法,传入请求对象,获取响应对象,并进行错误处理;使用 client.Add 方法调用远程服务的 Add 方法,传入另一个请求对象,获取响应对象,并进行错误处理。
在项目根目录下运行服务端代码
go run .
在项目根目录下运行客户端代码
go run ./client
如果代码编写正确,应该可以看到类似2023/08/17 10:09:55 Resp1({Res1:hello})和2023/08/17 10:09:56 Resp2({Res2:3})的输出。
4 常见错误汇总
- 导入kitex报错:
could not import github.com/apache/thrift/lib/go/thrift (no required module排除GOROOT或GOPATH的路径错误后若还不能解决,极有可能是版本依赖不一致,可以先在命令行通过 thrift --version 查看系统 thrift 版本,与go.mod中的版本要求进行比对,如果不一致,可在go.mod中删除对版本要求的require字段或将其更改。 - 运行时报错:
[happened in biz handler]这类报错表示panic发生在服务端中,需要注意的是handler中的入参与出参并未分配内存,需要手动进行分配。
其他错误类型可参考:Kitex 异常说明