入门 KiteX 基础篇

2022-12-05 11:05:30 浏览数 (1)

概览

KiteX 是 bytedance 开源的高性能 RPC 框架,实现了高吞吐、高负载、高性能等居多特性,具体请看 KiteX 的实践,文章介绍多传输协议、消息协议时,说到 KiteX 支持的协议类型:Thrift、Protobuf 等,今天我们主要来实践如何利用 KiteX 基于对应的 IDL 生成对应协议的代码。

Thrift 简介

Thrift 本身是一软件框架(远程过程调用框架),用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引 擎,以构建在 C , Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 这些编程语言间无缝结合的、高效的服务。同时,作为 IDL(接口定义语言 Interface Definition Language),允许你定义一个简单的定义文件中的数据类型和服务接口,以作为输入文件,编译器生成代码用来方便地生成 RPC 客户端和服务器通信的无缝跨编程语言。

Protobuf 简介

Protobuf 全称是 Google Protocol Buffer,是一种高效轻便的结构化数据存储方式,用于数据的通信协议、数据存储等。相对比 XML 来说,其特点:

  • 语言无关,平台无关
  • 高效
  • 扩展性、兼容性更强

基于 IDL 的 KiteX 实践

在 RPC 框架中,我们知道,服务端与客户端通信的前提是远程通信,但这种通信又存在一种关联,那就是通过一套相关的协议(消息、通信、传输等)来规范,但客户端又不用关心底层的技术实现,只要定义好了这种通信方式即可。

在 KiteX 中,也提供了一种生成代码的命令行工具:kitex,目前支持 thrift、protobuf 等 IDL,并且支持生成一个服务端项目的骨架。

安装命令行工具

环境
  • 如果您之前未搭建 Golang 开发环境, 可以参考 Golang 安装
  • 推荐使用最新版本的 Golang,我们保证最新三个正式版本的兼容性(现在 >= v1.16)。
  • 确保打开 go mod 支持 (Golang >= 1.15 时,默认开启)
  • kitex 暂时没有针对 Windows 做支持,如果本地开发环境是 Windows 建议使用 WSL2
安装
  • 确保 GOPATH 环境变量已经被正确地定义(例如 export GOPATH=~/go)并且将
GOPATH/bin添加到 PATH 环境变量之中(例如 export PATH=

GOPATH/bin:$PATH);请勿将 GOPATH 设置为当前用户没有读写权限的目录

  • 安装 kitex:go install github.com/cloudwego/kitex/tool/cmd/kitex@latest
  • 安装 thriftgo:go install github.com/cloudwego/thriftgo@latest

安装成功后,执行 kitex --version 可以看到如下信息:

代码语言:javascript复制
$ kitex --version
v0.4.2

如果在安装阶段发生问题,可能主要是由于对 Golang 的不当使用造成的,需要逐一排查。

编写一个 IDL

我们先新建一个项目:hz-kitex-examples,新建完之后,我们在该项目下新建一个目录:idl,然后我们编写一个 thrift IDL:

接下来,我们编写这个 IDL:

代码语言:javascript复制
namespace go hello

struct ReqBody {
    1: string name
    2: i32 type
    3: string email
}

struct Request {
 1: string data
 2: string message
 3: ReqBody reqBody
}

struct Msg {
 1: i64 status
 2: i64 code
 3: string msg
}

struct Response {
 1: Msg msg
 2: string data
}

service HelloService {
    Response echo(1: Request req)
    Response testHello4Get(1: Request req)
    Response testHello4Post(1: Request req)
}

这里我们定义了一个命名空间:hello,这个是代表生成的代码中有一个目录:hello,然后我们编写一个请求对象:ReqBody,接着定义一个泛对象,包括了那个请求对象,这块没要求,自己定义好就行,同时我们定义了响应对象Response,此外,我们还定义了一个类,类中存在三个函数方法。

生成代码

在定义完 IDL 后,我们来看如何生成代码呢?直接执行如下命令:

代码语言:javascript复制
kitex -module "hz-kitex-examples" -thrift frugal_tag -service helloserver idl/hello.thrift

这里有几个参数 tag:

-module module_name

  • 该参数用于指定生成代码所属的 Go 模块,会影响生成代码里的 import path。
  • 如果当前目录是在
GOPATH/src 下的一个目录,那么可以不指定该参数;kitex 会使用

GOPATH/src 开始的相对路径作为 import path 前缀。例如,在 $GOPATH/src/example.com/hello/world 下执行 kitex,那么 kitex_gen/example_package/example_package.go 在其他代码代码里的 import path 会是 example.com/hello/world/kitex_gen/example_package。

  • 如果当前目录不在 $GOPATH/src 下,那么必须指定该参数。
  • 如果指定了 -module 参数,那么 kitex 会从当前目录开始往上层搜索 go.mod 文件
    • 如果不存在 go.mod 文件,那么 kitex 会调用 go mod init 生成 go.mod;
    • 如果存在 go.mod 文件,那么 kitex 会检查 -module 的参数和 go.mod 里的模块名字是否一致,如果不一致则会报错退出;
    • 最后,go.mod 的位置及其模块名字会决定生成代码里的 import path。

-service service_name

  • 使用该选项时,kitex 会生成构建一个服务的脚手架代码,参数 service_name 给出启动时服务自身的名字,通常其值取决于使用 Kitex 框架时搭配的服务注册和服务发现功能。

对于当前项目,我们执行如下:

代码语言:javascript复制
kitex -module "hz-kitex-examples" -thrift frugal_tag -service helloserver idl/hello.thrift

由于当前项目不在环境路径下,需要指定 go.mod 所在的目录模块的名称,同时,我们指定一个服务名。

这样在执行后,我们会发现生成的目录结构如下图:

在生成的目录中根目录是kitex_gen,代表是 kitex 工具生成的,其次其目录下有一个 hello 目录,这是代表 IDL 文件中的 ns,在其下面有一个文件:定义了请求对象与响应对象的序列化、传输信息的读写等操作。

在其下面还存在一个 service 目录,用来生成跟客户端与服务端相关的 service 处理逻辑。其中也定义了 service 中处理的方法信息。

同时,我们可以看到生成了服务端的基础框架:

服务端

新建一个server 目录,然后在里面新建一个项目hello,此时把生成服务端的骨架代码拷贝到里面:

拷贝完之后,我们可以丰满服务端的函数的逻辑,以 Echo 函数为例:

代码语言:javascript复制
func (s *HelloApi) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) {
 klog.Info("hello service enter: "   GetIpAddr2())

 resp = &api.Response {
  Msg:  &api.Msg {
   Status: 200,
   Code:   10000,
   Msg:    req.Message,
  },
  Data: req.Message,
 }
 return resp, nil
}

func GetIpAddr2() string {
 conn, err := net.Dial("udp", "8.8.8.8:53")
 if err != nil {
  klog.Error(err)
  return ""
 }
 localAddr := conn.LocalAddr().(*net.UDPAddr)
 // 192.168.1.20:61085
 ip := strings.Split(localAddr.String(), ":")[0]

 return ip
}

然后再定义启动函数:

代码语言:javascript复制
svr := hello.NewServer(new(api.HelloApi),
    server.WithServiceAddr(&net.TCPAddr{Port: 2008}),
  server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{ServiceName: constants.HelloServiceName}),
    server.WithPayloadCodec(thrift.NewThriftCodecWithConfig(thrift.FastRead | thrift.FastWrite)),
  server.WithErrorHandler(func(err error) error {
   error := errno.ConvertErr(err)
   return error
  }),
  //指定默认 Codec 的包大小限制,默认无限制 option: codec.NewDefaultCodecWithSizeLimit
  server.WithCodec(codec.NewDefaultCodecWithSizeLimit(1024 * 1024 * 10)),//10M
  server.WithLimit(&limit.Option{MaxConnections: 10000, MaxQPS: 5000}),
  //连接多路复用(mux)
  server.WithMuxTransport(),
  server.WithMetaHandler(transmeta.ServerTTHeaderHandler),
  server.WithRegistry(registry.NewNacosRegistry(r1)),
 )

在这里,我们定义了服务端的端口:2008,同时被注册到 Nacos。启动之后:

可以看到被注册到 Nacos:

这里注册 Nacos 的代码前面已经讲过了,具体可以看:KiteX 的实践

客户端

关于客户端,也是一样,新建一个 client 目录,里面新建一个项目customer-service,新建启动 main 函数,这里与服务端类似不再赘述了。主要注意一点:这里由于需要提供 Http 协议接口,需要结合 Hertz 来进行:

至于 Hertz,它是一个高性能的 Http 微服务框架在后面的文章中会进一步讲解,此处不再赘述。

启动函数新建完后,我们需要初始化一个 RPC 连接的客户端:

此处客户端的初始化,也是基于之前生成的代码:

在这个函数初始化客户端时,需要定义请求的服务名、网络库、负载均衡策略、出错误处理机制等。同时,我们还需要复写需要调用的函数,去调用相关的接口:

写完 RPC 的部分,一个简单的 RPC 协议调用就能串联起来了,此时,我们来简单写下客户端的接口调用:

代码语言:javascript复制
func HelloDemo(ctx context.Context, c *app.RequestContext) {
 req := &api.Request{Message: "my request"}
  // TODO
 resp, err := rpc.Echo(context.Background(), req)
 if err != nil {
  log.Fatal(err)
 }
 klog.Info(resp)
  // TODO
 c.JSON(consts.StatusOK, (resp))
}

上面定义的是客户端的接口入口,进入后,会调用 rpc 的部分。同时在调用 RPC 前后,可以有自己的逻辑处理以及响应数据的处理,在 TODO 部分。

启动客户端进行服务注册:

测试

在启动完服务端、客户端后,我们访问客户端的 http 接口:

代码语言:javascript复制
http://192.168.6.51:3000/v1/hello/test

我们再多几次进行访问:

发现其访问的性能以及速度还是不错的,这得益于 KiteX 框架中使用了自研的 Netpoll 网络库以及实现了高效的吞吐编解码性能提升。

0 人点赞