Twirp против gRPC. Стоит ли?
Если вы используете микро-сервисную архитектуру, то скорее всего знаете, что накладные расходы на коммуникацию между сервисами часто становятся значительной проблемой и если Вы сталкивались с этой проблемой, то скорее всего начали использовать Protobuf и и его реализацию от Google gRPC или Go-Kit от Peter Bourgon или что-то другое. Пересказывать что это и как этим пользоваться нет смысла, все достаточно хорошо описано до меня. Я сам активно использую gRPC в своих проектах, но тут Twich решил выпустить свою реализацию protobuf Twirp. Если Вам интересно, зачем им это было нужно или чем она отличается проходите под кат.
Прежде всего давайте посмотрим на причины заставившие Twich релизить свою собственную версию ProtoBuf:
- Отсутствие поддержки HTTP 1.1. gRPS опирается на HTTP-трейлеры и полно-дуплексные потоки (full-duplex streams). Twirp поддерживает и HTTP 1.1 и HTTP/2, что очень важно потому что большое количество load-balancer-ов (как хардварных, так и софтверных) поддерживают только HTTP 1.1 — включая AWS Elastic Load Balancer. Но в отличии от gRPC Twirp не поддерживает стримингового RPC, что в случае когда Ваш API построен по принципу Request-Response и не требуется.
- Cложность реализации библиотеки grpc-go. Библиотека включает полную реализации HTTP/2, независимую от стандартных библиотек, что делает сложным ее пониманию и анализ возникающих ошибок.
- Cовместимость версий gRPC. В силу того, что gRPC довольно сложен, генерируемый Go код довольно простой и все запросы перенаправляются в grpc-go. Такая связанность приводит к тому, что клиент вынужден использовать ту же самую версию, что и сервер. И если у Вас большое количество клиентов и сервис взаимодействую друг с другом, то версия между ними должна быть идентичная. Понятно, что это приводит к сложностям в деплойменте и развертыванию микросервисов.
- Также Twitch указывают, что grpc-go требует определенную версию protobuf — github.com/golang/protobuf. Но для меня эта проблема кажется надуманной, так как protobuf имеет только один релиз версии v1.0.0, который используется всеми версиями grpc-go.
- gRPC поддерживает только бинарную форму сообщений и сниффинг сообщений очень сложным для анализа. Twirp поддерживает как бинарную форму сообщения в формате protobuf, так и в небинарные в формате JSON. Это вам дает преимущество, скажем если вы хотите взаимодействовать с сервисом через обычный HTTP Request посредством JSON
Как видите, простота это основновная причина, по которой Twich решили написать свою реализацию Protobuf.
Теперь давайте посмотрим, как же использовать эту библиотеку.
Если у вас уже настроена среда разработки на Go, то Вам нужно установить следующие пакеты
go get github.com/twitchtv/twirp/protoc-gen-twirp
go get github.com/golang/protobuf/protoc-gen-go
Для пример напишем простой сервис, который инкрементит значение переданное в качестве параметра.
syntax = "proto3";
service Service {
rpc Increment(Request) returns (Response);
}
message Request {
int32 valueToIncrement = 1; // must be > 0
}
message Response {
int32 IncrementedValue = 1; // must be > 0
}
Сгенерируем код для нашего клиента выполнив следующую команду
protoc --proto_path=$GOPATH/src:. --twirp_out=. --go_out=. ./paperclips.proto
В результате буду созданы два файла
- Increment.pb.go — содержит кодо-генерацию для сообщений
- Increment.twirp.go — содержит интерфейсы и функции сервиса
Дальше добавим реализацию нашего сервиса
package main
import (
"fmt"
"log"
"net/http"
"context"
pb "TwirpSample/Server/Twirp"
)
// Server implements the Increment service
type Server struct {
value int32
}
// NewServer creates an instance of our server
func NewServer() *Server {
return &Server{
value: 1,
}
}
// Increment returns the incremented value of request.ValueToIncrement
func (s *Server) Increment(ctx context.Context, request *pb.Request) (*pb.Response, error) {
return &pb.Response{
IncrementedValue: request.ValueToIncrement + 1,
}, nil
}
func main() {
fmt.Printf("Starting Increment Service on :6666")
server := NewServer()
twirpHandler := pb.NewServiceServer(server, nil)
log.Fatal(http.ListenAndServe(":6666", twirpHandler))
}
Теперь, если вы запустите клиента командой go run main.goм к сервису можно будет обратиться как по HTTP:
curl --request "POST" \
--location "http://localhost:6666/Service/Increment" \
--header "Content-Type:application/json" \
--data '{ValueToIncrement: 0}' \
--verbose
Output:
{"IncrementedValue":1}
Или в бинарном формате
package main
import
(
"fmt"
rpc "TwirpSample/Server/Twirp"
"net/http"
"context"
)
func main() {
fmt.Println("Twirp Client Example.")
client := rpc.NewServiceProtobufClient("http://localhost:6666", &http.Client{})
v, err := client.Increment(context.Background(), &rpc.Request{ValueToIncrement: 11})
if err != nil {
fmt.Println(err.Error())
}
fmt.Printf("Value: %d", v.IncrementedValue)
}
Output:
Twirp Client Example.
Value: 11
В целом сам фреймворк практически идентичен по подходам с gRPC, но прост в реализации и с одновременной поддержкой HTTP 1.1. На мой взгляд его применимость, если вам необходим RPC сервис, с который вы планируем одновременно взаимодействовать с UI посредством HTTP и между сервисами посредством Protobuf.
Ссылки: