
Nel 2017 abbiamo vinto un concorso per lo sviluppo del nucleo transazionale del business degli investimenti di Alfa-Bank e abbiamo iniziato il lavoro (al HighLoad++ 2018 con una relazione sul nucleo del business degli investimenti Vladimir Dryinkin, responsabile della direzione del nucleo transazionale del business degli investimenti di Alfa-Bank). Questo sistema doveva aggregare dati sulle transazioni da diverse fonti in vari formati, normalizzare i dati in un formato unificato, conservarli e fornire accesso ad essi.
Durante lo sviluppo, il sistema è evoluto e ha acquisito funzionalità, e a un certo punto abbiamo capito che stavamo cristallizzando qualcosa di molto più grande rispetto a un semplice software applicativo creato per risolvere un insieme ben definito di compiti: abbiamo ottenuto un sistema per la costruzione di applicazioni distribuite con storage persistente. L'esperienza che abbiamo acquisito ha gettato le basi per un nuovo prodotto — (TDG).
Voglio parlare dell'architettura di TDG e delle soluzioni a cui siamo arrivati nel processo di sviluppo, presentare le funzionalità principali e mostrare come il nostro prodotto possa diventare la base per la creazione di soluzioni complete.
Architettonicamente abbiamo diviso il sistema in moduli separati ruoli, ciascuno responsabile della risoluzione di specifici ambiti di compiti. Un'istanza dell'applicazione avviata implementa uno o più tipi di ruoli. Nel cluster possono esserci più ruoli dello stesso tipo:

Connector
Il connettore è responsabile della connessione con il mondo esterno; il suo compito è ricevere la richiesta, analizzarla e, se il processo ha successo, inviare i dati per l'elaborazione all'input processor. Supportiamo i formati HTTP, SOAP, Kafka e FIX. L'architettura consente di aggiungere facilmente supporto per nuovi formati, e a breve verrà aggiunto il supporto per IBM MQ. Se l'analisi della richiesta termina con un errore, il connettore restituirà un errore; altrimenti, risponderà che la richiesta è stata elaborata con successo, anche se si è verificato un errore durante la successiva elaborazione. Questo è stato fatto appositamente per lavorare con sistemi che non possono ripetere le richieste o, al contrario, lo fanno in modo troppo insistente. Al fine di non perdere dati, viene utilizzata una coda di riparazione: l'oggetto viene inizialmente inserito in essa e solo dopo un'elaborazione riuscita viene rimosso. L'amministratore può ricevere notifiche sugli oggetti rimasti nella coda di riparazione e, dopo aver risolto un errore software o un guasto hardware, può tentare nuovamente.
Input processor
L'input processor classifica i dati ricevuti in base a caratteristiche distintive e chiama i gestori appropriati. I gestori sono codice scritto in Lua, eseguito in un sandbox, quindi non possono influenzare il funzionamento del sistema. A questo punto, i dati possono essere trasformati nel formato richiesto e, se necessario, possono essere avviati un numero arbitrario di attività che possono implementare la logica necessaria. Ad esempio, nel prodotto MDM (Master Data Management), costruito su Tarantool Data Grid, quando aggiungiamo un nuovo utente, per non rallentare l'elaborazione della richiesta, la creazione di una registrazione principale viene avviata come attività separata. Il sandbox supporta richieste di lettura, modifica e aggiunta di dati, e consente di eseguire una determinata funzione su tutti i ruoli di tipo storage e di aggregare il risultato (map/reduce).
I gestori possono essere descritti in file:
sum.lua
local x, y = unpack(...)
return x + yE poi, dichiarati nella configurazione:
functions:
sum: { __file: sum.lua }
Perché Lua? Lua è un linguaggio molto semplice. Dalla nostra esperienza, dopo un paio d'ore di familiarizzazione, le persone iniziano a scrivere codice che risolve i loro problemi. E non solo sviluppatori professionisti, ma anche, ad esempio, analisti. Inoltre, grazie al compilatore JIT, Lua funziona molto velocemente.
Storage
Il sistema di archiviazione conserva i dati persistenti. Prima di essere salvati, i dati vengono convalidati rispetto allo schema dei dati. Per descrivere lo schema utilizziamo un formato esteso. . Esempio:
{
"name": "User",
"type": "record",
"logicalType": "Aggregate",
"fields": [
{ "name": "id", "type": "string"},
{"name": "first_name", "type": "string"},
{"name": "last_name", "type": "string"}
],
"indexes": ["id"]
}Da questa descrizione viene generato automaticamente il DDL (Data Definition Language) per il database Tarantool e lo schema per l'accesso ai dati.
È supportata la replica asincrona dei dati (è previsto l'aggiunta della replica sincrona).
Processore di output
A volte è necessario avvisare i consumatori esterni dell'arrivo di nuovi dati, per questo esiste il ruolo di Processore di output. Dopo aver salvato i dati, possono essere inviati al rispettivo gestore (ad esempio, per trasformarli nel formato richiesto dal consumatore) — e poi inviati al connettore per l'invio. Qui viene utilizzata anche una coda di riparazione: se nessuno ha accettato l'oggetto, l'amministratore può riprovare più tardi.
Scalabilità
I ruoli di connettore, processore di input e processore di output non hanno stato, il che ci consente di scalare orizzontalmente il sistema semplicemente aggiungendo nuove istanze dell'applicazione con il ruolo del tipo necessario. Per la scalabilità orizzontale, l'archiviazione utilizza all'organizzazione del cluster utilizzando bucket virtuali. Dopo l'aggiunta di un nuovo server, parte dei bucket dei vecchi server viene spostata in background sul nuovo server; questo avviene in modo trasparente per gli utenti e non influisce sul funzionamento complessivo del sistema.
Proprietà dei dati
Gli oggetti possono essere molto grandi e contenere altri oggetti. Garantiamo l'atomicità nell'aggiunta e nell'aggiornamento dei dati, memorizzando l'oggetto con tutte le sue dipendenze su un unico bucket virtuale. In questo modo si esclude la "distribuzione" dell'oggetto su più server fisici.
È supportata la versioning: ogni aggiornamento di un oggetto crea una nuova versione, e possiamo sempre fare un'istantanea temporale e vedere come appariva il mondo all'epoca. Per i dati che non richiedono una lunga storia, possiamo limitare il numero di versioni o persino conservare solo una — l'ultima — disabilitando di fatto la versioning per un certo tipo. È possibile anche limitare la storia nel tempo: ad esempio, eliminando tutti gli oggetti di un certo tipo che hanno più di 1 anno. È supportata anche l'archiviazione: possiamo esportare oggetti più vecchi di un certo periodo liberando spazio nel cluster.
Problemi
Tra le funzionalità interessanti, c'è la possibilità di eseguire task secondo un programma, su richiesta dell'utente o programmaticamente dalla sandbox:

Qui vediamo un altro ruolo — runner. Questo ruolo non ha stato e, se necessario, si possono aggiungere ulteriori istanze dell'applicazione con questo ruolo al cluster. La responsabilità del runner è l'esecuzione dei task. Come detto, dalla sandbox è possibile generare nuovi task; essi vengono memorizzati in coda nello storage e poi eseguiti dal runner. Questo tipo di task si chiama Job. Abbiamo anche un tipo di task chiamato Task — sono task definiti dall'utente e avviati secondo un programma (si utilizza la sintassi cron) o su richiesta. Per avviare e monitorare tali task abbiamo un comodo gestore di task. Affinché questa funzionalità sia disponibile, è necessario attivare il ruolo scheduler; questo ruolo ha stato, perciò non è scalabile, il che non è comunque necessario; tuttavia, come tutti gli altri ruoli, può avere una replica che inizia a funzionare se il master dovesse guasti.
Logger
Un altro ruolo è chiamato logger. Raccoglie i log da tutti i membri del cluster e fornisce un'interfaccia per esportarli e visualizzarli tramite l'interfaccia web.
Servizi
Vale la pena menzionare che il sistema consente di creare facilmente servizi. Nel file di configurazione è possibile specificare quali richieste inviare al gestore scritto dall'utente, eseguito nella sandbox. In questo gestore si può, ad esempio, eseguire una qualche query analitica e restituire il risultato.
Il servizio è descritto nel file di configurazione:
services:
sum:
doc: "aggiunge due numeri"
function: sum
return_type: int
args:
x: int
y: int
L'API GraphQL è generata automaticamente e il servizio diventa disponibile per le chiamate:
query {
sum(x: 1, y: 2)
} Questo porterà alla chiamata del gestore sum, che restituirà il risultato:
3
Profilazione delle richieste e metriche
Per comprendere il funzionamento del sistema e profilarne le richieste, abbiamo implementato il supporto per il protocollo OpenTracing. Il sistema può, su richiesta, inviare informazioni ad strumenti che supportano questo protocollo, come Zipkin, permettendoti di capire come è stata eseguita la richiesta:

Naturalmente, il sistema fornisce metriche interne che possono essere raccolte tramite Prometheus e visualizzate tramite Grafana.
Deploy
Tarantool Data Grid può essere distribuito tramite pacchetti RPM o archivi, utilizzando l'utilità fornita o Ansible, ed è anche supportato da Kubernetes ().
Le applicazioni che implementano la logica di business (configurazione, gestori) vengono caricate nel cluster distribuito di Tarantool Data Grid in forma di archivio tramite UI o usando uno script, attraverso l'API che abbiamo fornito.
Esempi di applicazioni
Quali applicazioni possono essere create con Tarantool Data Grid? In realtà, la maggior parte dei compiti aziendali sono in qualche modo legati all'elaborazione di flussi di dati, alla loro memorizzazione e accesso. Quindi, se hai grandi flussi di dati che devono essere memorizzati in modo affidabile e a cui accedere, il nostro prodotto può farti risparmiare molto tempo nello sviluppo e concentrarti sulla tua logica di business.
Ad esempio, vogliamo raccogliere informazioni sul mercato immobiliare per poter successivamente conoscere le offerte più vantaggiose. In questo caso, evidenzieremo i seguenti compiti:
- I robot che raccolgono informazioni da fonti aperte saranno le nostre fonti di dati. Questo compito puoi risolverlo utilizzando soluzioni pronte o scrivendo codice in qualsiasi lingua.
- Successivamente, Tarantool Data Grid accetterà e salverà i dati. Se il formato dei dati provenienti da diverse fonti è diverso, puoi scrivere codice in Lua che eseguirà la conversione in un formato unificato. Nella fase di pre-elaborazione, potrai anche, ad esempio, filtrare le offerte duplicate o aggiornare ulteriormente nel database le informazioni sugli agenti che operano sul mercato.
- Ora hai già una soluzione scalabile nel cluster che puoi popolare con dati e fare query sui dati. Puoi quindi implementare nuove funzionalità, ad esempio scrivere un servizio che interroghi i dati e fornisca l'offerta più vantaggiosa della giornata — questo richiederà alcune righe nel file di configurazione e un po' di codice in Lua.
Cosa succede dopo?
Per noi è prioritario aumentare la semplicità di sviluppo grazie a . Ad esempio, questa è un'IDE con supporto per il profiling e il debug degli handler che funzionano in sandbox.
Inoltre, prestiamo grande attenzione alla sicurezza. Proprio ora stiamo affrontando la certificazione del FSTEC russo per confermare l'elevato livello di sicurezza e soddisfare i requisiti per la certificazione dei prodotti software utilizzati nei sistemi informativi di dati personali e nei sistemi informativi statali.
Fonte: habr.com
