RPC和gRPC介绍
RPC(Remote Procedure Call),远程过程调用协议,一种通过网络从远程计算机请求服务,而不需要了解底层网络技术的协议。RPC它假定某些协议存在,例如TCP/UDP等,为通信程序之间携带信息数据。在OSI网络七层模型中,RPC跨越了传输层和应用层,RPC使得开发,包括网络分布式多程序在内的应用程序更加容易。所谓过程就是业务处理,计算任务,就是说程序像调用本地方法一样调用远程的过程。RPC采用客户端/服务端模式,通过request-response消息模式实现。
gRPC里客户端应用可以像调用本地对象一样直接调用另一台不同机器上的服务端应用的方法,使得能够更加容易的创建分布式应用和服务。与许多RPC系统类似,gRPC基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个gRPC服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。
底层协议
- HTTP2
- GRPC-WEB
HTTP2
- HTTP/1里面的header对应HTTP/2里面的HEADERS frame
- HTTP/1里面的playload对应HTTP/2里面的DATA frame
gRPC把元数据放到HTTP/2 Headers里面,请求参数序列化之后放到DATA frame里面
基于HTTP2协议的优点
- 公开标准
- 前身是google的SPDY,有过实践检验
- HTTP2天然支持物联网、手机、浏览器
- 基于HTTP2多语言客户端实现容易
- 每个流行的编程语言都有成熟的HTTP/2 Client
- 用clinet发送HTTP/2请求难度远低于用socket发送数据包/解析数据包
- HTTP/2支持Stream和流控
- 基于HTTP2在gageway/proxy容易支持
- HTTP2天然支持SSL,很多私有协议的RPC可能自己包装了一层TLS支持,使用复杂
- HTTP2在公网上传输有保证
- HTTP2鉴权成熟,前端到后端完全打通鉴权,不需要做任何转换适配
基于HTTP2协议的缺点
- rpc的元数据传输不够高效
- HTTP2里一次gRPC调用需要解码两次
- HTTP2标准本身只有一个TCP连接,但是实际gRPC里面会有多个TCP连接
gRPC选择基于HTTP2,性能不是顶尖,但是通用性和兼容性比较好,gRPC目前是k8生态里面的事实标准,而k8s又是容器编排的事实标准。rRPC已经广泛应用于Istio体系,包括:
- Envoy和istiod间的xds协议
- mixer的handler扩展协议
- MCP控制面的配置分发协议
在云原生的潮流下,开放互通的需求必然会产生基于HTTP/2的RPC
gRPC实例
项目定义了一个Hello Service,客户端发送包含字符串名字的请求,服务端返回Hello消息。
流程:
- 编写.proto描述文件
- 编译生成.pb.go文件
- 服务端实现约定的接口并提供服务
- 客户端按照约定调用.pb.go文件中的方法请求服务
项目结构
|—— hello/
|—— client/
|—— main.go // 客户端
|—— server/
|—— main.go // 服务端
|—— proto/
|—— hello/
|—— hello.proto // proto描述文件
|—— hello.pb.go // proto编译后文件
编写描述文件:hello.proto
syntax = "proto3"; // 指定proto版本
package hello; // 指定默认包名
// 指定golang包名
option go_package = "../hello";
// 定义Hello服务
service Hello {
// 定义SayHello方法
rpc SayHello(HelloRequest) returns (HelloResponse) {}
}
// HelloRequest 请求结构
message HelloRequest {
string name = 1; //代表序号
}
// HelloResponse 响应结构
message HelloResponse {
string message = 1;
}
hello.proto文件中定义了一个Hello Service,该服务包含一个SayHello方法,同时声明了HelloRequest和HelloResponse消息结构用于请求和响应。客户端使用HelloRequest参数调用SayHello方法请求服务端,服务端响应HelloResponse消息。一个最简单的服务就定义好了。
安装配置protoc-gen-go-grpc编译工具插件
#下载protoc-gen-go-grpc编译工具,专门编译为grpc格式
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
#下载grpc包
go get -u google.golang.org/grpc
#保存到/usr/local/bin/下
find / -name protoc-gen-go-grpc
cp /root/go/bin/protoc-gen-go-grpc /usr/local/bin/
#编辑/etc/profile环境变量增加
#:/usr/local/bin/protoc-gen-go-grpc
#加载环境变量
source /etc/profile
编译grpc proto
cd proto/hello
protoc --go-grpc_out=. hello.proto
生成的如下:
在当前目录内生成的hello.pb.go文件,按照.proto文件中的说明,包含服务端接口HelloServer描述,客户端接口及实现HelloClient,及HelloRequest、HelloResponse结构体。
实现服务端接口
//server/main.go
package main
import (
"fmt"
"net"
pb "go-project/go-project/go_basic2/gRpc/demo3/proto/hello" // 引入编译生成的包
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
)
const (
// Address gRPC服务地址
Address = "127.0.0.1:50052"
)
// 定义helloService并实现约定的接口
type helloService struct{}
// HelloService Hello服务
var HelloService = helloService{}
// SayHello 实现Hello服务接口
func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
//此处调用生成的hello_grpc.pb.go中的接口
//type HelloServer interface {
// 定义SayHello方法
//SayHello(context.Context, *HelloRequest) (*HelloResponse, error)
resp := new(pb.HelloResponse)
resp.Message = fmt.Sprintf("Hello %s.", in.Name)
return resp, nil
}
func main() {
listen, err := net.Listen("tcp", Address)
if err != nil {
grpclog.Fatalf("Failed to listen: %v", err)
}
// 实例化grpc Server
s := grpc.NewServer()
// 注册HelloService
pb.RegisterHelloServer(s, HelloService)
grpclog.Println("Listen on " + Address)
s.Serve(listen)
}
运行
$ go run main.go
Listen on 127.0.0.1:50052 //服务端已开启并监听50052端口
实现客户端调用 client/main.go
package main
import (
pb "go-project/go-project/go_basic2/gRpc/demo3/proto/hello" // 引入编译生成的包 // 引入proto包
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
)
const (
// Address gRPC服务地址
Address = "127.0.0.1:50052"
)
func main() {
// 连接
conn, err := grpc.Dial(Address, grpc.WithInsecure())
if err != nil {
grpclog.Fatalln(err)
}
defer conn.Close()
// 初始化客户端
c := pb.NewHelloClient(conn)
// 调用方法
req := &pb.HelloRequest{Name: "gRPC"}
res, err := c.SayHello(context.Background(), req)
if err != nil {
grpclog.Fatalln(err)
}
grpclog.Println(res.Message)
}
客户端初始化连接后直接调用hello.pb.go中实现的SayHello方法,即可向服务端发起请求,使用姿势就像调用本地方法一样。
$ go run main.go
Hello gRPC. // 接收到服务端响应
总结gRPC的配置坑很多,头都秃了。。。。