初步了解Kitex框架 | 青训营笔记

94 阅读4分钟

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

RPC框架

远程过程调用(Remote Procedure Call, RPC), 是分布式系统或微服务中一种常见的通信方法, 允许程序调用远程的过程或函数.

RPC框架通过参数传递的方式调用远程的方法, 并且回隐藏底层的通讯细节, 并且RPC隐藏了本地调用和远程调用的区别, 使程序员可以像调用本地方法一样调用远程方法.

Call ID

在远程调用中, 因为两个进程的地址空间不一, 所以在RPC框架中, 每个函数都必须有一个自己的ID, 这个ID在所有进程中都是唯一确定的, 而客户端在远程调用中, 需要使用这个ID确定想要那个要调用的方法. 并且需要在client和server分别维护一个{函数名:Call ID}的表, 通过这个对应表, 客户端确定方法的ID, 服务端通过ID查找方法.

序列化

在远程调用中, Client需要将参数转化为字节流, 传递给server, 接着server将其进行反序列化,

而在本地调用中, 至于要将参数放入栈中, 方法即可自己进行读取.

网络传输

在远程传输中, 所有的数据都需要网络进行传输, 主要进行Call ID和序列化后的的参数字节流的传输, 而server返回的结果也会进行序列化后再次传输给client.

KiteX

server

当Client需要进行远程调用时, 就需要server的接口, 传入参数以及返回值分别是什么样的, 为此, 通过IDL约定server和client双方的协议.

IDL的优势只要在于定义清晰, 并且能够辅助进行代码生成, 在团队项目中, 通过IDL整合不同服务的接口.

 namespace go api
 struct Request{
     1 : string message
 }
 struct Response{
     1 : string message
 }
 service Echo{
     Response echo(1: Request req)
 }

定义完成IDL之后, 可以通过kitex命令

$ kitex -module example -service example echo.thrift

生成server端代码.

生成代码的相关格式如下所示

 .
 |-- build.sh
 |-- echo.thrift
 |-- handler.go
 |-- kitex_gen
 |   `-- api
 |       |-- echo
 |       |   |-- client.go
 |       |   |-- echo.go
 |       |   |-- invoker.go
 |       |   `-- server.go
 |       |-- echo.go
 |       `-- k-echo.go
 |-- main.go
 `-- script
     |-- bootstrap.sh
     `-- settings.py

其中, build.sh为构建脚本, kitex_gen为IDL内容相关的代码, 主要包含server/client以及struct和编解码的优化, handler.go则需要用户子啊其中实现IDL service中定义的方法, 即实现echo(1: Requeset)的逻辑

kitex默认监听8888端口, 当方法逻辑较为复杂时, 推荐按照MVC等模式对代码进行分层, 尽量保证handler.go中存放的是较为简单的逻辑.

 func (s *EchoImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) {
   return &api.Response{Message: req.Message}, nil
 }

client

kitex执行的协议主要为Thrift, Protobuf以及gRPC, 只要满足其支持的协议, 客户端请求甚至可以由其他语言框架发起.

创建client

 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)
 }

echo.NewClient用于创建client, "example"处应当填入服务名, 第二个参数可选, 而其中client.WithHostPorts("x.x.x.x:xxxx")用于指定服务端地址, 当指定服务端地址时, kitex的服务发现将会失效

发起请求

 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)

服务发现与注册

kitex的服务发现与注册对接了主流的服务注册与发现中心, 如ETCD, Nacos等.

当我们使用微服务时, 更多地是使用服务发现与注册去调用下游服务.

通过将server注册到注册中心, client去注册中心获取数据, 以此实现负载均衡.

通过服务注册与发现的性能将会高于使用多层代理的方式, 因为实现了ip直连, 减少了多次代理带来的网络延时.

服务注册-server端

 type HelloImpl struct{}
 ​
 func (h *EchoImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) {
   return &api.Response{Message: req.Message}, nil
 }
 ​
 func main(){
     r,err := etcd.NewEtcdRegistry([]string{"127.0.0.1:2379"})
     if err != nil{
         log.Fatal(err)
     }
     server := hello.NewServer(new(HelloImpl), server.WithRegistry(r), server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{
         Service : "Hello",
     }))
     err = server.Run()
     if err!=nil{
         log.Fatal(err)
     }
 }

服务发现-client

 func main() {
     r,err := etcd.NewEtcdResolver([]string{"127.0.0.1:2379"})
     if err != nil {
         log.Fatal(err)
     }
     //传递服务名, 使用服务名进行注册过滤
     client := hello.MustNewClient("Hello",client.WithResolver(r))
     for {
         ctx,cancel := contex.WithTimeout(contex.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)
     }
 }

为了避免每次获取示例数据都前往服务注册与发现中心(IO时间), kitex在此处使用缓存, 并且在后台使用异步方法更新删除对象.

参考链接

  1. RPC框架(技术总结)
  2. Kitex文档