介绍如何使用 gRPC 和 ProtoBuf,快速了解 gRPC 可以参考这篇文章第一段:gRPC quick Start。

接口开发是软件开发占据举足轻重的地位,是现代软件开发之基石。体现在无论是前后端分离的 Web 前端还是移动客户端,乃至基于不同系统、编程语言构建的软件系统之间,API 都是充当桥梁的作用把不同端的系统链接在一起从而形成了一套稳固的商用系统。

基于 Web 的接口通常都是 RESTful API 结合 JSON 在前后端之间传递信息,这种模式比较适合于前后端分离及移动客户端于后端通信;但对于承载大规模并发、高性能要求的微服务架构,基于 JSON 传输的 RESTful 是否还适用于高并发、伸缩性强及业务逻辑复杂的软件架构吗?基于 RESTful 架构是否能够简单是想双向流 (bidrectional stream) 的接口。gRPC 和 protocol buffer 就是解决上述问题。

关于 gRPC 和 Protobuf 的简介可以看看这篇文章:Google Protocol Buffer 和 gRPC 简介

gRPC & Protocol Buffer 实践

我本地的 GOPATH 目录为 /Users/hww/work/go ,给我们的 demo 项目新建一个目录 cd $GOPATH/src && mkdir rpc-protobuf

定义 Protocol Buffer 的消息类型和服务

在项目根目录 rpc-protobuf 新建文件目录 customer。首先给 Protocol Bufffer 文件定义服务接口和 paylaod 信息的数据结构,$GOPATH/scr/rpc-protobuf/customer/customer.proto:

syntax = "proto3";
package customer;// The Customer sercie definition
service Customer {// Get all Customers with filter - A server-to-client streaming RPC.rpc GetCustomers(CustomerFilter) returns (stream CustomerRequest) {}// Create a new Customer - A simple RPCrpc CreateCustomer (CustomerRequest) returns (CustomerResponse) {}
}message CustomerRequest {int32 id = 1;   // Unique ID number for a Customer.string name = 2;string email = 3;string phone = 4;message Address {string street = 1;string city = 2;string state = 3;string zip = 4;bool isShippingAddress = 5;}repeated Address addresses = 5;
}message CustomerResponse {int32 id = 1;bool success = 2;
}message CustomerFilter {string keyword = 1;
}

.proto 文件,第一行代码为版本号,在这里我们使用了 proto3 ;第二行代码为包名,通过该文件生成的 Go 源码包名和这里的一致为 customer

我们定义了消息类型和服务接口。标准数据类型有 int32, float, double, 或 string 这些常见的类型。一种消息类型就是多个字段的集合,每个字段都被一个在该消息中唯一的整数标记;Customer 服务中有两个 RPC 方法:

service Customer {// Get all Customers with filter - A server-to-client streaming RPC.rpc GetCustomers(CustomerFilter) returns (stream CustomerRequest) {}// Create a new Customer - A simple RPCrpc CreateCustomer (CustomerRequest) returns (CustomerResponse) {}
}

解释 Customer 服务之前,我们首先来大概了解一下 gRPC 中的三种类型的 RPC 方法。

  • simple RPC
    应用于常见的典型的 Request/Response 模型。客户端通过 stub 请求 RPC 的服务端并等待服务端的响应。
  • Server-side streaming RPC
    客户端给服务端发送一个请求并获取服务端返回的流,用以读取一连串的服务端响应。stream 关键字在响应类型的前面。// 例子
    rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){}
  • Client-side streaming RPC
    客户端发送的请求 payload 有一连串的的信息,通过流给服务端发送请求。stream 关键字在请求类型前面。// 例子
    rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {}
  • Bidirectional streaming RPC
    服务端和客户端之间都使用 read-write stream 进行通信。stream 关键字在请求类型和响应类型前面。// 例子
    rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){}

理解 gRPC 提供的四种类型 RPC 方法之后,回到 Customer 的例子中。在 Customer 服务提供了两种类型的 RPC 方法,分别是 simple RPC(CreateCustomer) 和 server-side streaming(GetCustomers) 。CreateCustomer 遵循标准 Request/Response 规范新建一个用户;GetCustomers 方法中,服务端通过 stream 返回多个消费者信息的列表。

基于 proto 文件生成服务端和客户端的 Go 代码

定义好 proto 文件之后,然后生成你需要的编程语言源代码,这些源代码是服务端和客户端业务逻辑代码的接口。客户端代码通过消息类型和服务接口调用 RPC 方法。
protocol buffer 编译器通过 gRPC 的 Go 插件生成客户端和服务端的代码。在项目根目录下运行命令:

protoc -I customer/ customer/customer.proto --go_out=plugins=grpc:customer

在 customer 目录下生成了 customer.pb.go 文件。该源码包含三大类功能:

  • 读写和序列化请求和响应消息类型
  • 提供定义在 proto 文件中定义的客户端调用方法接口
  • 提供定义在 proto 文件中定义的服务端实现方法接口

新建 gRPC 服务

以下代码片段新建依据 proto 文件中定义的服务新建 gRPC 服务端。

// server/main.go
package mainimport ("log""net""strings""golang.org/x/net/context""google.golang.org/grpc"pb "rpc-protobuf/customer"
)const (port = ":50051"
)// server is used to implement customer.CustomerServer.
type server struct {savedCustomers []*pb.CustomerRequest
}// CreateCustomer creates a new Customer
func (s *server) CreateCustomer(ctx context.Context, in *pb.CustomerRequest) (*pb.CustomerResponse, error) {s.savedCustomers = append(s.savedCustomers, in)return &pb.CustomerResponse{Id: in.Id, Success: true}, nil
}// GetCustomers returns all customers by given filter
func (s *server) GetCustomers(filter *pb.CustomerFilter, stream pb.Customer_GetCustomersServer) error {for _, customer := range s.savedCustomers {if filter.Keyword != "" {if !strings.Contains(customer.Name, filter.Keyword) {continue}}if err := stream.Send(customer); err != nil {return err}}return nil
}func main() {lis, err := net.Listen("tcp", port)if err != nil {log.Fatal("failed to listen: %v", err)}//Create a new grpc servers := grpc.NewServer()pb.RegisterCustomerServer(s, &server{})s.Serve(lis)
}

服务端源码中,server 结构体定义在 customer.pb.go 中的 CustomerServer 接口;CreateCustomerGetCustomers 两个方法定义在 customer.pb.go 文件的 CustomerClient 接口中。

CreateCustomer 是一个 simple rpc 类型的 RPC 方法,在这里它接受两个参数,分别是 context objct 和客户端的请求信息,返回值为 proto 文件定义好的 CustomerResponse 对象;GetCustomers 是一个 server-side streaming 类型的 RPC 方法,接受两个参数:CustomerRequest 对象、以及用来作为服务端对客户端响应 stream 的 对象 Customer_GetCustomersServer 。
看看 customer.pb.go 中对 CustomerServer 接口的定义:

// Server API for Customer servicetype CustomerServer interface {// Get all Customers with filter - A server-to-client streaming RPC.GetCustomers(*CustomerFilter, Customer_GetCustomersServer) error// Create a new Customer - A simple RPCCreateCustomer(context.Context, *CustomerRequest) (*CustomerResponse, error)
}

对比理解服务端代码对两个方法的实现,我们就可以理解参数的传递原理。

服务端代码中 GetCustomers 方法内部有一行代码 stream.Send(customer) 这个 Send 方法是 customer.pb.go 给 Customer_GetCustomersServer 接口定义并好的方法,表示给客户端返回 stream

最后看看服务端代码中的 main 方法。
首先 grpc.NewServer 函数新建一个 gRPC 服务端;
然后调用 customer.pb.go 中的 RegisterCustomerServer(s *grpc.Server, srv CustomerServer) 函数注册该服务:pb.RegisterCustomerServer(s, &server{})
最后通过 gRPC 的 Golang API Server.Serve 监听指定的端口号:s.Serve(lis),新建一个 ServerTransportservice goroutine处理监听的端口收到的请求。

新建 gRPC 客户端

首先看 customer.pb.go 生成的客户端调用方法接口部分的代码:

// Client API for Customer servicetype CustomerClient interface {// Get all Customers with filter - A server-to-client streaming RPC.GetCustomers(ctx context.Context, in *CustomerFilter, opts ...grpc.CallOption) (Customer_GetCustomersClient, error)// Create a new Customer - A simple RPCCreateCustomer(ctx context.Context, in *CustomerRequest, opts ...grpc.CallOption) (*CustomerResponse, error)
}type customerClient struct {cc *grpc.ClientConn
}func NewCustomerClient(cc *grpc.ClientConn) CustomerClient {return &customerClient{cc}
}

*grpc.ClientConn 表示连接到 RPC 服务端的客户端,NewCustomerClient 函数返回一个 customerClient 结构体对象。CustomerClient 接口定义了两个能够被客户端服务调用的方法,另外我们可以在 customer.pb.go 看到给 customerClient 类型的结构体实现这两个函数的方法,故客户端对象能够调用 GetCustomersCreateCustomer 方法:

func (c *customerClient) GetCustomers(ctx context.Context, in *CustomerFilter, opts ...grpc.CallOption) (Customer_GetCustomersClient, error) {...
}...func (c *customerClient) CreateCustomer(ctx context.Context, in *CustomerRequest, opts ...grpc.CallOption) (*CustomerResponse, error) {...
}

接着回到实现客户端的源码:

// client/main.go
package mainimport ("io""log""golang.org/x/net/context""google.golang.org/grpc"pb "rpc-protobuf/customer"
)const (address = "localhost:50051"
)// createCustomer calls the RPC method CreateCustomer of CustomerServer
func createCustomer(client pb.CustomerClient, customer *pb.CustomerRequest) {resp, err := client.CreateCustomer(context.Background(), customer)if err != nil {log.Fatalf("Could not create Customer: %v", err)}if resp.Success {log.Printf("A new Customer has been added with id: %d", resp.Id)}
}// GetCustomers calls the RPC method GetCustomers of CustomerServer
func getCustomers(client pb.CustomerClient, filter *pb.CustomerFilter) {// calling the streaming APIstream, err := client.GetCustomers(context.Background(), filter)if err != nil {log.Fatal("Error on get customers: %v", err)}for {customer, err := stream.Recv()if err == io.EOF {break}if err != nil {log.Fatal("%v.GetCustomers(_) = _, %v", client, err)}log.Printf("Customer: %v", customer)}
}func main() {// Set up a connection to the RPC serverconn, err := grpc.Dial(address, grpc.WithInsecure())if err != nil {log.Fatal("did not connect: %v", err)}defer conn.Close()// creates a new CustomerClientclient := pb.NewCustomerClient(conn)customer := &pb.CustomerRequest{Id:    101,Name:  "Shiju Varghese",Email: "shiju@xyz.com",Phone: "732-757-2923",Addresses: []*pb.CustomerRequest_Address{&pb.CustomerRequest_Address{Street:            "1 Mission Street",City:              "San Francisco",State:             "CA",Zip:               "94105",IsShippingAddress: false,},&pb.CustomerRequest_Address{Street:            "Greenfield",City:              "Kochi",State:             "KL",Zip:               "68356",IsShippingAddress: true,},},}// Create a new customercreateCustomer(client, customer)customer = &pb.CustomerRequest{Id:    102,Name:  "Irene Rose",Email: "irene@xyz.com",Phone: "732-757-2924",Addresses: []*pb.CustomerRequest_Address{&pb.CustomerRequest_Address{Street:            "1 Mission Street",City:              "San Francisco",State:             "CA",Zip:               "94105",IsShippingAddress: true,},},}// Create a new customercreateCustomer(client, customer)//Filter with an empty Keywordfilter := &pb.CustomerFilter{Keyword: ""}getCustomers(client, filter)}

客户端需要建立 gRPC 通道(channel) 才可与服务端建立通信,调用 RPC 方法。grpc.Dial函数表示新建与 RPC 服务端的连接。Dial函数在 gRPC golang 实现的库中声明代码如下:

func Dial(target string, opts ...DialOption) (*ClientConn, error)

除了连接地址作为第一个参数外,还可以传多个可选参数。这些可选参数表示鉴权校验,例如 TLS 或者 JWT 。在这里的 grpc.WithInsecure 表示客户端连接的安全传输被禁用。

调用服务端的 RPC 方法前,首先需要新建客户端 stub :

// creates a new CustomerClient
client := pb.NewCustomerClient(conn)

在例子中,通过调用 RPC CreateCustomer 方法新增了两个 customer 数据 : createCustomer(client, customer) ;调用 RPC GetCustomers 方法获取所有 customers 数据。

至此,我们已经简单地实现了一套 gRPC 客户端和服务端代码。在项目根目录下运行命令:

➜  rpc-protobuf (nohup go run server/main.go &) && go run client/main.go
appending output to nohup.out
2017/10/28 18:08:02 A new Customer has been added with id: 101
2017/10/28 18:08:02 A new Customer has been added with id: 102
2017/10/28 18:08:02 Customer: id:101 name:"Shiju Varghese" email:"shiju@xyz.com" phone:"732-757-2923" addresses:<street:"1 Mission Street" city:"San Francisco" state:"CA" zip:"94105" > addresses:<street:"Greenfield" city:"Kochi" state:"KL" zip:"68356" isShippingAddress:true >
2017/10/28 18:08:02 Customer: id:102 name:"Irene Rose" email:"irene@xyz.com" phone:"732-757-2924" addresses:<street:"1 Mission Street" city:"San Francisco" state:"CA" zip:"94105" isShippingAddress:true >

https://zhuanlan.zhihu.com/p/30624616

gRPC amp; Protocol Buffer 构建高性能接口实践相关推荐

  1. golang 安装protoc protoc-gen-go,以及grpc和protocol buffer的一些理解。

    proto理解: ProtoBuf 是一种数据表达方式,根据 G 家自己的描述,应该叫做数据交换格式,注意这里使用的是 交换 字眼,也就是说着重于在数据的传输上,有别于 TOML 和 XML 较常用于 ...

  2. ringbuffer java例子_使用Ring Buffer构建高性能的文件写入程序

    最近常收到SOD框架的朋友报告的SOD的SQL日志功能报错:文件句柄丢失.经过分析得知,这些朋友使用SOD框架开发了访问量比较大的系统,由于忘记关闭SQL日志功能所以出现了很高频率的日志写入操作,从而 ...

  3. gRPC in ASP.NET Core 3.x -- Protocol Buffer, Go语言的例子(上)

    前两篇文章半年前写的: gRPC in ASP.NET Core 3.0 -- Protocol Buffer(1), gRPC in ASP.NET Core 3.0 -- Protocol Buf ...

  4. gRPC in ASP.NET Core 3.x -- Protocol Buffer, Go语言的例子(下)

    前两篇文章半年前写的: gRPC in ASP.NET Core 3.0 -- Protocol Buffer(1), gRPC in ASP.NET Core 3.0 -- Protocol Buf ...

  5. gRPC in ASP.NET Core 3.x -- Protocol Buffer(3)更新消息类型

    当你第一次定义Protocol Buffer的消息的时候,你肯定会给消息设定一套规则需求.但是随着时间的推进,你的业务可能会发生了变化,与此同时,你的Protocol Buffer消息类型的需求也会随 ...

  6. gRPC in ASP.NET Core 3.0 -- Protocol Buffer(1)

    开发环境: IDE: VSCode VSCode的扩展插件:vscode-proto3和Clang-Format这两个扩展 Windows还需要安装Clang,Windows 64位系统的地址如下:C ...

  7. [翻译]Protocol Buffer 基础: C++

    目录 Protocol Buffer Basics: C++ 为什么使用 Protocol Buffers 在哪可以找到示例代码 定义你的协议格式 编译你的 Protocol Buffers Prot ...

  8. 快来看看Google出品的Protocol Buffer,别仅仅会用Json和XML了

    前言 习惯用 Json.XML 数据存储格式的你们,相信大多都没听过Protocol Buffer Protocol Buffer 事实上 是 Google出品的一种轻量 & 高效的结构化数据 ...

  9. 在生产环境中,阿里云如何构建高性能云原生容器网络?(含 PPT 下载)

    作者 | 溪恒  阿里云技术专家 直播完整视频回顾:https://www.bilibili.com/video/BV1nC4y1x7mt/ 关注"阿里巴巴云原生"公众号,后台回复 ...

最新文章

  1. 简单两步使用node发送qq邮件
  2. HDLBits 系列(19) 12小时时钟的Verilog设计
  3. Android深度探索第四章
  4. 判断数组有哪些方法,100%准确的方法
  5. Android --- 解决 registerLocationListener 过时问题(百度地图)API
  6. html js更改title,如何使用js改变HTML中title里面固定的文字
  7. C# default關鍵字
  8. 【转】关闭特定虚拟机上声音嘟嘟声
  9. Android 顶部滑动切换实现(一)
  10. java二叉树 最大值_leetcode刷题笔记-654. 最大二叉树(java实现)
  11. 最近对项目代码做的一些更改和感想
  12. Android项目架构设计深入浅出
  13. 这4种分析方法,大牛产品经理都在用
  14. 数据结构C++版-图
  15. 高通WLAN驱动分析
  16. mPush实战笔记4安装mpush
  17. OpenEuler安装 20212802范辰宇
  18. 亚马逊测评有哪些误解?
  19. ntoskrnl.exe(01)
  20. 百兆网线和千兆网线做法的区别

热门文章

  1. CoreData / MagicalRecord
  2. linux下配置某程序的sudo不用输密码
  3. [深入浅出WP8.1(Runtime)]Socket编程之UDP协议
  4. 【MOSS】Sharepoint大附件上传
  5. PCL【Win10+VS2015+PCL_1.8.0环境配置】
  6. 数字图像处理与机器视觉——Visual C++与Matlab实现书中代码勘误
  7. 记第二期“研途同行“研究生论坛《出国交流经验分享》
  8. [MATLAB调试笔记]时变循环诊断——[Vx(x)],[Vx(t),Vy(t),Vz(t)],[Ex(x)],[波];[Eng(t)],[Ex(k)],[Ex(x,t)],[logEx(x.t)]
  9. Coursera吴恩达《序列模型》课程笔记(3)-- Sequence models Attention mechanism
  10. 华为手机asph啥机型_华为正式宣布!19款机型开启新系统内测,你的手机榜首有名吗?...