RPC
Client Stub / Server Skeleton
Client 通过本地调用,调用 Client Stub
Client Stub 将参数打包(Marshalling
)成一个消息,并发送这个消息
Client 所在的 OS 将消息发送给 Server
Server 端接收到消息后,将消息传递给 Server Stub
Server Stub 将消息解包(Unmarshalling
)得到参数
Server Stub 调用服务端子函数,处理完毕后,将最终结果按相反步骤返回给 Client
gRPC
Google
Remote Procedure Call
简介
由 Google 开发的高性能、开源、跨语言
的通用
RPC 框架
基于 HTTP 2.0
协议 + 默认采用 Protocol Buffers
数据序列化协议
特征
支持多语言
- Go、Java、C++、Node.js、Python 等
基于 IDL
- Interface Definition Language 文件定义服务
通过 proto3 工具生成指定语言的数据结构、Server API
以及 Client Stub
通信协议基于标准的 HTTP/2
支持双向流
、消息头压缩
、单 TCP 的多路复用
、服务端推送
等特征
支持 Protobuf
和 JSON
序列化数据格式
Protobuf 是一种语言无关
的高性能
序列化框架,可以减少网络传输流量,提高通信效率
调用过程
Protocol Buffers
由 Google 开发的一套对数据结构
进行序列化的方法,可用作通信协议
、数据存储格式
等,与 XML
、JSON
类似
Protocol Buffers 的传输性能
非常好,常用在一些对数据传输性能要求比较高的系统
主要特征
更快的数据传输速度
在传输时,会将数据序列化为二进制数据
,节省大量的 IO 操作
跨平台多语言
基于 IDL 文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端和客户端接口
扩展性
+ 兼容性
Protocol Buffers 在 gRPC 中的作用
定义数据结构
定义服务接口
通过 Protocol Buffers 序列化
和反序列化
,提高传输效率
示例 服务定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 syntax = "proto3" ; option go_package = "github.com/zhongmingmao/hello-grpc/helloworld" ;package helloworld;message HelloRequest { string name = 1 ; } message HelloResponse { string message = 1 ; } service Greeter { rpc SayHello(HelloRequest) returns (HelloResponse) {} }
支持的服务方法
Type
Desc
Simple RPC
客户端发起一次请求,服务端响应一个数据 rpc SayHello (HelloRequest) returns (HelloResponse) {}
Server-side streaming RPC
客户端发送一个请求,服务端返回数据流
响应 客户端从流中读取数据直到为空 rpc SayHello (HelloRequest) returns (stream HelloResponse) {}
Client-side streaming RPC
客户端将消息以流的方式发送给服务器 服务器全部处理
完成后返回一次响应 rpc SayHello (stream HelloRequest) returns (HelloResponse) {}
Bidirectional streaming RPC
客户端和服务端都可以向对方发送数据流 双方的数据流可以同时互相发送
,实时交互 rpc SayHello (stream HelloRequest) returns (stream HelloResponse) {}
生成代码 1 2 3 4 5 6 7 $ protoc -I. --go_out=plugins=grpc:$GOPATH/src helloworld.proto $ tree . └── helloworld ├── helloworld.pb.go └── helloworld.proto
实现 gRPC 服务端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package mainimport ( "context" "log" "net" pb "github.com/zhongmingmao/hello-grpc/helloworld" "google.golang.org/grpc" ) const ( port = ":50051" ) type server struct { pb.UnimplementedGreeterServer } func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error ) { log.Printf("Received: %v" , in.GetName()) return &pb.HelloResponse{Message: "hello " + in.GetName()}, nil } func main () { lis, err := net.Listen("tcp" , port) if err != nil { log.Fatalf("failed to listen: %v" , err) } s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v" , err) } }
实现 gRPC 客户端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package mainimport ( "context" "log" "os" "time" pb "github.com/zhongmingmao/hello-grpc/helloworld" "google.golang.org/grpc" ) const ( address = "localhost:50051" defaultName = "go" ) func main () { conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock()) if err != nil { log.Fatalf("did not connect: %v" , err) } defer conn.Close() c := pb.NewGreeterClient(conn) name := defaultName if len (os.Args) > 1 { name = os.Args[1 ] } ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name}) if err != nil { log.Fatalf("could not greet: %v" , err) } log.Printf("Greeting: %s" , r.Message) }
Server
1 2 $ go run server.go 2023/05/04 20:00:06 Received: go
Client
1 2 $ go run client.go 2023/05/04 20:00:06 Greeting: hello go
类型零值
通过指针
判断
场景:定义一个接口,接口会通过判断是否传入
某个参数,决定接口行为
如果客户端没有传入某个参数,Go 会默认赋值为类型零值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 syntax = "proto3" ; package proto;option go_package="github.com/zhongmingmao/hello-grpc/user" ;message GetUserRequest { string class = 1 ; optional string username = 2 ; optional string user_id = 3 ; } message GetUserResponse { string class = 1 ; string user_id = 2 ; string username = 3 ; string address = 4 ; string sex = 5 ; string phone = 6 ; } service User { rpc GetUser(GetUserRequest) returns (GetUserResponse) {} }
experimental_allow_proto3_optional - 打开 optional 选项
1 $ protoc --experimental_allow_proto3_optional --go_out=plugins=grpc:$GOPATH/src user.proto
user.pb.go - GetUserRequest.Username - 指针类型
1 2 3 4 5 6 7 8 9 type GetUserRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Class string `protobuf:"bytes,1,opt,name=class,proto3" json:"class,omitempty"` Username *string `protobuf:"bytes,2,opt,name=username,proto3,oneof" json:"username,omitempty"` UserId *string `protobuf:"bytes,3,opt,name=user_id,json=userId,proto3,oneof" json:"user_id,omitempty"` }
RESTful vs gRPC
对内
业务使用 gRPC
API,对外
业务使用 RESTful
API