Прежде всего давайте посмотрим на причины заставившие 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.
Ссылки:
Комментарии (17)
spein
01.05.2018 10:50Используем go-micro в проекте, как по мне, очень неплохая альтернатива grpc, если считать его альтернативой, ведь он поддерживает grpc. Стандартно работает по http/1.1 и обладает всеми перечисленными преимуществами даже более: github.com/micro/go-micro
awesomer
01.05.2018 12:05-1gRPC — это протокол обмена данными.
go-micro — это фреймворк для создания микросервисов, который берет на себя решение многих и многих проблем, в том числе и страшно далеких от протокола обмена данными.
go-micro — это не альтернатива gRPC.
Это инструмент для других целей.spein
01.05.2018 13:42godoc.org/google.golang.org/grpc
Я не спорю, что grpc это название протокола, но все-же это и библиотека одноименная. Я говорил о библиотеке и об ее функционале. В статье вроде не протокол же рассматривается?awesomer
01.05.2018 16:54А че, в Go есть и альтернативные реализации gRPC?
go-micro использует всю ту же библиотеку, что вы и напрямую бы стали.
imanushin
01.05.2018 11:51А есть сравнение — какая разница в скорости работы простых http вызовов против gRPC/Twirp ?
Еще интересно — какой вообще порядок величин, по сравнению с дальнейшей десериализацией?
Интересует именно цифры, естественно.
awesomer
01.05.2018 11:57то скорее всего начали использовать Protobuf и и его реализацию от Google gRPC
Protobuf — и есть реализация Google.
А gRPC — это протокол построенный поверх ProtobufBOOTLOADER Автор
01.05.2018 18:24Да, вы правы — получить не совсем корректно. Просто есть реализация от Google, есть вот от Twich и от многих других: github.com/google/protobuf/blob/master/docs/third_party.md
awesomer
01.05.2018 21:28gRPC — это вовсе не «реализация протокола Protobuf»
Написано неграмотно.
Ну чтобы вам было понятно, это как сказать: «http — это реализация протокола TCP/IP».
То, что TCP/IP лежит в основе http не делает http его реализацией. «Реализовать http на основе TCP/IP» — так сказать можно.
BOOTLOADER Автор
01.05.2018 21:56Да, gRPC использует Protobuf как язык описание интерфейсов, но такая форма не совсем понятная, поэтому для просто я использовал как «реализация протокола Protobuf».
slonpts
02.05.2018 03:46+1Статья хороша, но awesomer верно заметил, что фраза
Protobuf и и его реализацию от Google gRPC или Go-Kit от Peter Bourgon
будет путать следующие поколения программистов еще много лет. Наверное, лучше заменить наRPC фреймворк поверх Protobuf, например, gRPC от Google или Go-Kit от Peter Bourgon
Dmitri-D
gRPC тоже не менялся и остается совместимым с самим собой старых версий. Так что не вижу проблем в этом плане. Про балансер — да, согласен. Но они и не заботились, что у AWS что-то там не сделано.
Если говорить о проблемах в использовании gRPC, когда много разных клиентов сидят на разных версиях, вероятно это девелопер создавал сам себе такие проблемы. Наиболее простой способ выстрелить себе в ногу — это выпустить обновление на сервер, и там поменять тэги существующих полей или, например, добавить обязательное поле, о котором старые клиенты ничего знают и послать в запросе не могут. Поэтому правила очень простые — поля только добавляются, и теги только инкрементируются, все новые поля — необязательные. Если любое из этих условий выполнить невозможно, то создается новая точка RPC (т.е. новая процедура или метод), но старая должна продолжать работать пока есть хотя бы один клиент ее вызывающий.
BOOTLOADER Автор
У меня лично опыт негативного нет gRPC. Но у меня все инфраструктура новая на одной версии. Я пытался с ними связаться, чтобы получиться какие-то точные пояснения ошибок, но пока безуспешно. Насчет добавления удаления полей есть Reserved Fields которые вам позволяют избежать ошибок, но как я сказал — пока нет информации были ли причины именно в этом.
Dmitri-D
Ошибка возникнет, если например вы использовали необязательное поле «customer_id» с тегом 1. А затем вы удалили это поле из выдачи, а потом ввели поле допустим «order_id» и присвоили ему «свободный» тэг 1. Все старые клинеты начнут читать order_id думая что читают customer_id.
Что бы этого не произошло вы добавляете поля с новыми тегами, и никогда не переиспользуете удалённые ранее. Так же никогда не удаляете то, что было хоть 1 раз объявлено как обязательное. Вот и вся премудрость. С протоколом там всё в порядке.
Не в порядке с асинхронным режимом. Впрочем, я интенсивно тестировал прошлым летом. Может, с тех пор что-то и изменилось.
BOOTLOADER Автор
В спецификации так и написано, если вы удаляете или комментируете поле, используете reserved fields, чтобы избежать колизий.
Dmitri-D
Да, это об одном и том же. Просто reserved — это способ показать остальным _пользователям_, не gprs, что тег некоего поля был ранее использован.
Но, если вы сам девелопер некоей rpc коммуникации, т.е. вы пишете классы-сообщения, — ваши пользователи и так не смогут переиспользовать удаленные теги. Поэтому вы можете с тем же успехом и не объявлять их reserved, а просто сам с собой договориться, что пользовать те номера уже нельзя. Как это сделать наиболее простым способом? Инкрементируя некий счётчик тегов. Т.е. для новых тегов использовать последний номер + 1, о чем я и говорил в начале.