Go中的gRPC简介 - Go语言中文社区

Go中的gRPC简介


给使用 Go 语言的初学者的 gRPC 概述

RPC

RPC 是用于 软件应用之间点对点通信网络编程模型 或是 进程间通信技术

RPC 是一种 协议,一个程序能够使用该协议,对位于另外一台计算机中的程序请求服务,而无需了解网络的详细信息。

RPC 代表 “远程过程调用”,它是一种 客户端 - 服务器交互 的形式 - 调用者是客户端,执行者是服务器 - 通常通过 " 请求 - 响应消息传递系统 " 实现。

客户端运行时程序,知道如何去寻址远程服务器应用程序,以及通过网络发送请求远程过程的消息。类似的,服务器包括与远程过程本身的运行时程序和存根。

它是怎么工作的?

RPC 工作的方式是,发送方或者客户端以过程、函数或者方法调用的形式创建对 RPC 进行转换和发送的远程服务器的请求。当远程服务器接收到请求时,它会将响应发送回客户端,然后应用程序继续其进程。

RPC 工作的方式是,发送方或者客户端以过程、函数或者方法调用的形式创建对 RPC 进行转换和发送的远程服务器的请求。当远程服务器接收到请求时,它会将响应发送回客户端,然后应用程序继续其进程。

在这里插入图片描述

用例

我们将实现一个 Gravatar 服务,用以 生成 URLs,其包含相关邮件地址的 MD5 哈希。它们可用于从 Gravatar Web 服务器加载全局唯一的头像。

我们的客户端能够通过 RPC 协议与服务器通信,发送电子邮件和所需要的图像。作为响应,他们将获得一个在 https://gravatar.com 上配置的他们自己头像的个性化链接。

在这里插入图片描述

Protocol Buffers

Protobuf(或 Protocol Buffers)是 Google 发明的 与语言无关且与平台无关的序列化形式,每个 Protocol Buffers 消息都是一个 小的逻辑信息记录包含一系列的 name-value 对

与 XML 或者 JSON 不同,在这里你首先在一个 .proto 文件中定义模式。它们是一种类似 JSON 但更简单,更小,严格类型的格式,只有从客户端到服务器才能理解,而且 Marshall/Unmarshall 更快。例如:

 1	syntax = "proto3";
 2
 3	package gravatar;
 4
 5	service GravatarService {
 6    rpc Generate(GravatarRequest) returns (GravatarResponse) {}
 7	}
 8
 9	message GravatarRequest {
10    string email = 1;
11    int32 size = 2;
12	}
13
14	message GravatarResponse {
15    string url = 1;
16	}

一个消息类型是一个数值字段的列表,每一个字段有一个类型和一个名称。在定义了 .proto 文件之后,运行 protocol buffer 编译器去给对象(使用你选择的语言)生成代码,使用字段的 get/set 函数以及对象的序列化 / 反序列化函数。如你所见,你也可以在命名空间内打包信息。

安装

我们使用 protoc 编译器编译一个 protocol buffer,目标文件就是为一门编程语言生成的。对于 Go,编译器会为你的文件中的每一个消息类型生成一个 .pb.go 文件。

要安装编译器,运行:

1	brew install protobuf

然后,在你的 GOPATH 路径下创建并初始化一个新项目:

1	mkdir profobuf-example
2	cd profobuf-example
3	go mod INIt

最后,编译所有的 .proto 文件:

1	protoc --go_out=. *.proto

我编译后的文件如下所示:

  1	// Code generated by protoc-gen-go. DO NOT EDIT.
  2	// source: gravatar.proto
  3
  4	package gravatar
  5
  6	import proto "github.com/golang/protobuf/proto"
  7	import fmt "fmt"
  8	import math "math"
  9
 10	// Reference imports to suppress errors if they are not otherwise used.
 11	var _ = proto.Marshal
 12	var _ = fmt.Errorf
 13	var _ = math.Inf
 14
 15	// This is a compile-time assertion to ensure that this generated file
 16	// is compatible with the proto package it is being compiled against.
 17	// A compilation error at this line likely means your copy of the
 18	// proto package needs to be updated.
 19	const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
 20
 21	type GravatarRequest struct {
 22    Email                string   `protobuf:"bytes,1,opt,name=email,proto3" JSON:"email,omitempty"`
 23    Size                 int32    `protobuf:"varint,2,opt,name=size,proto3" JSON:"size,omitempty"`
 24    XXX_NoUnkeyedLiteral struct{} `json:"-"`
 25    XXX_unrecognized     []byte   `json:"-"`
 26    XXX_sizecache        int32    `json:"-"`
 27	}
 28
 29	func (m *GravatarRequest) Reset()         { *m = GravatarRequest{} }
 30	func (m *GravatarRequest) String() string { return proto.CompactTextString(m) }
 31	func (*GravatarRequest) ProtoMessage()    {}
 32	func (*GravatarRequest) Descriptor() ([]byte, []int) {
 33    return fileDescriptor_gravatar_d539f97f43eb2d2e, []int{0}
 34	}
 35	func (m *GravatarRequest) XXX_Unmarshal(b []byte) error {
 36    return xxx_messageInfo_GravatarRequest.Unmarshal(m, b)
 37	}
 38	func (m *GravatarRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
 39    return xxx_messageInfo_GravatarRequest.Marshal(b, m, deterministic)
 40	}
 41	func (dst *GravatarRequest) XXX_Merge(src proto.Message) {
 42    xxx_messageInfo_GravatarRequest.Merge(dst, src)
 43	}
 44	func (m *GravatarRequest) XXX_Size() int {
 45    return xxx_messageInfo_GravatarRequest.Size(m)
 46	}
 47	func (m *GravatarRequest) XXX_DiscardUnknown() {
 48    xxx_messageInfo_GravatarRequest.DiscardUnknown(m)
 49	}
 50
 51	var xxx_messageInfo_GravatarRequest proto.InternalMessageInfo
 52
 53	func (m *GravatarRequest) GetEmail() string {
 54    if m != nil {
 55        return m.Email
 56    }
 57    return ""
 58	}
 59
 60	func (m *GravatarRequest) GetSize() int32 {
 61    if m != nil {
 62        return m.Size
 63    }
 64    return 0
 65	}
 66
 67	type GravatarResponse struct {
 68    Url                  string   `protobuf:"bytes,1,opt,name=url,proto3" JSON:"url,omitempty"`
 69    XXX_NoUnkeyedLiteral struct{} `json:"-"`
 70    XXX_unrecognized     []byte   `json:"-"`
 71    XXX_sizecache        int32    `json:"-"`
 72	}
 73
 74	func (m *GravatarResponse) Reset()         { *m = GravatarResponse{} }
 75	func (m *GravatarResponse) String() string { return proto.CompactTextString(m) }
 76	func (*GravatarResponse) ProtoMessage()    {}
 77	func (*GravatarResponse) Descriptor() ([]byte, []int) {
 78    return fileDescriptor_gravatar_d539f97f43eb2d2e, []int{1}
 79	}
 80	func (m *GravatarResponse) XXX_Unmarshal(b []byte) error {
 81    return xxx_messageInfo_GravatarResponse.Unmarshal(m, b)
 82	}
 83	func (m *GravatarResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
 84    return xxx_messageInfo_GravatarResponse.Marshal(b, m, deterministic)
 85	}
 86	func (dst *GravatarResponse) XXX_Merge(src proto.Message) {
 87    xxx_messageInfo_GravatarResponse.Merge(dst, src)
 88	}
 89	func (m *GravatarResponse) XXX_Size() int {
 90    return xxx_messageInfo_GravatarResponse.Size(m)
 91	}
 92	func (m *GravatarResponse) XXX_DiscardUnknown() {
 93    xxx_messageInfo_GravatarResponse.DiscardUnknown(m)
 94	}
 95
 96	var xxx_messageInfo_GravatarResponse proto.InternalMessageInfo
 97
 98	func (m *GravatarResponse) GetUrl() string {
 99    if m != nil {
100        return m.Url
101    }
102    return ""
103	}
104
105	func INIt() {
106    proto.RegisterType((*GravatarRequest)(nil), "gravatar.GravatarRequest")
107    proto.RegisterType((*GravatarResponse)(nil), "gravatar.GravatarResponse")
108	}
109
110	func INIt() { proto.RegisterFile("gravatar.proto", fileDescriptor_gravatar_d539f97f43eb2d2e) }
111
112	var fileDescriptor_gravatar_d539f97f43eb2d2e = []byte{
113    // 158 bytes of a gzipped FileDescriptorProto
114    0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4b, 0x2f, 0x4a, 0x2c,
115    0x4b, 0x2c, 0x49, 0x2c, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x80, 0xf1, 0x95, 0xac,
116    0xb9, 0xf8, 0xdd, 0xa1, 0xec, 0xa0, 0xd4, 0xc2, 0xd2, 0xd4, 0xe2, 0x12, 0x21, 0x11, 0x2e, 0xd6,
117    0xd4, 0xdc, 0xc4, 0xcc, 0x1c, 0x09, 0x46, 0x05, 0x46, 0x0d, 0xce, 0x20, 0x08, 0x47, 0x48, 0x88,
118    0x8b, 0xa5, 0x38, 0xb3, 0x2a, 0x55, 0x82, 0x49, 0x81, 0x51, 0x83, 0x35, 0x08, 0xcc, 0x56, 0x52,
119    0xe1, 0x12, 0x40, 0x68, 0x2e, 0x2e, 0xc8, 0xcf, 0x2b, 0x4e, 0x15, 0x12, 0xe0, 0x62, 0x2e, 0x2d,
120    0x82, 0xe9, 0x05, 0x31, 0x8d, 0xc2, 0x10, 0x56, 0x04, 0xa7, 0x16, 0x95, 0x65, 0x26, 0xa7, 0x0a,
121    0x39, 0x73, 0x71, 0xb8, 0xa7, 0xe6, 0xa5, 0x16, 0x25, 0x96, 0xa4, 0x0a, 0x49, 0xea, 0xc1, 0x1d,
122    0x87, 0xe6, 0x12, 0x29, 0x29, 0x6c, 0x52, 0x10, 0x7b, 0x94, 0x18, 0x92, 0xd8, 0xc0, 0x7e, 0x31,
123    0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xfd, 0xd3, 0xc0, 0x5b, 0xdd, 0x00, 0x00, 0x00,
124}

gRPC

gRPC 是一个高性能的 RPC 框架,它使用 protocol buffers(作为其接口定义语言和基础消息交换格式)和 HTTP/2 构建。

一旦你指定了你的数据结构,你就可以在普通 .proto 文件中定义 gRPC 服务,并将 RPC 方法参数和返回类型指定为 protocol buffers 消息。在我们的例子中,它就是:

1	service GravatarService {
2    rpc Generate(GravatarRequest) returns (GravatarResponse) {}
3	}

当你使用 protoc(一个 gRPC 插件)从你的 proto 文件生成代码时,你不仅可以获得用于填充、序列化和检索消息类型的常规 protocol buffers 代码,还可以生成 gRPC 客户端和服务器代码。要做到这一点,只需运行:

1	protoc --go_out=plugins=grpc:. *.proto

差异如下:

 1+ import (
 2+     context "golang.org/x/net/context"
 3+     grpc "google.golang.org/grpc"
 4+ )
 5+
 6+ // Reference imports to suppress errors if they are not otherwise used.
 7+ var _ context.Context
 8+ var _ grpc.ClientConn
 9+
10+ // This is a compile-time assertion to ensure that this generated file
11+ // is compatible with the grpc package it is being compiled against.
12+ const _ = grpc.SupportPackageIsVersion4
13+
14+ // GravatarServiceClient is the client API for GravatarService service.
15+ //
16+ // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
17+ type GravatarServiceClient interface {
18+     Generate(ctx context.Context, in *GravatarRequest, opts ...grpc.CallOption) (*GravatarResponse, error)
19+ }
20+
21+ type gravatarServiceClient struct {
22+     cc *grpc.ClientConn
23+ }
24+
25+ func NewGravatarServiceClient(cc *grpc.ClientConn) GravatarServiceClient {
26+     return &gravatarServiceClient{cc}
27+ }
28+
29+ func (c *gravatarServiceClient) Generate(ctx context.Context, in *GravatarRequest, opts ...grpc.CallOption) (*GravatarResponse, error) {
30+     out := new(GravatarResponse)
31+     err := c.cc.Invoke(ctx, "/gravatar.GravatarService/Generate", in, out, opts...)
32+     if err != nil {
33+         return nil, err
34+     }
35+     return out, nil
36+ }
37+
38+ // GravatarServiceServer is the server API for GravatarService service.
39+ type GravatarServiceServer interface {
40+     Generate(context.Context, *GravatarRequest) (*GravatarResponse, error)
41+ }
42+
43+ func RegisterGravatarServiceServer(s *grpc.Server, srv GravatarServiceServer) {
44+     s.RegisterService(&_GravatarService_serviceDesc, srv)
45+ }
46+
47+ func _GravatarService_Generate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
48+     in := new(GravatarRequest)
49+     if err := dec(in); err != nil {
50+         return nil, err
51+     }
52+     if interceptor == nil {
53+         return srv.(GravatarServiceServer).Generate(ctx, in)
54+     }
55+     info := &grpc.UnaryServerInfo{
56+         Server:     srv,
57+         FullMethod: "/gravatar.GravatarService/Generate",
58+     }
59+     handler := func(ctx context.Context, req interface{}) (interface{}, error) {
60+         return srv.(GravatarServiceServer).Generate(ctx, req.(*GravatarRequest))
61+     }
62+     return interceptor(ctx, in, info, handler)
63+ }
64+
65+ var _GravatarService_serviceDesc = grpc.ServiceDesc{
66+     ServiceName: "gravatar.GravatarService",
67+     HandlerType: (*GravatarServiceServer)(nil),
68+     Methods: []grpc.MethodDesc{
69+         {
70+             MethodName: "Generate",
71+             Handler:    _GravatarService_Generate_Handler,
72+         },
73+     },
74+     Streams:  []grpc.StreamDesc{},
75+     Metadata: "gravatar.proto",
76+ }

实现

现在我们已经生成了服务器和客户端代码,因此我们需要在应用中去实现并且调用这些方法。

让我们开始为我们的“核心业务”实现基础的逻辑:

 1	func gravatarHash(email string) [16]byte {
 2    return md5.Sum([]byte(email))
 3	}
 4
 5	func gravatarURL(hash [16]byte, size uint32) string {
 6    return fmt.Sprintf("https://www.gravatar.com/avatar/%x?s=%d", hash, size)
 7	}
 8
 9	func gravatar(email string, size uint32) string {
10    hash := gravatarHash(email)
11
12    return gravatarURL(hash, size)
13	}
 1	import (
 2    "fmt"
 3    "github.com/stretchr/testify/assert"
 4    "testing"
 5)
 6
 7	func TestGravatar(t *testing.T) {
 8    var size uint32 = 10
 9    endpoint := "https://www.gravatar.com/avatar/cf38500a2cd3b6a2c8c1d4d8259e83f8?s=%v"
10    email := "kamil@lelonek.me"
11    url := gravatar(email, size)
12    expected := fmt.Sprintf(endpoint, size)
13
14    assert.Equal(t, url, expected, "URLs are not the same.")
15	}

它并没有什么特别之处,只是 GO 中常规的 MD5 生成。

但是,服务器端的实现更加有趣:

 1	const port = ":50051"
 2
 3	type gravatarService struct{}
 4
 5	func (s *gravatarService) Generate(ctx context.Context, in *pb.GravatarRequest) (*pb.GravatarResponse, error) {
 6    log.Printf("Received email %v with size %v", in.Email, in.Size)
 7    return &pb.GravatarResponse{Url: gravatar(in.Email, in.Size)}, nil
 8	}
 9
10	func main() {
11    lis, err := net.Listen("tcp", port)
12    if err != nil {
13        log.Fatal(errors.Wrap(err, "Failed to listen on port!"))
14    }
15
16    server := grpc.NewServer()
17    pb.RegisterGravatarServiceServer(server, &gravatarService{})
18    if err := server.Serve(lis); err != nil {
19        log.Fatal(errors.Wrap(err, "Failed to start server!"))
20    }
21	}

我们定义了运行服务器的端口,同时 gravatarService 的结构覆盖了通过 .proto 文件定义的 GravatarService。如你所见,我们还可以在其上实现需要的 Generate 方法,它接收 GravatarRequest 并且产生一个相应的 GravatarResponse。

我们在给定端口打开一个 tcp 连接,创建一个新的 gRPC 服务器,它注册我们的处理程序并在打开的监听器上启动它。我们现在准备去处理请求。

客户端的实现也不难,我会说更容易一些:

 1	const address = "localhost:50051"
 2
 3	func main() {
 4    conn, err := grpc.Dial(address, grpc.WithInsecure())
 5    if err != nil {
 6        log.Fatalf("did not connect: %v", err)
 7    }
 8    defer conn.Close()
 9
10    c := pb.NewGravatarServiceClient(conn)
11    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
12    defer cancel()
13
14    r, err := c.Generate(ctx, &pb.GravatarRequest{Email: "name", Size: 10})
15    if err != nil {
16        log.Fatalf("could not greet: %v", err)
17    }
18
19    log.Printf("Greeting: %s", r.Url)
20	}

我们在给定的地址上开启一个特定的连接(在我们的例子中,它就是 localhost 与先前定义的端口),我们在给定的连接上注册一个新的客户端。记住,当退出我们的程序时,必须要同时关闭连接并停掉 context。

最后,在我们的客户端使用 GravatarRequest 调用 Generate 方法,同时我们的数据也在里面。如果成功,我们可以使用哈希打印接收到的 URL。

总结

Protocol Buffers 在编码和解码速度,线路上数据大小等方面提供了非常实际的优势。现在你可能想知道,gRPC 相对于常规的 JSON REST API 有什么优势呢。让我

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/ITqingliang/article/details/100558650
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢