Что такое GRPC и зачем он если есть REST?
GRPC - это еще один способ обмена данными между сервисами. Он быстрее и эффективнее чем REST. Далее будет пример
как сделать обмен данными между клиентом на Python и сервером Golang используя GRPC.
Пример REST API
Представьте сервер который по ID пользователя возвращает его имя и email. Если бы мы использовали REST, то
клиент должен был сделать GET запрос:
GET https://localhost/user/<ID>
И получил бы в ответ какой-то JSON
{
"id": 1,
"name": "foo",
"email": "foo@localhost"
}
Что нужно сделать чтобы такая схема заработала?
Сделать сервер:
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
e.GET("/user/:id", func(c echo.Context) error {
return c.JSON(http.StatusOK,
struct {
ID string
Name string
Email string
}{
c.Param("id"),
"foo",
"foo@localhost",
})
})
e.Logger.Fatal(e.Start(":8081"))
}
Написать клиент:
import requests
response = requests.get('http://localhost:8081/user/1')
print(response)
В случае GRPC все не так просто, но этого способа обмена есть много плюсов о которых сказано в конце.
Пример GRPC
Для начала нужно создать .proto
файл, описывающий какими данными и как мы обмениваемся.
syntax = "proto3";
option go_package="grpc-example/pb";
// Есть некий сервис User
service User {
// У него есть функция Info которая
// принимает Request и возвращает Response
rpc Info(Request) returns (Response) {}
}
// Описываем что принимает функция на вход
message Request {
int32 ID = 1;
}
// А здесь описываем, что она возвращает
message Response {
int32 ID = 1;
string name = 2;
string email = 3;
}
Этот файл формата protocol buffers который придумал Google. Для чего он
придуман и какие у него есть плюсы это тема отдельной статьи.
Важно что в файле мы описываем сервисы, их функции и структуры которые являются параметрами этих функций.
Дальше при помощи специального компилятора из .proto
файла можно сгенерировать код на Go, Python и еще куче
языков программирования который в дальнейшем подключается как библиотека к нашему клиенту и серверу.
Выглядят команды запуска компилятора примерно так:
# Генерация кода на Go
protoc --go_out=. --go-grpc_out=. *.proto
# Для Python
python -m grpc_tools.protoc -Iexample --python_out=. --grpc_python_out=.
Важно понять, что в результате этих манипуляций мы получим
сгенерированные библиотеки на go и python которые используем в своей работе.
Вот как это выглядит.
package main
import (
"context"
// Здесь подключим сгенерированную библиотеку
"grpc-vs-rest/grpc-example/pb"
"log"
"net"
// Стандартная библиотека гугла для работы с GRPC
"google.golang.org/grpc"
)
// Создадим сервер
type UserServer struct {
// Встраиваем структуру из
// сгенерированной библиотеки
pb.UnimplementedUserServer
}
// Реализуем конкретную функцию
// нам не нужно ничего кодировать
// grpc все берет на себя
func (s UserServer) Info(_ context.Context, r *pb.Request) (*pb.Response, error) {
resp := pb.Response{
ID: r.ID,
Name: "foo",
Email: "foo@localhost",
}
return &resp, nil
}
func main() {
lis, err := net.Listen("tcp", ":50005")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
server := grpc.NewServer()
pb.RegisterUserServer(server, UserServer{})
if err := server.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
Теперь клиент:
# dirty hack
import os
import sys
sys.path.append(os.getcwd() + '/pb')
import grpc
from pb import user_pb2, user_pb2_grpc
channel = grpc.insecure_channel('localhost:50005')
stub = user_pb2_grpc.UserStub(channel)
# Вот тут весь запрос к серверу
req = user_pb2.Request(ID=1)
req.ID = 1
response = stub.Info(req)
print(response)
Мы указываем адрес сервера и вызываем функцию сгенерированной библиотеки,
всю остальную работу GRPC берет на себя.
Заключение
GRPC выглядит монструознее REST, нужно больше подготовительных операций.
Если у вас сервис с двумя эндпойтами, без высокой
нагрузки, то GRPC не ваш выбор.
Но у GRPC много плюсов:
- быстрее
- потребляет меньше траффика
- при большом количестве сервисов и обменов поддержка проще
- возможны двусторонние обмены в потоковом режиме, но это тема отдельной статьи
Рабочий пример из статьи с замером времени
https://github.com/pahanini/grpc-vs-rest