Go三件套之Kitex学习笔记 | 青训营

255 阅读5分钟

Kitex 是一种用于构建高性能、可扩展和易于维护的分布式应用程序的开源框架,主要用于 Golang 编程语言。它旨在帮助开发人员构建基于微服务架构的应用程序,提供了一系列强大的功能和工具,包括网络通信、序列化、负载均衡、服务注册与发现等。

接下来简单介绍Kitex的使用方法。

1 定义服务接口和数据传输格式

Kitex 框架及命令行工具,默认支持 thrift 和 proto3 两种 IDL,对应的 Kitex 支持 thrift 和 protobuf 两种序列化协议。 传输上 Kitex 使用扩展的 thrift 作为底层的传输协议。此处以thrift为案例定义IDL。

1.1 初始化项目

为方便操作,此处在$GOPATH的src下创建项目

mkdir KitexLearn
cd KitexLearn
go mod init KitexLearn

1.2 编写 thrift IDL

1.2.1 什么是IDL?

DL(Interface Definition Language,接口定义语言)是一种用于描述计算机系统中组件之间接口的语言。它允许不同的软件模块、服务、组件或系统之间进行通信和交互,而无需考虑它们的实现细节和编程语言。

IDL的主要目的是提供一个统一的、独立于编程语言的方式来定义接口,使得不同的程序能够相互通信,即使它们是用不同的编程语言编写的。这对于分布式系统、跨平台应用、远程过程调用(RPC)等场景非常有用。

在IDL中,开发人员可以定义数据结构、接口方法、异常、服务等,以及它们之间的关系。然后,基于IDL的定义,可以使用特定的工具将其翻译成各种编程语言的代码,用于实际的开发和集成。

1.2.2 thrift IDL

Kitex 默认支持 thrift 和 proto3 两种 IDL, Kitex 使用扩展的 thrift 作为底层的传输协议。

此处以thrift IDL 进行案例讲解,在项目文件夹下新建myserver.thrift

namespace go api

struct Req1 {
    1: required string req1,
}

struct Resp1 {
    2: required string res1,
}

struct Req2 {
    1: required i64 a,
    2: required i64 b,
}

struct Resp2 {
    2: required i64 res2,
}

service Myserver {
    Resp1 echo(1: Req1 req1)
    Resp2 add(1: Req2 req2)
}

这段代码定义了一个包含两个操作的服务:echo和add,分别视线回响 服务和返回两个整型数相加结果的服务。这些操作接受不同类型的请求并返回相应类型的响应。这种定义使得可以通过Thrift生成的代码在Go语言中实现客户端和服务器端的通信,并进行数据交换。注意,这里只是定义了数据结构和服务操作的接口,实际的操作逻辑需要在生成的代码中实现。

使用如下命令根据IDL生成代码:

kitex -service myserver ./myserver.thrift

其中-service是指定服务名

2 修改生成代码

生成代码后,项目结构如下:

├── build.sh
├── go.mod
├── go.sum
├── `handler.go`
├── kitex_gen
│   └── api
│       ├── k-consts.go
│       ├── k-myserver.go
│       ├── myserver
│       │   ├── client.go
│       │   ├── invoker.go
│       │   ├── myserver.go
│       │   └── server.go
│       └── myserver.go
├── kitex_info.yaml
├── main.go
├── myserver.thrift
└── script
    └── bootstrap.sh

这个项目结构中,kitex_gen 目录是 Kitex 生成的代码的存放位置,包括服务接口、客户端、服务端等代码文件。myserver.thrift 文件是服务接口的定义,用于生成服务相关的代码。handler.go 和 main.go 文件包含了服务的处理逻辑和项目的入口代码。整个结构体现了一个典型的基于 Kitex 的微服务项目的组织方式。

我们需要进行修改的代码主要是handler.go,Kitex已经为我们生成了主要框架,我们只需要对TODO的部分进行修改以完成我们的业务逻辑,修改handler.go如下:

// Echo implements the MyserverImpl interface.
func (s *MyserverImpl) Echo(ctx context.Context, req1 *api.Req1) (resp *api.Resp1, err error) {
	// TODO: Your code here...
	resp = &api.Resp1{Res1: req1.Req1}
	return
}

// Add implements the MyserverImpl interface.
func (s *MyserverImpl) Add(ctx context.Context, req2 *api.Req2) (resp *api.Resp2, err error) {
	// TODO: Your code here...
	resp = &api.Resp2{Res2: req2.A + req2.B}
	return
}

3 自定义客户端代码

在项目目录下新建client代码:

mkdir client && cd client

新建client.go如下:

package main

import (...)

func main() {
	client, err := myserver.NewClient("my-example", client.WithHostPorts("0.0.0.0:8888"))
	if err != nil {
		log.Fatal(err)
	}
	for {
		req := &api.Req1{Req1: "hello"}
		resp, err := client.Echo(context.Background(), req)
		if err != nil {
			log.Fatal(err)
		}
		log.Println(resp)
		time.Sleep(time.Second)

		req2 := &api.Req2{A: 1, B: 2}
		resp2, err := client.Add(context.Background(), req2)
		if err != nil {
			log.Fatal(err)
		}
		log.Println(resp2)
		time.Sleep(time.Second)
	}
}

其中,myserver结构体来源于之前IDL文件中定义的服务名Myserver,Echo和Add方法名来源于之前在IDL文件中定义的方法名echo和add,Kitex会自动更改其大小写。

可以看出,在Kitex已经生成了相关代码后,可以直接使用 client.Echo 方法调用远程服务的 Echo 方法,传入请求对象,获取响应对象,并进行错误处理;使用 client.Add 方法调用远程服务的 Add 方法,传入另一个请求对象,获取响应对象,并进行错误处理。

在项目根目录下运行服务端代码

go run .

在项目根目录下运行客户端代码

go run ./client

如果代码编写正确,应该可以看到类似2023/08/17 10:09:55 Resp1({Res1:hello})2023/08/17 10:09:56 Resp2({Res2:3})的输出。

4 常见错误汇总

  1. 导入kitex报错:could not import github.com/apache/thrift/lib/go/thrift (no required module 排除GOROOT或GOPATH的路径错误后若还不能解决,极有可能是版本依赖不一致,可以先在命令行通过 thrift --version 查看系统 thrift 版本,与go.mod中的版本要求进行比对,如果不一致,可在go.mod中删除对版本要求的require字段或将其更改。
  2. 运行时报错:​[happened in biz handler] 这类报错表示panic发生在服务端中,需要注意的是handler中的入参与出参并未分配内存,需要手动进行分配。

其他错误类型可参考:Kitex 异常说明