Das erste Mal, dass ich von RPC hörte, war in einem Kurs über verteilte Systeme, als ich Informatik studierte. Ich fand es cool, aber ich erinnere mich, dass ich damals nicht genau verstand, warum ich beispielsweise RPC anstelle des REST-Standards verwenden würde. Die Zeit vergeht und ich arbeite in einem Unternehmen, in dem ein Teil des Altsystems SOAP verwendet. Ich erinnere mich, dass ich dachte: „Hmm, interessant! Es sieht aus wie RPC, geht aber über XML.“ Jahre später hörte ich zum ersten Mal von gRPC, aber ich verstand nie ganz, was es war, was es aß und wozu es diente.
Da mein Blog viele persönliche Dokumentationen enthält, dachte ich, es wäre cool, hier zu dokumentieren, was ich gelernt habe, angefangen bei RPC bis hin zu gRPC.
RPC ist ein Akronym für Remote Procedure Call. Mit anderen Worten: Sie senden Prozeduren/Befehle an einen Remote-Server. Einfach ausgedrückt ist dies RPC. Es funktioniert wie folgt:
RPC funktioniert sowohl über UDP als auch über TCP. Es liegt an Ihnen, herauszufinden, was für Ihren Anwendungsfall sinnvoll ist! Wenn Ihnen eine mögliche Antwort oder sogar der Verlust von Paketen nichts ausmacht, UDP. Andernfalls verwenden Sie TCP. Wer gerne die RFCs liest, findet den Link hier!
Bei beiden handelt es sich um Möglichkeiten zur Architektur von APIs. Allerdings verfügt die REST-Architektur über sehr klar definierte Prinzipien, die befolgt werden müssen, um eine vollständige REST-Architektur zu erhalten. RPC hat sogar Prinzipien, aber diese werden zwischen Client und Server definiert. Für den RPC-Client ist es so, als würde er eine lokale Prozedur aufrufen.
Ein weiterer wichtiger Punkt ist, dass es für RPC keine große Rolle spielt, ob die Verbindung TCP oder UDP ist. Was REST-APIs betrifft: Wenn Sie RESTfull folgen möchten, können Sie UDP nicht verwenden.
Für diejenigen, die mehr darüber erfahren möchten, empfehle ich diesen hervorragenden AWS-Leitfaden zu RPC x REST.
Wir haben zwei Haupteinheiten, den Client und den Server.
Der Server ist ein WEB-Server, der üblicherweise in jedem Microservice verwendet wird. Definieren wir dann den Verbindungstyp, den wir verwenden werden, in unserem Fall wurde TCP gewählt:
func main() { addr, err := net.ResolveTCPAddr("tcp", "0.0.0.0:52648") if err != nil { log.Fatal(err) } conn, err := net.ListenTCP("tcp", addr) if err != nil { log.Fatal(err) } defer conn.Close() // ... }
Wenn unser Server instanziiert ist, benötigen wir einen Handler, d. h. unsere auszuführende Prozedur. Es ist wichtig zu sagen, dass wir immer definieren müssen, von welchen Argumenten wir kommen und worauf wir in unserer HTTP-Verbindung reagieren. Um unseren Proof of Concept zu vereinfachen, erhalten wir eine Argumentstruktur und antworten auf dieselbe Struktur:
type Args struct { Message string } type Handler int func (h *Handler) Ping(args *Args, reply *Args) error { fmt.Println("Received message: ", args.Message) switch args.Message { case "ping", "Ping", "PING": reply.Message = "pong" default: reply.Message = "I don't understand" } fmt.Println("Sending message: ", reply.Message) return nil }
Nachdem Sie unseren Prozessor erstellt haben, lassen Sie ihn nun nur noch Verbindungen akzeptieren:
func main() { // ... h := new(Handler) log.Printf("Server listening at %v", conn.Addr()) s := rpc.NewServer() s.Register(h) s.Accept(conn) }
Da Client und Server derselben definierten Struktur folgen müssen, definieren wir hier die Argumentstruktur neu, die von unserem Client gesendet werden soll:
type Args struct { Message string }
Um es einfacher zu machen, erstellen wir einen interaktiven Client: Er liest Einträge in STDIN und sendet ihn, wenn er einen neuen Eintrag erhält, an unseren Server. Zu Bildungszwecken werden wir die erhaltene Antwort aufschreiben.
func main() { client, err := rpc.Dial("tcp", "localhost:52648") if err != nil { log.Fatal(err) } for { log.Println("Please, inform the message:") scanner := bufio.NewScanner(os.Stdin) scanner.Scan() args := Args{Message: scanner.Text()} log.Println("Sent message:", args.Message) reply := &Args{} err = client.Call("Handler.Ping", args, reply) if err != nil { log.Fatal(err) } log.Println("Received message:", reply.Message) log.Println("-------------------------") } }
Sie können sehen, dass wir die Adresse angeben müssen, unter der der Server läuft, und welchen Handler (Prozedur) wir ausführen möchten.
Ein wichtiger Zusatz ist, dass wir Binärdaten transportieren und Go standardmäßig Encoding/Gob verwendet. Wenn Sie einen anderen Standard wie JSON verwenden möchten, müssen Sie Ihren Server anweisen, diesen neuen Codec zu akzeptieren.
Für diejenigen, die den vollständigen Code sehen möchten, greifen Sie einfach auf den PoC zu.
gRPC ist ein Framework zum Schreiben von Anwendungen mit RPC! Dieses Framework wird derzeit von der CNCF gepflegt und wurde laut offizieller Dokumentation von Google erstellt:
gRPC wurde ursprünglich von Google entwickelt, das seit über einem Jahrzehnt eine einzige universelle RPC-Infrastruktur namens Stubby verwendet, um die große Anzahl von Mikrodiensten zu verbinden, die in und zwischen seinen Rechenzentren ausgeführt werden. Im März 2015 beschloss Google, die nächste Version von Stubby zu entwickeln und als Open Source bereitzustellen. Das Ergebnis war gRPC, das heute in vielen Organisationen außerhalb von Google verwendet wird, um Anwendungsfälle von Microservices bis zur „letzten Meile“ der Datenverarbeitung (mobil, Web und Internet der Dinge) voranzutreiben.
gRPC funktioniert nicht nur auf unterschiedlichen Betriebssystemen und auf unterschiedlichen Architekturen, sondern bietet auch folgende Vorteile:
Para nossa sorte, Go é uma das 11 linguagens que tem bibliotecas oficiais para o gRPC! É importante falar que esse framework usa o Protocol Buffer para serializar a mensagem. O primeiro passo então é instalar o protobuf de forma local e os plugins para Go:
brew install protobuf go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
E adicionar os plugins ao seu PATH:
export PATH="$PATH:$(go env GOPATH)/bin"
Vamos então criar nossos arquivos .proto! Nesse arquivo vamos definir nosso serviço, quais os handlers que ele possui e para cada handler, qual a requisição e qual resposta esperadas.
syntax = "proto3"; option go_package = "github.com/mfbmina/poc_grpc/proto"; package ping_pong; service PingPong { rpc Ping (PingRequest) returns (PingResponse) {} } message PingRequest { string message = 1; } message PingResponse { string message = 1; }
Com o arquivo .proto, vamos fazer a mágica do gRPC + protobuf acontecer. Os plugins instalados acima, conseguem gerar tudo o que for necessário para um servidor ou cliente gRPC com o seguinte comando:
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/ping_pong.proto
Esse comando vai gerar dois arquivos: ping_pong.pb.go e ping_pong_grpc.pb.go. Recomendo dar uma olhada nesses arquivos para entender melhor a estrutura do servidor e do cliente. Com isso, podemos então construir o servidor:
Para conseguir comparar com o RPC comum, vamos utilizar a mesma lógica: recebemos PING e respondemos PONG. Aqui definimos um servidor e um handler para a requisição e usamos as definições vindas do protobuf para a requisição e resposta. Depois, é só iniciar o servidor:
type server struct { pb.UnimplementedPingPongServer } func (s *server) Ping(_ context.Context, in *pb.PingRequest) (*pb.PingResponse, error) { r := &pb.PingResponse{} m := in.GetMessage() log.Println("Received message:", m) switch m { case "ping", "Ping", "PING": r.Message = "pong" default: r.Message = "I don't understand" } log.Println("Sending message:", r.Message) return r, nil } func main() { l, err := net.Listen("tcp", ":50051") if err != nil { log.Fatal(err) } s := grpc.NewServer() pb.RegisterPingPongServer(s, &server{}) log.Printf("Server listening at %v", l.Addr()) err = s.Serve(l) if err != nil { log.Fatal(err) } }
Para consumir o nosso servidor, precisamos de um cliente. o cliente é bem simples também. A biblioteca do gRPC já implementa basicamente tudo que precisamos, então inicializamos um client e só chamamos o método RPC que queremos usar, no caso o Ping. Tudo vem importado do código gerado via plugins do protobuf.
func main() { conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Fatal(err) } defer conn.Close() c := pb.NewPingPongClient(conn) for { log.Println("Enter text: ") scanner := bufio.NewScanner(os.Stdin) scanner.Scan() msg := scanner.Text() log.Printf("Sending message: %s", msg) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() r, err := c.Ping(ctx, &pb.PingRequest{Message: msg}) if err != nil { log.Fatal(err) } log.Printf("Received message: %s", r.GetMessage()) log.Println("-------------------------") } }
Quem tiver interesse para ver o código completo, pode acessar a PoC gRPC.
O gRPC não é nada mais que uma abstração em cima do RPC convencional utilizando o protobuf como serializador e o protocolo http/2. Existem algumas considerações de performance ao se utilizar o http/2 e em alguns cenários, como em requisições com o corpo simples, o http/1 se mostra mais performático que o http/2. Recomendo a leitura deste benchmark e desta issue aberta no golang/go sobre o http/2. Contudo, em requisições de corpo complexo, como grande parte das que resolvemos dia a dia, gRPC se torna uma solução extremamente atraente devido ao serializador do protobuf, que é extremamente mais rápido que JSON. O Elton Minetto fez um blog post explicando melhor essas alternativas e realizando um benchmark. Um consideração também é o protobuf consegue resolver o problema de inconsistência de contratos entre servidor e cliente, contudo é necessário uma maneira fácil de distribuir os arquivos .proto.
Por fim, minha recomendação é use gRPC se sua equipe tiver a necessidade e a maturidade necessária para tal. Hoje, grande parte das aplicações web não necessitam da performance que gRPC visa propor e nem todos já trabalharam com essa tecnologia, o que pode causar uma menor velocidade e qualidade nas entregas. Como nessa postagem eu citei muitos links, decidi listar todas as referências abaixo:
Espero que vocês tenham gostado do tema e obrigado!
Das obige ist der detaillierte Inhalt vongRPC: Wo wohnst du? Was isst du?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!