初识Go框架三件套之Kitex|青训营笔记

268 阅读4分钟

这是我参与「第五届青训营 」笔记创作活动的第4天

一、本堂课重点内容:

RPC 框架——Kitex简介

通过阅读 www.cloudwego.io/zh/docs/kit… 尝试运行 Kitex 的示例代码

  • kitex 暂时没有针对 Windows 做支持,如果本地开发环境是 Windows 建议使用 WSL2 (kitex框架代码是可以跑在Windows上,但是kitex代码生成工具目前不支持Windows环境,预估2月份开始支持)
  • KItex 框架地址: github.com/cloudwego/k…

RPC

IPC(进程间通信,Interprocess communication)是在多任务操作系统或者联网的计算机之间运行的程序和进程所用的通信技术,分为LPC(本地过程调用,Local Procedure Call)和RPC(远程过程调用,Remote Procedure Call)两种类型的进程间通信技术。

LPC用在多任务操作系统中,使得同时运行的任务能互相会话。这些任务共享内存空间使任务同步和互相发送信息。远程过程调用(RPC)RPC类似于LPC,只是在网上工作。

Kitex基础使用

安装代码生成工具

安装 kitex:go install github.com/cloudwego/kitex/tool/cmd/kitex@latest 安装 thriftgo:go install github.com/cloudwego/thriftgo@latest 安装成功后,执行 kitex --version 和 thriftgo --version 应该能够看到具体版本号的输出

Kitex命令行工具

Kitex 自带了一个同名的命令行工具 kitex,用来帮助大家很方便地生成代码,新项目的生成以及之后我们会学到的 server、client 代码的生成都是通过 kitex 工具进行。

编写IDL

IDL简介

接口描述语言(Interface description language,缩写IDL),是用来描述软件组件介面的一种计算机语言。IDL通过一种独立于编程语言的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信交流;比如,一个组件用C++写成,另一个组件用Java写成。

Kitex 框架及命令行工具,默认支持 thrift 和 proto3 两种 IDL,对应的 Kitex 支持 thrift 和 protobuf 两种序列化协议。 传输上 Kitex 使用扩展的 thrift 作为底层的传输协议(注:thrift 既是 IDL 格式,同时也是序列化协议和传输协议)。

IDL解决的问题:如果我们要进行 RPC,就需要知道对方的接口是什么,需要传什么参数,同时也需要知道返回值是什么样的,就好比两个人之间交流,需要保证在说的是同一个语言、同一件事。 这时候,就需要通过 IDL 来约定双方的协议,就像在写代码的时候需要调用某个函数,我们需要知道函数签名一样。

定义IDL

首先我们需要编写一个 IDL,这里以 thrift IDL 为例。

首先创建一个名为 echo.thrift 的 thrift IDL 文件。

然后在里面定义我们的服务

namespace go api  //go语言的命名空间,也可使用在python等

struct Request {   //请求结构体
  1: string message
}

struct Response {  //响应结构体 ,该两个结构为thrift标准语法
  1: string message
}

service Echo {   //service中定义的方法(比如echo)在handler.go中实现
    Response echo(1: Request req)
}

生成echo服务代码(服务端)

有了 IDL 以后我们便可以通过 kitex 工具生成项目代码了,执行如下命令:

$ kitex -module example -service example echo.thrift

上述命令中,-module 表示生成的该项目的 go module 名,-service 表明我们要生成一个服务端项目,后面紧跟的 example 为该服务的名字。最后一个参数则为该服务的 IDL 文件。

生成后的项目结构如下:

.
|-- build.sh  //构建脚本,执行此脚本后可生成此项目的二进制可执行文件
|-- echo.thrift
|-- handler.go   //用户在该文件里实现IDL service中定义的方法
|-- kitex_gen  //此目录下的均为IDL内容相关的生成代码,主要是基础的Server/Client代码(包含一些优化)
|   `-- api
|       |-- echo
|       |   |-- client.go
|       |   |-- echo.go
|       |   |-- invoker.go
|       |   `-- server.go
|       |-- echo.go
|       `-- k-echo.go
|-- main.go  //程序入口
`-- script
    |-- bootstrap.sh
    `-- settings.py

编写echo服务逻辑

我们需要编写的服务端逻辑都在 handler.go 这个文件中,现在这个文件应该如下所示:(kitex中服务默认监听8888端口)

package main

import (
  "context"
  "example/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     //这里的 `Echo` 函数就对应了我们之前在 IDL 中定义的 `echo` 方法。

  return &api.Response{Message: req.Message}, nil  //demo  对请求做出回应

}

编译运行

kitex 工具已经帮我们生成好了编译和运行所需的脚本:
编译: $ sh build.sh
执行上述命令后,会生成一个 output 目录,里面含有我们的编译产物。
运行: $ sh output/bootstrap.sh
执行上述命令后,Echo 服务就开始运行啦!

编写客户端

有了服务端后,接下来就让我们编写一个客户端用于调用刚刚运行起来的服务端。 首先,同样的,先创建一个目录用于存放我们的客户端代码:
$ mkdir client
进入目录:
$ cd client
创建一个 main.go 文件,然后就开始编写客户端代码了。

创建Client

创建Client可以使用Kitex中的方法,也可以使用其他框架或语言中的方法,只要保持协议一致即可(thrift协议等)。echo.NewClient 用于创建 client,其第一个参数为调用的 服务名,第二个参数为 options,用于传入参数, 此处的 client.WithHostPorts 用于指定服务端的地址。

import "example/kitex_gen/api/echo"
import "github.com/cloudwego/kitex/client"
...
c, err := echo.NewClient("example", client.WithHostPorts("0.0.0.0:8888"))
if err != nil {
  log.Fatal(err)
}

发起调用

创建好Client后,我们就可以继续编写用于发起调用的代码

import "example/kitex_gen/api"
...
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)

调用

经过上述 client/main.go 文件中的简单客户端的实现,现在我们可以发起调用了。 你可以通过下述命令来完成这一步骤:
$ go run main.go

当输出: 2021/05/20 16:51:35 Response({Message:my request})

至此便成功编写了一个 Kitex 的服务端和客户端,并完成了一次调用!

Kitex服务注册与发现

目前Kitex的服务注册与发现已经对接了主流了服务注册与发现中心,如ETCD,Nacos等。

把server注册到注册中心,Client去注册中心获取数据。相比多层代理的方式,通过服务注册与发现可以更快速地获取数据(IP直连)、也更具扩展性(负载均衡等)。

以ETCD为例,示例来源:

https://github.com/kitex-contrib/registry-etcd/blob/main/example/server/main.go

服务注册

package main

import (
	"context"
	"log"

	"github.com/cloudwego/kitex-examples/hello/kitex_gen/api"
	"github.com/cloudwego/kitex-examples/hello/kitex_gen/api/hello"
	"github.com/cloudwego/kitex/pkg/rpcinfo"
	"github.com/cloudwego/kitex/server"
	etcd "github.com/kitex-contrib/registry-etcd"
)

type HelloImpl struct{}

func (h *HelloImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) {
	resp = &api.Response{
		Message: req.Message,
	}
	return
}

func main() {
	r, err := etcd.NewEtcdRegistry([]string{"127.0.0.1:2379"}) //导入注册扩展,指定etcd集群地址
	if err != nil {
		log.Fatal(err)
	}
        //初始化server对象
	server := hello.NewServer(new(HelloImpl), server.WithRegistry(r), server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{
		ServiceName: "Hello",
	}))
	err = server.Run()   //运行服务,开始监听8888端口,完成注册到etcd中
	if err != nil {
		log.Fatal(err)
	}
}

服务发现

func main() {
	r, err := etcd.NewEtcdResolver([]string{"127.0.0.1:2379"})  //Client调用Resolver
	if err != nil {
		log.Fatal(err)  //注意记得处理err
	}
        //发现场景,在之前注册过的多个服务中过滤出我们需要的服务(服务过滤)——Hello服务
	client := hello.MustNewClient("Hello", client.WithResolver(r))
	for {
		ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
		resp, err := client.Echo(ctx, &api.Request{Message: "Hello"})  //发出请求
		cancel()
		if err != nil {
			log.Fatal(err)
		}
		log.Println(resp)
		time.Sleep(time.Second)
	}
}

扩展

image.png

相关技术

Etcd 与 Opentracing 是什么

IDL 是什么

参考资料

RPC百度百科

IDL维基百科

CloudWeGo——Kitex文档