Java 转 go 学习 - kitex(1)

文章目录

  • [1. kitex 是什么](#1. kitex 是什么)
  • [2. 基础示例](#2. 基础示例)
  • [3. 文件解释](#3. 文件解释)
    • [3.1 hello.thrift](#3.1 hello.thrift)
    • [3.2 hello.go](#3.2 hello.go)
    • [3.3 k-hello.go 和 k-consts.go](#3.3 k-hello.go 和 k-consts.go)
    • [3.4 helloservice.go](#3.4 helloservice.go)
    • [3.5 server.go](#3.5 server.go)
    • [3.6 client.go](#3.6 client.go)
  • [4. 生成命令](#4. 生成命令)
    • [4.1 常见命令](#4.1 常见命令)
      • [4.1.1 只生成基础代码](#4.1.1 只生成基础代码)
      • [4.1.2 生成带服务端骨架的代码](#4.1.2 生成带服务端骨架的代码)
      • [4.1.3 修改之后重新生成](#4.1.3 修改之后重新生成)
    • [4.2 Kitex 命令里几个常见参数](#4.2 Kitex 命令里几个常见参数)
    • [4.3 如果直接用 thriftgo,命令怎么写](#4.3 如果直接用 thriftgo,命令怎么写)
      • [4.3.1 基础写法](#4.3.1 基础写法)
      • [4.3.2 带 include 路径](#4.3.2 带 include 路径)
      • [4.3.3 带 go 生成参数](#4.3.3 带 go 生成参数)
  • [5. 小结](#5. 小结)

本系列文章:


1. kitex 是什么

这篇文章来学习 kitex 的内容,Kitex 是字节跳动开源的 Go 语言微服务 RPC 框架,底层默认传输协议 Thrift,同时也支持 Protobuf,下面是几个核心的特点。

  • 高性能: 面向大规模内部服务调用场景设计,性能和资源利用率都比较强。
  • 支持多种协议: 常见是 Thrift,也支持 protobuf、gRPC 等能力。
  • 配套治理能力比较完整: 比如超时、重试、熔断、限流、负载均衡、服务发现、中间件扩展。
  • 更贴近 Go 微服务开发: 在 Go 场景里,Kitex 的工程化体验比较强,尤其适合服务间 RPC。

跟 gRPC 对比,有下面几个不同点。

  • 定位不同: gRPC 是一个通用 RPC 标准和框架,跨语言能力很强,而 Kitex 是一个更偏 Go 微服务落地的 RPC 框架,工程化和治理能力更贴近服务开发。
  • 协议和 IDL: gRPC 默认核心是 protobuf,Kitex 常见是 Thrift,也支持 protobuf。
  • 跨语言能力: gRPC 跨语言生态非常成熟,Kitex 虽然也支持协议层互通的一些能力,但主战场还是 Go,如果项目是 Go、Java、Python、Node 多语言混合,那么 gRPC 通常更自然。如果项目主要是 Go,那么用 Kitex 比较好。
  • 服务治理能力: gRPC 和 Kitex 都能做超时、重试、负载均衡、中间件扩展,但风格不同,gRPC 更偏基础通信框架,很多治理能力要结合外围生态,Kitex 更强调微服务治理的一体化体验,也就是说 Kitex 工程整合感比较强。
  • 生态风格: gRPC 生态国际化、标准化、跨语言强,Kitex 更偏国内 Go 微服务生态,和字节系 CloudWeGo 体系更近。

下面就来看下如何使用 Kitex。

2. 基础示例

go 复制代码
go install github.com/cloudwego/kitex/tool/cmd/kitex@latest

首先通过上面的 install 命令安装 kitex,然后通过下面的命令判断 kitex 是否安装成功。

go 复制代码
kitex --version

下面是本次示例的目录。

text 复制代码
kitex-01
├─ cmd
│  ├─ client
│  │  └─ main.go
│  └─ server
│     └─ main.go
├─ idl
│  └─ hello.thrift
├─ internal
│  └─ service
│     └─ hello.go
├─ kitex_gen
│  └─ hello
└─ go.mod

核心文件是 idl/hello.thrift,在这个文件中定义接口。

go 复制代码
namespace go hello

struct HelloRequest {
  1: string name
}

struct HelloResponse {
  1: string message
}

service HelloService {
  HelloResponse Hello(1: HelloRequest req)
}

在这个文件中定义了

  • 一个请求结构体 HelloRequest
  • 一个响应结构体 HelloResponse
  • 一个服务 HelloService
  • 一个方法 Hello

接下来进入 kitex-01 目录,执行命令 kitex -module example.com/kitex-01 .\idl\hello.thrift,生成的文件目录如下。

其中包括:

  • 数据结构代码
  • 服务接口代码
  • 客户端代码
  • 服务端注册代码

接下来在 internal/service/hello.go 中实现生成出来的接口。

go 复制代码
package service

import (
	"context"
	"fmt"

	hello "example.com/kitex-01/kitex_gen/hello"
)

type HelloServiceImpl struct{}

func (s *HelloServiceImpl) Hello(ctx context.Context, req *hello.HelloRequest) (*hello.HelloResponse, error) {
	name := "Kitex"
	if req != nil && req.Name != "" {
		name = req.Name
	}

	return &hello.HelloResponse{
		Message: fmt.Sprintf("hello, %s", name),
	}, nil
}

接下来是服务端的代码。

go 复制代码
package main

import (
	"log"
	"net"

	"example.com/kitex-01/internal/service"
	helloservice "example.com/kitex-01/kitex_gen/hello/helloservice"
	"github.com/cloudwego/kitex/server"
)

func main() {
	addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8888")
	if err != nil {
		log.Fatalf("resolve addr failed: %v", err)
	}

	svr := helloservice.NewServer(
		&service.HelloServiceImpl{},
		server.WithServiceAddr(addr),
	)

	log.Println("kitex server listening on 127.0.0.1:8888")

	if err := svr.Run(); err != nil {
		log.Fatalf("run server failed: %v", err)
	}
}

还有客户端的代码。

go 复制代码
package main

import (
	"context"
	"log"
	"time"

	hello "example.com/kitex-01/kitex_gen/hello"
	helloservice "example.com/kitex-01/kitex_gen/hello/helloservice"
	"github.com/cloudwego/kitex/client"
)

func main() {
	cli, err := helloservice.NewClient(
		"HelloService",
		client.WithHostPorts("127.0.0.1:8888"),
	)
	if err != nil {
		log.Fatalf("create client failed: %v", err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	resp, err := cli.Hello(ctx, &hello.HelloRequest{
		Name: "kitex learner",
	})
	if err != nil {
		log.Fatalf("call Hello failed: %v", err)
	}

	log.Printf("response: %s", resp.Message)
}

接下来启动 Server 和 Client,看日志输出。

3. 文件解释

3.1 hello.thrift

go 复制代码
namespace go hello

struct HelloRequest {
  1: string name
}

struct HelloResponse {
  1: string message
}

service HelloService {
  HelloResponse Hello(1: HelloRequest req)
}

下面看下各个部分的介绍。

  • namespace go hello :这行是在声明命名空间,这份 Thrift 文件生成 Go 代码时,放到 Go 包 hello 里,所以后面生成目录是 kitex_gen/hello。
  • struct HelloRequest:定义结构体。
  • 1: string message:编号为 1 的字段是 message,类型是 string。

3.2 hello.go

hello.go 是生成的文件之一。

go 复制代码
// Code generated by thriftgo (0.4.3). DO NOT EDIT.

package hello

import (
	"context"
	"fmt"
)

// 结构体
type HelloRequest struct {
	Name string `thrift:"name,1" frugal:"1,default,string" json:"name"`
}

// HelloRequest 结构体
func NewHelloRequest() *HelloRequest {
	return &HelloRequest{}
}

// 初始化
func (p *HelloRequest) InitDefault() {
}

// get set
func (p *HelloRequest) GetName() (v string) {
	return p.Name
}
func (p *HelloRequest) SetName(val string) {
	p.Name = val
}

// string
func (p *HelloRequest) String() string {
	if p == nil {
		return "<nil>"
	}
	return fmt.Sprintf("HelloRequest(%+v)", *p)
}

// id -> name 的映射, id 就是 thrift 文件里面的结构体字段编号, name 就是字段名
var fieldIDToName_HelloRequest = map[int16]string{
	1: "name",
}

// 下面的响应结果同理
type HelloResponse struct {
	Message string `thrift:"message,1" frugal:"1,default,string" json:"message"`
}

func NewHelloResponse() *HelloResponse {
	return &HelloResponse{}
}

func (p *HelloResponse) InitDefault() {
}

func (p *HelloResponse) GetMessage() (v string) {
	return p.Message
}
func (p *HelloResponse) SetMessage(val string) {
	p.Message = val
}

func (p *HelloResponse) String() string {
	if p == nil {
		return "<nil>"
	}
	return fmt.Sprintf("HelloResponse(%+v)", *p)
}

var fieldIDToName_HelloResponse = map[int16]string{
	1: "message",
}

// rpc 接口
type HelloService interface {
	Hello(ctx context.Context, req *HelloRequest) (r *HelloResponse, err error)
}

// rpc 接口的参数结构体
type HelloServiceHelloArgs struct {
	// 现在只有一个参数
	Req *HelloRequest `thrift:"req,1" frugal:"1,default,HelloRequest" json:"req"`
}

func NewHelloServiceHelloArgs() *HelloServiceHelloArgs {
	return &HelloServiceHelloArgs{}
}

func (p *HelloServiceHelloArgs) InitDefault() {
}

var HelloServiceHelloArgs_Req_DEFAULT *HelloRequest

func (p *HelloServiceHelloArgs) GetReq() (v *HelloRequest) {
	if !p.IsSetReq() {
		return HelloServiceHelloArgs_Req_DEFAULT
	}
	return p.Req
}
func (p *HelloServiceHelloArgs) SetReq(val *HelloRequest) {
	p.Req = val
}

func (p *HelloServiceHelloArgs) IsSetReq() bool {
	return p.Req != nil
}

func (p *HelloServiceHelloArgs) String() string {
	if p == nil {
		return "<nil>"
	}
	return fmt.Sprintf("HelloServiceHelloArgs(%+v)", *p)
}

var fieldIDToName_HelloServiceHelloArgs = map[int16]string{
	1: "req",
}

// 响应结果结构体
type HelloServiceHelloResult struct {
	Success *HelloResponse `thrift:"success,0,optional" frugal:"0,optional,HelloResponse" json:"success,omitempty"`
}

func NewHelloServiceHelloResult() *HelloServiceHelloResult {
	return &HelloServiceHelloResult{}
}

func (p *HelloServiceHelloResult) InitDefault() {
}

var HelloServiceHelloResult_Success_DEFAULT *HelloResponse

func (p *HelloServiceHelloResult) GetSuccess() (v *HelloResponse) {
	if !p.IsSetSuccess() {
		return HelloServiceHelloResult_Success_DEFAULT
	}
	return p.Success
}
func (p *HelloServiceHelloResult) SetSuccess(x interface{}) {
	p.Success = x.(*HelloResponse)
}

func (p *HelloServiceHelloResult) IsSetSuccess() bool {
	return p.Success != nil
}

func (p *HelloServiceHelloResult) String() string {
	if p == nil {
		return "<nil>"
	}
	return fmt.Sprintf("HelloServiceHelloResult(%+v)", *p)
}

var fieldIDToName_HelloServiceHelloResult = map[int16]string{
	0: "success",
}

kitex-01/kitex_gen/hello/hello.go 是 hello.thrift 生成出来的 核心数据类型和服务接口定义 文件,这个文件主要干三件事:

  • 生成 Thrift 里的结构体对应的 Go 类型。比如 HelloRequest、HelloResponse。
  • 生成服务接口定义,就是 type HelloService interface 这个接口,以及这个接口的请求参数和响应参数结构体 HelloServiceHelloArgs 和 HelloServiceHelloResult,可以这么理解,这两个结构体就是存储请求和响应的参数,而上面的就是参数的具体信息。
  • 生成这些结构体的配套方法,比如下面这些方法,这些方法主要是为了让 Thrift 编解码和 Kitex 调用更方便,不是每次手写业务时必须都去调。
    • NewHelloRequest()
    • GetName()
    • SetName()
    • String()
    • IsSet...()

3.3 k-hello.go 和 k-consts.go

kitex-01/kitex_gen/hello/k-hello.go 主要是给 hello.go 里的那些结构体补上 Thrift 编解码能力 的,可以这么理解:

  • kitex-01/kitex_gen/hello/hello.go 定义 数据长什么样。
  • kitex-01/kitex_gen/hello/k-hello.go 定义 这些数据怎么序列化、反序列化、读写字节流。

这个文件会包含下面的方法逻辑:

  • 把 HelloRequest 写入 Thrift 协议
  • 从 Thrift 协议里读出 HelloRequest
  • 计算对象编码后的长度
  • 快速序列化/反序列化相关方法

RPC 调用不是直接把 Go struct 扔到网络上,而是要先编码成二进制数据发出去,对端再解码回来。k-hello.go 就是在做这层协议处理。

kitex-01/kitex_gen/hello/k-consts.go 一般是放这份 Thrift 生成代码里要用到的一些 常量定义 的,一般会包含下面的值。

  • 字段默认值相关常量
  • 枚举值常量
  • 协议辅助常量
  • 生成代码内部复用的一些固定值

但是我们这个例子比较简单,hello.thrift 文件中没有上面说的,所以这个文件内容很少。

go 复制代码
package hello

// KitexUnusedProtection is used to prevent 'imported and not used' error.
var KitexUnusedProtection = struct{}{}

所以为什么要单独拆出一个 k-consts.go 呢,生成器拆出不同文件为了让代码逻辑更清晰。

  • kitex-01/kitex_gen/hello/hello.go:数据结构、接口定义
  • kitex-01/kitex_gen/hello/k-hello.go:编解码逻辑
  • kitex-01/kitex_gen/hello/k-consts.go:常量和辅助固定值

由于 k-hello.go 里面的编解码逻辑比较多,这里就不贴出来了,感兴趣可以用命令生成文件去看。

3.4 helloservice.go

上面几个文件将数据结构、接口和编解码的基本逻辑都定义好了,下面我们来看下具体的 rpc 服务文件。

kitex-01/kitex_gen/hello/helloservice/helloservice.go 是这组生成代码里的 服务描述文件 。这个文件的核心作用就是将这个 RPC 服务的元信息组织起来,交给 Kitex 框架使用。可以这么理解,这个文件就是告诉 Kitex:这里有一个叫 HelloService 的服务,它有哪些方法,每个方法怎么收参、怎么返回、收到请求后该怎么分发。

下面来解析下这个文件,首先就是 serviceMethods 集合中定义了有哪些方法。

go 复制代码
var serviceMethods = map[string]kitex.MethodInfo{
	// Hello 表示有一个 RPC 方法叫 Hello
	"Hello": kitex.NewMethodInfo(
		// 方法处理函数
		helloHandler,
		// 创建请求参数对象, 当请求到来的时候会用一个结构体将所有参数存起来
		newHelloServiceHelloArgs,
		// 调完业务方法之后要把结果塞进这个对象, 再由 Kitex 编码后发回客户端
		newHelloServiceHelloResult,
		// 是否是单向调用, 意思是调用之后要等服务端返回结果
		false,
		// 这个是附加配置,表示这个方法是不是流式调用
		kitex.WithStreamingMode(kitex.StreamingNone),
	),
}

// 请求处理函数
func helloHandler(ctx context.Context, handler interface{}, arg, result interface{}) error {
	// 请求参数
	realArg := arg.(*hello.HelloServiceHelloArgs)
	// 请求结果
	realResult := result.(*hello.HelloServiceHelloResult)
	// 调用我们实现的业务函数
	success, err := handler.(hello.HelloService).Hello(ctx, realArg.Req)
	if err != nil {
		return err
	}
	// 返回结果
	realResult.Success = success
	return nil
}

接下来看下 server 的入口,上面基础示例服务端启动的时候会通过 NewServer 创建 rpc 服务,在 NewServer 方法中会调用 serviceInfo 将方法和处理函数,也就是上面的 helloHandler 绑定起来,同时 helloServiceServiceInfoForClient 是创建出 ServiceInfo 给客户端用的,下面三个都是调用同一个方法 newServiceInfo 来初始化的,只是传参不同。

go 复制代码
var (
	helloServiceServiceInfo                = NewServiceInfo()
	helloServiceServiceInfoForClient       = NewServiceInfoForClient()
	helloServiceServiceInfoForStreamClient = NewServiceInfoForStreamClient()
)

// NewServiceInfo creates a new ServiceInfo containing all methods
// NewServiceInfo creates a new ServiceInfo containing all methods
func NewServiceInfo() *kitex.ServiceInfo {
	return newServiceInfo(false, true, true)
}

// NewServiceInfo creates a new ServiceInfo containing non-streaming methods
func NewServiceInfoForClient() *kitex.ServiceInfo {
	return newServiceInfo(false, false, true)
}
func NewServiceInfoForStreamClient() *kitex.ServiceInfo {
	return newServiceInfo(true, true, false)
}


func newServiceInfo(hasStreaming bool, keepStreamingMethods bool, keepNonStreamingMethods bool) *kitex.ServiceInfo {
	// kitex 服务名称
	serviceName := "HelloService"
	// 处理的接口类型 HelloService
	handlerType := (*hello.HelloService)(nil)
	// kitex 方法
	methods := map[string]kitex.MethodInfo{}
	// 遍历方法
	for name, m := range serviceMethods {
		// stream 方法判断
		if m.IsStreaming() && !keepStreamingMethods {
			continue
		}
		if !m.IsStreaming() && !keepNonStreamingMethods {
			continue
		}
		methods[name] = m
	}
	// 附加信息, 包名 hello
	extra := map[string]interface{}{
		"PackageName": "hello",
	}
	// 不是 stream 流式传参
	if hasStreaming {
		extra["streaming"] = hasStreaming
	}
	// 生成 svcInfo
	svcInfo := &kitex.ServiceInfo{
		ServiceName:     serviceName,
		HandlerType:     handlerType,
		Methods:         methods,
		PayloadCodec:    kitex.Thrift,
		KiteXGenVersion: "v0.16.1",
		Extra:           extra,
	}
	return svcInfo
}

下面来看下这三个参数。

  1. hasStreaming: 这个 ServiceInfo 是否带 流式能力 标记。
  2. keepStreamingMethods: 是否保留流失方法。
  3. keepNonStreamingMethods: 是否保留普通方法。

NewServiceInfo 的传参是 false、true、true,因为我们现在的接口都是一元 RPC 接口,不是流式发送,所以第一个参数是 false,同时创建出来的 ServiceInfo 保留流式方法的同时也保留普通方法,所以它是 最完整 的 service info,这个通常给服务端用,因为服务端要知道这个服务里所有可注册的方法,不管是普通方法还是流式方法,都可能要注册进去。

NewServiceInfoForClient 的传参是 false、false、true,意思是 不标记 streaming 、不保留流式方法 、只保留普通方法,所以它是普通客户端专用的 service info,普通 client 只负责调普通 RPC,它不需要流式方法信息,因为我们现在这个示例就是普通 RPC,不是流式方法。

NewServiceInfoForStreamClient 的传参是 true、true、false,意思是 把这个 service info 标记成 streaming ,也就是 只保留流式方法 ,不保留普通方法 ,所以它是 流式客户端专用 的 service info,因为流式客户端和普通客户端的调用模型不同。

  • 普通客户端:一次请求,一次响应
  • 流式客户端:可能是客户端流、服务端流、双向流

所以 Kitex 会单独给 streaming client 准备一份只包含流式方法的服务描述。客户端和服务端关注的信息不一样,服务端需要知道所有能注册的方法,所以通常要完整方法表。

普通客户端:

  • 只关心普通 RPC 方法
  • 不需要看到 streaming 方法

流式客户端:

  • 只关心 streaming 方法
  • 不需要看到普通方法

所以生成器干脆把它们拆开,分别提供最合适的 ServiceInfo,避免混用。

最后还剩下一部分内容,就是客户端使用的 client。

go 复制代码
// 客户端使用的
type kClient struct {
	c client.Client
}

func newServiceClient(c client.Client) *kClient {
	return &kClient{
		c: c,
	}
}

// 客户端调用 hello 方法,最终会走到这里
func (p *kClient) Hello(ctx context.Context, req *hello.HelloRequest) (r *hello.HelloResponse, err error) {
	// 请求参数
	var _args hello.HelloServiceHelloArgs
	_args.Req = req
	// 请求结果
	var _result hello.HelloServiceHelloResult
	// 调用方法, 也就是请求 kitex 服务端的 Hello 方法
	if err = p.c.Call(ctx, "Hello", &_args, &_result); err != nil {
		return
	}
	return _result.GetSuccess(), nil
}

3.5 server.go

最后我们看下 server.go 和 client.go,这两个文件是创建 kitex 服务端和客户端的入口,方法依赖是:

  • 程序入口 -> server.go/client.go -> helloservice.go
go 复制代码
// Code generated by Kitex v0.16.1. DO NOT EDIT.
package helloservice

import (
	hello "example.com/kitex-01/kitex_gen/hello"
	server "github.com/cloudwego/kitex/server"
)

// NewServer creates a server.Server with the given handler and options.
func NewServer(handler hello.HelloService, opts ...server.Option) server.Server {
	var options []server.Option

	options = append(options, opts...)
	options = append(options, server.WithCompatibleMiddlewareForUnary())

	svr := server.NewServer(options...)
	if err := svr.RegisterService(serviceInfo(), handler); err != nil {
		panic(err)
	}
	return svr
}

func RegisterService(svr server.Server, handler hello.HelloService, opts ...server.RegisterOption) error {
	return svr.RegisterService(serviceInfo(), handler, opts...)
}

NewServer 就是创建一个服务端,传入我们实现的接口处理类。

3.6 client.go

最后看下 client.go 文件。

go 复制代码
// Code generated by Kitex v0.16.1. DO NOT EDIT.

package helloservice

import (
	"context"
	hello "example.com/kitex-01/kitex_gen/hello"
	client "github.com/cloudwego/kitex/client"
	callopt "github.com/cloudwego/kitex/client/callopt"
)

// client 结构体,实现 Hello 方法,在里面去调用服务端的 Hello
type Client interface {
	Hello(ctx context.Context, req *hello.HelloRequest, callOptions ...callopt.Option) (r *hello.HelloResponse, err error)
}

// NewClient creates a client for the service defined in IDL.
func NewClient(destService string, opts ...client.Option) (Client, error) {
	var options []client.Option
	options = append(options, client.WithDestService(destService))

	options = append(options, opts...)

	kc, err := client.NewClient(serviceInfoForClient(), options...)
	if err != nil {
		return nil, err
	}
	return &kHelloServiceClient{
		kClient: newServiceClient(kc),
	}, nil
}

// MustNewClient creates a client for the service defined in IDL. It panics if any error occurs.
func MustNewClient(destService string, opts ...client.Option) Client {
	kc, err := NewClient(destService, opts...)
	if err != nil {
		panic(err)
	}
	return kc
}

type kHelloServiceClient struct {
	*kClient
}

func (p *kHelloServiceClient) Hello(ctx context.Context, req *hello.HelloRequest, callOptions ...callopt.Option) (r *hello.HelloResponse, err error) {
	ctx = client.NewCtxWithCallOptions(ctx, callOptions)
	return p.kClient.Hello(ctx, req)
}

通过 NewClient 创建出客户端之后,再用 Client#Hello 去调用上面的 Hello 方法,最终会调用到 helloservice.go 的 Hello 方法,这个上面已经看了。

4. 生成命令

下面我们来看下 thrift 代码生成的命令,还是一样,我们以最简单的 thrift 例子来做演示。

go 复制代码
namespace go hello

struct HelloRequest {
  1: string name
}

struct HelloResponse {
  1: string message
}

service HelloService {
  HelloResponse Hello(1: HelloRequest req)
}

项目结构如下。

在 Kitex 项目里,通常优先使用 kitex 命令来生成,而不是直接手写 thriftgo 命令。

powershell 复制代码
kitex -module example.com/kitex-02 .\idl\hello.thrift

kitex 会调用底层的 thriftgo,但它不只生成数据结构,还会额外生成 Kitex 需要的客户端、服务端、服务描述代码,所以 thriftgo 更像 Thrift 代码生成器 ,而 kitex 更像 Kitex 项目的完整生成器。

4.1 常见命令

4.1.1 只生成基础代码

powershell 复制代码
kitex -module example.com/kitex-02 .\idl\hello.thrift

这个就是最常用的命令,用于生成基础代码。

  • kitex:调用 Kitex 代码生成器。
  • -module example.com/kitex-02:指定当前 Go 模块名,就是 go.mod 里面配置的 module 名称。
  • .\idl\hello.thrift:指定要处理的 Thrift IDL 文件。

执行后通常会生成:

  • kitex_gen/hello/hello.go
  • kitex_gen/hello/k-hello.go
  • kitex_gen/hello/k-consts.go
  • kitex_gen/hello/helloservice/client.go
  • kitex_gen/hello/helloservice/server.go
  • kitex_gen/hello/helloservice/helloservice.go

4.1.2 生成带服务端骨架的代码

powershell 复制代码
kitex -module example.com/kitex-02 -service HelloService .\idl\hello.thrift

这条命令比上一条多了 -service HelloService,这个配置的意思是:告诉 Kitex 这是一个服务端项目,除了 kitex_gen 之外,还会生成服务端启动和 handler 骨架。

如果我们要从 0 开始搭建一个 kitex 服务,那么就可以加上这条命令,但是如果我们只是想重新生成 kitex_gen,已经有自己的 cmd/server 和业务实现,这种情况下就不用加上这行命令。

我们执行下这条命令,会看到对比上面的项目结构,这次生成的文件多了下面几个。

主要看 handler.go,这里面其实就是生成了一个结构体来实现服务端接口,只是生成一个架子,里面的实现都是空的。

go 复制代码
package main

import (
	"context"
	hello "example.com/kitex-02/kitex_gen/hello"
)

// HelloServiceImpl implements the last service interface defined in the IDL.
type HelloServiceImpl struct{}

// Hello implements the HelloServiceImpl interface.
func (s *HelloServiceImpl) Hello(ctx context.Context, req *hello.HelloRequest) (resp *hello.HelloResponse, err error) {
	// TODO: Your code here...
	return
}

4.1.3 修改之后重新生成

powershell 复制代码
kitex -module example.com/kitex-02 .\idl\hello.thrift

修改 thrift 文件之后还是调用原来的文件重新生成即可,也就是说:

  • 改了 struct
  • 改了 service
  • 加了字段
  • 加了方法

都可以重新跑这条命令。那么这样一来就有一个问题了,对于一个服务,新加入的接口我们自己的实现类没有实现的情况下生成的代码是怎么保证不报错的。

这里要注意,不像 proto 生成的代码,kitex 生成的代码没有兜底,也就是说如果你加入了一个新的接口,那么你的实现结构体必须要实现这个方法,否则就报错。

就比如上面的 main 方法,我们用 kitex -module example.com/kitex-02 .\idl\hello.thrift 重新生成了代码,但是 handler.go 不变,所以这里就报错了,因为 HelloServiceImpl 结构体没有实现 Hi 方法。

4.2 Kitex 命令里几个常见参数

-module,作用就是 指定当前项目的 Go module 名称 ,也就是 go.mod 里面设置的 module 名称。

-service,作用是告诉 Kitex 生成服务端项目骨架,一般不常用,反正我自己写代码都是自己写实现的。

-gen-path,用来指定生成代码目录,就比如我们上面的代码,你不使用这个命令,默认生成的代码就全放到 kitex_gen 下面,如果你想改成其他目录就可以用这个参数,我自己开发也不会指定这个,下面是参考命令。

powershell 复制代码
kitex -module example.com/kitex-02 -gen-path gen .\idl\hello.thrift

-I,比如命令 kitex -module example.com/kitex-02 -I .\idl .\idl\hello.thrift,作用是指定 include 搜索路径,当你的 thrift 文件里有 include "common.thrift" 这种写法的时候就要用这个参数指定搜索路径,include 是用来引入另一个文件的。

我们下面写一个 -I 的例子。

go 复制代码
include "common.thrift"

namespace go hello

struct HelloRequest {
  1: string name
}

struct HelloResponse {
  1: string message
  2: common.BaseResp base_resp
}

service HelloService {
  HelloResponse Hello(1: HelloRequest req)
}

在 hello.thrift 中引入 common.thrift,common.thrift 中定义另一个返回结果。

go 复制代码
namespace go common

struct BaseResp {
  1: i32 code
  2: string message
}

然后调用命令 kitex -module example.com/kitex-02 -I .\idl -service HelloService .\idl\hello.thrift 去生成代码,生成结构如下。

4.3 如果直接用 thriftgo,命令怎么写

4.3.1 基础写法

powershell 复制代码
thriftgo -g go -o .\gen .\idl\hello.thrift

这条命令的意思是:

  • thriftgo
    调用 ThriftGo 生成器
  • -g go
    生成 Go 代码
  • -o .\gen
    输出到 gen 目录
  • .\idl\hello.thrift
    输入的 IDL 文件

这个命令只会生成 hello.go 一个文件,虽然有两个 thrift,但是我们没有用 -i 指定 include 搜索的路径。

所以这个命令就是用来解析 .thrift,生成基础 Go 结构体和协议代码,我们自己开发还是用 kitex。

4.3.2 带 include 路径

我们用下面的命令去同时生成 hello.thrift 和 common.thrift 的代码。

powershell 复制代码
thriftgo -r -i .\idl -g go:package_prefix=example.com/kitex-02/gen/ -o .\gen .\idl\hello.thrift

下面来看下上面的命令是什么意思:

  • -r :递归生成,把 hello.thrift 里 include "common.thrift" 的 common.thrift 一起生成。
  • -i .\idl:让编译器去 idl 目录找被 include 的 thrift。
  • -g go:package_prefix=... :这个就是指定引入包名/包前缀,也就是生成 hello.go 的时候依赖 common.go,会 import "example.com/kitex-02/gen/common",否则 hello.go 用不了 common.go 里面的结构体,-g go 的意思使用 go 生成器。
  • -o .\gen:输出到你当前项目的 gen 目录。

4.3.3 带 go 生成参数

powershell 复制代码
thriftgo -r -i .\idl -g go:naming_style=golint,gen_setter,gen_deep_equal,package_prefix=example.com/kitex-02/gen/ -o .\gen .\idl\hello.thrift

go:xxx 就是设置额外的参数,比如上面的 package_prefix,这里的几个额外参数的意思是:

  • naming_style=golint 生成更符合 Go 风格的命名
  • gen_setter 生成 SetXxx 方法
  • gen_deep_equal 生成深比较方法 DeepEqual
  • reserve_comments 保留 thrift 文件里的注释
  • package_prefix 设置需要 import 导入的包的前缀路径,比如 thrift 文件里面有 include "common.thrift",如果不加 package_prefix,生成器未必知道 hello.thrift 要用什么前缀来引用这个包,但是加了之后就知道了需要引入 example.com/kitex-02/gen/common。

5. 小结

好了,这里简单介绍下 kitex 的代码生成,介绍了基础示例,生成的文件以及生成命令,下一篇文章来看下里面的基本数据类型和校验规则。

如有错误,欢迎指出!!!

相关推荐
li星野2 小时前
HAVE — have / has / had (One-Page Notes)
学习
传奇开心果编程2 小时前
【声明式UI开发实用技术学与练】第8课 状态提升与下放
学习·flutter·react native·ui·swiftui·composer
谢亮_vipxieliang2 小时前
Go select 多路复用:从语法到实战的完整指南
开发语言·后端·golang
阳光九叶草LXGZXJ10 小时前
达梦数据库-报错-15-列【XXX】长度超出定义
linux·运维·数据库·sql·学习
mlidongfeng13 小时前
【学习】mKernel 分析
学习
谢亮_vipxieliang14 小时前
Go map与结构体的正确使用
开发语言·golang·哈希算法
一只小小的芙厨14 小时前
【线性DP】
学习·算法·动态规划
microrain15 小时前
先应答,再入库:SagooIoT 接入 GB/T 32960 的四层宿主改造
物联网·golang·开源·sagooiot
li星野15 小时前
DO — Auxiliary Verb (One-Page Notes)
学习