这是我参与「第五届青训营 」伴学笔记创作活动的第 9 天
Kitex[kaɪt’eks](读法是 Kite X)字节跳动开源的基于 Go 语言的微服务 RPC 框架,具有高性能、强可扩展的特点。
在 Kitex 之前字节内部的 Golang 框架是 Kite,但 Kite 与 Thrift 深度耦合、生成代码逻辑重, 很难从网络模型或编解码层面改造优化,继续支持新特性势必会造成代码越发臃肿迭代受阻问题,于是字节针对曾经的痛点设计了新的框架 Kitex。虽然 Kitex 是新框架,但已经在线上应用一年多,目前字节内部超过 50% 的 Golang 微服务使用 Kitex。
Kitex 是字节开源的微服务中间集合 CloudWeGo 最早开源的项目之一。目前支持 Thrift 和 Protobuf 两种 IDL,不过目前字节内部用的是 Thrift ,所以对 Thrift 的支持会完善一些,故本文主要基于 Thrift 进行介绍。
(截止目前:2023年1月30日 Kitex 已经可以在 Windows 下编译运行了,但是还不支持代码生成工具。)
架构设计
这个图的结构是指 Kitex 本身的架构设计,不是用 Kitex 开发的项目的架构。和 Hertz 的架构图一样,入门的话也是看看就行。
不过也同样可以从图上看出 Kitex 的支持的一些特性。
框架特点
-
高性能
使用自研的高性能网络库 Netpoll,性能相较 go net 具有显著优势。
-
扩展性
提供了较多的扩展接口以及默认扩展实现,使用者也可以根据需要自行定制扩展,具体见下面的框架扩展。
-
多消息协议
RPC 消息协议默认支持 Thrift、Kitex Protobuf、gRPC。Thrift 支持 Buffered 和 Framed 二进制协议;Kitex Protobuf 是 Kitex 自定义的 Protobuf 消息协议,协议格式类似 Thrift;gRPC 是对 gRPC 消息协议的支持,可以与 gRPC 互通。除此之外,使用者也可以扩展自己的消息协议。
-
多传输协议
传输协议封装消息协议进行 RPC 互通,传输协议可以额外透传元信息,用于服务治理,Kitex 支持的传输协议有 TTHeader、HTTP2。TTHeader 可以和 Thrift、Kitex Protobuf 结合使用;HTTP2 目前主要是结合 gRPC 协议使用,后续也会支持 Thrift。
-
多种消息类型
支持 PingPong、Oneway、双向 Streaming。其中 Oneway 目前只对 Thrift 协议支持,双向 Streaming 只对 gRPC 支持,后续可能会支持 Thrift 的双向 Streaming。
-
服务治理
支持服务注册/发现、负载均衡、熔断、限流、重试、监控、链路跟踪、日志、诊断等服务治理模块,大部分均已提供默认扩展,使用者可选择集成。
-
代码生成
Kitex 内置代码生成工具,可支持生成 Thrift、Protobuf 以及脚手架代码。
快速开始
环境搭建
安装好 Go 语言环境并确保 GOPATH 环境变量已经被正确地定义(例如 export GOPATH=~/go)并且将$GOPATH/bin添加到 PATH 环境变量之中(例如 export PATH=$GOPATH/bin:$PATH)。(如果未配置好环境变量可能会找不到命令)
[root@localhost KitexLearn]# kitex
bash: kitex: command not found...
命令行安装 kitex:go install github.com/cloudwego/kitex/tool/cmd/kitex@latest
使用kitex --version或者 kitex 查看是否安装成功。
[root@localhost KitexLearn]# kitex
No IDL file found.
[root@localhost KitexLearn]# kitex --version
v0.4.4
命令行安装 thriftgo:go install github.com/cloudwego/thriftgo@latest
同样使用 thriftgo 或者 thriftgo --version 查看是否安装成功。
[root@localhost KitexLearn]# thriftgo
require exactly 1 argument for the IDL parameter, got: 0
[root@localhost KitexLearn]# thriftgo --version
thriftgo 0.2.5
编写IDL
Thriftgo 是 Go 语言实现的 Thrift IDL 解析和代码生成器,支持完善的 Thrift IDL 语法和语义检查,相较 Apache Thrift 官方的 Golang 生成代码,Thriftgo 做了一些问题修复且支持插件机制,用户可根据需求自定义生成代码。
Thrift IDL 语法可参考官方文档:Thrift interface description language。
初学者直接看官方文档可能有点抽象,可以看下这篇文章:Thrift & IDL 介绍
namespace go add
struct AddRequest {
1: i64 a
2: i64 b
}
struct AddResponse {
1: i64 sum
}
service Add {
AddResponse add(1: AddRequest req)
}
生成 add 服务代码
有了 IDL 以后我们便可以通过 kitex 工具生成项目代码了,执行如下命令:
$ kitex -module example -service example add.thrift
上述命令中,-module 表示生成的该项目的 go module 名,-service 表明我们要生成一个服务端项目,后面紧跟的 example 为该服务的名字。最后一个参数则为该服务的 IDL 文件。
生成后的项目结构如下:
.
|-- build.sh
|-- add.thrift
|-- handler.go
|-- kitex_gen
| `-- api
| |-- add
| | |-- add.go
| | |-- client.go
| | |-- invoker.go
| | `-- server.go
| |-- add.go
| |-- k-add.go
| `-- k-consts.go
|-- main.go
`-- script
|-- bootstrap.sh
`-- settings.py
上面这个图是通过 tree 命令生成的,Windows貌似自带有,Linux则需要额外安装。
CentOS使用 yum -y install tree 进行安装。
[root@localhost KitexLearn]# yum -y install tree
(安装信息...)
Installed:
tree.x86_64 0:1.6.0-10.el7
Complete!
[root@localhost KitexLearn]# tree
(就上面那个图)
22 directories, 40 files
编写 add 服务逻辑
我们需要编写的服务端逻辑都在 handler.go 这个文件中,现在这个文件应该如下所示:
package main
import (
api "KitexLearn/kitex_gen/api"
"context"
"log"
)
// EchoImpl implements the last service interface defined in the IDL.
type AddImpl struct{}
// Add implements the AddImpl interface.
func (s *AddImpl) Add(ctx context.Context, req *api.AddRequest) (resp *api.AddResponse, err error) {
// TODO: Your code here...
return
}
这里的 Add 函数就对应了我们之前在 IDL 中定义的 add 方法。
现在让我们修改一下服务端逻辑,让 Add 服务名副其实。
修改 Add 函数为下述代码:
func (s *EchoImpl) Add(ctx context.Context, req *api.Request) (resp *api.Response, err error) {
log.Printf("两个数是%d,%d", req.A, req.B)
return &api.AddResponse{Sum: req.A + req.B}, nil
}
编译运行
kitex 工具已经帮我们生成好了编译和运行所需的脚本:
编译:
$ sh build.sh
执行上述命令后,会生成一个 output 目录,里面含有我们的编译产物。(编译时间可能有点久,看着不动的话耐心等待即可)
生成好的 output 文件夹结构
[root@localhost output]# tree
.
├── bin
│ └── example
├── bootstrap.sh
└── log
├── app
└── rpc
运行:
[root@localhost KitexLearn]# sh output/bootstrap.sh
2023/01/30 22:44:01.154991 server.go:81: [Info] KITEX: server listen at addr=[::]:8888
执行上述命令后,add 服务就开始运行啦!
编写 rpc 客户端
创建一个 add 文件夹,在该文件夹下新建一个 main.go 文件
package main
import (
"KitexLearn/kitex_gen/api"
"KitexLearn/kitex_gen/api/add"
"context"
"log"
"time"
"github.com/cloudwego/kitex/client"
"github.com/cloudwego/kitex/client/callopt"
)
func main() {
// 创建客户端
c, err := add.NewClient("example", client.WithHostPorts("0.0.0.0:8888"))
if err != nil {
log.Fatal(err)
}
// 发起调用
req := &api.AddRequest{A: 1, B: 2}
resp, err := c.Add(context.Background(), req, callopt.WithRPCTimeout(3*time.Second))
if err != nil {
log.Fatal(err)
}
log.Println(resp)
}
上述代码中,add.NewClient 用于创建 client,其第一个参数为调用的 服务名,第二个参数为 options,用于传入参数, 此处的 client.WithHostPorts 用于指定服务端的地址,更多参数可参考基本特性一节。
然后创建了一个请求结构体 , 通过 c.Add 发起了调用。
其第一个参数为 context.Context,通过通常用其传递信息或者控制本次调用的一些行为,你可以在后续章节中找到如何使用它。
其第二个参数为本次调用的请求。
其第三个参数为本次调用的 options ,Kitex 提供了一种 callopt 机制,顾名思义——调用参数 ,有别于创建 client 时传入的参数,这里传入的参数仅对此次生效。 此处的 callopt.WithRPCTimeout 用于指定此次调用的超时(通常不需要指定,此处仅作演示之用)同样的,可以在基本特性一节中找到更多的参数。
发起调用
新建一个终端,切换到add目录下执行Go代码即可。
[root@localhost KitexLearn]# cd rpc/add/
[root@localhost add]# go run .
2023/01/30 22:49:15 AddResponse({Sum:3})
在微服务里,一般在 handler 里调用 rpc 包(RPC客户端)下定义的方法来调用实际业务(RPC服务端)的代码。