Traduzione e adattamento dei microservizi Java: una guida pratica . Parti precedenti della guida:
Diamo un'occhiata ai problemi inerenti ai microservizi in Java, iniziando con cose astratte e finendo con le librerie concrete.
Come rendere resiliente un microservizio Java?
Ricorda che quando crei microservizi, stai essenzialmente scambiando chiamate al metodo JVM con chiamate HTTP sincrone o messaggistica asincrona. Mentre il completamento di una chiamata al metodo è per lo più garantito (salvo un arresto imprevisto della JVM), una chiamata di rete è inaffidabile per impostazione predefinita. Potrebbe funzionare, ma potrebbe non funzionare per vari motivi: la rete è sovraccarica, è stata implementata una nuova regola del firewall e così via. Per vedere come questo fa la differenza, diamo un'occhiata all'esempio BillingService.Modelli di resilienza HTTP/REST
Supponiamo che i clienti possano acquistare e-book sul sito Web della tua azienda. Per fare ciò, hai appena implementato un microservizio di fatturazione in grado di chiamare il tuo negozio online per generare fatture PDF effettive. Per ora, effettueremo questa chiamata in modo sincrono, su HTTP (anche se ha più senso chiamare questo servizio in modo asincrono, poiché la generazione di PDF non deve essere istantanea dal punto di vista dell'utente. Utilizzeremo lo stesso esempio nel prossimo sezione e osservare le differenze).@Service
class BillingService {
@Autowired
private HttpClient client;
public void bill(User user, Plan plan) {
Invoice invoice = createInvoice(user, plan);
httpClient.send(invoiceRequest(user.getEmail(), invoice), responseHandler());
// ...
}
}
Per riassumere, ecco tre possibili risultati di questa chiamata HTTP.
- OK: la chiamata è andata a buon fine, l'account è stato creato correttamente.
- RITARDO: la chiamata è andata a buon fine, ma il completamento ha richiesto troppo tempo.
- ERRORE. La chiamata non è riuscita, potresti aver inviato una richiesta incompatibile o il sistema potrebbe non funzionare.
Modelli di resilienza della messaggistica
Diamo uno sguardo più da vicino alla comunicazione asincrona. Il nostro programma BillingService potrebbe ora assomigliare a questo, presupponendo che stiamo utilizzando Spring e RabbitMQ per la messaggistica. Per creare un account, ora inviamo un messaggio al nostro broker di messaggi RabbitMQ, dove ci sono diversi lavoratori in attesa di nuovi messaggi. Questi lavoratori creano fatture PDF e le inviano agli utenti appropriati.@Service
class BillingService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void bill(User user, Plan plan) {
Invoice invoice = createInvoice(user, plan);
// преобразует счет, например, в json и использует его How тело messages
rabbitTemplate.convertAndSend(exchange, routingkey, invoice);
// ...
}
}
I potenziali errori ora appaiono leggermente diversi, poiché non ricevi più risposte OK o ERROR immediate come avveniva con una connessione HTTP sincrona. Invece, potremmo avere tre potenziali scenari che potrebbero andare storti, il che potrebbe sollevare le seguenti domande:
- Il mio messaggio è stato consegnato e utilizzato dal dipendente? Oppure è perduto? (L'utente non riceve fattura).
- Il mio messaggio è stato recapitato una sola volta? Oppure consegnato più di una volta ed elaborato una sola volta? (L'utente riceverà più fatture).
- Configurazione: da "Ho utilizzato le chiavi/nomi di instradamento corretti per lo scambio" a "Il mio broker di messaggi è configurato e gestito correttamente o le sue code sono piene?" (L'utente non riceve fattura).
- Se utilizzi implementazioni JMS come ActiveMQ, puoi scambiare la velocità con la garanzia di commit a due fasi (XA).
- Se utilizzi RabbitMQ, leggi prima questa guida, quindi pensa attentamente alle conferme, alla tolleranza agli errori e all'affidabilità dei messaggi in generale.
- Forse qualcuno è esperto nella configurazione di server Active o RabbitMQ, soprattutto in combinazione con clustering e Docker (qualcuno? ;))
Quale framework sarebbe la soluzione migliore per i microservizi Java?
Da un lato puoi installare un'opzione molto popolare come Spring Boot . Semplifica molto la creazione di file .jar, viene fornito con un server Web integrato come Tomcat o Jetty e può essere eseguito rapidamente e ovunque. Ideale per creare applicazioni di microservizi. Recentemente sono comparsi un paio di framework di microservizi specializzati, Kubernetes o GraalVM , parzialmente ispirati alla programmazione reattiva. Ecco alcuni contendenti più interessanti: Quarkus , Micronaut , Vert.x , Helidon . Alla fine dovrai scegliere tu stesso, ma possiamo darti un paio di consigli che potrebbero non essere del tutto standard: ad eccezione di Spring Boot, tutti i framework di microservizi sono generalmente commercializzati come incredibilmente veloci, con avvio quasi istantaneo , utilizzo ridotto della memoria, scalabilità all'infinito. I materiali di marketing in genere presentano una grafica impressionante che mostra la piattaforma accanto al colosso Spring Boot o l'una con l'altra. Questo, in teoria, risparmia i nervi agli sviluppatori che supportano progetti legacy, che a volte richiedono diversi minuti per essere caricati. Oppure sviluppatori che lavorano nel cloud e desiderano avviare/arrestare tutti i microcontenitori di cui hanno attualmente bisogno entro 50 ms. Il problema, tuttavia, è che questi tempi (artificiali) di avviamento bare metal e di ridistribuzione difficilmente contribuiscono al successo complessivo del progetto. Almeno influenzano molto meno di una solida infrastruttura quadro, una solida documentazione, una comunità e forti capacità di sviluppo. Quindi è meglio vederla in questo modo: Se finora:- Lasci che i tuoi ORM dilaghino, generando centinaia di query per flussi di lavoro semplici.
- Hai bisogno di infiniti gigabyte per gestire il tuo monolite moderatamente complesso.
- Hai così tanto codice e la complessità è così elevata (non stiamo parlando di avviatori potenzialmente lenti come Hibernate ora) che il caricamento della tua applicazione richiede diversi minuti.
Quali librerie sono le migliori per le chiamate REST Java sincrone?
Dal punto di vista tecnico di basso livello, probabilmente ti ritroverai con una delle seguenti librerie client HTTP: HttpClient nativo di Java (da Java 11), HttpClient di Apache o OkHttp . Tieni presente che dico "probabilmente" qui perché ci sono altre opzioni, che vanno dai buoni vecchi client JAX-RS ai moderni client WebSocket . In ogni caso, la tendenza è quella di generare un client HTTP, evitando di armeggiare personalmente con le chiamate HTTP. Per fare ciò, devi dare un'occhiata al progetto OpenFeign e alla sua documentazione come punto di partenza per ulteriori letture.Quali sono i migliori broker per la messaggistica Java asincrona?
Molto probabilmente incontrerai i popolari ActiveMQ (Classic o Artemis) , RabbitMQ o Kafka .- ActiveMQ e RabbitMQ sono broker di messaggi tradizionali e completi. Implicano l’interazione di un “broker intelligente” e di “utenti stupidi”.
- Storicamente, ActiveMQ ha avuto il vantaggio di un facile incorporamento (per i test), che può essere mitigato con le impostazioni RabbitMQ/Docker/TestContainer.
- Kafka non può essere definito un tradizionale broker “intelligente”. Si tratta invece di un archivio di messaggi relativamente "stupido" (file di registro) che richiede l'elaborazione da parte di consumatori intelligenti.
Quali librerie posso utilizzare per testare i microservizi?
Dipende dal tuo stack. Se disponi di un ecosistema Spring distribuito, sarebbe saggio utilizzare gli strumenti specifici del framework . Se JavaEE è qualcosa come Arquillian . Potrebbe valere la pena dare un'occhiata a Docker e all'ottima libreria Testcontainers , che aiuta in particolare a configurare facilmente e rapidamente un database Oracle per lo sviluppo locale o i test di integrazione. Per testare simulatamente interi server HTTP, controlla Wiremock . Per testare la messaggistica asincrona, prova a implementare ActiveMQ o RabbitMQ e quindi a scrivere i test utilizzando Awaitility DSL . Inoltre, vengono utilizzati tutti gli strumenti abituali: Junit , TestNG per AssertJ e Mockito . Tieni presente che questo non è un elenco completo. Se non trovi il tuo strumento preferito qui, pubblicalo nella sezione commenti.Come abilitare la registrazione per tutti i microservizi Java?
Il logging nel caso dei microservizi è un argomento interessante e piuttosto complesso. Invece di avere un file di registro che puoi manipolare con i comandi less o grep, ora hai n file di registro e non vuoi che siano troppo sparsi. Le caratteristiche dell'ecosistema del logging sono ben descritte in questo articolo (in inglese). Assicurati di leggerlo e presta attenzione alla sezione Registrazione centralizzata dal punto di vista dei microservizi . In pratica vi imbatterete in diversi approcci: l'amministratore di sistema scrive determinati script che raccolgono e uniscono file di registro da diversi server in un unico file di registro e li mette sui server FTP per il download. Esecuzione di combinazioni cat/grep/unig/sort in sessioni SSH parallele. Questo è esattamente ciò che fa Amazon AWS e puoi farlo sapere al tuo manager. Utilizza uno strumento come Graylog o ELK Stack (Elasticsearch, Logstash, Kibana)Come si trovano i miei microservizi?
Finora abbiamo dato per scontato che i nostri microservizi si conoscessero tra loro e conoscessero il corrispondente IPS. Parliamo di configurazione statica. Quindi, il nostro monolite bancario [ip = 192.168.200.1] sa che deve comunicare con il server dei rischi [ip = 192.168.200.2], che è codificato nel file delle proprietà. Tuttavia, puoi rendere le cose più dinamiche:- Utilizza un server di configurazione basato su cloud da cui tutti i microservizi estraggono le proprie configurazioni invece di distribuire file application.properties sui propri microservizi.
- Poiché le istanze del tuo servizio possono cambiare dinamicamente la loro posizione, vale la pena guardare i servizi che sanno dove risiedono i tuoi servizi, quali sono i loro IP e come instradarli.
- Ora che tutto è dinamico, sorgono nuovi problemi, come l’elezione automatica di un leader: chi è il maestro che lavora su determinati compiti, per non elaborarli due volte, ad esempio? Chi sostituisce un leader quando fallisce? Su quali basi avviene la sostituzione?
Come organizzare l'autorizzazione e l'autenticazione utilizzando i microservizi Java?
Anche questo argomento merita una storia a parte. Anche in questo caso, le opzioni vanno dall'autenticazione HTTPS di base codificata con framework di sicurezza personalizzati all'esecuzione di un'installazione Oauth2 con il proprio server di autorizzazione.Come posso assicurarmi che tutti i miei ambienti abbiano lo stesso aspetto?
Ciò che vale per le distribuzioni senza microservizio vale anche per le distribuzioni che ne hanno uno. Prova una combinazione di Docker/Testcontainers e Scripting/Ansible.Nessuna domanda: brevemente su YAML
Allontaniamoci per un momento dalle librerie e dai problemi correlati e diamo una rapida occhiata a Yaml. Questo formato di file viene utilizzato di fatto come formato per "scrivere la configurazione come codice". Viene utilizzato anche da strumenti semplici come Ansible e giganti come Kubernetes. Per provare il problema del rientro YAML, prova a scrivere un semplice file Ansible e vedi quanto devi modificare il file prima che funzioni come previsto. E questo nonostante il formato sia supportato da tutti i principali IDE! Dopodiché, torna per finire di leggere questa guida.Yaml:
- is:
- so
- great
E le transazioni distribuite? Test delle prestazioni? Altri argomenti?
Forse un giorno, nelle future edizioni del manuale. Per ora, questo è tutto. Resta con noi!Problemi concettuali con i microservizi
Oltre ai problemi specifici dei microservizi in Java, ci sono altri problemi, diciamo, che compaiono in qualsiasi progetto di microservizi. Si riferiscono principalmente all'organizzazione, al team e al management.Mancata corrispondenza di frontend e backend
La mancata corrispondenza di frontend e backend è un problema molto comune in molti progetti di microservizi. Cosa significa? Solo che nei buoni vecchi monoliti, gli sviluppatori dell'interfaccia web avevano una fonte specifica per ottenere dati. Nei progetti di microservizi, gli sviluppatori front-end improvvisamente hanno n fonti da cui ottenere dati. Immagina di creare una sorta di progetto di microservizi IoT (Internet of Things) in Java. Diciamo che gestisci macchine geodetiche e forni industriali in tutta Europa. E questi forni ti inviano aggiornamenti regolari con le loro temperature e simili. Prima o poi potresti voler trovare i forni nell'interfaccia utente di amministrazione, magari utilizzando i microservizi "ricerca forno". A seconda della severità con cui le controparti backend applicano le leggi sulla progettazione basata sul dominio o sui microservizi, un microservizio "trova forno" può restituire solo gli ID forno e non altri dati come tipo, modello o posizione. Per fare ciò, gli sviluppatori frontend dovranno effettuare una o n chiamate aggiuntive (a seconda dell'implementazione del paging) nel microservizio "ottieni dati forno" con gli ID ricevuti dal primo microservizio. E sebbene questo sia solo un semplice esempio, seppure tratto da un progetto reale (!), dimostra anche il seguente problema: i supermercati sono diventati estremamente popolari. Questo perché con loro non devi andare in 10 posti diversi per comprare verdura, limonata, pizza surgelata e carta igienica. Invece, vai in un posto: è più facile e veloce. Lo stesso vale per gli sviluppatori front-end e di microservizi.Aspettative della gestione
Il management ha l’errata impressione di dover assumere un numero infinito di sviluppatori per un progetto (generale), poiché ora gli sviluppatori possono lavorare in modo completamente indipendente l’uno dall’altro, ciascuno sul proprio microservizio. Alla fine (poco prima del lancio) è necessario solo un piccolo lavoro di integrazione. In realtà, questo approccio è estremamente problematico. Nei paragrafi successivi cercheremo di spiegare il perché.I “pezzi più piccoli” non equivalgono a “pezzi migliori”
Sarebbe un grosso errore presumere che il codice diviso in 20 parti sarà necessariamente di qualità superiore rispetto a un pezzo intero. Anche se consideriamo la qualità da un punto di vista puramente tecnico, i nostri singoli servizi potrebbero comunque eseguire 400 query di Hibernate per selezionare un utente dal database, attraversando strati di codice non supportato. Ancora una volta, torniamo alla citazione di Simon Brown: se non si riescono a costruire correttamente i monoliti, sarà difficile costruire microservizi adeguati. Spesso è estremamente tardi per parlare di tolleranza agli errori nei progetti di microservizi. Tanto che a volte è spaventoso vedere come funzionano i microservizi nei progetti reali. La ragione di ciò è che gli sviluppatori Java non sono sempre pronti a studiare la tolleranza agli errori, il networking e altri argomenti correlati al livello adeguato. I “pezzi” stessi sono più piccoli, ma le “parti tecniche” sono più grandi. Immagina che al tuo team di microservizi venga chiesto di scrivere un microservizio tecnico per l'accesso a un sistema di database, qualcosa del genere:@Controller
class LoginController {
// ...
@PostMapping("/login")
public boolean login(String username, String password) {
User user = userDao.findByUserName(username);
if (user == null) {
// обработка варианта с несуществующим пользователем
return false;
}
if (!user.getPassword().equals(hashed(password))) {
// обработка неверного пароля
return false;
}
// 'Ю-ху, залогинorсь!';
// установите cookies, делайте, что угодно
return true;
}
}
Ora il tuo team potrebbe decidere (e forse anche convincere gli uomini d'affari) che questo è tutto troppo semplice e noioso, invece di scrivere un servizio di accesso, è meglio scrivere un microservizio UserStateChanged davvero utile senza alcuna implicazione aziendale reale e tangibile. E poiché alcune persone attualmente trattano Java come un dinosauro, scriviamo il nostro microservizio UserStateChanged nell'elegante Erlang. E proviamo a usare gli alberi rosso-neri da qualche parte, perché Steve Yegge ha scritto che devi conoscerli a fondo per candidarti a Google. Dal punto di vista dell'integrazione, della manutenzione e della progettazione complessiva, questo è altrettanto negativo quanto scrivere strati di codice spaghetti all'interno di un singolo monolite. Un esempio artificiale e banale? Questo è vero. Tuttavia, questo può accadere nella realtà.
Meno pezzi, meno comprensione
Quindi sorge naturalmente la domanda sulla comprensione del sistema nel suo insieme, dei suoi processi e flussi di lavoro, ma allo stesso tempo tu, come sviluppatore, sei responsabile solo di lavorare sul tuo microservizio isolato [95: login-101: updateUserProfile]. Si armonizza con il paragrafo precedente, ma a seconda dell'organizzazione, del livello di fiducia e di comunicazione, ciò può portare a molta confusione, alzate di spalle e colpe in caso di interruzione accidentale della catena dei microservizi. E non c’è nessuno che si assumerebbe la piena responsabilità di quanto accaduto. E non è affatto una questione di disonestà. In effetti, è molto difficile collegare le diverse parti e comprenderne il posto nel quadro generale del progetto.Comunicazioni e servizi
Il livello di comunicazione e di servizio varia notevolmente a seconda delle dimensioni dell'azienda. Tuttavia, la relazione generale è ovvia: più sono, più sono problematici.- Chi esegue il microservizio n. 47?
- Hanno appena distribuito una nuova versione incompatibile di un microservizio? Dove è stato documentato ciò?
- Con chi devo parlare per richiedere una nuova funzionalità?
- Chi supporterà quel microservizio a Erlang, dopo che l’unico che conosceva questa lingua lasciò l’azienda?
- Tutti i nostri team di microservizi lavorano non solo con linguaggi di programmazione diversi, ma anche con fusi orari diversi! Come coordiniamo correttamente tutto questo?
GO TO FULL VERSION