Questo documento è il terzo di una serie in quattro parti sulla progettazione, sulla creazione e sul deployment dei microservizi. Questa serie descrive i vari elementi di un'architettura di microservizi. La serie include informazioni sui vantaggi e sugli svantaggi del pattern di architettura di microservizi e su come applicarlo.
- Introduzione ai microservizi
- Eseguire il refactoring di un monolite in microservizi
- Comunicazione tra servizi in una configurazione di microservizi (questo documento)
- Monitoraggio distribuito in un'applicazione di microservizi
Questa serie è rivolta a sviluppatori e architetti di applicazioni che progettano e implementano la migrazione per eseguire il refactoring di un'applicazione monolitica in un'applicazione di microservizi.
Questo documento descrive i compromessi tra la messaggistica asincrona e le API sincrone nei microservizi. Il documento illustra la decostruzione di un'applicazione monolitica e mostra come convertire una richiesta sincrona nell'applicazione originale in un flusso asincrono nella nuova configurazione basata su microservizi. Questa conversione include l'implementazione di transazioni distribuite tra i servizi.
Applicazione di esempio
In questo documento utilizzerai un'applicazione di e-commerce predefinita chiamata Online Boutique. L'applicazione implementa flussi di e-commerce di base come la navigazione tra gli articoli, l'aggiunta di prodotti a un carrello e il pagamento. L'applicazione offre inoltre consigli e annunci in base alla selezione dell'utente.
Separazione logica del servizio
In questo documento, isoli il servizio di pagamento dal resto dell'applicazione. Tutti i flussi nell'applicazione Online Boutique originale sono sincroni. Nell'applicazione rifattoriale, la procedura di pagamento viene convertita in un flusso asincrono. Pertanto, quando ricevi una richiesta di acquisto, anziché elaborarla immediatamente, fornisci all'utente una conferma "richiesta ricevuta". In background, viene attivata una richiesta asincrona al servizio di pagamento per elaborare il pagamento.
Prima di spostare i dati e la logica di pagamento in un nuovo servizio, devi isolare i dati e la logica di pagamento dal monolite. Quando isoli i dati e la logica di pagamento nel monolite, è più facile eseguire il refactoring del codice nella stessa base di codice se sbagli i confini del servizio di pagamento (logica aziendale o dati).
I componenti dell'applicazione monolitica in questo documento sono già modularizzati, quindi sono isolati l'uno dall'altro. Se la tua applicazione presenta interdipendenze più strette, devi isolare la logica di business e creare classi e moduli separati. Devi anche disaccoppiare eventuali dipendenze del database nelle relative tabelle e creare classi di repository separate. Quando scolleghi le dipendenze del database, possono esserci relazioni di chiave esterna tra le tabelle divise. Tuttavia, dopo aver disaccoppiato completamente il servizio dal monolito, queste dipendenze non esistono più e il servizio interagisce esclusivamente tramite contratti RPC o API predefiniti.
Transazioni distribuite ed errori parziali
Dopo aver isolato il servizio e averlo separato dal monolite, una transazione locale nel sistema monolitico originale viene distribuita tra più servizi. Nell'implementazione monolitica, la procedura di pagamento seguiva la sequenza mostrata nel seguente diagramma.
Figura 1. Una sequenza di procedura di pagamento in un'implementazione monolitica.
Nella figura 1, quando l'applicazione riceve un ordine di acquisto, il controllore di pagamento chiama il servizio di pagamento e il servizio di ordine per elaborare rispettivamente il pagamento e salvare l'ordine. Se un passaggio non va a buon fine, è possibile eseguire il rollback della transazione del database. Prendiamo in considerazione uno scenario di esempio in cui la richiesta di ordine viene archiviata correttamente nella tabella degli ordini, ma il pagamento non va a buon fine. In questo scenario, viene eseguito il rollback dell'intera transazione e la voce viene rimossa dalla tabella ordini.
Dopo aver disaccoppiato il pagamento in un proprio servizio, il flusso di pagamento modificato è simile al seguente diagramma:
Figura 2. Una sequenza della procedura di pagamento dopo il pagamento viene disaccoppiata in un suo servizio.
Nella figura 2, la transazione ora si estende su più servizi e sui relativi database corrispondenti, quindi si tratta di una transazione distribuita. Al ricevimento di una richiesta di ordine, il controller di pagamento salva i dettagli dell'ordine nel proprio database locale e chiama altri servizi per completare l'ordine. Questi servizi, come il servizio di pagamento, possono utilizzare il proprio database locale per archiviare i dettagli dell'ordine.
Nell'applicazione monolitica, il sistema di database garantisce che le transazioni locali siano atomiche. Tuttavia, per impostazione predefinita, il sistema basato su microservizi che ha un database separato per ogni servizio non dispone di un coordinatore delle transazioni globale che copra i diversi database. Poiché le transazioni non sono coordinate centralmente, un errore nell'elaborazione di un pagamento non rollback le modifiche apportate nel servizio degli ordini. Pertanto, il sistema è in uno stato incoerente.
I seguenti pattern sono comunemente utilizzati per gestire le transazioni distribuite:
- Protocollo di commit in due fasi (2PC): fa parte di una famiglia di protocolli di consenso, 2PC coordina il commit di una transazione distribuita e mantiene le garanzie di atomicità, coerenza, isolamento, durabilità (ACID). Il protocollo è suddiviso nelle fasi di preparazione e commit. Una transazione viene eseguita solo se tutti i partecipanti hanno votato a favore. Se i partecipanti non raggiungono un consenso, l'intera transazione viene annullata.
- Saga: Il pattern Saga consiste nell'eseguire transazioni locali all'interno di ciascun microservizio che costituiscono la transazione distribuita. Un evento viene attivato al termine di ogni operazione riuscita o non riuscita. Tutti i microservice coinvolti nella transazione distribuita si iscrivono a questi eventi. Se i seguenti microservizi ricevono un evento di successo, eseguono la loro operazione. In caso di errore, i microservizi precedenti completano le azioni di compensazione per annullare le modifiche. Saga fornisce una visione coerente del sistema garantendo che, al termine di tutti i passaggi, tutte le operazioni vadano a buon fine o che le azioni compensative annullino tutto il lavoro.
Consigliamo Saga per le transazioni di lunga durata. In un'applicazione basata su microservizi, ti aspetti chiamate tra servizi e comunicazioni con sistemi di terze parti. Pertanto, è meglio progettare per la coerenza finale: riprova per gli errori recuperabili ed esponi eventi compensativi che alla fine correggono gli errori non recuperabili.
Esistono vari modi per implementare una saga. Ad esempio, puoi utilizzare motori di attività e flussi di lavoro come Apache Airflow o Apache Camel. Puoi anche scrivere i tuoi gestori eventi utilizzando sistemi basati su Kafka, RabbitMQ o ActiveMQ.
L'applicazione Online Boutique utilizza il servizio di pagamento per orchestrare i servizi di pagamento, spedizione e notifica via email. Il servizio di pagamento gestisce anche il flusso di lavoro aziendale e degli ordini. In alternativa alla creazione del tuo motore di flusso di lavoro, puoi utilizzare un componente di terze parti come Zeebe. Zeebe fornisce un modello basato su interfaccia utente. Ti consigliamo di valutare attentamente le opzioni per l'orchestratore di microservizi in base ai requisiti della tua applicazione. Questa scelta è fondamentale per l'esecuzione e la scalabilità dei microservizi.
Applicazione sottoposta a refactoring
Per abilitare le transazioni distribuite nell'applicazione rifattoriale, il servizio di pagamento gestisce la comunicazione tra il servizio di pagamento, spedizione ed email. Il flusso di lavoro Business Process Model and Notation (BPMN) generico utilizza il seguente flusso:
Figura 3. Un flusso di lavoro degli ordini che contribuisce a garantire transazioni distribuite nei microservizi tipici.
Il diagramma precedente mostra il seguente flusso di lavoro:
- Il servizio frontend riceve una richiesta di ordine ed esegue le seguenti operazioni:
- Invia gli articoli dell'ordine al servizio del carrello. Il servizio carrello salva quindi i dettagli dell'ordine (Redis).
- Reindirizza alla pagina di pagamento. Il servizio di pagamento estrae gli ordini dal servizio del carrello, imposta lo stato dell'ordine su
Pending
e chiede al cliente il pagamento. - Conferma che l'utente ha pagato. Una volta confermata, il servizio di pagamento chiede al servizio email di generare un'email di conferma e di inviarla al cliente.
- Il servizio di pagamento elabora successivamente la richiesta.
- Se la richiesta di pagamento va a buon fine, il servizio di pagamento aggiorna lo stato dell'ordine su
Complete
. - Se la richiesta di pagamento non va a buon fine, il servizio di pagamento avvia una transazione di compensazione.
- La richiesta di pagamento viene annullata.
- Il servizio di pagamento imposta lo stato dell'ordine su
Failed
.
- Se il servizio di pagamento non è disponibile, la richiesta scade dopo N secondi e il servizio di pagamento avvia una transazione di compensazione.
- Il servizio di pagamento imposta lo stato dell'ordine su
Failed
.
- Se la richiesta di pagamento va a buon fine, il servizio di pagamento aggiorna lo stato dell'ordine su
Obiettivi
- Esegui il deployment dell'applicazione monolitica Online Boutique su Google Kubernetes Engine (GKE).
- Convalida il flusso di pagamento monolitico.
- Esegui il deployment della versione di microservizi dell'applicazione monolitica sottoposta a refactoring
- Verifica che il nuovo flusso di pagamento funzioni.
- Verifica che le azioni di compensazione e transazioni distribuite funzionino in caso di errore.
Costi
In questo documento utilizzi i seguenti componenti fatturabili di Google Cloud:
Per generare una stima dei costi in base all'utilizzo previsto,
utilizza il Calcolatore prezzi.
Al termine di questo documento, puoi evitare la fatturazione continua eliminando le risorse che hai creato. Per ulteriori informazioni, vedi Pulizia.
Prima di iniziare
-
In the Google Cloud console, on the project selector page, select or create a Google Cloud project.
-
Make sure that billing is enabled for your Google Cloud project.
-
In the Google Cloud console, activate Cloud Shell.
At the bottom of the Google Cloud console, a Cloud Shell session starts and displays a command-line prompt. Cloud Shell is a shell environment with the Google Cloud CLI already installed and with values already set for your current project. It can take a few seconds for the session to initialize.
Abilita le API per Compute Engine, Google Kubernetes Engine, Cloud SQL, Artifact Analysis e Container Registry:
gcloud services enable \ compute.googleapis.com \ sql-component.googleapis.com \ servicenetworking.googleapis.com\ container.googleapis.com \ containeranalysis.googleapis.com \ containerregistry.googleapis.com \ sqladmin.googleapis.com
Esporta le seguenti variabili di ambiente:
export PROJECT=$(gcloud config get-value project) export CLUSTER=$PROJECT-gke export REGION="us-central1"
Esegui il deployment del monolite di e-commerce
In questa sezione esegui il deployment dell'applicazione monolitica Online Boutique in un cluster GKE. L'applicazione utilizza Cloud SQL come database relazionale. Il seguente diagramma illustra l'architettura dell'applicazione monolitica:
Figura 4. Un client si connette all'applicazione in un cluster GKE e l'applicazione si connette a un database Cloud SQL.
Per eseguire il deployment dell'applicazione, completa i seguenti passaggi:
Clona il repository GitHub:
git clone https://github.com/GoogleCloudPlatform/monolith-to-microservices-example
Sostituisci il segnaposto
PROJECT_ID
nel file manifest delle variabili Terraform:cd monolith-to-microservices-example/setup && \ sed -i -e "s/\[PROJECT_ID\]/$PROJECT/g" terraform.tfvars
Esegui gli script Terraform per completare la configurazione e il deployment dell'infrastruttura. Per scoprire di più su Terraform, consulta Introduzione a Terraform su Google Cloud:
terraform init && terraform apply -auto-approve
Lo script Terraform crea quanto segue:
- Una rete VPC denominata
PROJECT_ID-vpc
- Cluster GKE denominato
PROJECT_ID-gke
- Un'istanza Cloud SQL denominata
PROJECT_ID-mysql
- Un database denominato
ecommerce
utilizzato dall'applicazione - Un utente
root
con la password impostata supassword
- Un database denominato
Puoi modificare lo script Terraform per generare automaticamente una password. Questa configurazione utilizza un esempio semplificato che non deve essere utilizzato in produzione.
Il provisioning dell'infrastruttura può richiedere fino a 10 minuti. Se lo script è riuscito, l'output è simile al seguente:
... Apply complete! Resources: 8 added, 0 changed, 0 destroyed. Outputs: kubernetes_cluster_name = PROJECT_ID-gke sql_database_name = PROJECT_ID-mysql vpc_name = PROJECT_ID-vpc
- Una rete VPC denominata
Connettiti al cluster e crea uno spazio dei nomi denominato
monolith
. Esegui il deployment dell'applicazione nel proprio spazio dei nomi nel cluster GKE:gcloud container clusters get-credentials $CLUSTER \ --region $REGION \ --project $PROJECT && \ kubectl create ns monolith
L'applicazione in esecuzione su GKE utilizza Kubernetes Secrets per accedere al database Cloud SQL. Crea un segreto che utilizzi le credenziali dell'utente per il database:
kubectl create secret generic dbsecret \ --from-literal=username=root \ --from-literal=password=password -n monolith
Crea l'immagine del monolite e caricala su Container Registry:
cd ~/monolith gcloud builds submit --tag gcr.io/$PROJECT_ID/ecomm
Aggiorna il riferimento nel file
deploy.yaml
all'immagine Docker appena creata:cd ~/monolith sed -i -e "s/\[PROJECT_ID\]/$PROJECT/g" deploy.yaml
Sostituisci i segnaposto nei file manifest di deployment e poi esegui il deployment dell'applicazione:
cd .. && \ DB_IP=$(gcloud sql instances describe $PROJECT-mysql | grep "ipAddress:" | tail -1 | awk -F ":" '{print $NF}') sed -i -e "s/\[DB_IP\]/$DB_IP/g" monolith/deploy.yaml kubectl apply -f monolith/deploy.yaml
Controlla lo stato del deployment:
kubectl rollout status deployment/ecomm -n monolith
L'output è simile al seguente.
Waiting for deployment "ecomm" rollout to finish: 0 of 1 updated replicas are available... deployment "ecomm" successfully rolled out
Ottieni l'indirizzo IP dell'applicazione di cui è stato eseguito il deployment:
kubectl get svc ecomm -n monolith \ -o jsonpath="{.status.loadBalancer.ingress[*].ip}" -w
Attendi che l'indirizzo IP del bilanciatore del carico venga pubblicato. Per uscire dal comando, premi
Ctrl+C
. Prendi nota dell'indirizzo IP del bilanciatore del carico e accedi all'applicazione all'URLhttp://IP_ADDRESS
. Potrebbe essere necessario un po' di tempo prima che il bilanciatore del carico diventi stabile e inizi a inoltrare il traffico.
Convalida il flusso di pagamento monolitico
In questa sezione, creerai un ordine di prova per convalidare il flusso di pagamento.
- Vai all'URL che hai annotato nella sezione precedente,
http://IP_ADDRESS
. - Nella home page dell'applicazione visualizzata, seleziona un prodotto e poi fai clic su Aggiungi al carrello.
- Per creare un acquisto di prova, fai clic su Effettua l'ordine:
- Al termine del pagamento, viene visualizzata la finestra di conferma dell'ordine con un ID di conferma dell'ordine.
Per visualizzare i dettagli dell'ordine, connettiti al database:
gcloud sql connect $PROJECT-mysql --user=root
Puoi anche utilizzare qualsiasi altro metodo supportato per connetterti al database. Quando richiesto, inserisci la password come
password
.Per visualizzare i dettagli dell'ordine salvato, esegui il seguente comando:
select cart_id from ecommerce.cart;
L'output è il seguente:
+--------------------------------------+ | cart_id | +--------------------------------------+ | 7cb9ab11-d268-477f-bf4d-4913d64c5b27 | +--------------------------------------+
Esegui il deployment dell'applicazione di e-commerce basata su microservizi
In questa sezione esegui il deployment dell'applicazione ristrutturata. Questo documento si concentra solo sul disaccoppiamento dei servizi di frontend e di pagamento. Il documento successivo di questa serie, Rastreggiamento distribuito in un'applicazione di microservizi, descrive altri servizi, come i servizi di consigli e annunci, che puoi disaccoppiare dal monolito. Il servizio di pagamento gestisce le transazioni distribuite tra il frontend e i servizi di pagamento e viene implementato come servizio Kubernetes nel cluster GKE, come mostrato nel seguente diagramma:
Figura 5. Il servizio di pagamento orchestra le transazioni tra i servizi di pagamento, del carrello e email.
Esegui il deployment dei microservizi
In questa sezione utilizzi l'infrastruttura di cui hai eseguito il provisioning in precedenza per eseguire il deployment dei microservizi nel proprio spazio dei nomi microservice
:
Assicurati di soddisfare i seguenti requisiti:
- Progetto Google Cloud
- Ambiente shell con
gcloud
,git
ekubectl
In Cloud Shell, clona il repository dei microservizi:
git clone https://github.com/GoogleCloudPlatform/microservices-demo cd microservices-demo/
Imposta il progetto e la regione Google Cloud e assicurati che l'API GKE sia abilitata:
export PROJECT_ID=PROJECT_ID export REGION=us-central1 gcloud services enable container.googleapis.com \ --project=${PROJECT_ID}
Sostituisci
PROJECT_ID
con l'ID del tuo progetto Google Cloud.Crea un cluster GKE e ottieni le relative credenziali:
gcloud container clusters create-auto online-boutique \ --project=${PROJECT_ID} --region=${REGION}
La creazione del cluster potrebbe richiedere alcuni minuti.
Esegui il deployment dei microservizi nel cluster:
kubectl apply -f ./release/kubernetes-manifests.yaml
Attendi che i pod siano pronti:
kubectl get pods
Dopo alcuni minuti, i pod sono nello stato
Running
.Accedi al frontend web in un browser utilizzando l'indirizzo IP esterno del frontend:
kubectl get service frontend-external | awk '{print $4}'
Visita
http://EXTERNAL_IP
in un browser web per accedere alla tua istanza di Online Boutique.
Convalida il nuovo flusso di pagamento
- Per verificare il flusso della procedura di pagamento, seleziona un prodotto e effettua un ordine, come descritto nella sezione precedente Convalida il flusso di pagamento monolitico.
- Al termine del pagamento dell'ordine, la finestra di conferma non visualizza un ID conferma. La finestra di conferma ti invita invece a controllare la tua email per i dettagli di conferma.
Per verificare che l'ordine sia stato ricevuto, che il servizio di pagamento abbia elaborato il pagamento e che i dettagli dell'ordine siano stati aggiornati, esegui il seguente comando:
kubectl logs -f deploy/checkoutservice --tail=100
L'output è il seguente:
[...] {"message":"[PlaceOrder] user_id=\"98828e7a-b2b3-47ce-a663-c2b1019774a3\" user_currency=\"CAD\"","severity":"info","timestamp":"2023-08-10T04:19:20.498893921Z"} {"message":"payment went through (transaction_id: f0b4a592-026f-4b4a-9892-ce86d2711aed)","severity":"info","timestamp":"2023-08-10T04:19:20.528338189Z"} {"message":"order confirmation email sent to \"someone@example.com\"","severity":"info","timestamp":"2023-08-10T04:19:20.540275988Z"}
Per uscire dai log, premi
Ctrl+C
.Verifica che il pagamento sia andato a buon fine:
kubectl logs -f deploy/paymentservice -n --tail=100
L'output è il seguente:
[...] {"severity":"info","time":1691641282208,"pid":1,"hostname":"paymentservice-65cc7795f6-r5m8r","name":"paymentservice-charge","message":"Transaction processed: visa ending 0454 Amount: CAD119.30128260"} {"severity":"info","time":1691641300051,"pid":1,"hostname":"paymentservice-65cc7795f6-r5m8r","name":"paymentservice-server","message":"PaymentService#Charge invoked with request {\"amount\":{\"currency_code\":\"USD\",\"units\":\"137\",\"nanos\":850000000},\"credit_card\":{\"credit_card_number\":\"4432-8015-6152-0454\",\"credit_card_cvv\":672,\"credit_card_expiration_year\":2039,\"credit_card_expiration_month\":1}}"}
Per uscire dai log, premi
Ctrl+C
.Verifica che l'email di conferma dell'ordine sia stata inviata:
kubectl logs -f deploy/emailservice -n --tail=100
L'output è il seguente:
[...] {"timestamp": 1691642217.5026057, "severity": "INFO", "name": "emailservice-server", "message": "A request to send order confirmation email to kalani@examplepetstore.com has been received."}
I messaggi di log per ciascun microservizio indicano che la transazione distribuita tra i servizi di pagamento, di pagamento e di email è stata completata correttamente.
Convalidare l'azione di compensazione in una transazione distribuita
Questa sezione simula uno scenario in cui un cliente sta effettuando un ordine e il servizio di pagamento si arresta in modo anomalo.
Per simulare l'indisponibilità del servizio, elimina il deployment e il servizio di pagamento:
kubectl delete deploy paymentservice && \ kubectl delete svc paymentservice
Accedi di nuovo all'applicazione e completa il flusso di pagamento. In questo esempio, se il servizio di pagamento non risponde, la richiesta scade e viene attivata un'azione di compensazione.
Nel frontend dell'interfaccia utente, fai clic sul pulsante Effettua ordine. L'output è simile al seguente:
HTTP Status: 500 Internal Server Error rpc error: code = Internal desc = failed to charge card: could not charge the card: rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing: dial tcp: lookup paymentservice on 34.118.224.10:53: no such host" failed to complete the order main.(*frontendServer).placeOrderHandler /src/handlers.go:360
Esamina i log del servizio frontend:
kubectl logs -f deploy/frontend --tail=100
L'output è simile al seguente:
[...] {"error":"failed to complete the order: rpc error: code = Internal desc = failed to charge card: could not charge the card: rpc error: code = Unavailable desc = connection error: desc = \"transport: Error while dialing: dial tcp: lookup paymentservice on 34.118.224.10:53: no such host\"","http.req.id":"0a4cb058-ee9b-470a-9bb1-3a965636022e","http.req.method":"POST","http.req.path":"/cart/checkout","message":"request error","session":"96c94881-a435-4490-9801-c788dc400cc1","severity":"error","timestamp":"2023-08-11T18:25:47.127294259Z"}
Esamina i log del servizio di pagamento:
kubectl logs -f deploy/frontend --tail=100
L'output è simile al seguente:
[...] {"message":"[PlaceOrder] user_id=\"96c94881-a435-4490-9801-c788dc400cc1\" user_currency=\"USD\"","severity":"info","timestamp":"2023-08-11T18:25:46.947901041Z"} {"message":"[PlaceOrder] user_id=\"96c94881-a435-4490-9801-c788dc400cc1\" user_currency=\"USD\"","severity":"info","timestamp":"2023-08-11T19:54:21.796343643Z"}
Tieni presente che non viene effettuata alcuna chiamata successiva al servizio email per inviare la notifica. Non è presente un log delle transazioni, come
payment went through (transaction_id: 06f0083f-fa47-4d91-8258-6d61edfab1ca)
.Esamina i log del servizio email:
kubectl logs -f deploy/emailservice --tail=100
Tieni presente che non sono state create voci di log per la transazione non riuscita nel servizio email.
In qualità di orchestratore, se una chiamata di servizio non riesce, il servizio di pagamento restituisce uno stato di errore ed esce dalla procedura di pagamento.
Esegui la pulizia
Se prevedi di completare i passaggi nel documento successivo di questa serie, Rastreggiamento distribuito in un'applicazione di microservizi, puoi riutilizzare il progetto e le risorse anziché eliminarli.
Elimina il progetto
- In the Google Cloud console, go to the Manage resources page.
- In the project list, select the project that you want to delete, and then click Delete.
- In the dialog, type the project ID, and then click Shut down to delete the project.
Elimina le risorse
Se vuoi mantenere il progetto Google Cloud utilizzato in questo documento, elimina le singole risorse.
In Cloud Shell, esegui questo comando:
cd setup && terraform destroy -auto-approve
Per eliminare il cluster di microservizi utilizzando Google Cloud CLI, esegui il seguente comando:
gcloud container clusters delete online-boutique \ --location $REGION
Passaggi successivi
- Scopri di più sull'architettura dei microservizi.
- Leggi il primo documento di questa serie per scoprire di più su microservizi, vantaggi, sfide e casi d'uso.
- Leggi il secondo documento di questa serie per scoprire le strategie di refactoring delle applicazioni per decomporre i microservizi.
- Leggi il documento finale di questa serie per scoprire di più sul monitoraggio distribuito delle richieste tra i microservizi.