API 是现代应用程序的基础。 API为Web客户端和移动客户端应用程序的后端提供了动力,并且无论技术和平台如何,API都可用于应用程序之间的通信。考虑构建基于Web的API时,通常会选择RESTful API和JSON作为在应用程序之间交换数据的标准。这种方法很好,移动客户端应用程序可以轻松使用这些基于JSON的RESTful API。现在,我们正在为云原生应用程序时代构建应用程序,在该时代,我们的微服务应该能够大规模扩展,性能至关重要。我们绝对需要一种高性能的通信机制来在各种微服务之间进行通信。然后最大的问题是基于JSON的API是否提供现代应用程序所需的高性能和可伸缩性。 JSON是否真的是用于在应用程序之间交换数据的快速数据格式? RESTful架构能够构建复杂的API吗?我们是否可以轻松地使用RESTful架构构建双向流API? HTTP / 2协议比以前的版本提供了很多功能,因此在构建下一代API时,我们需要利用这些功能。然后输入gRPC和协议缓冲区。
协议缓冲区简介
协议缓冲区,也称为protobuf,是Google的语言无关,平台无关的可扩展机制,用于序列化结构化数据。 与XML和JSON等其他标准相比,协议缓冲区更小,更快,更简单,可提供更高的性能。 通过使用协议缓冲区,您可以定义结构化数据,然后使用名为protoc的协议缓冲区编译器生成用于选择编程语言的源代码,以使用该协议来编写和读取结构化数据。 协议缓冲区的当前版本是proto3。 proto3版本当前支持多种语言的生成代码,包括C ++, Go , Java ,Python, Ruby 和C#。
为了从协议缓冲区定义文件生成代码,请执行以下操作:
· 从此处下载并安装协议编译器:https://github. com /google/protobuf。 将协议二进制文件的位置添加到PATH环境变量中,以便您可以从任何位置调用协议编译器。
· 根据您的语言安装protoc插件。 对于Go,请运行go get命令以安装Go的protoc插件:
go get -u github.com/golang/protobuf/proto
go get -u github.com/golang/protobuf/protoc-gen-go
g RPC 简介
gRPC是可以在任何地方运行的高性能,开源远程过程调用(RPC)框架。 它使客户端和服务器应用程序可以透明地进行通信,并使构建连接的系统更加容易。 gRPC框架由 go ogle开发和开源。 长期以来,Google一直在为其许多产品(包括Google的多种云产品)使用gRPC中的许多基础技术和概念。 可从以下位置获得gRPC的动机和设计原理:
gRPC在HTTP / 2上遵循HTTP语义。 它允许您使用同步和异步通信模型来构建服务。 它支持传统的请求/响应模型和双向流。 它具有构建全双工流的功能,使您可以将其用于客户端和服务器应用程序都可以异步发送数据流的高级方案。 gRPC在构建时就考虑到了移动客户端,这将为您提供许多性能优势,并简化了使用API的过程。 与RESTful方法相比,gRPC具有许多优势,包括性能提升。
默认情况下,gRPC使用协议缓冲区作为接口定义语言( Id L)和其基础消息交换格式。 与JSON和XML不同,协议缓冲区不仅是消息交换格式,还用于描述服务接口(服务端点)。 因此,协议缓冲区既用于服务接口,又用于有效负载消息的结构。 在gRPC中,您定义服务及其方法以及有效载荷消息。 就像客户端应用程序和RPC系统之间的典型通信一样,gRPC客户端应用程序可以直接调用远程服务器上的方法,就好像它是客户端应用程序中的本地对象一样。
这是一张图像(取自gRPC网站:
gRPC服务器实现服务接口并运行RPC服务器以处理对其服务方法的客户端调用。 在客户端,客户端具有一个存根(在某些语言中仅称为客户端),提供与服务器相同的方法。
Go中带有gRPC和协议缓冲区的示例API
让我们使用gRPC和Protocol Buffer在Go中构建一个API示例。 这是用于该应用程序的目录结构:
为了在Go中使用gRPC,必须安装gRPC的Go实现:
go get google.golang.org/grpc
在协议缓冲区中定义消息类型和服务定义
让我们在协议缓冲区文件中定义服务接口和有效负载消息的结构。 文件扩展名.proto用于创建协议缓冲区文件。 这是客户目录中的customer.proto文件的来源:
syntax = "proto3";
package customer;
// The Customer service 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 RPC
rpc CreateCustomer (CustomerRequest) returns (CustomerResponse) {}
}
// Request message for creating a new customer
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 Customer filter {
string keyword = 1;
}
.proto文件以协议缓冲区的版本和程序包声明开头。 我们使用协议缓冲区语言的最新proto3版本。 该程序包以名称”customer”声明。 从原型文件生成Go源代码时,它将添加Go软件包名称作为”客户”。
在.proto文件中,您可以定义消息类型和服务接口。 标准数据类型(例如int32,float,double和string)可用作字段类型,用于声明消息类型中的元素。 用户定义的消息类型也可以用作字段类型。 消息类型的每个元素上的” = 1″,” = 2″标记指定该字段在二进制编码中使用的唯一”标记”。 如果未指定元素值,则使用默认值:数字类型为零,字符串为空字符串,布尔值为false。 协议缓冲区语言指南可从以下网站获得:https://developers.google.com/protocol-buffers/docs/proto3。
命名服务”客户”用于定义服务,该服务具有两种RPC方法。
// The Customer service definition.
service Customer {
// Get all Customers with a filter — A server-to-client streaming RPC.
rpc GetCustomers(CustomerFilter) returns (stream CustomerRequest) {}
// Create a new Customer — A simple RPC
rpc CreateCustomer (CustomerRequest) returns (CustomerResponse) {}
}
以下是您可以使用gRPC定义的另一种RPC方法:
· 一种简单的RPC方法,可与典型的”请求/响应”模型一起使用,在该模型中,客户端使用存根将请求发送到RPC服务器,然后等待响应。
· 服务器端流式RPC,客户端在其中向服务器发送请求,并获取流以读取回一系列消息。 在响应类型之前指定stream关键字,以使其成为服务器端流式RPC方法。
· 客户端流式RPC,其中客户端编写一系列消息,然后使用提供的流将它们发送到服务器。 在请求类型之前指定stream关键字,以使其成为客户端流式RPC方法。
· 双向流式RPC,其中客户端和服务器均使用读写流发送一系列消息。 在请求类型和响应类型之前都指定了stream关键字,以使其成为双向流式RPC方法。
客户服务提供两种RPC方法:一个名为CreateCustomer的简单RPC方法和一个名为GetCustomers的服务器端流式RPC方法。 CreateCustomer创建一个新客户,并作为请求/响应范例执行。 GetCustomers提供客户列表,其中服务器以流形式提供客户数据。
为客户端和服务器生成Go代码
定义了原型文件后,下一步就是为gRPC客户端和服务器接口生成源代码,以编写服务器实现,并根据原型文件中定义的消息类型和服务接口进行客户端调用。 协议缓冲区编译器协议与gRPC Go插件一起使用可生成客户端和服务器代码。 在应用程序的根目录中,使用gRPC Go插件运行协议编译器:
protoc -I customer/ customer/customer.proto --go_out=plugins=grpc:customer
这将在客户目录中生成一个名为customer.pb.go的Go源文件。 生成的源文件提供了创建服务器并通过RPC进行客户端调用的所有基本代码。
创建gRPC服务器
服务器目录中的以下main.go文件通过提供服务定义中定义的RPC方法的实现,显示了创建gRPC服务器的源:
package main
import (
"log"
" net "
"strings"
"golang.org/x/net/context"
"google.golang.org/grpc"
pb "github.com/shijuvar/go-recipes/grpc/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.Fatalf("failed to listen: %v", err)
}
// Creates a new gRPC server
s := grpc.NewServer()
pb.RegisterCustomerServer(s, &server{})
s.Serve(lis)
}
结构类型服务器用于实现在customer.pb.go文件中定义的CustomerServer。它提供了两种RPC方法的实现:CreateCustomer和GetCustomers。在方法CreateCustomer中,上下文对象被传递给RPC和类型为CustomerRequest的客户端的协议缓冲区请求,并返回类型为CustomerResponse的协议缓冲区对象。 GetCustomers方法是服务器端流式RPC。与CreateCustome RPC(一种简单的RPC方法,其中将请求对象指定为输入参数,将响应对象指定为返回类型)不同,在GetCustomers RPC中,CustomCustomer RPC是类型为CustomerFilter的请求对象,用于过滤Customer数据,而特殊的方法指定类型Customer_GetCustomersServer来定义RPC方法。 Customer_GetCustomersServer对象用于使用Customer_GetCustomersServer的Send方法将RPC响应作为流写入。在生成的代码文件customer.pb.go中定义了类型Customer_GetCustomersServer。
函数grpc.NewServer创建一个新的gRPC服务器,我们在其中注册CustomerServer的实例。 服务方法在给定的侦听器上接受传入连接,为每个侦听器创建一个新的ServerTransport和服务goroutine。 服务goroutine读取gRPC请求,然后调用已注册的处理程序以回复它们。
创建gRPC客户端
生成的customer.pb.go文件提供了从客户端应用程序进行RPC调用的基本代码。 客户端目录中的以下main.go文件通过提供服务定义中定义的RPC方法的实现,显示了创建gRPC客户端的源:
package main
import (
"io"
"log"
"golang.org/x/net/context"
"google.golang.org/grpc"
pb "github.com/shijuvar/go-recipes/grpc/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 API
stream, err := client.GetCustomers(context.Background(), filter)
if err != nil {
log.Fatalf("Error on get customers: %v", err)
}
for {
customer, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("%v.GetCustomers(_) = _, %v", client, err)
}
log.Printf("Customer: %v", customer)
}
}
func main() {
// Set up a connection to the gRPC server.
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
// Creates a new CustomerClient
client := 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 customer
createCustomer(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 customer
createCustomer(client, customer)
// Filter with an empty Keyword
filter := &pb.CustomerFilter{Keyword: ""}
getCustomers(client, filter)
}
创建了一个gRPC通道来与服务器通信,以便调用RPC方法。 函数grpc.Dial用于与RPC服务器通信。 调用grpc.Dial时,可以传递DialOptions来设置身份验证凭据,例如TLS和JWT。 在此示例中,我们将grpc.WithInsecure作为DialOptions传递,这将禁用客户端连接的传输安全性。 为了调用RPC方法,我们需要创建一个客户端存根,该存根是使用函数NewCustomerClient创建的,该函数返回CustomerClient的对象。 函数NewCustomerClient和类型CustomerClient在生成的代码文件customer.pb.go中定义。
为了举例说明,我们使用RPC方法CreateCustomer创建两个Customer,并使用RPC方法GetCustomers获取Customer数据。
让我们通过从目录服务器运行main.go文件来运行gRPC服务器:
go run main.go
要通过从客户端应用程序调用来测试RPC方法,请从目录客户端运行main.go文件:
go run main.go
您应该看到类似于以下内容的输出:
(本文翻译自Shiju Varghese的文章《Building High Performance APIs In Go Using gRPC And Protocol Buffers》,参考: