,," /> " />

目 录CONTENT

文章目录

RPC(Remote Procedure Call)

Administrator
2024-03-05 / 0 评论 / 0 点赞 / 22 阅读 / 16612 字

示例:gRpc

gRpc简介

gRPC 是Google公司开发的一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。

gRpc官网地址:https://www.grpc.io

gRpc中文文档地址:http://doc.oschina.net/grpc

gRPC是一款RPC框架,那么先了解Rpc是什么。

Rpc基本概念

RPC(Remote Procedure Call)远程过程调用,是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议,简单的理解是一个节点请求另一个节点提供的服务。RPC只是一套协议,基于这套协议规范来实现的框架都可以称为 RPC 框架,比较典型的有 Dubbo、Thrift 和 gRPC。

RPC 机制和实现过程

RPC 是远程过程调用的方式之一,涉及调用方和被调用方两个进程的交互。因为 RPC 提供类似于本地方法调用的形式,所以对于调用方来说,调用 RPC 方法和调用本地方法并没有明显区别。

RPC的机制的诞生和基础概念

1984 年,Birrell 和 Nelson 在 ACM Transactions on Computer Systems 期刊上发表了名为“Implementing remote procedure calls”的论文,该文对 RPC 的机制做了经典的诠释:
    RPC 远程过程调用是指计算机 A 上的进程,调用另外一台计算机 B 上的进程的方法。其中A 上面的调用进程被挂起,而 B 上面的被调用进程开始执行对应方法,并将结果返回给 A,计算机 A 接收到返回值后,调用进程继续执行。
    发起 RPC 的进程通过参数等方式将信息传送给被调用方,然后被调用方处理结束后,再通过返回值将信息传递给调用方。这一过程对于开发人员来说是透明的,开发人员一般也无须知道双方底层是如何进行消息通信和信息传递的,这样可以让业务开发人员更专注于业务开发,而非底层细节     RPC 让程序之间的远程过程调用具有与本地调用类似的形式。比如说某个程序需要读取某个文件的数据,开发人员会在代码中执行 read 系统调用来获取数据。
    当 read 实际是本地调用时,read 函数由链接器从依赖库中提取出来,接着链接器会将它链接到该程序中。虽然 read 中执行了特殊的系统调用,但它本身依然是通过将参数压入堆栈的常规方式调用的,调用方并不知道 read 函数的具体实现和行为。
    当 read 实际是一个远程过程时(比如调用远程文件服务器提供的方法),调用方程序中需要引入 read 的接口定义,称为客户端存根(client-stub)。远程过程 read 的客户端存根与本地方法的 read 函数类似,都执行了本地函数调用。不同的是它底层实现上不是进行操作系统调用读取本地文件来提供数据,而是将参数打包成网络消息,并将此网络消息发送到远程服务器,交由远程服务执行对应的方法,在发送完调用请求后,客户端存根随即阻塞,直到收到服务器发回的响应消息为止。      下图展示了远程方法调用过程中的客户端和服务端各个阶段的操作

当客户端发送请求的网络消息到达服务器时,服务器上的网络服务将其传递给服务器存根(server-stub)。服务器存根与客户端存根一一对应,是远程方法在服务端的体现,用来将网络请求传递来的数据转换为本地过程调用。服务器存根一般处于阻塞状态,等待消息输入。

当服务器存根收到网络消息后,服务器将方法参数从网络消息中提取出来,然后以常规方式调用服务器上对应的实现过程。从实现过程角度看,就好像是由客户端直接调用一样,参数和返回地址都位于调用堆栈中,一切都很正常。实现过程执行完相应的操作,随后用得到的结果设置到堆栈中的返回值,并根据返回地址执行方法结束操作。以 read 为例,实现过程读取本地文件数据后,将其填充到 read 函数返回值所指向的缓冲区。
read 过程调用完后,实现过程将控制权转移给服务器存根,它将结果(缓冲区的数据)打包为网络消息,最后通过网络响应将结果返回给客户端。网络响应发送结束后,服务器存根会再次进入阻塞状态,等待下一个输入的请。 客户端接收到网络消息后,客户操作系统会将该消息转发给对应的客户端存根,随后解除对客户进程的阻塞。客户端存根从阻塞状态恢复过来,将接收到的网络消息转换为调用结果,并将结果复制到客户端调用堆栈的返回结果中。当调用者在远程方法调用 read 执行完毕后重新获得控制权时,它唯一知道的是 read 返回值已经包含了所需的数据,但并不知道该 read 操作到底是在本地操作系统读取的文件数据,还是通过远程过程调用远端服务读取文件数据。

总结下RPC执行步骤:

  1. 调用客户端句柄,执行传递参数。

  2. 调用本地系统内核发送网络消息。

  3. 消息传递到远程主机,就是被调用的服务端。

  4. 服务端句柄得到消息并解析消息。

  5. 服务端执行被调用方法,并将执行完毕的结果返回给服务器句柄。

  6. 服务器句柄返回结果,并调用远程系统内核。

  7. 消息经过网络传递给客户端。

  8. 客户端接受数据。

RPC框架的组成

一个完整的 RPC 框架包含了服务注册发现、负载、容错、序列化、协议编码和网络传输等组件。不同的 RPC 框架包含的组件可能会有所不同,但是一定都包含 RPC 协议相关的组件,RPC 协议包括序列化、协议编解码器和网络传输栈,如下图所示:

 RPC 协议一般分为公有协议和私有协议。例如,HTTP、SMPP、WebService 等都是公有协议。如果是某个公司或者组织内部自定义、自己使用的,没有被国际标准化组织接纳和认可的协议,往往划为私有协议,例如 Thrift 协议和蚂蚁金服的 Bolt 协议。

分布式架构所需要的企业内部通信模块,往往采用私有协议来设计和研发。相较公有协议,私有协议虽然有很多弊端,比如在通用性上、公网传输的能力上,但是高度定制化的私有协议可以最大限度地降低成本,提升性能,提高灵活性与效率。定制私有协议,可以有效地利用协议里的各个字段,灵活满足各种通信功能需求,比如:CRC 校验、Server Fail-Fast 机制和自定义序列化器。

在协议设计上,你还需要考虑以下三个关键问题:

  1. 协议包括的必要字段与主要业务负载字段。协议里设计的每个字段都应该被使用到,避免无效字段。

  2. 通信功能特性的支持。比如,CRC 校验、安全校验、数据压缩机制等。

  3. 协议的升级机制。毕竟是私有协议,没有长期的验证,字段新增或者修改,是有可能发生的,因此升级机制是必须考虑的。

RPC和HTTP区别

RPC 和 HTTP都是微服务间通信较为常用的方案之一,其实RPC 和 HTTP 并不完全是同一个层次的概念,它们之间还是有所区别的。

  1. RPC 是远程过程调用,其调用协议通常包括序列化协议和传输协议。序列化协议有基于纯文本的 XML 和 JSON、二进制编码的Protobuf和Hessian。传输协议是指其底层网络传输所使用的协议,比如 TCP、HTTP。

  2. 可以看出HTTP是RPC的传输协议的一个可选方案,比如说 gRPC 的网络传输协议就是 HTTP。HTTP 既可以和 RPC 一样作为服务间通信的解决方案,也可以作为 RPC 中通信层的传输协议(此时与之对比的是 TCP 协议)。

RPC:

  1. RPC是一种远程过程调用协议,允许客户端应用程序调用远程服务器上的过程或函数,就像调用本地函数一样。
  2. RPC通常更高效,因为它们可以使用更紧凑且专门化的消息格式进行通信,而不必遵循HTTP的文本格式。
  3. 在某些情况下,例如需要高性能、低延迟的内部系统间通信时,RPC可能是更好的选择。

HTTP:

  1. HTTP是一种应用层协议,最初设计用于在客户端和服务器之间传输超文本文档。然而,随着时间的推移,它演变成了一种通用的协议,用于传输各种类型的数据。
  2. HTTP具有良好的兼容性,易于在各种环境中被部署和使用。
  3. 当涉及到跨网络通信,尤其是在Web开发领域,HTTP通常是首选协议。

RPC和HTTP场景

RPC和HTTP各自有其优点和适用场景,因此RPC不能完全取代HTTP的原因如下:

  1. 灵活性和通用性:HTTP是一种通用的协议,不仅可以传输文本、图像等资源,还可以支持各种应用程序之间的通信。而RPC更注重于过程调用,虽然效率更高,但在某些情况下可能会受到一定的限制。

  2. 跨平台和跨语言性:HTTP协议得到了广泛的支持和实现,几乎所有编程语言和平台都提供了对HTTP的内置支持。相比之下,RPC的实现可能会有一定的限制,尤其在跨语言和跨平台方面。

  3. Web开发标准:HTTP是Web开发的基础,它与HTML、CSS、JavaScript等技术紧密结合,构成了现代Web应用的基础架构。RPC虽然效率更高,但在Web开发中可能无法替代HTTP的角色。

常见的 PRC 框架

目前流行的开源 RPC 框架还是比较多的,有阿里巴巴的 Dubbo、Google 的 gRPC、Facebook 的 Thrift 和 Twitter 的 Finagle 等。

  1. Go RPC:Go 语言原生支持的 RPC 远程调用机制,简单便捷。

  2. gRPC:Google 发布的开源 RPC 框架,是基于 HTTP 2.0 协议的,并支持众多常见的编程语言,它提供了强大的流式调用能力,目前已经成为最主流的 RPC 框架之一。

  3. Thrift:Facebook 的开源 RPC 框架,主要是一个跨语言的服务开发框架,作为老牌开源 RPC 协议,以其高性能和稳定性成为众多开源项目提供数据的方案选项。

关于更多RPC内容可以看这篇:https://www.cs.rutgers.edu/~pxk/417/notes/03-rpc.html

Go语言RPC过程调用

Go语言原生有RPC包,RPC过程调用实现起来非常简单。服务端只需实现对外提供的远程过程方法和结构体,然后将其注册到 RPC 服务中,客户端就可以通过其服务名称和方法名称进行 RPC 方法调用

包文档地址:https://studygolang.com/pkgdoc

这里面有两个重要方法:

  1. 服务端

2.客户端调用

// 编写服务端代码 server.go
package main

import (
    "fmt"
    "net/rpc"
    "io"
    "net"
    "net/http"
)

//创建一个int类型对象
type Panda int 

/**
 * argType是客户端发送过来的内容
 * replyType是服务端返回给客户端的内容
 */
func (this *Panda)GetInfo(argType int, replyType *int) error {//GetInfo首字母大小 因为要被外部访问
    fmt.Println("打印对方发送过来的数据:",argType)
    //执行
    *replyType = argType + 123456
    return nil
}

func pandatext(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "hello panda")
}

func main() {
    //客户端页面的请求
    http.HandleFunc("/panda", pandatext)
    //将类实例化为对象
    pd := new(Panda)
    //服务端注册一个对象,该对象就作为一个服务被暴露出去
    rpc.Register(pd)
    //连接到网络
    rpc.HandleHTTP()
    //监听端口
    ln,err := net.Listen("tcp", ":10086")
    if err != nil {
        fmt.Println("network error")
    }
    http.Serve(ln,nil)

}

// 客户端

package main

import (
    "fmt"
    "net/rpc"
)

func main() {
    //建立网络连接
    cli, err := rpc.DialHTTP("tcp", "127.0.0.1:10086")
    if err != nil {
        fmt.Println("network failed")
    }
    var pd int 
    //客户端调用服务端GetInfo方法,并传递参数
    err = cli.Call("Panda.GetInfo", 10086, &pd)
    if err != nil {
        fmt.Println("call() failed")
    }
    fmt.Println("服务端输出的值:", pd)
    

}

编写好之后先运行server.go 在运行client.go文件

go run client.go

gRPC特点

在gRPC的客户端应用可以想调用本地对象一样直接调用另一台不同的机器上的服务端的应用的对象或者方法,这样在创建分布式应用的时候更容易。下面看看gRPC的特点:

  1. 语言无关,支持多种语言;
  2. 基于 IDL 文件定义服务,gRPC使用protocol buffer 作为接口定义语言(IDL)来描述服务接口和有效负载消息的结构。通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub。
  3. 通信协议基于标准的 HTTP/2 设计,支持双向流、消息头压缩、单 TCP 的多路复用、服务端推送等特性,这些特性使得 gRPC 在移动端设备上更加省电和节省网络流量;
  4. 序列化支持 PB(Protocol Buffer)和 JSON,PB 是一种语言无关的高性能序列化框架,基于 HTTP/2 + PB, 保障了 RPC 调用的高性能。

gRPC使用说明

gRPC使用和上面RPC使用方法类似,首先定义服务,指定其能够被远程调用的方法,包括参数和返回类型,这里使用protobuf来定义服务。在服务端实现定义的服务接口,并运行一个gRPC服务器来处理客户端调用。

gRPC安装

使用go命令下载

go get -u google.golang.org/grpc

如果上面不行就使用git下载。

使用git下载:

git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc
git clone https://github.com/golang/net.git $GOPATH/src/golang.org/x/net
git clone https://github.com/golang/text.git $GOPATH/src/golang.org/x/text  
git clone https://github.com/google/go-genproto.git $GOPATH/src/google.golang.org/genprot

 因为gRPC要使用proto及相关依赖,安装protobuf可以看我这篇文章:https://www.cnblogs.com/songgj/p/11560565.html

 都安装好之后,可以测试gRPC是否可以正常运行。

#进入服务端 (先启动)  
cd /Users/songguojun/go/src/google.golang.org/grpc/examples/helloworld/greeter_server/
#进入客户端端 (服务端启动后在启动)
cd /Users/songguojun/go/src/google.golang.org/grpc/examples/helloworld/greeter_client/

 运行结果,输出hello world表面可以通信。

gRPC案例

  1. 先使用protobuf定义服务。

创建myProtobuf.proto文件,编辑如下内容。

syntax = "proto3" ;

//package myproto ;        
#上面注释掉是因为报错 参考https://www.cnblogs.com/tomtellyou/p/12936651.html这篇文章第七点  
#https://learnku.com/articles/43758  
option go_package = ".;protoes"; 

//定义服务 
service HelloServer {
  rpc SayHello (HelloReq) returns (HelloRsp){}
  rpc SayName (NameReq) returns (NameRsp){}
}

//客户端发送给服务端
message HelloReq {
  string name = 1 ;
}

//服务端返回给客户端
message HelloRsp {
  string msg = 1 ;
}

//客户端发送给服务端
message NameReq {
  string name = 1 ;
}

//服务端返回给客户端
message NameRsp {
  string msg= 1 ;
}

 定义了两个服务SayHello,SayName及对应的四个消息(message)。

 然后在执行命令生成pd.go文件

protoc --go_out=plugins=grpc:./ *.proto     #添加grpc插

 2. 编写服务端server.go

package main     

import (
    "fmt"
    "net"
    "google.golang.org/grpc"
    pd "demo/myproto"  //导入proto
    "context"
)

type server struct {}

func (this *server) SayHello(ctx context.Context, in *pd.HelloReq) (out *pd.HelloRsp,err error) {
    return &pd.HelloRsp{Msg:"hello"}, nil
}

func (this *server) SayName(ctx context.Context, in *pd.NameReq) (out *pd.NameRsp,err error){
    return &pd.NameRsp{Msg:in.Name + "it is name"}, nil
}

func main()  {
    ln, err := net.Listen("tcp", ":10088")
    if err != nil {
        fmt.Println("network error", err)
    }
    //创建grpc服务
    srv := grpc.NewServer()
    //注册服务
    pd.RegisterHelloServerServer(srv, &server{})
    err = srv.Serve(ln)
    if err != nil {
        fmt.Println("Serve error", err)
    }
}
  1. 编写客户端client.go
package main     

import (
    "fmt"
    "google.golang.org/grpc"
    pd "demo/myproto"  //导入proto
    "context"
)

func main() {
    //客户端连接服务端
    conn, err := grpc.Dial("127.0.0.1:10088", grpc.WithInsecure())
    if err != nil {
        fmt.Println("network error", err)
    }
    //网络延迟关闭
    defer  conn.Close()
    //获得grpc句柄
    c := pd.NewHelloServerClient(conn)
    //通过句柄进行调用服务端函数SayHello
    re1, err := c.SayHello(context.Background(),&pd.HelloReq{Name:"songguojun"})
    if err != nil {
        fmt.Println("calling SayHello() error", err)
    }
    fmt.Println(re1.Msg)
    //通过句柄进行调用服务端函数SayName
    re2, err := c.SayName(context.Background(),&pd.NameReq{Name:"songguojun"})
    if err != nil {
        fmt.Println("calling SayName() error", err)
    }
    fmt.Println(re2.Msg)
}
go run server.go
go run client.go

gRPC四种通信方式

  gRPC 允许你定义四类服务方法:

  1. 简单RPC(Simple RPC):即客户端发送一个请求给服务端,从服务端获取一个应答,就像一次普通的函数调用。

    rpc SayHello(HelloRequest) returns (HelloResponse){}

  2. 服务端流式RPC(Server-side streaming RPC):一个请求对象,服务端可以传回多个结果对象。即客户端发送一个请求给服务端,可获取一个数据流用来读取一系列消息。客户端从返回的数据流里一直读取直到没有更多消息为止。

    rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){}

  3. 客户端流式RPC(Client-side streaming RPC):客户端传入多个请求对象,服务端返回一个响应结果。即客户端用提供的一个数据流写入并发送一系列消息给服务端。一旦客户端完成消息写入,就等待服务端读取这些消息并返回应答。

    rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {}

  4. 双向流式RPC(Bidirectional streaming RPC):结合客户端流式rpc和服务端流式rpc,可以传入多个对象,返回多个响应对象。即两边都可以分别通过一个读写数据流来发送一系列消息。这两个数据流操作是相互独立的,所以客户端和服务端能按其希望的任意顺序读写,例如:服务端可以在写应答前等待所有的客户端消息,或者它可以先读一个消息再写一个消息,或者是读写相结合的其他方式。每个数据流里消息的顺序会被保持。

    rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){}

RPC vs RESTFUL

RPC 的消息传输可以通过 TCP、UDP 或者 HTTP等RPC 通过 HTTP 传输消息的时候和 RESTful的架构是类似的,但是也有不同。

  1. 从使用方面来看,RPC 的客户端和服务器端师紧耦合的。RESTful基于 http的语义操作资源,参数的顺序一般没有关系,也很容易的通过代理转换链接和资源位置,从这一点上来说,RESTful 更灵活。而且它们操作的对象不一样。 RPC 操作的是方法对象。 RESTful 操作的是资源(resource),而不是方法

  2. 从性能角度看,使用Http时,Http本身提供了丰富的状态功能与扩展功能,但也正由于Http提供的功能过多,导致在网络传输时,需要携带的信息更多,从性能角度上讲,较为低效。而RPC服务网络传输上仅传输与业务内容相关的数据,传输数据更小,性能更高。

  3. RPC(Remote Procedure Call)和HTTP都是用于实现远程通信和服务调用的协议,它们各自有着不同的特点和适用场景。

Thrift

Thrift是一种接口描述语言和二进制通讯协议,它被用来定义和创建跨语言的服务。它被当作一个远程过程调用(RPC)框架来使用,是由Facebook为“大规模跨语言服务开发”而开发的。它通过一个代码生成引擎联合了一个软件栈,来创建不同程度的、无缝的跨平台高效服务,可以使用C#、C++(基于POSIX兼容系统)Cappuccino、Cocoa、Delphi、Erlang、Go、Haskell、Java、Node.js、OCaml、Perl、PHP、Python、Ruby和Smalltalk编程语言开发。 2007由Facebook开源,2008年5月进入Apache孵化器, 2010年10月成为Apache的顶级项目

Thrift是一种C/S的架构体系.在最上层是用户自行实现的业务逻辑代码.第二层是由Thrift编译器自动生成的代码,主要用于结构化数据的解析,发送和接收。TServer主要任务是高效的接受客户端请求,并将请求转发Processor处理。Processor负责对客户端的请求做出响应,包括RPC请求转发,调用参数解析和用户逻辑调用,返回值写回等处理。从TProtocol以下部分是thirft的传输协议和底层I/O通信。TProtocol是用于数据类型解析的,将结构化数据转化为字节流给TTransport进行传输。TTransport是与底层数据传输密切相关的传输层,负责以字节流方式接收和发送消息体,不关注是什么数据类型。底层IO负责实际的数据传输,包括socket、文件和压缩数据流等

数据类型
  1. Base Types:基本类型
  2. Struct:结构体类型
  3. Container:容器类型,即List、Set、Map
  4. Exception:异常类型
  5. Service: 定义对象的接口,和一系列方法
协议

Thrift可以让你选择客户端与服务端之间传输通信协议的类别,在传输协议上总体上划分为文本(text)和二进制(binary)传输协议, 为节约带宽,提供传输效率,一般情况下使用二进制类型的传输协议为多数,但有时会还是会使用基于文本类型的协议,这需要根据项目/产品中的实际需求:

  1. TBinaryProtocol – 二进制编码格式进行数据传输。
  2. TCompactProtocol – 这种协议非常有效的,使用Variable-Length Quantity (VLQ) 编码对数据进行压缩。
  3. TJSONProtocol – 使用JSON的数据编码协议进行数据传输。
  4. TSimpleJSONProtocol – 这种节约只提供JSON只写的协议,适用于通过脚本语言解析。
  5. TDebugProtocol – 在开发的过程中帮助开发人员调试用的,以文本的形式展现方便阅读。
传输层
  1. TSocket- 使用堵塞式I/O进行传输,也是最常见的模式。
  2. TFramedTransport- 使用非阻塞方式,按块的大小,进行传输,类似于Java中的NIO。
  3. TFileTransport- 顾名思义按照文件的方式进程传输,虽然这种方式不提供Java的实现,但是实现起来非常简单。
  4. TMemoryTransport- 使用内存I/O,就好比Java中的ByteArrayOutputStream实现。
  5. TZlibTransport- 使用执行zlib压缩,不提供Java的实现。
服务端类型
  1. TSimpleServer - 单线程服务器端使用标准的堵塞式I/O。
  2. TThreadPoolServer - 多线程服务器端使用标准的堵塞式I/O。
  3. TNonblockingServer – 多线程服务器端使用非堵塞式I/O,并且实现了Java中的NIO通道

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区