本文目录
- 一、Kitex概述
- 二、第一个Kitex应用
- 三、IDL
- 四、服务注册与发现
一、Kitex概述
长话短说,就是字节跳动内部的 Golang 微服务 RPC 框架,具有高性能、强可扩展的特点,在字节内部已广泛使用。
如果对微服务性能有要求,又希望定制扩展融入自己的治理体系,Kitex 会是一个不错的选择。
可以看到下面是Kitex的架构设计。
这个框架有一些特点,比如:
- 高性能
使用自研的高性能网络库 Netpoll,性能相较 go net 具有显著优势。
- 扩展性
提供了较多的扩展接口以及默认扩展实现,使用者也可以根据需要自行定制扩展,具体见下面的框架扩展。
- 多消息协议
RPC 消息协议默认支持 Thrift、Kitex Protobuf、gRPC。Thrift 支持 Buffered 和 Framed 二进制协议,与支持原生 Thrift 协议的多语言框架都能互通; Kitex Protobuf 是 Kitex 自定义的 Protobuf 消息协议,协议格式类似 Thrift;gRPC 是对 gRPC 消息协议的支持,可以与 gRPC 互通。除此之外,使用者也可以扩展自己的消息协议,目前社区也提供了 Dubbo 协议的支持,可以与 Dubbo 互通。
- 多传输协议
传输协议封装消息协议进行 RPC 互通,传输协议可以额外透传元信息,用于服务治理,Kitex 支持的传输协议有 TTHeader、HTTP2。TTHeader 可以和 Thrift、Kitex Protobuf 结合使用;HTTP2 目前主要是结合 gRPC 协议使用,后续也会支持 Thrift。
- 多种消息类型
支持 PingPong、Oneway、双向 Streaming。其中 Oneway 目前只对 Thrift 协议支持,双向 Streaming 只对 gRPC 支持,后续会考虑支持 Thrift 的双向 Streaming。
- 服务治理
支持服务注册/发现、负载均衡、熔断、限流、重试、监控、链路跟踪、日志、诊断等服务治理模块,大部分均已提供默认扩展,使用者可选择集成。
- 代码生成
Kitex 内置代码生成工具,可支持生成 Thrift、Protobuf 以及脚手架代码。
二、第一个Kitex应用
首先我们创建一个新的Go项目,然后安装对应的插件:kitex
和thriftgo
。
然后在终端输入对应的安装命令:
go install github.com/cloudwego/kitex/tool/cmd/kitex@latest
go install github.com/cloudwego/thriftgo@latest
然后等待对应的安装即可。
在项目路径下安装新建thrift文件,然后输入如下代码:
namespace go api
struct Request{
1:string message
}
struct Response{
1:string message
}
service Echo{
Response echo(1:Request req)
}
简单来说,这段代码定义一个简单的服务 Echo,它包含一个方法 echo。这个方法接收一个 Request 类型的对象作为输入,处理后返回一个 Response 类型的对象。Request 和 Response 都包含一个字符串字段 message,分别用于传递请求消息和响应消息。这种定义方式使得 Thrift 可以生成不同语言的代码(例如 Go、Java、Python 等),方便跨语言的服务调用。
然后在命令行输入命令:
kitex -module exampleKitex -service exampleKitex echo.thrift
来生成基本的代码。
等待一会后,可以看到目录已经更新了。
在main.go
中输入如下代码:
package main
import (
api "exampleKitex/kitex_gen/api/echo"
"log"
)
/*
api.NewServer 是一个函数,用于创建一个新的服务实例。
new(EchoImpl) 创建了一个 EchoImpl 类型的实例,并将其传递给 api.NewServer。
调用 svr.Run() 方法启动服务。
*/
func main() {
svr := api.NewServer(new(EchoImpl))
err := svr.Run()
if err != nil {
log.Println(err.Error())
}
}
如果有报错,可以输入go mod tidy
来补充对应需要的库。
然后在handler.go
中来输入逻辑代码。
(s *EchoImpl):这是方法的接收者,表示这个方法属于 EchoImpl 类型的指针 s。
EchoImpl 是一个结构体类型,通常是由 Thrift 生成的接口的具体实现。
echo是方法名称,对应 Thrift 定义中的 echo 方法。
ctx context.Context:这是方法的第一个参数,类型为 context.Context。
context 是 Go 标准库中的一个包,用于在程序中传递上下文信息,例如超时、取消信号等。
req *api.Request:这是方法的第二个参数,类型为 *api.Request,表示这是一个指向 api.Request 类型的指针。
api.Request 是 Thrift 定义的 Request 结构体,通过 Thrift 生成的 Go 代码中的 api 包来访问。
&api.Response{}:创建了一个 api.Response 类型的实例,并取其地址(返回一个指针)。
Message: req.Message:将 req 参数中的 Message 字段值赋给新创建的 Response 实例的 Message 字段。
这是一个简单的回显服务,即客户端发送一个消息,服务端原样返回这个消息。
package main
import (
"context"
api "exampleKitex/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 &api.Response{Message: req.Message}, nil
}
然后我们来启动main.go和handler.go. 启动之后会默认在监听端口进行对应的监听。
然后我们新建一个客户端,并且写入对应的客户端代码。
package main
import (
"context"
"github.com/cloudwego/kitex/client/callopt"
"log"
"exampleKitex/kitex_gen/api"
"exampleKitex/kitex_gen/api/echo"
"time"
)
import "github.com/cloudwego/kitex/client"
func main() {
//下面的destService对应服务名称是我们在启动kitex代码生成工具命令中的service名称
// kitex -moudle exampleKitex -service exampleKitex(服务名称) echo.thrift
c, err := echo.NewClient("exampleKitex", 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 .\client.go
即可。
然后可以看到返回的Response如下:
所以总结一下,就是,先在echo.thrift
中编写接口,定义发送什么样的请求,请求里边有什么样的一个信息,然后就是继续定义一个响应,以及服务的逻辑。
三、IDL
IDL(Interface Definition Language,接口定义语言)是一种用于定义软件组件之间交互接口的规范语言。它允许开发者以一种语言无关的方式描述服务接口、数据结构和通信协议,从而实现不同编程语言之间的互操作性。IDL 的核心目的是提供一种标准化的方式来定义服务的接口和数据结构,使得这些接口和数据结构可以在多种编程语言中被实现和使用。
IDL就是我们刚刚定义的这个thrift文件。
如果进行PRC,那么就需要知道对方的接口是什么,需要传什么参数,也需要知道返回值是什么样子的。这个时候就需要通过IDL来约定双方的协议。就像调用某个函数,需要知道函数签名一样。
定义好thrift文件之后,就可以使用命令生成代码了。
kitex -module example -service example echo.thrift
生成代码。
服务是默认监听8888端口的。
四、服务注册与发现
目前Kitex的服务注册与发现已经对接了主流的服务注册与发现中心,如ETCD、Nacos、zookeeper、Eureka等。
接下来我们进行一个简单的实战。
首先我们下载etcd:https://github.com/etcd-io/etcd/releases
,找到对应的电脑的版本。
下载好之后找个文件夹进行解压缩,然后打开,双击打开即可。
运行起来之后可以看到对应的2380和2379两个端口。
到这一步,服务器已经启用成功了。
接下来我们需要把服务注册到etcd注册中心去
。修改handler.go
的相关代码。
简单说明下作用:实现一个 Kitex 服务端程序,通过 ETCD 注册中心将自身注册为一个可发现的服务,并提供了一个简单的 Echo 方法。程序的主要功能包括:
package main
import (
"context"
api "exampleKitex/kitex_gen/api"
"exampleKitex/kitex_gen/api/echo"
"github.com/cloudwego/kitex/pkg/rpcinfo"
"github.com/cloudwego/kitex/server"
etcd "github.com/kitex-contrib/registry-etcd"
"log"
)
// EchoImpl implements the last service interface defined in the IDL.EchoImpl:定义了一个结构体,用于实现服务接口。
type EchoImpl struct{}
// Echo implements the EchoImpl interface.
// ctx context.Context:上下文对象,用于传递超时和取消信号。
// req *api.Request:请求对象,包含客户端发送的数据。
func (s *EchoImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) {
// TODO: Your code here...
return &api.Response{Message: req.Message}, nil
}
func main() {
//etcd.NewEtcdRegistry:创建一个 ETCD 注册中心实例。
//参数 []string{"127.0.0.1:2379"} 指定了 ETCD 服务的地址。
r, err := etcd.NewEtcdRegistry([]string{"127.0.0.1:2379"})
if err != nil {
log.Fatal(err)
}
//echo.NewServer:创建一个服务实例。
//new(EchoImpl):创建一个 EchoImpl 实例,作为服务的具体实现。
//server.WithRegistry(r):指定使用 ETCD 注册中心实例 r,将服务注册到 ETCD 中。
//server.WithServerBasicInfo:设置服务的基本信息,例如服务名称。ServiceName: "Hello":将服务名称设置为 "Hello",客户端可以通过这个名称找到服务。
server := echo.NewServer(new(EchoImpl), server.WithRegistry(r), server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{
ServiceName: "Hello",
}))
err = server.Run()
if err != nil {
log.Fatal(err)
}
}
同时我们修改client.go
的代码如下:
一句话概括下作用,就是:使用 Kitex 框架的客户端程序,通过 ETCD 注册中心动态发现服务,并调用名为 Hello 的服务的 Echo 方法。代码的主要功能是定期发送请求到服务端,并打印响应结果。
package main
import (
"context"
"exampleKitex/kitex_gen/api"
"exampleKitex/kitex_gen/api/echo"
"github.com/cloudwego/kitex/client"
etcd "github.com/kitex-contrib/registry-etcd"
"log"
"time"
)
func main() {
//etcd.NewEtcdResolver:创建一个 ETCD 解析器实例,用于从 ETCD 服务注册中心获取服务实例的地址。
//参数 []string{"127.0.0.1:2379"} 指定了 ETCD 服务的地址。
r, err := etcd.NewEtcdResolver([]string{"127.0.0.1:2379"})
if err != nil {
log.Fatal(err)
}
//echo.MustNewClient:创建一个服务客户端实例。
//"Hello" 是服务名称,与服务端在 ETCD 中注册的名称一致。
//client.WithResolver(r):指定使用 ETCD 解析器 r 来动态发现服务实例。这意味着客户端会从 ETCD 中获取服务实例的地址,而不是直接指定服务地址。
client := echo.MustNewClient("Hello", client.WithResolver(r))
//创建一个带有超时时间的上下文,超时时间为 3 秒。
for {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
//调用服务端的 Echo 方法,传递一个请求对象,其中 Message 字段为 "hello"。
resp, err := client.Echo(ctx, &api.Request{Message: "hello"})
cancel() //调用 cancel() 释放上下文资源。
if err != nil {
log.Fatal(err)
}
log.Println(resp)
time.Sleep(time.Second)
}
}
来捋顺一下步骤:
实现一个 Kitex 客户端程序,通过 ETCD 注册中心动态发现服务,并调用名为 Hello 的服务的 Echo 方法:
创建 ETCD 解析器实例,用于从 ETCD 注册中心获取服务实例的地址。然后创建服务客户端实例,指定使用 ETCD 解析器。并且在一个无限循环中,每隔 1 秒调用一次服务的 Echo 方法。打印服务端返回的响应。
这种设计适用于微服务架构,其中服务实例可能动态变化,通过 ETCD 注册中心可以实现服务发现和负载均衡。
通过命令 go run .\handler.go
来启动服务端,会将我们的服务注册到etcd里面去。
然后运行client,会看到如下的消息。