Kitex
Kitex 是一个 RPC 框架,既然是 RPC,底层就需要两大功能:Serialization 序列化和Transport 传输
Kitex 框架及命令行工具,默认支持 thrift 和 proto3 两种 IDL,对应的 Kitex 支持 thrift 和protobuf
两种序列化协议。 传输上 Kitex 使用扩展的 thrift 作为底层的传输协议(注:thrift 既是 IDL 格式,
同时也是序列化协议和传输协议)。IDL 全称是 Interface Definition Language,接口定义语言。
为什么要使用 IDL
在 RPC 框架中,我们知道,服务端与客户端通信的前提是远程通信,但这种通信又存在一种关联,那就是通过一套相关的协议(消息、通信、传输等)来规范,但客户端又不用关心底层的技术实现,只要定义好了这种通信方式即可。
简单来说,就是如果我们要进行 RPC,就需要知道对方的接口是什么,需要传什么参数,同时也需要知道返回值是什么样的,就好比两个人之间交流,需要保证在说的是同一个语言、同一件事。 这时候,就需要通过 IDL 来约定双方的协议,就像在写代码的时候需要调用某个函数,我们需要知道函数签名一样。
Thrift 本身是一软件框架(远程过程调用框架),用来进行可扩展且跨语言的服务的开发。
Protobuf 全称是 Google Protocol Buffer,是一种高效轻便的结构化数据存储方式,用于数据的通信协议、数据存储等。
Kitex 命令行工具:Kitex 自带了一个同名的命令行工具 kitex,用来帮助大家很方便地生成代码,新项目的生成以及之后我们会学到的 server、client 代码的生成都是通过 kitex 工具进行。
首先,我们先进入到自己文件的一个路径上,然后创建项目目录
mkdir example
然后进入到这个目录
cd example
然后我们进行安装
go install github.com/cloudwego/kitex/tool/cmd/kitex@latest
安装好之后看一下是否安装成功,输入kitex --version可以看到下面信息
kitex --version
v0.4.2
注意一下,kitex 暂时没有针对 Windows 做支持,如果本地开发环境是 Windows 建议使用 WSL2
编写IDL
首先我们需要编写一个 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)
string sayHello(1: string msg)
}
这里我们定义了一个命名空间:hello,这个是代表生成的代码中有一个目录:hello,然后我们编写一个请求对象:ReqBody,接着定义一个泛对象,包括了那个请求对象,这块没要求,自己定义好就行,同时我们定义了响应对象Response,此外,我们还定义了一个类,类中存在三个函数方法。
生成服务代码
kitex -module example -service example echo.thrift
这里有几个参数 tag:
-module module_name
- 该参数用于指定生成代码所属的 Go 模块,会影响生成代码里的 import path。
-service service_name
- 使用该选项时,kitex 会生成构建一个服务的脚手架代码,参数 service_name 给出启动时服务自身的名字,通常其值取决于使用 Kitex 框架时搭配的服务注册和服务发现功能。
后面紧跟的 example 为该服务的名字。最后一个参数则为该服务的 IDL 文件。
生成之后就是会得到很多文件,生成后的项目结构如下:
.
|-- build.sh
|-- echo.thrift
|-- handler.go
|-- kitex_gen
| `-- api
| |-- echo
| | |-- client.go
| | |-- echo.go
| | |-- invoker.go
| | `-- server.go
| |-- echo.go
| `-- k-echo.go
|-- main.go
`-- script
|-- bootstrap.sh
`-- settings.py
编写 echo 服务逻辑
需要编写的服务端逻辑都在 handler.go 这个文件中,现在这个文件应该如下所示:
package main
import (
"context"
api "example/kitex_gen/api"
)
//实现IDL中定义的最后一个服务接口
type EchoImpl struct{}
//实现EchoImpl接口
func (s *EchoImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) {
// 代码....
return
}
//实现这个接口
func (s *EchoImpl) SayHello(ctx context.Context, msg string) (resp string, err error) {
// 代码...
return
}
这里的 Echo 函数就对应了我们之前在 IDL 中定义的 echo 方法。
现在让我们修改一下服务端逻辑,补充完整。
修改 Echo 函数为下述代码(sayHello同理)
// Echo implements the EchoImpl interface.
func (s *EchoImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) {
return &api.Response{
Message: req.GetMessage(),
}, nil
}
// SayHello implements the EchoImpl interface.
func (s *EchoImpl) SayHello(ctx context.Context, msg string) (resp string, err error) {
return msg + ", 你好吗?", nil
}
编译运行
kitex 工具已经帮我们生成好了编译和运行所需的脚本:
编译:
sh build.sh
执行上述命令后,编译结果会被生成至 output 目录
运行:
sh output/bootstrap.sh
执行上述命令后,Echo 服务就开始运行啦!服务会在默认的 8888 端口上开始运行
编写客户端
有了服务端后,接下来就让我们编写一个客户端用于调用刚刚运行起来的服务端。
首先,同样的,在当前的目录下(example)创建一个client.go用于存放我们的客户端代码:
package main
import (
"context"
"example/kitex_gen/api"
"example/kitex_gen/api/echo"
"fmt"
"github.com/cloudwego/kitex/client"
"log"
)
func main() {
c, err := echo.NewClient("example", client.WithHostPorts("0.0.0.0:8888"))
if err != nil {
log.Fatal(err)
}
req := &api.Request{Message: "我的第一个go请求"}
rsp, err := c.Echo(context.Background(), req)
if err != nil {
log.Fatal(err)
}
fmt.Println(rsp)
rsp2, err := c.SayHello(context.Background(), "str")
if err != nil {
log.Fatal(err)
}
fmt.Println(rsp2)
}
echo.NewClient 用于创建 client,其第一个参数为调用的 服务名,第二个参数为 options,用于传入参数,
上述代码中,我们首先创建了一个请求 req , 然后通过 c.Echo 发起了调用。
其第一个参数为 context.Context,通过通常用其传递信息或者控制本次调用的一些行为
其第二个参数为本次调用的请求。
其第三个参数为本次调用的 options ,Kitex 提供了一种 callopt 机制,顾名思义——调用参数 ,有别于创建 client 时传入的参数,这里传入的参数仅对此次生效。 此处的 callopt.WithRPCTimeout 用于指定此次调用的超时(通常不需要指定)
发起调用
在编写完一个简单的客户端后,我们终于可以发起调用了。
你可以通过下述命令来完成这一步骤:
go run ./client.go
如果不出意外,你可以看到类似如下输出:
C02DV2V0ML7L:example bytedance$ go run ./client.go
Response({Message:我的第一个go请求})
str, 你好吗?
恭喜你!至此你成功编写了一个 Kitex 的服务端和客户端,并完成了一次调用!