这是我参与「第五届青训营」笔记创作活动的第11天
KiteX 上手体验~
前言:KiteX 是什么?
KiteX [kaɪt’eks] 是字节跳动内部的 Golang 微服务 RPC 框架,已经在 GitHub 上开源,具有高性能、强可扩展的特点,在字节内部已广泛使用。
🎈如果不清楚 RPC,请阅读上一篇笔记:由浅入深了解 RPC 框架 | 青训营笔记 - 掘金 (juejin.cn)
由于 KiteX 目前还未完全支持 Windows 平台,Windows 用户要想体验 KiteX 的完整功能需要使用 Win10 提供的 Linux 容器 WSL2 进行,所以在学习使用 KiteX 之前,先让我们学习如何在 WSL2 中搭建 Go 语言环境。
KiteX 概览
KiteX 是字节开源的微服务中间集合 CloudWeGo 最早开源的项目之一。在 KiteX 之前字节内部的 Golang 框架是 Kite,但 Kite 与 Thrift 深度耦合、生成代码逻辑重, 很难从网络模型或编解码层面改造优化,继续支持新特性势必会造成代码越发臃肿迭代受阻问题,于是字节针对曾经的痛点设计了新的框架 KiteX。
虽然 KiteX 是新框架,但已经在线上应用一年多,目前字节内部超过 50% 的 Golang 微服务使用 KiteX。
KiteX 框架特性
- KiteX 使用了字节自研网络库 Netpoll,性能更高;
- 内置代码生成工具,支持 Thrift 与 Protobuf 两种 IDL;
- 支持服务注册/发现、负载均衡、熔断、限流、重试、监控、链路跟踪、日志、诊断等服务治理模块,大部分均已提供默认扩展,使用者可选择集成;
- 传输协议封装消息协议进行 RPC 互通,传输协议可以额外透传元信息,用于服务治理,Kitex 支持的传输协议有 TTHeader、HTTP2;
- 多种消息类型,支持 PingPong、Oneway、双向 Streaming。
GoLand + WSL2 搭建 Go 开发环境
KiteX 在 0.4.0 版本已支持在 Windows 环境下编译运行了,但代码生成工具暂未支持 Windows 环境。
如果你使用的是 Windows 系统的电脑,官方推荐的方式是使用 WSL2。WSL 是 Win10 开始系统内置的一个 Linux 系统兼容层,可以安装各种 Linux 发行版,资源占用相比于虚拟机要低的多,方便开发者们调试 Linux 程序。
有了 WSL 的帮助,我们可以直接用 Windows 平台下的 GoLand 执行 WSL 中的 Golang 代码。
✔ 环境要求: Win10 19041+、GoLand 2021.1.3+、Go 1.16+
1. 安装 WSL2
🎈微软官方文档: 安装 WSL | Microsoft Learn
以管理员身份打开 PowerShell 或 Windows 命令提示符,输入下面的指令:
wsl --install
此命令将启用所需的可选组件,下载最新的 Linux 内核,将 WSL 2 设置为默认值,并安装 Linux 发行版,默认安装的发行版为 Ubuntu。
ps: 如果之前已经启用了 WSL,但并没有安装任何 Linux 发行版,可以在微软商店 Microsoft Store 中手动下载,如果有必要,在安装新的发行版时需要按照 旧版 WSL 的手动安装步骤 | Microsoft Learn 中的提示安装升级包,确保启用 WSL2 而不是 WSL1。
安装完成后可以在开始菜单中找到 Ubuntu 的控制面板程序,初次使用需要创建账号和密码。
比如我创建的用户名为:dev
,密码为:123456
。
2. 配置 WSL 的 Go 语言环境
下载 Go 安装包,使用 KiteX 需要 Go v1.16 及以上的版本,目前 Go 已经推出了 v1.18,这里建议下载 v1.17 的包,因为 GoLand 可能和最新版的 Go 标准不兼容。
下载指令如下:
$ wget https://studygolang.com/dl/golang/go1.17.13.linux-amd64.tar.gz
解压安装包到 /usr/local
目录下:
$ tar -C /usr/local -xzf go1.18.2.linux-amd64.tar.gz
提前创建好 Gopath 目录,并在 Gopath 路径下放 pkg
、bin
、src
三个文件夹:
cd /home/dev
mkdir go
cd go/
mkdir pkg bin src
接下来需要设置环境变量,使用 vim 编辑器打开 /etc/profile
:
ps: 还不会 vim 的话建议先学习一下:【保姆级入门】Vim编辑器_哔哩哔哩_bilibili
sudo vim /etc/profile
将以下内容添加进文件开头:
export GOPATH=/home/dev/go
export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
ps: /home/dev
中的 dev
是创建用户时的用户名,要注意改成你自己的
最后执行 source /etc/profile
命令刷新配置文件。
3. 测试环境配置
成功配置好 Go 语言环境,可以使用 go version
指令查看安装版本:
$ go version
go version go1.17.13 linux/amd64
新建一个测试项目,来检查一下 GOPATH 和 GOROOT 是否配置正确:
$ mkdir $GOPATH/src/test -p
$ cd $GOPATH/src/test
$ touch test.go
$ sudo vim test.go
在 test.go 中添加控制台输出代码:
package main
import "fmt"
func main() {
fmt.Println("hello");
}
保存退出,执行 go run 命令跑下代码:
$ go run test.go
hello
自此,WSL 中的 Go 语言环境已经配置好了,可以愉快地在 Linux 中跑 Golang 代码了。
4. 使用 WSL 在 GoLand 中开发
🎈JetBrains 官方文档:WSL | GoLand Documentation (jetbrains.com)
如果你使用的是 GoLand 的最新版本,可以直接在创建项目时直接选择 WSL 中的 GOROOT:
低版本的 GoLand,比如我正在使用的 v2021.1.3(最后一个能免账号试用的版本,懂的都懂😁) 在创建项目时直接选择 WSL 中的 GOROOT 会提示 “检测不到 GO SDK”,可以暂时先选择 Windows 上的。
这里注意创建的项目位置要在 WSL 中的 GOPATH 路径的 /src
下。
创建项目,添加运行配置,在这里面选择 Run on WSL
,并把 Build on remote target
勾选上。
ps: Build on remote target
一定要开启,不然运行代码会报错:go: RLock ... Incorrect function.
。
弄好以后就可以正常进行开发了,在 GoLand 中打开 Terminal 会自动连接到 Ubuntu 上,并且配置好 WSL 我们运行的代码就会跑在 WSL 容器中了,轻松解决 KiteX 不兼容 Windows 的问题。
第一个 KiteX 项目
下面让我们跟着官方文档的基础教程,搭建 KiteX 项目,完成一次客户端向服务端的调用。
🎈KiteX 基础教程:快速开始 | CloudWeGo
1. 创建项目,我这里取名为 kitex_demo
:
2. 模块安装:
PS: 国内使用 Go mod 下载第三方模块很可能因为网络问题下载不下来,建议先配置 Go proxy 代理。
🎈七牛云 - 中国最可靠的 GO 模块代理:goproxy.cn/
安装 KiteX 与 thrift 编译器,安装之前要确保环境变量已经正确被定义。
go install github.com/cloudwego/kitex/tool/cmd/kitex@latest
go install github.com/cloudwego/thriftgo@v0.13.0
执行 kitex
命令,出现如下输出说明安装成功:
$ kitex
No IDL file found.
这里要格外注意 thriftgo 的版本,官方指定的是 v0.13.0,直接安装最新版的话 KiteX 可能会和它不兼容,导致后续运行代码会爆出各种语法错误以及下载依赖时可能会导致包冲突。
如果你之前已经安装了最新版的 thriftgo,可以执行下述命令把版本切换至 v0.13.0:
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
3. 编写 IDL:
要进行 RPC,就需要知道对方的接口和要传什么参数,同时也需要知道要返回的值是什么样的,这时就需要通过 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)
}
4. 生成代码:
有了 IDL 以后我们便可以通过 kiteX 工具生成项目代码了,执行如下命令:
$ kitex -module kitex_demo -service kitex_demo echo.thrift
上述命令中,-module
表示生成的该项目的 Go module 名,-service
表明我们要生成一个服务端项目,后面紧跟的 kitex_demo
为该服务的名字,最后一个参数则为该服务的 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
执行命令如果报错 "没有在 go.mod 文件中找到对应的模块名",可以把 -module 可选项去掉,让 KiteX 自动检索文件。
$ kitex -service kitex_demo echo.thrift
同时要检查一下 go.mod 文件中的模块名与 GoLand 运行配置中的 package path 要保持一致,并且如果你的项目路径跟我一样是 ~$GOPATH/src/study/kitex_demo
,那么模块名应该为 study/kitex_demo
,使用 go mod tidy
可以自动整理 go.mod 文件。
PS: 如果指定了 -module
参数,那么 KiteX 会从当前目录开始往上层搜索 go.mod 文件
- 如果不存在 go.mod 文件,那么 KiteX 会调用
go mod init
生成 go.mod; - 如果存在 go.mod 文件,那么 KiteX 会检查
-module
的参数和 go.mod 里的模块名字是否一致,如果不一致则会报错退出; - 最后,go.mod 的位置及其模块名字会决定生成代码里的 import path。
5. 更新依赖包:
生成的代码中引入了很多第三方依赖,使用 Go mod 下载依赖包,命令如下:
$ go mod tidy
该指令会自动根据代码中引用的依赖包修改 go.mod 文件,正常会顺带把包下载下来,如果没有下载,可以接着使用命令:
$ go mod download
如果 GoLand 中查看 go.mod 文件依赖项全部爆红,可能是 GoLand 中配置的 GOPATH 还是 Windows 系统下的,到 file->setting->GO->GOPATH
检查一下配置,在 project GOPATH 中把 WSL 的 GOPATH 路径添加进去,并且勾选上 Index entire GOPATH
选项。
最后需要重启一下 GoLand 以重新检索依赖库。
6. 编写 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()
函数对应了我们之前在 IDL 中定义的 echo 方法,让我们在 Echo()
函数中修改服务端逻辑:
func (s *EchoImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) {
return &api.Response{Message: req.Message}, nil
}
7. 编译运行:
在生成代码部分,KiteX 工具已经帮我们生成好了编译和运行所需的脚本 build.sh
,执行命令:
$ sh build.sh
执行上述命令后,会生成一个 /output
目录,里面含有我们的编译产物。
运行:
$ sh output/bootstrap.sh
执行上述命令后,Echo
服务就开始运行啦!
PS: 如果出现报错,很可能是由于 thriftgo 的版本不兼容导致的,请回到上文 模块安装 部分下载指定版本的 thriftgo,并使用 go mod tidy
命令重新下载依赖包。
8. 编写客户端:
有了服务端后,接下来就让我们编写一个客户端用于调用刚刚运行起来的服务端。
首先,同样的,先创建一个目录 /client
用于存放我们的客户端代码,在目录中创建 main.go 文件就可以开始编写客户端代码了。
我们需要先创建一个调用所需的 client
,然后编写发起调用的代码:
package main
import (
"context"
"github.com/cloudwego/kitex/client"
"github.com/cloudwego/kitex/client/callopt"
"log"
"study/kitex_demo/kitex_gen/api"
"study/kitex_demo/kitex_gen/api/echo"
"time"
)
func main() {
// 创建调用所需的 client
c, err := echo.NewClient("kitex_demo", 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)
}
上述代码中,echo.NewClient
用于创建 client
,其第一个参数为调用的 服务名,第二个参数为 options,用于传入参数,此处的 client.WithHostPorts
用于指定服务端的地址。
9. 发起调用:
在编写完一个简单的客户端后,我们终于可以发起调用了。
在 GoLand 中右键运行客户端代码,或者另起一个 Terminal 使用指令运行:
$ go run main.go
(注意执行的 main.go
文件是 /client
目录下的)
如果不出意外,可以看到类似如下输出:
2023/01/28 16:51:35 Response({Message:my request})
至此我们成功编写了一个 KiteX 的服务端和客户端代码,并完成了一次调用!
关于 KiteX 的使用
从上面的实践中我们可以发现 KiteX 提供的代码生成工具非常方便,基本不需要额外的学习成本来使用 KiteX。在使用 KiteX 框架的过程中,需要我们手动完成的部分是 IDL 文件的编写、补全服务端逻辑,以及客户端中的调用代码。
Thrift IDL 简单入门
Thrift IDL 是目前常见的 IDL 定义标准,利用 IDL(Interface Description Language)文件来定义接口和数据类型,通过 Thrift 提供的编译器编译成不同语言代码,以此实现跨语言调用。
🎈官方文档:thrift.apache.org/docs/idl
thrift 支持支持以下几种数据类型:
Type | Desc | Java | Go |
---|---|---|---|
i8/i16/i32/i64 | 有符号整数 | byte/short/int/long | int8/.../int64 |
double | 双精度浮点数 | double | float64 |
bool | 布尔值 | boolean | bool |
string | 文本字符串(utf8) | java.lang.String | string |
此外还有很多特殊数据类型,包括集合容器、常量、枚举、异常、结构体等等,这里就不一一介绍了。
结构体是比较常用的,目的是将一些数据聚合在一起,方便传输管理,定义形式如下:
struct People {
1: string name;
2: i32 age;
3: string sex;
}
thrift 的各种类型都是为 service(服务) 准备的,类似于 Java 中接口的定义:
service HelloWordService {
string doAction(1: string name, 2: i32 age);
}
创建的 service 经过代码生成命令之后就会生成客户端和服务端的框架代码。
客户端 & 服务端代码
KiteX 会根据我们定义的 IDL 文件自动生成服务端代码框架,服务端代码在 handler.go
文件中,我们只需要在对应的方法中补全业务逻辑即可。
// ...
// 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
}
方法中的 req
参数代表客户端调用传来的参数数据,在 req.Message
下可以拿到;方法需要返回一个 *api.Response
类型,代表服务端返回给客户端的响应结果。
客户端发起调用时,需要使用 echo.NewClient
创建一个 client
,通过 client.Echo()
调用服务,该方法返回一个 *api.Response
可以拿到服务端的响应。同时该方法接收 *api.Request
类型的参数用以向服务端传送参数数据。
KiteX 生态
KiteX 框架远远不止本文上手体验的 RPC 调用,它支持超时控制、负载均衡、限流、熔断、日志等诸多特性,此外 KiteX 还支持丰富的扩展用以服务发现、链路跟踪等。
- XDS 扩展 - kitex-contrib/xds (github.com)
- Zookeeper 服务注册与发现 - kitex-contrib/registry-zookeeper (github.com)
- 链路追踪 opentelemetry - kitex-contrib/registry-zookeeper (github.com)
- 示例代码与业务 Demo - cloudwego/kitex-examples: Examples for Kitex. (github.com)
🚀【参考】🚀