Java in pista!

Un sistema di telemetria per la Formula 1di e

Un sistema di distribuzione di dati in tempo reale progettato per la telemetria in Formula 1: problemi e soluzioni di un‘applicazione in un ambiente veramente impegnativo. La prossima volta che guardate un gran premio in televisione, ricordatevi che, dietro le quinte, Java svolge il suo importante compito.

Il contesto

Quando pensate, in riferimento ad un progetto, ad un "contesto impegnativo" cosa vi viene in mente? La Formula Uno? Be‘, sà¬, si tratta veramente di un ambiente estremamente impegnativo. In TV vediamo le monoposto sfrecciare sulla pista e sembra che si tratti "semplicemente" di correre più velocemente degli altri, ma dietro la facciata c‘è un mondo molto più complesso. Un mondo di computer che acquisiscono e trasmettono flussi di dati di telemetria in tempo reale e li distribuiscono agli ingegneri di gara e a software automatizzati per supportare le decisioni sulla condotta di gara, che vanno prese ogni volta in pochi secondi. E questo non accade nell‘ambiente comodo ed ovattato di un centro di calcolo di una banca, dove tutto è al suo posto, ci si siede in postazioni ergonomiche e con l‘aria condizionata. Accade invece in un garage stretto, rumoroso, tipicamente molto caldo (nel garage non c‘è l‘aria condizionata), dove personale molto impegnato si muove da una postazione all‘altra e dove non è improbabile che prima o poi un cavo si stacchi. A differenza della banca, qui tutta la rete e i dispositivi vengono montati e smontati ogni settimana in una diversa parte del mondo, sia che si tratti di gare che di sessioni di prova. Ma anche in queste condizioni tutto deve funzionare alla perfezione: quel flusso di dati di telemetria deve comunque arrivare a destinazione, perchà© un "buco" anche di soli pochi secondi può far sଠche un motorista non si accorga di una fluttuazione di pressione da qualche parte nel motore, qualcosa che in pochissimo tempo può diventare un problema imminente per il pilota ? e in poche parole, si perde la gara. Tutto ciò descrive abbastanza chiaramente dove stanno le difficoltà  di progettare e realizzare software che lavori in questo contesto.
Magneti Marelli non ha certo bisogno di presentazioni. L‘azienda produce componenti elettronici per il mercato dell‘automobile. La sua divisione Motorsport si dedica in particolare ai componenti per le auto da competizione (dalla Formula 1 ai rally): sistemi di controllo elettronici, sistemi di telemetria, acquisizione ed analisi dati, cruscotti e visualizzatori dati per i piloti (tanto per citare qualcosa di eclatante: i volanti delle monoposto di Schumacher e Barrichello), alternatori, regolatori di voltaggio, centraline di controllo per i motori, sistemi di ignezione elettronica, sensori vari. In particolare, il sistema di telemetria di Magneti Marelli è lo standard de facto in Formula 1 e gli annali ci dicono che dal 1992 ad oggi ha sempre fornito, tra le altre, la squadra che ha vinto il campionato.

Ma in cosa consiste, in sintesi, un sistema di telemetria? Alcuni componenti, gli acquisitori dati, sono installati sull‘auto. Basati su un sistema di networking interno chiamato CAN-BUS, queste centraline raccolgono una quantità  enorme di informazioni (più di un migliaio di canali diversi, dalla velocità  e numero di giri del motore fino a dati di pressione e temperatura delle varie componenti del motore) che vengono sia memorizzate dentro una specie di "scatola nera", sia trasmesse in tempo reale via radio. I ricevitori di questi canali sono situati in quei camion che si trovano in prossimità  dei box: chiunque abbia guardato in televisione almeno un paio di gare li avrà  sicuramente notati nelle riprese effettuate dall‘elicottero.

Un camion per ogni scuderia contiene l‘apparato radio di ricezione vero e proprio (in foto si possono notare le antenne) e i sistemi di networking in grado di fare il routing del traffico verso la rete interna della scuderia. Gli altri camion contengono le postazioni di lavoro degli ingegneri di gara, i quali possono monitorare i dati sui propri laptop. Altri ingegneri di gara sono situati dentro il box, con laptop e PC fissi (anche se, come vedremo tra poco, parlare di PC "fissi" in una scuderia di F1 è un po‘ un controsenso). Il numero di postazioni di lavoro varia da scuderia a scuderia, ma le più importanti ormai arrivano a parecchie decine. I software usati dagli ingegneri di gara consentono di graficare in tempo reale i dati ricevuti dall‘auto, ma anche di compiere operazioni molto più complesse, come l‘analisi con strumenti matematici per cose tipo il calcolo dell‘efficienza aerodinamica o per scoprire eventuali problemi.

Recentemente, proprio il numero di utenti del sistema di telemetria, sempre crescente, ha introdotto il problema della scalabilità , noto già  da parecchi anni nel mondo enterprise (a questo proposito aggiungiamo che, oltre agli utenti umani, esistono una serie di client automatici sviluppati dalle scuderie: software che segnalano allarmi, che memorizzano i dati nel database interno, in alcuni casi gateway che trasmettono alcuni dati ad un server web che a sua volta li diffonde in streaming agli utenti di Internet). Le classiche soluzioni client-server, basate in generale su Windows, hanno mostrato quindi tutti i loro limiti, essendo adatte a gestire un numero di client molto più basso. Come già  detto, si è riprodotta la stessa situazione nota nel mondo enterprise già  da qualche anno. Proprio quest‘esigenza ha convinto Magneti Marelli della necessità  di progettare un nuovo sistema di distribuzione dei dati ed è nato il progetto RTTS (Real Time Telemetry System), condotto congiuntamente da Magneti Marelli Motorsport e Sun Microsystems Italia.

Specifiche

Possiamo a questo punto elencare le specifiche di RTTS perchà© si possano contestualizzare le soluzioni progettuali che stiamo per presentare. RTTS deve:

  • fornire un flusso di dati di telemetria in tempo reale (soft real time);
  • pubblicare e recuperare dati di ambiente (si tratta di valori che vengono impostati manualmente dagli ingegneri di gara e che consistono in cose tipo la quantità  di carburante imbarcato all‘ultimo pit-stop, i settaggi degli alettoni, la temperatura dellegomme (sempre rilevata all‘ultimo pit-stop), eccetera;
  • fornire strumenti per la gestione ed il monitoraggio del suo funzionamento;
  • fornire una Java API per l‘integrazione di software ad-hoc sviluppato dalle scuderie;
  • integrarsi con i dispositivi esistenti richiedendo il minimo numero di cambiamenti.

Da un punto di vista non funzionale:

  • RTTS deve gestire fino a sei centraline differenti (due per auto in ridondanza, con un massimo di tre auto in pista durante le prove)
  • ogni canale può trasportare più di 2 Mbyte al secondo di dati;
  • la latenza massima (il tempo che trascorre dalla ricezione del dato da parte dell‘apparato radio fino alla ricezione sul client) dev‘essere inferiore ai 50ms;
  • devono essere supportati fino ad un centinaio di client;
  • il sistema deve effettuare un eventuale switching su un sistema di backup in pochi secondi da quando viene rilevato un problema;
  • il sistema deve essere portabile (alcune scuderie usano Windows, altre Linux);
  • opzionalmente, deve essere possibile garantire di non perdere alcun dato (mediante bufferizzazione e ritrasmissione) anche in caso di disconnessione del sistema per alcuni secondi;
  • l‘installazione deve essere semplificata al massimo: in particolare sui client è richiesta la funzionalità  "plug and play" (il sistema non deve richiedere configurazione, ma una semplice installazione da CD-ROM); i client devono inoltre aggiornare il software in
  • automatico, richiedendo al massimo una installazione fresca all‘anno.

Ci piace subito sottolineare, a riprova di quanti passi avanti abbia compiuto Java in questi dieci anni, che uno dei problemi che non abbiamo avuto è stato quello della performance. In particolare, sul requisito della latenza siamo riusciti, senza troppi problemi, a garantire tempi non superiori a 10ms, molto al di sotto dei requisiti richiesti (per completezza sarebbe necessario dare qualche riferimento alla classe di hardware utilizzato: in sommi capi, perchà© varia da scuderia a scuderia, i client sono laptop di fascia altra e i server tipicamente sono macchine biprocessore di fascia medio-bassa. Insomma, niente di fantascientifico). Chissà  se in futuro finirà  finalmente la leggenda urbana che dice che "Java è lento"?
Anticipiamo subito che i punti chiave dei requisiti sono affidabilità  (gestione di eventuali disconnessioni) e della funzionalità  "plug and play": essi hanno guidato la scelta delle tecnologie utilizzate, focalizzando la nostra attenzione in particolare su Jini. Ã? quindi il momento di parlare delle tecnologie utilizzate.

Tecnologie utilizzate

Due sono i pilastri tecnologici di RTTS: Jini e Rio. Per chi non li conoscesse, vediamo una descrizione a grandi linee della loro struttura.
Jini è una piattaforma per la realizzazione di architetture service-oriented basate su proxy. L‘idea è quella di un sistema distribuito in cui varie parti collaborano tra di loro ed il progettista si focalizza sulla descrizione ad alto livello delle loro interfacce, senza doversi preoccupare troppo dei protocolli di comunicazione sottostanti. Anzi, questi devono poter essere cambiati con semplicità  senza che il sistema abbia troppi impatti. Inoltre, i vari componenti devono "trovarsi" automaticamente, senza conoscere a priori la struttura della rete e la loro posizione sui vari nodi. A parte un registro centralizzato, che serve unicamente alla localizzazione, il sistema è un peer-to-peer: non esiste un componente centrale che abbia responsabilità  di controllo nelle interazioni, ma ogni coppia di componenti è autonoma. A tale proposito si dice che Jini consente la creazione di "federazioni di servizi".

Nella figura 4 vediamo cosa succede quando un nuovo servizio viene pubblicato (cioè quando viene attivato per la prima volta). In questi diagrammi abbiamo indicato in verde un servizio, in giallo i client del servizio ed in blu i componenti standard di Jini. Il servizio prima di tutto cerca di scoprire dove si trovi il registro (chiamato Lookup Service, in breve LUS) mediante un protocollo di discovery che per default è basato su multicasting UDP.

Una volta individuato, la comunicazione diretta con il LUS procede con un protocollo punto punto, e in maggior dettaglio attraverso RMI (Remote Method Invocation). Il servizio serializza il codice del proprio proxy e lo trasmette al LUS, il quale lo memorizza permanentemente al suo interno associandolo al nome del servizio ed ad un identificativo univoco. Il proxy altro non è che l‘implementazione dell‘interfaccia del servizio, che contiene tutto quanto serve a contattare remotamente il servizio originale. Tipicamente Jini fornisce un‘implementazione di default per i proxy, basata sul citato RMI; ma è possibile specificarne anche di customizzate (e, come vedremo, questa caratteristica è stata fondamentale per RTTS).
Nella figura 5 vediamo cosa accade quando un client chiede la connessione con il servizio. Dopo aver "scoperto" il LUS, con modalità  identiche a quelle descritte precedentemente, il client specifica al LUS l‘identificativo (o il nome) del servizio richiesto. Il LUS risponde fornendo il proxy precedentemente pubblicato.
A questo punto (figura 6) client e servizio dialogano direttamente tra di loro, per mezzo del proxy, senza più alcun intervento del LUS.

A questa infrastruttura di base, Jini aggiunge meccanismi di "service leasing" che servono a verificare periodicamente che il servizio che sta dietro al proxy continui ad esistere e che sia raggiungibile. In caso negativo, al client viene notificato un messaggio di errore quando si cerca di invocare un metodo del servizio. Inoltre, il LUS gestisce un sistema di eventi distribuiti, basati sul pattern "observer" (o "listener"), attraverso i quali ogni client può avere una notifica asincrona della pubblicazione di un nuovo servizio o della sparizione di un servizio esistente.Jini fornisce anche un sofisticato sistema di configurazione dei componenti basato su una specie di linguaggio di scripting simile a Java, che consente di mescolare definizioni statiche a variabili valorizzate eseguendo espressioni Java specifiche.
Jini supporta anche altre funzionalità , come transazioni distribuite o sistemi di persistenza distribuiti, ma non sono state utilizzate da RTTS e pertanto non ne parliamo.
Da questa sommaria descrizione si capisce come Jini si occupi di implementare le funzionalità  di installazione "plug and play" e di gestione della disconnessione dei cavi. Tuttavia, come vedremo tra poco, esse sono state notevolmente customizzate dai progettisti di RTTS per ottenere tempi di reazione compatibili con le specifiche: di sua iniziativa Jini prende le cose con un po‘ più di calma. Fortunatamente, la struttura modulare ed espandibile di questa piattaforma tecnologica ha reso tutto più semplice.

Il secondo pilastro tecnologico, come anticipato, è Rio. Rio è una piattaforma open-source, sviluppata sempre da Sun Microsystems, che si appoggia a Jini (del quale mantiene le caratteristiche fondamentali) ed aggiunge alcune funzionalità  fondamentali:
Una struttura container / componente. I servizi Rio sono implementati sotto forma di ServiceBeans (JSB), cioè componenti che implementano una specifica interfaccia che consente al container di controllare il loro ciclo di vita, in modo simile a quanto avviene con gli EJB di J2EE. I container vengon chiamati "Cybernode".
Un sistema di provisioning che controlla dinamicamente il deploying dei ServiceBean (e qui Rio si differenzia nettamente da J2EE in quanto gli EJB vengono deployati staticamente) secondo regole predefinite. Tali regole possono per esempio basarsi sulla quantità  di memoria e di disco liberi e sulla percentuale di CPU utilizzata (sà¬, se qualcuno sta pensando al grid computing la pensata è giusta: Rio è un prodotto pensato proprio per questo scopo). La versione attuale di RTTS, tuttavia, non utilizza Rio in modo cosଠsofisticato: le regole sono solo statiche e definiscono semplicemente i profili dei vari nodi di rete (server primario, server secondario e client). Rio però risolve egregiamente il problema dell‘aggiornamento del software dal momento che il codice dei ServiceBean viene sempre scaricato dinamicamente dai client: pertanto, per aggiornare il sistema è sufficiente aggiornare il server primario.
Un sistema di fault detection che comprende lo switching a caldo di un sistema primario con la sua controparte di backup. Questa funzionalità  è stata utilizzata per gestire il distacco inaspettato di un cavo di rete o il fallimento del server primario; ma anche in questo caso si è intervenuti con codice customizzato per migliorare i tempi di reazione.
Un sistema di monitoraggio che consiste sostanzialmente in una API Java per verificare la raggiungibilità  e l‘operatività  dei vari componenti del sistema (inclusi i ServiceBean). Questa API è stata la base per sviluppare MoMa, la console grafica di controllo di RTTS che permette all‘amministratore di sistema di vedere tutto RTTS in un solo colpo d‘occhio, inclusi eventuali segnali di allarme che indicano un malfunzionamento.
A questi due pilastri già  citati aggiungiamo JGroups, un prodotto (sempre open-source) per l‘implementazione di protocolli di reliable multicasting. Dal momento che esso serve ad implementare una specifica opzionale, per la quale gli utilizzatori hanno dimostrato sul campo una necessità  molto inferiore alle loro stesse aspettative, ne discutiamo brevemente più avanti.

Architettura di base

A questo punto possiamo finalmente delineare l‘architettura di base di RTTS.

In figura è stato usato il colore giallo per indicare i componenti pre-esistenti del sistema: essi sono il ricevitore radio ("Device Rx") che fornisce i dati di telemetria, i visualizzatori dei dati ("Data Viewer") e i client che consentono la lettura e la pubblicazione dei dati di ambiente.
Esistono due tipi di "cybernode" (container Rio): un profilo "Server", che gira solo su una / due macchine del sistema (in regime di ridondanza) ed un profilo "Client" che gira su ogni client del sistema, e quindi in particolare sui laptop degli ingegneri di gara.
Prima di RTTS, i "Data Viewer" e i "Device Rx" colloquiavano direttamente, mediante un protocollo proprietario di Magneti Marelli chiamato "PBE Protocol", basato su pacchetti UDP. In pratica il "Data Viewer" spedisce al "Device Rx" una richiesta di invio del prossimo blocco dati, a cui segue in risposta il blocco dati stesso. Il "Device Rx" ha una limitata capacità  di buffering al suo interno, e questo meccanismo consente al "Data Viewer" di gestire eventuali periodi di intensa attività  senza perdere alcun dato, dal momento che è questo componente a controllarne la frequenza di invio. Una decina di "Data Viewer" potevano condividere i dati ricevuti da un "master" mediante tecnologia COM di Windows.

Con RTTS si è voluta mantenere invariata la modalità  di trasmissione dei dati, pertanto anche RTTS è capace di gestire il protocollo PBE. Questa volta è un componente interno, il "Device Handler", ad effettuare l‘interrogazione in vece del "Data Viewer". I dati ricevuti vengono subito inoltrati ad un canale di multicasting che permette la consegna contemporanea a tutti i client in ascolto in modo completamente scalabile (in linea teorica il sistema mantiene le stesse prestazioni indipendentemente dal numero di client collegati). I dati vengono ricevuti da un "PBE-Proxy" che a sua volta li memorizza in un buffer ed espone al suo "Data Viewer" una emulazione del "Device Rx". In questo modo ogni "Data Viewer" crede di avere a che fare con un "Device Rx" dedicato e quindi è mantenuta la compatibilità  con il funzionamento esistente.

Il "Device Handler" è implementato come servizio Jini ed il "PBE-Proxy" ne contiene al suo interno il proxy (ci si perdoni il gioco di parole). Pertanto i due componenti possono colloquiare indipendentemente dal canale di comunicazione, che effettivamente è interscambiabile. A seconda delle circostanze la scuderia può affiancare al multicasting standard una versione di reliable multicasting (implementata attraverso JGroups) o in casi estremi un canale TCP/IP dedicato. Quest‘ultima soluzione viene usata spesso con i collegamenti con il muretto dove il direttore tecnico ha la sua postazione; si tratta di un client molto importante, che però è servito generalmente da un link laser ad infrarossi che garantisce una qualità  di segnale inferiore a quella di una rete standard. Il TCP/IP spesso è l‘unica soluzione in grado di garantire la continuità  di trasmissione senza interruzioni. Nonostante questo approccio sia in grande contrasto con la scalabilità  garantita dal multicasting, il numero estremamente limitato di postazioni che lo richiedono non pone problemi in pratica.

I "Device Handler" sono controllati e contenuti da un altro componente, il "Telemetry Service". Esso monitorizza continuamente lo stato dei "Device Rx" (verificando la loro responsività  alle richieste di scarico dei dati) e si occupa di far coincidere il ciclo di vita di ogni "Device Handler" con quello del device ad esso associato. In pratica, appena viene acceso (o connesso) un "Device Rx", viene pubblicato un "Device Handler"; appena il "Device Rx" viene spento (o sconnesso) il relativo "Device Handler" viene tolto di mezzo. Da questo punto di vista, i "Device Handler" rappresentano, nella prospettiva degli altri componenti, in tutto e per tutto le sorgenti di dati delle automobili, che pertanto risultano "jinizzate". Questa soluzione, è chiamata "idioma surrogate" nel mondo Jini e viene utilizzata tutte le volte in cui vogliamo rendere disponibile "alla Jini" un dispositivo dove non è possibile far girare una macchina virtuale Java.
Il resto del sistema è relativamente semplice. Il "Context Service" è un semplice database persistente in grado di memorizzare e recuperare a richiesta le variabili di ambiente. La sua struttura dati è basata su un modello DOM di XML (in poche parole, il "Context Service" è un semplice naming service a struttura gerarchica). L‘"Authentication Service" è un adattatore che permette di recuperare i dati di autenticazione degli utenti, mediante flat file, accesso a fonti dati JDBC o JNDI.
L‘accesso al "Context Service" viene fornito ai "Data Writer" per mezzo di un‘interfaccia SOAP implementata da Axis, un prodotto open-source di Apache. Questa soluzione è stata scelta per disaccoppiare al massimo le dipendenze tra RTTS e "Data Writers", che sono un‘applicazione realizzata in C++ da Magneti Marelli.
MoMa è la console grafica di monitoraggio del sistema che usa le citate API di Rio per tracciare in tempo reale lo stato di ogni componente. Essa fornisce una visione d‘insieme di RTTS sia in formato iconico sia in forma di albero; a titolo di esempio mostriamo la schermata principale. MoMa consente anche la configurazione del sistema, come per esempio l‘assegnazione di un canale radio ad una certa auto, che può essere effettuata mediante drag-n-drop o con maschere di input testuali. Ovviamente MoMa è stato realizzato con Swing.

Customizzazioni

Come accennato precedentemente, le strette specifiche del progetto ci hanno costretto ad implementare alcune customizzazioni del sistema. In particolare, abbiamo operato nelle seguenti aree:

  • gestione della connessione / disconnessione accidentale dei cavi
  • zero configurazioni sui client anche in presenza di schede di rete multiple
  • alta affidabilità  con switch su un sistema secondario
  • reliable multicasting

Per quanto riguarda la disconnessione dei cavi, Jini in realtà  ha un suo modo di gestire la situazione. Ogni due minuti il LUS segnala al mondo la sua esistenza spontaneamente (cioè senza che nessun client cerchi di localizzarlo esplicitamente) emettendo un messaggio "multicast announcement".

La prima ipotesi che abbiamo preso in considerazione è stata di ridurre notevolmente il periodo di emissione di questo pacchetto, portandolo a 500 millisecondi ed usandolo come un "heartbeat" del server: in pratica l‘idea è che se un client non riceve più il "multicast announcement" del LUS, può presumere che il suo cavo sia staccato ed agire di conseguenza. L‘ipotesi è stata scartata: mentre l‘operazione è tecnicamente fattibile (praticamente tutti i dettagli implementativi di Jini possono essere facilmente riconfigurati), una frequenza di announcement cosଠelevata pone seri problemi di performance (il punto è che Jini fa tutta una serie di operazioni collegate all‘announcement, che non è stato progettato per funzionare come un heartbeat).

La seconda ipotesi è stata di utilizzare il supporto di monitoraggio di Rio. Rio fornisce una propria API per implementare un heartbeat associato ad ogni ServiceBean. L‘implementazione di default è stata scartata in quanto basata su TCP/IP: ma questo protocollo non è scalabile e soprattutto usa timeout di disconnessione troppo lenti (nell‘ordine delle decine di secondi). Spesso si può intervenire con tuning del sistema operativo, ma sarebbe stato in contrasto con le specifiche di "zero configurazione" dei client. Fortunatamente Rio è facilmente espandibile ed è stato possibile implementare una gestione ad-hoc di heartbeat basata su un protocollo multicasting. In questo modo si sono ottenuti tempi di reazione nell‘ordine di pochi secondi.
Dal punto di vista del design, tutte le operazioni di gestione della discovery dei servizi Jini sono state implementte con una classe ad-hoc, RTTSServiceDiscovery, con la seguente interfaccia:

public interface RTTSServiceDiscoveryListener {void serviceAdded (ServiceItem serviceItem); void serviceRemoved (ServiceItem serviceItem); void serviceReachable (ServiceItem serviceItem);void serviceUnreachable (ServiceItem serviceItem);}

che aggiunge la semantica di "reachable / unreachable" a quella "added / removed" fornita da Jini. In pratica, le disconnessioni "lunghe" vengono gestite da Jini che le giudica come una rimozione definitiva del servizio; le disconnessioni "corte" vengono gestite da RTTS e considerate temporanee.
Esiste però un‘altra circostanza da gestire. Cosa succede se:

  1. un client C viene disconnesso;
  2. viene pubblicato un nuovo servizio S;
  3. successivamente C viene di nuovo connesso dopo pochi secondi.

Spesso, la notifica di pubblicazione di S raggiunge C dopo un periodo medio di un minuto (due minuti al massimo). Ã? infatti con questa frequenza che il LUS segnala eventuali cambiamenti allo stato del sistema, mediante il già  citato "multicast announcement". Il client C, dal canto suo, non fa richieste esplicite al LUS dal momento che, dal punto di vista di Jini, le disconnesioni "corte" non vengono rilevate.
Abbiamo ovviato con l‘aggiunta di una nuova classe, FastServiceDiscovery. Essa monitorizza l‘heartbeat di cui abbiamo parlato precedentemente e, nel caso di una disconnessione/riconnessione "corta" interroga esplicitamente il LUS e fa un confronto con l‘ultima situazione nota. In caso di differenze, genera autonomamente eventi "added / removed" mediante la stessa infrastruttura di Jini, filtrando eventuali duplicati che potrebbero essere generati dal LUS. Il bello di questa soluzione è che è completamente trasparente, dal momento che ogni client riceve "semplicemente" gli eventi giusti al momento giusto, ignorando se essi siano generati dal LUS o da FastServiceDiscovery. Ancora una volta la facile espandibilità  di Jini ci ha permesso di trovare una soluzione elegante e poco intrusiva.
Veniamo al problema delle schede di rete multiple. Alcune scuderie, per vari motivi, usano una doppia connessione di rete tra i propri computer (generalmente per ottimizzare il traffico). Cioè, la maggior parte dei computer sono dotati di una doppia scheda di rete e questo consente di avere due alternative per le comunicazioni. Abbiamo detto "maggior parte", ma non tutti. Il server RTTS è tipicamente raggiungibile solo attraverso una delle due sottoreti. Questo pone un problema a Jini.
Supponiamo che le due sottoreti siano individuate da due indirizzi differenti, p.es. 192.168.x.x e 192.122.x.x. Il server sa benissimo quale sia la sottorete "giusta" (p.es. 192.168.x.x) perchà© è l‘unica che vede (e comunque qui è tollerabile un minimo di configurazione). Il server, quindi, aprirà  i propri socket TCP/IP in modo corretto.
Ma i client? A priori non possono conoscere la risposta giusta e, purtroppo, Jini è implementato in modo che ogni nodo apre socket passivi, in modalità  "listen", cosa che in caso di schede di rete multiple richiede di fare una scelta esplicita. Se un client scegliesse erroneamente 192.122.x.x, si pregiudicherebbe ogni possibilità  di comunicazione con il server (nella pratica le cose sono un po‘ più complesse e le connessioni vengono comunque tentate, ma falliscono con errori tipo "no route to host"). La soluzione standard di Jini è che ogni nodo dichiari esplicitamente nei suoi file di configurazione il proprio indirizzo IP: ma, come detto, sul client le specifiche vietano ogni tipo di configurazione esplicita.
La soluzione non è stata complicata. Nel citato heartbeat, usato per rilevare le disconnessioni, è stata inserita l‘informazione relativa all‘indirizzo di rete usato dal server (per i lettori esperti: in linea di principio questo indirizzo potrebbe essere ricavato dinamicamente dalle API dei socket, in quanto dovrebbe essere possibile capire da quale sottorete è stato ricevuto un certo pacchetto; ma purtroppo la cosa funziona su Linux, non su Windows...). Ogni client, prima di partire, attende l‘heartbeat del server ed estrae l‘informazione necessaria. In pratica ciò è stato implementato in modo molto elegante, utilizzando l‘aspetto dinamico dei file di configurazione di Jini:

import NetworkInterfaceSelector;hostAddress = NetworkInterfaceSelector.getSelectedHostAddress();serverExporter = new BasicJeriExporter(TcpServerEndpoint.getInstance(hostAddress, 0), new BasicILFactory(), false, true);

L‘esempio sopra riportato non è codice Java, ma un file dinamico di configurazione nel quale è possibile fare riferimento a classi Java, sia di Jini che proprietarie. NetworkInterfaceSelector è la nostra classe custom che si mette in attesa dell‘heartbeat e getSelectedHostAddress() è il metodo che restituisce l‘indirizzo richiesto. Questo dato poi viene memorizzato nella variabile hostAddress, utilizzata successivamente per configurare esplicitamente tutti i TCP endpoint usati dal sistema. Nella pratica le cose sono solo quantitativamente più complesse (ci sono parecchi endpoint da configurare), ma il concetto è lo stesso
L‘alta affidabilità , cioè la capacità  di utilizzare un canale di backup in caso di fallimento del canale primario, è stata implementata prendendo a prestito un pattern di J2EE, il "Business Delegate". La situazione è descritta in figura:

Come già  detto nell‘introduzione, ogni automobile ha due centraline e quindi due canali radio di trasmissione dei dati di telemetria, che trasmettono in parallelo gli stessi dati. Il sistema li rappresenta istanziando due "Device Handler" (surrogate delle centraline). I due "Telemetry Service" istanziati sui server primario e secondario cooperano in modo che ognuno gestisce un "Device Handler" differente. In questo modo, ogni client ha due canali indipendenti che forniscono i dati anche se una centralina, una radio o un server si dovessero rompere (le combinazioni di rottura "incrociata" di una centralina di un canale e di un server dell‘altro canale sono considerate estremamente improbabili e non sono prese in considerazione").
Il ClientAdapter non interagisce direttamente con i proxy dei "Device Handler", ma mediante una classe intermedia, il "Business Delegate". Questa espone sostanzialmente la stessa interfaccia del proxy, ma rileva eventuali messaggi "removed / disconnected" ed effettua autonomamente uno switch.
Questa soluzione ha permesso di gestire con estrema facilità  una richiesta di modifica espressa da una scuderia durante il progetto. Quando, durante le sessioni di test, un‘auto rientra temporaneamente nei box, l‘auto viene collegata alla rete del garage via cavo. Ã? stato richiesto di effettuare automaticamente uno switch sul canale di comunicazione via cavo indipendentemente dalla contemporanea disponibilità  del canale radio, in quanto il cavo è ovviamente più affidabile. Il canale via cavo viene rappresentato da un nuovo "Device Handler" con un funzionamento del tutto analogo a quelli che rappresentano il ricevitore via radio. Tutto è stato gestito all‘interno del "Business Delegate" semplicemente associando un profilo di priorità  tra diversi "Device Handler", per cui alcuni prendono il sopravvento su altri indipendentemente dalla rilevazione di disconnessioni.
Infine, un accenno al citato problema del reliable multicast. Il multicasting, come già  detto, è la soluzione più elegante al problema della scalabilità , dal momento che (almeno in linea teorica) svincola la banda disponibile dal numero di client collegati. Purtroppo, essendo una soluzione basata su protocollo UDP, non è "affidabile", nel senso che nessuno garantisce che alcuni pacchetti non vengano persi (cosa che può accadere in reti non sufficientemente dimensionate o durante un picco di carico su un client). La soluzione è quella di effettuare un compromesso ed usare un protocollo di "reliable multicast": un‘infrastruttura dedicata numera i pacchetti e i client possono cosଠrilevare quelli persi e richiederne la ritrasmissione, con varie modalità . La scalabilità  non è più infinita, perchà© è limitata dal traffico generato dalla ritrasmissione (che è proporzionale al numero di client). Ma con il giusto dimensionamento si può ottenere il risultato voluto.
Il problema sta nei dettagli, come spesso accade. Ã? molto difficile realizzare un protocollo di reliable multicast "in casa", visto che la sua progettazione richiede un accurato modello matematico della rete utilizzata. D‘altro canto esistono parecchi protocolli di reliable multicast noti, ma un‘analisi preliminare ci ha permesso di verificare che nessuno di essi era il compromesso auspicabile per RTTS: alcuni garantivano troppo poca affidabilità , altri erano troppo onerosi computazionalmente. Inoltre andavano escluse le implementazioni non 100% Java. Come coniugare l‘esigenza di un protocollo ad-hoc con l‘oggettiva difficoltà  di progettarlo?
La soluzione è venuta da JGroups, una libreria open-source realizzata dal gruppo JBoss. Essa fornisce alcuni "mattoni fondamentali" (aderenti ad una specifica standard, la RFC 3048) che possono essere composti insieme in varie combinazioni per ottenere il compromesso desiderato tra affidabilità  e performance. L‘integrazione in RTTS è avvenuta in modo del tutto trasparente e non intrusivo, mediante il già  citato meccanismo dei proxy di Jini. Tuttavia, JGroups oggi non è sostanzialmente utilizzato sul campo: la percentuale di CPU utilizzata da RTTS sui client, estremamente bassa, e il dimensionamento della rete ethernet (una gigabit) fanno sଠche la percentuale di pacchetti persi dal multicast standard sia assolutamente trascurabile.

Conclusioni

Dobbiamo dire subito una cosa: il progetto è stato stressante, ma ci siamo divertiti: non capita molto spesso di poter utilizzare una tecnologia di frontiera, come Jini e Rio, in un ambito cosଠdelicato e challenging come la Formula Uno. Ã? stata una grande soddisfazione portare a termine la realizzazione di un progetto cosଠdifficile (oltretutto in pochi mesi e con un sostanziale rispetto dei tempi) in un‘area dove in molti non avrebbero scommesso a priori l‘effettiva usabilità  di Java. Oltre all‘assoluta affidabilità  delle tecnologie usate, oltretutto customizzabili in tutti i loro aspetti, ci piace concludere con una considerazione collegata al metodo di sviluppo. Abbiamo usato Unified Process, iterativo ed incrementale: possiamo dire semplicemente che funziona. Il progetto, su un elapsed time di sei mesi, è stato diviso in tre iterazioni: ad un mese e mezzo dall‘inizio era pronto il primo propotipo da laboratorio e a tre mesi dall‘inizio eravamo già  a provare il sistema in pista, a Jerez de la Frontera. Questo ci ha permesso di affrontare fin da subito, e quindi portare sotto controllo, tutti i rischi collegati specialmente all‘integrazione di RTTS in un ambiente cosଠcomplicato. Ovviamente una chiave di volta del successo è stata l‘elevata competenza delle due aziende coinvolte, Magneti Marelli e Sun, sia per le rispettive tecnologie utilizzate che, per quanto riguarda Magneti Marelli nello specifico, nell‘esperienza di pista. Senza un esperto che vi assista, vi assicuriamo che è difficile monitorare e debuggare un sistema sotto test dovendo sincronizzare le vostre operazioni con gli unici due minuti in cui, periodicamente, la macchina rientra al box e in cui potete mettere le mani sul laptop dell‘ingegnere di pista!
Infine, essendo supporter del concetto di open-source, ci piace ricordare che tutte le tecnologie usate, da Java a Jini a Rio a JGroups, sono ? in vari modi ? proprio tecnologie open-source; a completamento di questa visuale, va ricordato che alcune scuderie usano Linux per i propri server. Questo per convincere gli scettici che l‘open-source può realmente funzionare anche in ambienti cosଠsofisticati.

Condividi

Pubblicato nel numero
100 ottobre 2005
Fabrizio Giudici ha iniziato a occuparsi di Java durante il suo dottorato di ricerca, concluso nel 1998 presso l‘Università di Genova e focalizzato sulle applicazioni industriali della tecnologia di Sun. In quegli anni ha iniziato la collaborazione con MokaByte, scrivendo articoli tecnici e partecipando al gruppo di consulenti che iniziavano…
Antonella Balduzzi è in Sun dal 2001e fa parte del gruppo Enterprise Web Services presso la sede di Milano. Si occupa di progettazione e sviluppo di soluzioni basate su tecnologia Java e del supporto ai progetti basati sulle tecnologie J2EE e RFID. In precedenza si è occupata di varie tematiche…
Ti potrebbe interessare anche