Condividi tramite


Perché usare i flussi in Orleans?

Ci sono già numerose tecnologie che consentono di creare sistemi di elaborazione dei flussi. Tra queste ci sono i sistemi per archiviare in modo permanente i dati dei flussi (ad esempio, Hub eventie Kafka) e i sistemi per esprimere operazioni di calcolo sui dati dei flussi (ad esempio, Analisi di flusso di Azure, Apache Storm e Apache Spark Streaming). Si tratta di ottimi sistemi che consentono di creare pipeline di elaborazione dei flussi di dati efficienti.

Limitazioni dei sistemi esistenti

Tuttavia, questi sistemi non sono adatti per operazioni di calcolo freeform con granularità fine sui dati dei flussi. I sistemi di calcolo per i flussi menzionati in precedenza consentono di specificare un grafico unificato del flusso di dati delle operazioni applicate nello stesso modo a tutti gli elementi del flusso. Si tratta di un modello potente quando i dati sono uniformi e si vuole esprimere lo stesso set di operazioni di trasformazione, filtro o aggregazione su questi dati. Ci sono tuttavia altri casi d'uso in cui è necessario esprimere operazioni fondamentalmente diverse su elementi di dati differenti. E in alcuni casi, come parte di questa elaborazione, è necessario eseguire occasionalmente una chiamata esterna, ad esempio richiamare un'API REST arbitraria. I motori di elaborazione dei flussi di dati unificati non supportano questi scenari oppure li supportano in modo limitato e vincolato o ancora non sono efficienti nel supportarli. Ciò è dovuto al fatto che sono intrinsecamente ottimizzati per un volume elevato di elementi simili e in genere limitati in termini di espressività ed elaborazione. I flussi di Orleans consentono di gestire questi altri scenari.

Motivazione

Tutto è iniziato con le richieste da parte degli utenti di Orleans di supportare la restituzione di una sequenza di elementi da una chiamata a un metodo del grano. Come si può immaginare, questa era solo la punta dell'iceberg. Era necessario fare molto di più.

Uno scenario tipico per i flussi di Orleans è il caso in cui si dispone di flussi per utente e si vuole eseguire un'elaborazione diversa per ogni utente, all'interno del contesto di un singolo utente. Possono esserci milioni di utenti, ma alcuni di loro sono interessati al meteo e possono sottoscrivere avvisi meteo per una determinata località, mentre altri sono interessati agli eventi sportivi e altri ancora vogliono monitorare lo stato di un determinato volo. L'elaborazione di tali eventi richiede una logica diversa, ma non si vogliono eseguire due istanze indipendenti dell'elaborazione di flussi. Alcuni utenti sono interessati solo a un'azione specifica e solo se si applica una determinata condizione esterna, che potrebbe non necessariamente far parte dei dati del flusso (e pertanto deve essere controllata dinamicamente in fase di esecuzione come parte dell'elaborazione).

Gli utenti cambiano continuamente interessi, quindi le sottoscrizioni di flussi di eventi specifici cambiano dinamicamente e di conseguenza anche la topologia dei flussi cambia in modo dinamico e rapido. Oltre a ciò, anche la logica di elaborazione per utente si evolve e cambia in modo dinamico, in base allo stato dell'utente e a eventi esterni. Gli eventi esterni possono modificare la logica di elaborazione per un determinato utente. Ad esempio, in un sistema di rilevamento degli imbrogli durante un gioco, quando viene individuato un nuovo modo di imbrogliare, è necessario aggiornare la logica di elaborazione con la nuova regola per rilevare questa nuova violazione. Questa operazione deve essere eseguita naturalmente senza interrompere la pipeline di elaborazione in corso. I motori di elaborazione dei flussi di dati in blocco non sono stati creati per supportare questi scenari.

Ovviamente, un sistema di questo tipo deve essere eseguito in più computer connessi in rete, non in un singolo nodo. Di conseguenza, la logica di elaborazione deve essere distribuita in modo scalabile ed elastico in un cluster di server.

Nuovi requisiti

Sono stati identificati 4 requisiti di base per il sistema di elaborazione di flussi che consentono di gestire gli scenari precedenti.

  1. Logica di elaborazione dei flussi flessibile
  2. Supporto per topologie altamente dinamiche
  3. Granularità fine dei flussi
  4. Distribuzione

Logica di elaborazione dei flussi flessibile

Il sistema deve supportare diversi modi per esprimere la logica di elaborazione dei flussi. I sistemi esistenti menzionati in precedenza richiedono che lo sviluppatore scriva un grafico di calcolo del flusso di dati dichiarativo, in genere seguendo uno stile di programmazione funzionale. Ciò limita l'espressività e la flessibilità della logica di elaborazione. I flussi di Orleans non sono influenzati dal modo in cui viene espressa la logica di elaborazione. Può essere espressa come flusso di dati (ad esempio, usando le estensioni Rx (Reactive Extensions) in .NET), come programma funzionale, come query dichiarativa o in una logica imperativa generale. La logica può essere con stato o senza stato, può avere o meno effetti collaterali e può attivare azioni esterne. Tutto il potere è nelle mani dello sviluppatore.

Supporto per topologie dinamiche

Il sistema deve consentire topologie che si evolvono in modo dinamico. I sistemi esistenti indicati in precedenza sono in genere limitati solo alle topologie statiche, fissate in fase di distribuzione e che non possono evolversi in fase di esecuzione. Nell'esempio seguente di espressione di un flusso di dati, tutto è perfetto fino a quando non è necessario apportare modifiche.

Stream.GroupBy(x=> x.key).Extract(x=>x.field).Select(x=>x+2).AverageWindow(x, 5sec).Where(x=>x > 0.8) *

Si immagini di modificare la condizione di soglia nel filtro Where, aggiungere un'istruzione Select o aggiungere un altro ramo nel grafico del flusso di dati e produrre un nuovo flusso di output. Nei sistemi esistenti, questo non è possibile senza rimuovere l'intera topologia e riavviare il flusso di dati da zero. In pratica, questi sistemi controllano il calcolo esistente e possono eseguire il riavvio dal checkpoint più recente. Un riavvio di questo tipo è tuttavia dannoso e costoso per un servizio online che produce risultati in tempo reale. Un riavvio di questo tipo diventa particolarmente poco pratico in presenza di un numero elevato di espressioni di questo tipo eseguite con parametri simili ma diversi (per utente, per dispositivo e così via) e che cambiano continuamente.

È necessario che il sistema consenta l'evoluzione del grafico di elaborazione dei flussi in fase di esecuzione, aggiungendo nuovi collegamenti o nodi al grafico di calcolo o modificando la logica di elaborazione all'interno dei nodi di calcolo.

Granularità fine dei flussi

Nei sistemi esistenti, l'unità di astrazione più piccola è in genere l'intero flusso (topologia). Molti degli scenari esaminati qui richiedono tuttavia che un singolo nodo/collegamento nella topologia sia un'entità logica autonoma. In questo modo ogni entità può essere potenzialmente gestita in modo indipendente. In una topologia di flusso di grandi dimensioni che comprende più collegamenti, ad esempio, collegamenti diversi possono avere caratteristiche differenti e possono essere implementati in diversi trasporti fisici. Alcuni collegamenti possono passare attraverso socket TCP, mentre altri in code affidabili. Collegamenti diversi possono avere garanzie di recapito differenti. I diversi nodi possono avere strategie di checkpoint differenti e la logica di elaborazione può essere espressa in modelli diversi o anche in linguaggi diversi. Tale flessibilità non è in genere possibile nei sistemi esistenti.

L'argomento relativo all'unità di astrazione e alla flessibilità è simile a un confronto tra SoA (Service Oriented Architecture) e attori. I sistemi basati su attori consentono una maggiore flessibilità poiché ogni attore è essenzialmente un piccolo servizio gestito in modo indipendente. Analogamente, anche il sistema di flussi deve consentire un controllo con granularità fine.

Distribuzione

E, naturalmente, il sistema dovrebbe avere tutte le proprietà di un "buon sistema distribuito". Ovvero:

  1. Scalabilità: supporto di un numero elevato di flussi ed elementi di calcolo.
  2. Elasticità: possibilità di aggiungere/rimuovere risorse per aumentare/ridurre le istanze in base al carico.
  3. Affidabilità: resilienza agli errori.
  4. Efficienza: uso delle risorse sottostanti in modo efficiente.
  5. Velocità di risposta: capacità di gestire scenari quasi in tempo reale.

Questi sono stati i requisiti su cui si è basata la creazione dei flussi di Orleans.

Chiarimento: Orleans attualmente non supporta direttamente la scrittura di espressioni di flussi di dati dichiarative come nell'esempio precedente. Le attuali API di flusso di Orleans sono blocchi predefiniti di livello più basso, come descritto qui. Fornire espressioni di flussi di dati dichiarative è un obiettivo per il futuro.

Vedi anche

API di programmazione dei flussi di Orleans