gRPC, сокращение от «Удаленный вызов процедур Google», — это высокопроизводительная платформа с открытым исходным кодом, обеспечивающая связь между клиентскими и серверными приложениями. Он использует формат сериализации данных буферов протокола и поддерживает несколько языков программирования для создания эффективных и масштабируемых распределенных систем. В этой статье мы рассмотрим, как работает gRPC, и приведем примеры кода различных методов.
Содержание:
-
Основы gRPC
-
Буферы протокола
-
Настройка проекта gRPC
-
Унарный RPC
-
Потоковый RPC сервера
-
Потоковый RPC клиента
-
Двунаправленный потоковый RPC
-
Обработка ошибок с помощью gRPC
-
Перехватчики в gRPC
-
Вывод
-
Основы gRPC.
По своей сути gRPC опирается на концепцию удаленного вызова процедур (RPC), при которой клиентское приложение может вызывать методы серверного приложения, как если бы они были локальными. gRPC поддерживает различные шаблоны связи, включая унарный RPC, потоковый RPC сервера, потоковый RPC клиента и двунаправленный потоковый RPC. -
Буферы протокола:
gRPC использует буферы протокола (protobuf) в качестве языка определения интерфейса (IDL). Protobuf — это независимый от языка и платформенный механизм сериализации структурированных данных. Он позволяет вам определять структуру ваших данных с помощью простого языка и автоматически генерирует код для нескольких языков программирования. -
Настройка проекта gRPC.
Чтобы начать работу с gRPC, вам необходимо настроить проект и определить свой сервис с помощью protobuf. Сначала установите необходимые зависимости и сгенерируйте код сервера и клиента на основе определения protobuf. Затем реализуйте логику на стороне сервера и создайте клиентское приложение для связи с сервером. -
Унарный RPC.
Унарный RPC — это простейшая форма RPC, при которой клиент отправляет один запрос серверу и получает один ответ. Вот пример унарного RPC в gRPC с использованием Python:
# gRPC client code
import grpc
from generated import my_service_pb2, my_service_pb2_grpc
def main():
channel = grpc.insecure_channel('localhost:50051')
stub = my_service_pb2_grpc.MyServiceStub(channel)
response = stub.MyMethod(my_service_pb2.MyRequest(param='Hello gRPC!'))
print(response.result)
if __name__ == '__main__':
main()
# gRPC server code
import grpc
from concurrent import futures
from generated import my_service_pb2, my_service_pb2_grpc
class MyService(my_service_pb2_grpc.MyServiceServicer):
def MyMethod(self, request, context):
return my_service_pb2.MyResponse(result='Response: ' + request.param)
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
my_service_pb2_grpc.add_MyServiceServicer_to_server(MyService(), server)
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()
if __name__ == '__main__':
serve()
- Потоковый RPC сервера:
При потоковой передаче RPC сервера клиент отправляет запрос серверу, а сервер отвечает потоком сообщений. Вот пример потоковой передачи RPC с сервера в gRPC с использованием Golang:
// gRPC client code
package main
import (
"context"
"fmt"
"log"
"google.golang.org/grpc"
"github.com/your-protobuf-package"
)
func main() {
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("could not connect: %v", err)
}
defer conn.Close()
client := your_protobuf_package.NewYourServiceClient(conn)
stream, err := client.StreamingMethod(context.Background(), &your_protobuf_package.StreamingRequest{Param: "Hello gRPC!"})
if err != nil {
log.Fatalf("error while calling StreamingMethod: %v", err)
}
for {
response, err := stream.Recv()
if err != nil {
log.Fatalf("error while receiving response: %v", err)
}
fmt.Println(response.Result)
}
}
// gRPC server code
package main
import (
"fmt"
"log"
"net"
"google.golang.org/grpc"
"github.com/your-protobuf-package"
)
type server struct {
your_protobuf_package.UnimplementedYourServiceServer
}
func (s *server) StreamingMethod(request *your_protobuf_package.StreamingRequest, stream your_protobuf_package.YourService_StreamingMethodServer) error {
for i := 0; i < 5; i++ {
result := fmt.Sprintf("Response %d: %s", i+1, request.Param)
stream.Send(&your_protobuf_package.StreamingResponse{Result: result})
}
return nil
}
func main() {
listener, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
srv := grpc.NewServer()
your_protobuf_package.RegisterYourServiceServer(srv, &server{})
if err := srv.Serve(listener); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
- Потоковый RPC клиента.
При потоковом RPC клиента клиент отправляет поток запросов на сервер, а сервер отвечает одним ответом. Вот пример клиентской потоковой передачи RPC в gRPC с использованием C#:
// gRPC client code
using Grpc.Core;
using System;
using System.Threading.Tasks;
using YourProtobufPackage;
class Program
{
static async Task Main(string[] args)
{
Channel channel = new Channel("localhost:50051", ChannelCredentials.Insecure);
var client = new YourService.YourServiceClient(channel);
var streamingCall = client.ClientStreamingMethod();
for (int i = 0; i < 5; i++)
{
await streamingCall.RequestStream.WriteAsync(new StreamingRequest { Param = $"Request {i + 1}" });
}
await streamingCall.RequestStream.CompleteAsync();
var response = await streamingCall.ResponseAsync;
Console.WriteLine(response.Result);
await channel.ShutdownAsync();
}
}
// gRPC server code
using Grpc.Core;
using System.Threading.Tasks;
using YourProtobufPackage;
class YourServiceImplementation : YourService.YourServiceBase
{
public override async Task<StreamingResponse> ClientStreamingMethod(IAsyncStreamReader<StreamingRequest> requestStream, ServerCallContext context)
{
string result = "";
await foreach (var request in requestStream.ReadAllAsync())
{
result += $"Received: {request.Param}\n";
}
return new StreamingResponse { Result = result };
}
}
class Program
{
static void Main(string[] args)
{
Server server = new Server
{
Services = { YourService.BindService(new YourServiceImplementation()) },
Ports = { new ServerPort("localhost", 50051, ServerCredentials.Insecure) }
};
server.Start();
Console.WriteLine("Server started.");
Console.WriteLine("Press any key to stop the server...");
Console.ReadKey();
server.ShutdownAsync().Wait();
}
}
- Двунаправленный потоковый RPC:
При двунаправленном потоковом RPC клиент и сервер могут отправлять поток запросов и ответов одновременно. Вот пример двунаправленной потоковой передачи RPC в gRPC с использованием Node.js:
// gRPC client code
const grpc = require('grpc');
const protoLoader = require('@grpc/proto-loader');
const packageDefinition = protoLoader.loadSync('your_protobuf_package.proto');
const yourPackage = grpc.loadPackageDefinition(packageDefinition).your_package;
const client = new yourPackage.YourService('localhost:50051', grpc.credentials.createInsecure());
const call = client.BidirectionalMethod();
call.on('data', response => {
console.log(response.result);
});
call.on('end', () => {
console.log('Streaming ended.');
});
for (let i = 0; i < 5; i++) {
call.write({ param: `Request ${i + 1}` });
}
call.end();
// gRPC server code
const grpc = require('grpc');
const protoLoader = require('@grpc/proto-loader');
const packageDefinition = protoLoader.loadSync('your_protobuf_package.proto');
const yourPackage = grpc.loadPackageDefinition(packageDefinition).your_package;
function bidirectionalMethod(call) {
call.on('data', request => {
console.log(`Received: ${request.param}`);
call.write({ result: `Response: ${request.param}` });
});
call.on('end', () => {
call.end();
});
}
const server = new grpc.Server();
server.addService(yourPackage.YourService.service, { BidirectionalMethod: bidirectionalMethod });
server.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure());
server.start();
-
Обработка ошибок с помощью gRPC:
gRPC обеспечивает структурированный способ обработки ошибок с использованием кодов состояния и сообщений об ошибках. И клиент, и сервер могут распространять ошибки, возвращая соответствующий код состояния и сообщение. Обработка ошибок может выполняться с помощью блоков try-catch или промежуточного программного обеспечения для обработки ошибок, в зависимости от используемого языка программирования и платформы. -
Перехватчики в gRPC.
Перехватчики позволяют перехватывать запросы и ответы gRPC и управлять ими на различных этапах жизненного цикла RPC. Их можно использовать для добавления аутентификации, ведения журналов, метрик или любой пользовательской логики к вызовам gRPC. Перехватчики могут быть реализованы на стороне клиента, на стороне сервера или на обеих сторонах. -
gRPC