Il 26 febbraio abbiamo tenuto un meetup su Apache Ignite GreenSource, dove hanno parlato i contributori del progetto open source . Un evento importante nella vita di questa comunità è stata la ristrutturazione del componente , che consente di distribuire microservizi personalizzati direttamente nel cluster Ignite. Di questo processo complesso ha parlato al meetup , ingegnere del software e da oltre due anni contributore di Apache Ignite.

Iniziamo con cosa sia Apache Ignite in generale. È un database che rappresenta un archivio distribuito Key/Value con supporto per SQL, transazionalità e caching. Inoltre, Ignite consente di distribuire servizi personalizzati direttamente nel cluster Ignite. Gli sviluppatori hanno accesso a tutti gli strumenti forniti da Ignite: strutture dati distribuite, Messaging, Streaming, Compute e Data Grid. Ad esempio, utilizzando il Data Grid, si elimina il problema della gestione di un'infrastruttura separata per l'archiviazione dei dati e, di conseguenza, le spese generali correlate.

Utilizzando l'API Service Grid, è possibile distribuire un servizio semplicemente specificando nella configurazione lo schema di distribuzione e, di conseguenza, il servizio stesso.
Di solito, lo schema di distribuzione indica il numero di istanze che devono essere distribuite sui nodi del cluster. Ci sono due schemi di distribuzione tipici. Il primo è Cluster Singleton: in qualsiasi momento nel cluster sarà garantito che ci sia un solo esemplare del servizio personalizzato. Il secondo è Node Singleton: su ogni nodo del cluster è distribuito un esemplare del servizio.

Inoltre, l'utente può specificare il numero di istanze del servizio nell'intero cluster e definire un predicato per filtrare i nodi appropriati. In questo scenario, il Service Grid calcolerà autonomamente la distribuzione ottimale per la distribuzione dei servizi.
Inoltre, esiste una funzionalità chiamata Affinity Service. Affinity è una funzione che determina la connessione tra le chiavi e le partizioni e la connessione tra le partizioni e i nodi nella topologia. Con una chiave, è possibile determinare il nodo primario su cui sono memorizzati i dati. In questo modo, puoi associare il tuo servizio con una chiave e la cache della funzione di affinità. In caso di modifica della funzione di affinità, si verificherà un ridistribuzione automatica. Così il servizio sarà sempre posizionato vicino ai dati con cui deve lavorare, riducendo di conseguenza i costi per l'accesso alle informazioni. Questo schema può essere definito una sorta di calcolo collocato.
Ora che abbiamo esplorato il fascino del Service Grid, parleremo della sua storia di sviluppo.
Cosa c'era prima
La precedente implementazione del Service Grid si basava su una cache di sistema replicata transazionale chiamata Ignite. Con la parola "cache" in Ignite si intende uno storage. Cioè, non è qualcosa di temporaneo, come si potrebbe pensare. Nonostante la cache sia replicata e ogni nodo contenga l'intero set di dati, all'interno la cache ha una rappresentazione partizionata. Questo è correlato all'ottimizzazione degli storage.

Cosa succedeva quando un utente voleva distribuire un servizio?
- Tutti i nodi nel cluster si iscrivevano agli aggiornamenti dei dati nello storage tramite un meccanismo integrato di Continuous Query.
- Il nodo iniziatore, sotto una transazione read-committed, inseriva nel database una registrazione che conteneva la configurazione del servizio, incluso l'istanza serializzata.
- Quando riceveva una notifica di una nuova registrazione, il coordinatore calcolava la distribuzione in base alla configurazione. L'oggetto ottenuto veniva scritto di nuovo nel database.
- Se il nodo era incluso nella distribuzione, il coordinatore doveva distribuirlo.
Cosa non andava bene
A un certo punto, siamo giunti alla conclusione che non si poteva lavorare in questo modo con i servizi. Ci sono state diverse ragioni.
Se durante la distribuzione si verificava un errore, si poteva venire a conoscenza solo dai log di quel nodo, dove tutto era accaduto. Esisteva solo una distribuzione asincrona, quindi dopo che il controllo veniva restituito all'utente dal metodo di distribuzione, ci voleva del tempo aggiuntivo per l'avvio del servizio — e in quel momento l'utente non poteva gestire nulla. Per sviluppare ulteriormente il Service Grid, implementare nuove funzionalità, attirare nuovi utenti e rendere la vita più semplice a tutti, era necessario cambiare qualcosa.
Nella progettazione del nuovo Service Grid, la nostra priorità era garantire un deployment sincrono: non appena il controllo torna all'utente dall'API, può immediatamente utilizzare i servizi. Volevamo anche dare all'iniziatore la possibilità di gestire gli errori di deployment.
Inoltre, volevamo semplificare l'implementazione, ossia allontanarci dalle transazioni e dal rebalance. Nonostante la cache fosse replicabile e non ci fossero bilanciamenti, durante un grande deployment con molte nodi si sono presentati problemi. Quando la topologia cambia, le nodi devono scambiarsi informazioni, e durante un grande deployment, questi dati possono pesare molto.
Quando la topologia era instabile, il coordinatore doveva ricalcolare la distribuzione dei servizi. E in generale, quando si lavora con transazioni su una topologia instabile, questo può portare a errori difficilmente prevedibili.
Problemi
Quali possono essere le grandi trasformazioni senza problemi collaterali? Il primo è stato il cambiamento della topologia. È importante capire che in qualsiasi momento, anche durante il deployment di un servizio, un nodo può entrare o uscire dal cluster. Inoltre, se durante il deployment un nodo entra nel cluster, sarà necessario trasferire in modo coerente tutte le informazioni sui servizi al nuovo nodo. E non si tratta solo di ciò che è già stato implementato, ma anche dei deployment attuali e futuri.
Questa è solo una delle problematiche che possono essere raccolte in un elenco separato:
- Come si può deployare servizi configurati staticamente all'avvio di un nodo?
- Cosa fare se un nodo esce dal cluster ed era l'host di servizi?
- Cosa fare se il coordinatore cambia?
- Cosa fare se un client si riconnette al cluster?
- È necessario elaborare richiesta di attivazione/disattivazione e come?
- E se fosse stata chiamata la distruzione della cache, e noi abbiamo servizi affini legati ad essa?
E questo è solo l'inizio.
Soluzione
Come obiettivo abbiamo scelto l'approccio Event Driven con comunicazione tra processi tramite messaggi. In Ignite sono già stati implementati due componenti che consentono ai nodi di inviare messaggi tra loro: communication-spi e discovery-spi.

Communication-spi consente ai nodi di comunicare direttamente e inviare messaggi. È particolarmente adatto per la trasmissione di grandi volumi di dati. Discovery-spi permette di inviare un messaggio a tutti i nodi nel cluster. Nella sua implementazione standard, questo avviene con una topologia "anello". Esiste inoltre un'integrazione con Zookeeper, nel qual caso si utilizza una topologia "stella". Vale la pena notare un aspetto importante: discovery-spi fornisce garanzie sul fatto che il messaggio sarà consegnato nell'ordine corretto a tutti i nodi.
Consideriamo il protocollo di deploy. Tutte le richieste degli utenti per il deploy e l'un-deploy vengono inviate tramite discovery-spi. Questo offre le seguenti garanzie:
- La richiesta sarà ricevuta da tutti i nodi nel cluster. Questo consentirà di continuare a elaborare la richiesta in caso di cambio del coordinatore. Significa anche che per un singolo messaggio ogni nodo avrà a disposizione tutte le metadati necessarie, come la configurazione del servizio e la sua istanza serializzata.
- L'ordine rigido di consegna dei messaggi consente di risolvere conflitti tra configurazioni e richieste concorrenti.
- Poiché l'ingresso del nodo nella topologia viene gestito anch'esso tramite discovery-spi, anche il nuovo nodo riceverà tutti i dati necessari per interagire con i servizi.
Quando ricevono una richiesta, i nodi nel cluster la convalidano e formano compiti da elaborare. Questi compiti vengono messi in coda e successivamente elaborati in un altro thread da un lavoratore distinto. Questo è stato implementato in questo modo perché il deploy può richiedere un tempo significativo e ritardare il costoso flusso discovery è inaccettabile.
Tutte le richieste in coda vengono elaborate dal deployment-manager. Esso dispone di un lavoratore speciale che estrae un compito da questa coda e lo inizializza per avviare il deploy. Successivamente si svolgono le seguenti operazioni:
- Ogni nodo calcola autonomamente la distribuzione grazie a una nuova funzione di assegnazione deterministica.
- I nodi creano un messaggio con i risultati del deploy e lo inviano al coordinatore.
- Il coordinatore aggrega tutti i messaggi e forma il risultato dell'intero processo di deploy, che viene inviato tramite discovery-spi a tutti i nodi nel cluster.
- Al ricevimento del risultato, il processo di deploy termina, dopodiché il compito viene rimosso dalla coda.

Nuovo design event-driven: org.apache.ignite.internal.processors.service.IgniteServiceProcessor.java
Se si verifica un errore durante il dispiegamento, il nodo include immediatamente questo errore nel messaggio che invia al coordinatore. Dopo l'aggregazione dei messaggi, il coordinatore avrà informazioni su tutti gli errori durante il dispiegamento e invierà questo messaggio tramite discovery-spi. Le informazioni sugli errori saranno disponibili su qualsiasi nodo nel cluster.
Secondo questo algoritmo, vengono elaborati tutti gli eventi importanti nel Service Grid. Ad esempio, un cambiamento nella topologia è anch'esso un messaggio tramite discovery-spi. In generale, rispetto al passato, il protocollo risulta abbastanza leggero e affidabile. Tanto da poter gestire qualsiasi situazione durante il dispiegamento.
Cosa succederà dopo
Ora parliamo dei progetti. Qualsiasi grande modifica nel progetto Ignite viene eseguita come iniziativa per migliorare Ignite, il cosiddetto IEP. Anche il redesign del Service Grid ha il suo IEP — con il divertente titolo «Sostituzione dell'olio nel Service Grid». Ma in realtà abbiamo cambiato non solo l'olio nel motore, ma l'intero motore.
Abbiamo suddiviso i compiti nell'IEP in 2 fasi. La prima è una fase importante che consiste nella revisione del protocollo di dispiegamento. È già stata integrata nel master, quindi si può provare il nuovo Service Grid, che sarà disponibile nella versione 2.8. La seconda fase comprende molte altre attività:
- Ridispiegamento caldo
- Versionamento dei servizi
- Aumento della resilienza
- Client leggero
- Strumenti di monitoraggio e per il conteggio di varie metriche
Infine, possiamo consigliarvi il Service Grid per costruire sistemi ad alta disponibilità resilienti. Vi invitiamo anche a unirvi a noi in e per condividere la vostra esperienza. La vostra esperienza è realmente importante per la comunità, aiuterà a capire in che direzione andare e come sviluppare ulteriormente il componente.
Fonte: habr.com
