RPC
Client 通过本地调用,调用 Client Stub
Client Stub 将参数打包(Marshalling)成一个消息 ,然后发送这个消息
Client 所在的 OS 将消息发送给 Server
Server 接收到消息后,将消息传递给 Server Stub(或者 Server Skeleton )
Server Stub 将消息解包(Unmarshalling)后得到消息
Server Stub 调用服务端的子程序,处理完成后,将最终结果按照相反的步骤返回给 Client
gRPC 概述
gRPC:google Remote Procedure Call
gRPC 是由 Google 开发的高性能 、开源、跨语言 的通用 RPC 框架 ,基于 HTTP 2.0 ,默认使用 Protocol Buffers 序列化
gRPC 的特点
支持多语言
基于 IDL (Interface Definition Language)文件定义服务
通过 proto3 生成指定语言的数据结构 、服务端接口 和客户端 Stub
通信协议基于标准的 HTTP/2 ,支持特性:双向流 、消息头压缩 、单 TCP 的多路复用 、服务端推送 等
支持的序列化方式:Protobuf 、JSON
Protobuf 是语言无关 的高性能 序列化框架,可以减少网络传输流量 、提高通信效率
Protocol Buffers
Protocol Buffers 是由 Google 开发的序列化 方法,可用作数据通信 协议和数据存储 格式,非常灵活高效
Protocol Buffers 的传输性能非常优秀 ,常用于对数据传输性能要求比较高的系统中,作为数据传输格式
Protocol Buffers 的特点
更快的传输速度 :二进制序列化,与 JSON 和 XML 等文本序列化 方式相比,可以节省大量 IO
跨平台多语言 :protoc 基于 protobuf 定义文件,编译 出不同语言的客户端或者服务端
非常好的扩展性和兼容性 :可以更新已有的数据结构,而不会破坏和影响原有的程序
基于 IDL 文件定义服务 :通过 proto3 生成指定语言的数据结构、服务端和客户端接口
Protocol Buffers 在 gRPC 框架中发挥的作用
定义数据结构
定义服务接口
通过 protobuf 进行序列化 和反序列化 ,提升传输效率
示例 定义服务
gRPC 支持定义 4 种类型的服务方法
Simple RPC
客户端发起一次请求,服务端响应一个数据
rpc SayHello (HelloRequest) returns (HelloReply) {}
Server-side streaming RPC
客户端发送一个请求,服务端返回数据流响应,客户端从流中读取数据直到为空
rpc SayHello (HelloRequest) returns (stream HelloReply) {}
Client-side streaming RPC
客户端将消息以流的方式发送给服务器,服务器全部处理完成后返回一次响应
rpc SayHello (stream HelloRequest) returns (HelloReply) {}
Bidirectional streaming RPC
客户端和服务端都可以向对方发送数据流 ,双方的数据可以同时互相发送
rpc SayHello (stream HelloRequest) returns (stream HelloReply) {}
helloworld.proto 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 syntax = "proto3" ; option go_package = "github.com/zhongmingmao/grpc/helloworld" ;package helloworld;message HelloRequest { string name = 1 ; } message HelloReply { string message = 1 ; } service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) {} }
生成代码 1 2 3 4 5 6 7 ❯ ls helloworld.proto ❯ protoc -I. --go_out=plugins=grpc:$GOPATH/src helloworld.proto ❯ ls helloworld.pb.go helloworld.proto
helloworld.pb.go 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 type HelloRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` } type HelloReply struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` } type GreeterServer interface { SayHello(context.Context, *HelloRequest) (*HelloReply, error ) } type GreeterClient interface { SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error ) }
实现 Server server.go 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" pb "github.com/zhongmingmao/grpc/helloworld" "google.golang.org/grpc" "log" "net" ) const ( port = ":50505" ) type server struct { pb.UnimplementedGreeterServer } func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error ) { log.Printf("Received: %v" , in.GetName()) return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil } func main () { listen, 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(listen); err != nil { log.Fatalf("failed to serve: %v" , err) } }
实现 Client
屏蔽 了底层的网络通信细节(调用方便) + 入参和出参都是 Go 结构体 (不需要打包和解包)
client.go 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 package mainimport ( "context" pb "github.com/zhongmingmao/grpc/helloworld" "google.golang.org/grpc" "log" "os" "time" ) const ( address = "localhost:50505" defaultName = "grpc" ) 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) }
1 2 3 4 5 ❯ go run client.go 2022/04/16 15:32:15 Greeting: Hello grpc ❯ go run server.go 2022/04/16 15:32:15 Received: grpc
optional + nil user.proto 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/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
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"` }
user.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package userimport "context" type User struct {} func (receiver *User) GetUser(ctx context.Context, in *GetUserRequest) (*GetUserResponse, error ) { if in.Username != nil { return nil , nil } return nil , nil }
RESTful vs gRPC
RESTful API 和 gRPC API 是合作关系,对内业务 使用 gRPC API,对外业务 使用 RESTful API