Begrijpen van gRPC-concepten, gebruiksvoorbeelden en beste praktijken

Terwijl we vooruitgang boeken met de ontwikkeling van toepassingen, zijn er verschillende zaken waar we ons minder zorgen over maken: rekenkracht. Door de komst van cloudproviders maken we ons minder zorgen over het beheren van datacentra. Alles is binnen enkele seconden op aanvraag beschikbaar. Dit leidt ook tot een toename in de omvang van gegevens. Big data wordt gegenereerd en getransporteerd via verschillende media in enkele aanvragen. 

Met de toename in de omvang van gegevens, komen er activiteiten zoals serialiseren, deserialiseren en transportkosten bij. Hoewel we ons geen zorgen hoeven te maken over rekenbronnen, wordt latentie een last. We moeten af van transport. Er zijn in het verleden veel berichtensprotocollen ontwikkeld om dit aan te pakken. SOAP was omvangrijk, en REST is een versimpelde versie, maar we hebben een nog efficiënter raamwerk nodig. Daar komt Remote Procedure Calls – RPC – om de hoek kijken.

In dit artikel gaan we begrijpen wat RPC is, en de verschillende implementaties van RPC, met een focus op gRPC, wat Google’s implementatie van RPC is. We zullen ook REST vergelijken met RPC en verschillende aspecten van gRPC begrijpen, inclusief beveiliging, hulpmiddelen en nog veel meer.

Wat Is RPC?

RPC staat voor Remote Procedure Calls. De definitie zit zelfs in de naam. Procedure calls betekenen gewoon functie-/methodeaanroepen; het is het woord “Remote” dat het verschil maakt. Wat als we een functieaanroep op afstand kunnen maken?

Eenvoudig gezegd, als een functie op een server staat en om vanaf de clientzijde worden aangeroepen, kunnen we het dan zo eenvoudig maken als een methode/functieaanroep? Eigenlijk biedt RPC de illusie aan de client dat het een lokale methode aanroept, maar in werkelijkheid roept het een methode op een verre machine aan die de netwerklaagtaken abstracteert. Het mooie hiervan is dat het contract zeer strikt en transparant wordt gehouden (daarover later meer in dit artikel).

Stappen die bij een RPC-oproep betrokken zijn:

RPC-sequentieflow

Dit is hoe een typisch REST-proces eruit ziet:

RPC’s vereenvoudigen het proces tot onder:

Dit komt omdat alle complicaties die verband houden met het maken van een aanvraag nu van ons geabstraheerd zijn (daarover later meer in code-generatie). Het enige waar we ons zorgen over hoeven te maken is de data en logica.

gRPC: Wat, Waarom en Hoe ervan

Tot nu toe hebben we RPC besproken, wat in wezen betekent dat we functie-/methodeaanroepen op afstand doen – waardoor we voordelen krijgen zoals “strikte contractdefinitie,” “abstractie van overdracht en conversie van data,” “latentievermindering,” enzovoort, waarover we gaan praten terwijl we verdergaan met deze post. Waar we echt dieper op in willen gaan is een van de implementaties van RPC. RPC is een concept en gRPC is een framework erop gebaseerd.

Er zijn verschillende implementaties van RPC’s. Ze zijn:

  • gRPC (google)

  • Thrift (Facebook)

  • Finalge (Twitter)

De versie van RPC van Google wordt aangeduid als gRPC. Deze werd in 2015 geïntroduceerd en heeft sindsdien aan populariteit gewonnen. Het is een van de meest gekozen communicatiemechanismen in een microservices-architectuur.

gRPC maakt gebruik van protocol buffers (een open-source berichtindeling) als de standaardmethode van communicatie tussen client en server. Daarnaast gebruikt gRPC HTTP/2 als standaardprotocol. Er zijn vier soorten communicatie die gRPC ondersteunt:

Komend op het berichtformaat dat breed wordt gebruikt in gRPC — protocol buffers, ook wel bekend als protobufs. Een protobuf-bericht ziet er ongeveer zo uit:

message Person {
string name = 1;
string id = 2;
string email = 3;
}

Hier is ‘Person’ het bericht dat we willen overdragen (als onderdeel van een aanvraag/antwoord) met de velden ‘name’ (string-type), ‘id’ (string-type) en ‘email’ (string-type). De nummers 1,2,3 geven de positie van de gegevens aan (zoals in ‘name’, ‘id’ en ‘has_ponycopter’) wanneer deze wordt gerenderd naar binaire indeling.

Nadat de ontwikkelaar het Protocol Buffer-bestand(en) heeft gemaakt met alle berichten, kunnen we een Protocol Buffer-compiler (een binair bestand) gebruiken om het geschreven Protocol Buffer-bestand te compileren. Dit genereert alle nuttige klassen en methoden die nodig zijn om met het bericht te werken. Bijvoorbeeld, zoals getoond hier, zal de gegenereerde code (afhankelijk van de gekozen taal) eruit zien als dit.

Hoe definiëren we diensten?

We moeten diensten definiëren die de bovenstaande berichten gebruiken om te verzenden/ontvangen.

Na het schrijven van de benodigde aanvraag- en antwoordberichttypen, is de volgende stap het schrijven van de service zelf.

gRPC-diensten worden ook gedefinieerd in Protocol Buffers en ze gebruiken de “service” en “RPC” sleutelwoorden om een service te definiëren.

Bekijk de inhoud van het onderstaande proto-bestand:

bericht HelloRequest {
string name = 1;
string description = 2;
int32 id = 3;
}

bericht HelloResponse {
string processedMessage = 1;
}

service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}

Hier zijn HelloRequest en HelloResponse de berichten en HelloService onthult één unitaire RPC genaamd SayHello die HelloRequest als invoer neemt en HelloResponse als uitvoer geeft.

Zoals vermeld, bevat HelloService op dit moment slechts een enkele unary RPC. Maar het kan meerdere RPC’s bevatten. Ook kan het een verscheidenheid aan RPC’s bevatten (unary/client-side streaming/server-side streaming/Bidirectional).

Om een streaming RPC te definiëren, hoeft u alleen maar ‘stream ‘ voor de aanvraag/responsargumenten te plaatsen, Streaming RPCs proto definities, en gegenereerde code.

In bovenstaande code-base link:

gRPC Vs. REST

We hebben redelijk veel gesproken over gRPC. Ook werd REST genoemd. Wat we gemist hebben, was het bespreken van het verschil. Ik bedoel, als we een goed geïnstalleerde, lichtgewicht communicatieframework in de vorm van REST hebben, waarom was er dan behoefte om naar een ander communicatieframework te zoeken? Laten we meer over gRPC leren met betrekking tot REST, samen met de voordelen en nadelen van elk.

Om te vergelijken, hebben we parameters nodig. Dus laten we de vergelijking opsplitsen in de onderstaande parameters:

  • Berichtformaat: Protocol Buffers vs JSON

    • De snelheid van serialisatie en deserialisatie is veel beter bij protocol buffers voor alle gegevensgroottes (klein/gemiddeld/groot). Benchmarks-Testresultaten

    • Na serialisatie is JSON menselijk leesbaar, terwijl protobufs (in binaire vorm) dat niet zijn. Het is niet zeker of dit een nadeel is, omdat je soms de aanvraagdetails in de ontwikkelaarstools van Google of Kafka-onderwerpen wilt zien en bij protobufs kun je niets uitmaken. 

  • Communicatieprotocol: HTTP 1.1 vs. HTTP/2T

    • REST is gebaseerd op HTTP 1.1; de communicatie tussen een REST-client en server vereist een geïnstalleerde TCP-verbinding die op zijn beurt een 3-wegsgreep inhoudt. Wanneer we een reactie van de server ontvangen nadat we een verzoek van de client hebben verzonden, bestaat de TCP-verbinding daarna niet meer. Er moet een nieuwe TCP-verbinding worden opgezet om een ander verzoek te verwerken. Deze oprichting van een TCP-verbinding bij elke aanvraag draagt bij aan de latentie.

    • Dus gRPC, dat is gebaseerd op HTTP 2, heeft deze uitdaging aangepakt door een persistente verbinding te hebben. We moeten onthouden dat persistente verbindingen in HTTP 2 anders zijn dan die in web sockets, waar een TCP-verbinding wordt gekaapt en de gegevensoverdracht ongemerkt is. In een gRPC-verbinding wordt eenmaal een TCP-verbinding geïnstalleerd, deze hergebruikt voor meerdere verzoeken. Alle verzoeken van dezelfde client- en serverpaar worden gemultiplext op dezelfde TCP-verbinding.

  • Alleen maar zorgen over gegevens en logica: Codegeneratie als eerste-klasse burger

    • Codegeneratie-functies zijn standaard aanwezig in gRPC via zijn ingebouwde protoc-compiler. Met REST-API’s is het noodzakelijk om een derde-partijtool zoals Swagger te gebruiken om de code voor API-oproepen in verschillende talen automatisch te genereren.

    • In het geval van gRPC abstracteert het het proces van marshalling/unmarshalling, het opzetten van een verbinding en het verzenden/ontvangen van berichten; waar we ons allemaal zorgen over maken is de gegevens die we willen verzenden of ontvangen en de logica.

  • Transmissiesnelheid

    • Omdat het binaire formaat veel lichter is dan het JSON-formaat, is de transmissiesnelheid bij gRPC 7 tot 10 keer sneller dan bij REST.

Kenmerk

REST

gRPC

Communicatieprotocol

Volgt het aanvraag-antwoordmodel. Het kan werken met zowel HTTP-versie, maar wordt meestal gebruikt met HTTP 1.1

Volgt het client-antwoordmodel en is gebaseerd op HTTP 2. Sommige servers hebben oplossingen om het te laten werken met HTTP 1.1 (via rest gateways)

Browserondersteuning

Werkt overal

Beperkte ondersteuning. Moet gebruik maken van gRPC-Web, wat een uitbreiding voor het web is en is gebaseerd op HTTP 1.1

Payload data structuur

Gebruikt meestal JSON en XML-gebaseerde payloads om gegevens over te dragen

Gebruikt standaard protocol buffers om payloads over te dragen

Code-generatie

Moet derden gebruiken zoals Swagger om clientcode te genereren

gRPC heeft native ondersteuning voor code-generatie voor verschillende talen

Request caching

Gemakkelijk om verzoeken op de client- en servers zijde te cachen. De meeste clients/servers ondersteunen dit standaard (bijvoorbeeld via cookies)

Ondersteunt standaard geen request/response caching

Nogmaals, voorlopig heeft gRPC geen browser ondersteuning, aangezien de meeste UI-frameworks nog beperkte of geen ondersteuning hebben voor gRPC. Hoewel gRPC in de meeste gevallen de automatische keuze is bij interne microservicescommunicatie, is het niet hetzelfde voor externe communicatie die UI-integratie vereist.

Nu we een vergelijking hebben gemaakt van beide frameworks: gRPC en REST. Welke te gebruiken en wanneer?

  • In een microservicesarchitectuur met meerdere lichtgewicht microservices, waar de efficiëntie van gegevensoverdracht van vitaal belang is, zou gRPC een ideale keuze zijn.

  • Als code-generatie met ondersteuning voor meerdere talen een vereiste is, zou gRPC de standaard framework moeten zijn.

  • Met de streamingmogelijkheden van gRPC zouden realtime apps zoals handel of OTT er meer van profiteren dan met polling via REST.

  • Als bandbreedte een beperking is, zou gRPC veel lagere latentie en doorvoer bieden.

  • Als snellere ontwikkeling en hoogtempo iteratie een vereiste is, zou REST de standaard optie moeten zijn.

gRPC Concepten

Load Balancing

Hoewel de permanente verbinding het latentieprobleem oplost, brengt het een ander uitdaging met zich mee in de vorm van load balancing. Aangezien gRPC (of HTTP2) permanente verbindingen creëert, vormt de client, zelfs bij aanwezigheid van een load balancer, een permanente verbinding met de server die zich achter de load balancer bevindt. Dit is vergelijkbaar met een sticky session.

We kunnen het probleem of de uitdaging begrijpen via een demo. En de code en implementatiebestanden zijn aanwezig op: https://github.com/infracloudio/grpc-blog/tree/master/grpc-loadbalancing.

Uit de bovenstaande demo-code-basis kunnen we afleiden dat de verantwoordelijkheid voor load balancing bij de client ligt. Dit leidt ertoe dat het voordeel van gRPC, namelijk de permanente verbinding, niet meer bestaat met deze wijziging. Maar gRPC kan nog steeds worden gebruikt vanwege zijn andere voordelen.

Lees meer over load balancing in gRPC.

In de bovenstaande demo-code-basis wordt alleen een round-robin load balancing strategie gebruikt/getoond. Maar gRPC ondersteunt ook een andere client-gebaseerde load balancing strategie OOB genaamd “pick-first.”

Bovendien wordt aangepaste client-side load balancing ook ondersteund.

Nette Contract

In REST wordt het contract tussen de client en server gedocumenteerd, maar niet strikt. Als we verder teruggaan naar SOAP, werden contracten blootgesteld via wsdl-bestanden. In REST stellen we contracten bloot via Swagger en andere voorzieningen. Maar de striktheid ontbreekt, we kunnen niet met zekerheid weten of het contract is gewijzigd aan de serverkant terwijl de clientcode wordt ontwikkeld.

Met gRPC wordt het contract, hetzij via proto-bestanden of gegenereerde stub van proto-bestanden, gedeeld met zowel de client als de server. Dit is als het maken van een functieaanroep, maar dan op afstand. En aangezien we een functieaanroep maken, weten we precies wat we moeten sturen en wat we als reactie verwachten. De complexiteit van het maken van verbindingen met de client, het afhandelen van beveiliging, serialisatie-deserialisatie, enzovoort, wordt geabstraheerd. Het enige waar we ons om bekommeren is de gegevens.

Overweeg de onderstaande codebasis:

https://github.com/infracloudio/grpc-blog/tree/master/greet_app     

De client gebruikt de stub (gegenereerde code uit het proto-bestand) om een clientobject te maken en de externe functieaanroep uit te voeren: 

 

```sh

import greetpb "github.com/infracloudio/grpc-blog/greet_app/internal/pkg/proto"



cc, err := grpc.Dial(“<server-address>”, opts)

if err != nil {

    log.Fatalf("could not connect: %v", err)

}



c := greetpb.NewGreetServiceClient(cc)



res, err := c.Greet(context.Background(), req)

if err != nil {

    log.Fatalf("error while calling greet rpc : %v", err)

}

```

Evenzo gebruikt de server ook dezelfde stub (gegenereerde code uit het proto-bestand) om het aanvraagobject te ontvangen en het antwoordobject te maken: 

 

```sh

import greetpb "github.com/infracloudio/grpc-blog/greet_app/internal/pkg/proto"



func (*server) Greet(_ context.Context, req *greetpb.GreetingRequest) (*greetpb.GreetingResponse, error) {

 

  // doe iets met 'req'

 

   return &greetpb.GreetingResponse{

    Result: result,

      }, nil

}

```

Beiden gebruiken dezelfde stub die is gegenereerd uit het proto-bestand dat hier ligt.

En de stub is gegenereerd met behulp van de onderstaande proto-compiler opdracht. 

 

```sh

protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative internal/pkg/proto/*.proto

```

Beveiliging

gRPC authenticatie en autorisatie werken op twee niveaus:

  • Authenticatie/autorisatie op oproepniveau wordt meestal verwerkt via tokens die worden toegepast in metadata wanneer de oproep wordt gedaan. Voorbeeld van token-based authenticatie.

  • Authenticatie op kanaalniveau gebruikt een clientcertificaat dat wordt toegepast op het verbindingsniveau. Het kan ook oproepniveau authenticatie/autorisatiereferenties omvatten om automatisch op elke oproep op het kanaal toe te passen. Voorbeeld van certificaat-based authenticatie.

Beide of beide van deze mechanismen kunnen worden gebruikt om diensten te beveiligen.

Middleware

In REST, we gebruiken middleware voor verschillende doeleinden zoals:

  • Rate limiting

  • Pre/Post request/response validation

  • Address security threats

We kunnen hetzelfde bereiken met gRPC ook. De terminologie is anders in gRPC – ze worden aangeduid als interceptors, maar ze verrichten vergelijkbare activiteiten.

In de middleware branch van de ‘greet_app’ codebase, hebben we logger en Prometheus interceptors geïntegreerd. 

Kijk hoe de interceptors worden geconfigureerd om Prometheus en logging packages te gebruiken hier.

Maar we kunnen andere pakketten integreren in interceptors voor doeleinden zoals het voorkomen van paniek en herstel (om uitzonderingen te behandelen), tracing, zelfs authenticatie, enzovoort.

Ondersteunde middleware door gRPC framework.

Verpakking, Versiebeheer en Codepraktijken van Proto Files

Verpakking

Laten we de verpakkingstak volgen.

Begin eerst met ‘Taskfile.yaml’, de taak ‘gen-pkg’ zegt ‘protoc –proto_path=verpakking verpakking/*.proto –go_out=verpakking’. Dit betekent dat ‘protoc’ (de compiler) alle bestanden in ‘verpakking/*.proto’ zal converteren naar hun equivalente ‘go’ bestanden zoals aangegeven door het kenmerk ‘–go_out=verpakking’ in de ‘verpakking’ directory zelf.

Ten tweede zijn in het ‘processor.proto’ bestand 2 berichten gedefinieerd, namelijk ‘CPU’ en ‘GPU’. Terwijl CPU een simpel bericht is met 3 velden van ingebouwde gegevenstypen, heeft GPU daarentegen een aangepast gegevenstype genaamd ‘Memory’. ‘Memory’ is een apart bericht en is gedefinieerd in een geheel ander bestand.

Dus hoe gebruik je het ‘Memory’ bericht in het ‘processor.proto’ bestand? Door middel van import.

Zelfs als je probeert een proto-bestand te genereren door de taak ‘gen-pkg’ uit te voeren na het vermelden van import, zal het een fout genereren. Omdat ‘protoc’ standaard aanneemt dat beide bestanden ‘memory.proto’ en ‘processor.proto’ tot verschillende pakketten behoren. Daarom moet je dezelfde pakketnaam in beide bestanden vermelden.

Het optionele ‘go_package’ geeft aan de compiler aan om een pakketnaam te creëren als ‘pb’ voor go-bestanden. Als er proto-bestanden voor een andere taal zouden worden gemaakt, zou de pakketnaam ‘laptop_pkg’ zijn.

Versiebeheer

  • Er kunnen twee soorten wijzigingen in gRPC zijn: brekende en niet-brekende wijzigingen.

  • Niet-brekende wijzigingen omvatten het toevoegen van een nieuwe service, het toevoegen van een nieuwe methode aan een service, het toevoegen van een veld aan een aanvraag of antwoord proto, en het toevoegen van een waarde aan een enum

  • Brekende wijzigingen zoals het hernoemen van een veld, het wijzigen van het veldtype, het veldnummer, het hernoemen of verwijderen van een pakket, service of methoden vereisen het versioneren van services

  • Optioneel verpakken.

Codepractices 

  • Aanvraagbericht moet eindigen op aanvraag `CreateUserRequest`

  • Antwoordbericht moet eindigen op aanvraag `CreateUserResponse`

  • Als de antwoordbericht leeg is, kun je ofwel een leeg object `CreateUserResponse` gebruiken of de `google.protobuf.Empty`

  • De pakketnaam moet zinvol zijn en gevormd, bijvoorbeeld: pakket `com.ic.internal_api.service1.v1`

Tooling

De gRPC-ecosysteem ondersteunt een reeks tools om het leven gemakkelijker te maken bij niet-ontwikkelings taken zoals documentatie, rest gateway voor een gRPC-server, integreren van aangepaste validatoren, linting, enz. Hier zijn enkele tools die ons kunnen helpen hetzelfde te bereiken:

  • protoc-gen-grpc-gateway — plugin voor het maken van een gRPC REST API gateway. Het staat gRPC eindpunten toe als REST API eindpunten en voert de vertaling van JSON naar proto uit. Kort gezegd, je definieert een gRPC-service met enkele aangepaste annotaties en het maakt die gRPC-methoden toegankelijk via REST met behulp van JSON-aanvragen.

  • protoc-gen-swagger — een geassocieerd plugin voor grpc-gateway. Het kan swagger.json genereren op basis van de aangepaste annotaties die nodig zijn voor de gRPC-gateway. Je kunt vervolgens die bestand in je REST-client van keuze (zoals Postman) importeren en REST API-oproepen uitvoeren naar de methoden die je hebt blootgelegd.

  • protoc-gen-grpc-web — een plugin dat onze front-end toestaat om te communiceren met de back-end via gRPC-oproepen. Een apart blogbericht over dit onderwerp komt in de toekomst aan bod.

  • protoc-gen-go-validators — een plugin die het mogelijk maakt validatieregels voor proto berichtvelden te definiëren. Het genereert een Validate() error methode voor proto berichten die je in GoLang kunt aanroepen om te controleren of het bericht overeenkomt met je vooraf bepaalde verwachtingen.

  • https://github.com/yoheimuta/protolint — een plugin om lint regels toe te voegen aan proto bestanden

Testen met POSTMAN

In tegenstelling tot het testen van REST API’s met postman of gelijkwaardige tools zoals Insomnia, is het niet erg comfortabel om gRPC diensten te testen.

Opmerking: gRPC-services kunnen ook vanaf de CLI worden getest met hulpprogramma’s zoals evans-cli. Maar daarvoor moet reflectie worden ingeschakeld (indien niet ingeschakeld is het pad naar het proto-bestand vereist). Reflectie inschakelen in gRPC-servers vereist wijzigingen en instructies om in de repl-modus van evans-cli te treden. Na het betreden van de repl-modus van evans-cli, kunnen gRPC-services rechtstreeks vanaf de CLI worden getest en het proces wordt beschreven op de github-pagina van evans-cli.

Postman biedt een betaversie voor het testen van gRPC-services.

Hier zijn de stappen om het te doen:

  1. Open Postman

  2. Ga naar ‘APIs’ in de linkernavigatiebalk

    

  1. Klik op het ‘+’-teken om een nieuwe API te maken:

    

  1. In het pop-upvenster, voer ‘Naam’, ‘Versie’ en ‘Schema-details’ in en klik op maken [tenzij u wilt importeren van bronnen zoals github/bitbucket]. Dit is relevant als u de proto-overeenkomst wilt kopiëren-plakken.

5. Je API wordt zoals hieronder weergegeven aangemaakt. Klik op versie ‘1.0.0’, ga naar definitie en voer je proto-overeenkomst in.

  1. Onthoud dat importeren hier niet werkt, dus is het beter om alle afhankelijke protos op één plaats te houden.

  2. De bovenstaande stappen zullen helpen om contracten voor toekomstig gebruik te behouden.

  3. Klik vervolgens op ‘Nieuw’ en selecteer ‘gRPC-verzoek’:

  1. Voer de URI in en kies de proto uit de lijst met opgeslagen APIs:

    

  1. Voer je verzoekbericht in en klik op ‘Aanroepen’:

In de bovenstaande stappen hebben we het proces ontdekt om onze gRPC-API’s via POSTMAN te testen. Het proces om gRPC-eindpunten te testen is anders dan dat van REST-eindpunten met POSTMAN. Het is belangrijk om te onthouden dat bij het maken en opslaan van het proto-contract zoals in #5, alle proto berichten en service definities op dezelfde plaats moeten staan. Er is geen mogelijkheid om proto-berichten over versies heen te raadplegen in POSTMAN.

Conclusie

In deze post hebben we een idee ontwikkeld over RPC, vergelijkingen getrokken met REST en hun verschillen besproken, vervolgens zijn we verder gegaan met het bespreken van een implementatie van RPC, namelijk gRPC ontwikkeld door Google. 

gRPC als framework kan cruciaal zijn, vooral voor microservice-gebaseerde architectuur voor interne communicatie. Het kan ook worden gebruikt voor externe communicatie, maar zal een REST-gateway vereisen. gRPC is essentieel voor streaming- en realtime-apps. 

De manier waarop Golang zichzelf aantoont als een server-side scriptingtaal, toont gRPC zichzelf als een standaard communicatieframework.

Source:
https://dzone.com/articles/understanding-grpc-concepts-use-cases-amp-best-pra