Throttling monitor con ASP.NET MVC

Il "throttling" spiegato in due parole? Capacità di un sistema informativo di tracciare l'occorrenza delle chiamate ad un particolare risorsa (o una funzione) disponibile nel sistema stesso; identificando il chiamante ed, eventualmente, imponendo delle limitazione all'utilizzo della risorsa in funzione di alcune policy prestabilite.

A cosa serve e come è possibile sfruttarlo? Il campo di applicabilità è certamente molto vasto, ma è una tecnica maggiormente utilizzata per la sicurezza dei sistemi, e la misurazione della responsività degli stessi a fronte di un elevato numero di richieste. Per esempio è possibile monitorare il numero di invocazioni ad un particolare metodo esposto da un vostro servizio web, e la frequenza con cui avvengono. Questo, per prevenire attacchi esterni, provenienti da malintenzionati che vogliono mettere in crisi il servizio che offrite.

Ultimamente mi è capitato di dover realizzare una serie di servizi - esposti pubblicamente su rete internet - che fossero fruiti da una discreta quantità di client remoti. La difficoltà di implementazione non era particolarmente elevata (si è trattato di erogare alcune liste di informazioni, o poco più), ma la frequenza con cui sarebbe stato utilizzato - devo ammetterlo - ha destato diverse perplessità.

Queste (tutte perfettamente motivate, ve lo garantisco) stavano nel fatto che un eccessivo sfruttamento delle risorse avrebbe potuto rendere il sistema instabile, confluendo nei casi più gravi anche in una indisponibilità del servizio per l'utente finale.

Prima di mettere in campo il classico "cannone per sparare alle mosche", ho preferito accertarmi che le preoccupazioni sollevate (ripeto, giustamente) dal cliente, fossero giustificate. Ho quindi realizzato un rudimentale sistema di monitoraggio sfruttando gli ActionFilter di ASP.NET MVC.

L'idea è semplice. Ho una serie di controller e una serie di action. Usando un action filter posso identificare il client chiamante, quindi verificare quante volte (nell'arco di un periodo temporale prestabilito) questo client eseguiva la medesima chiamata al servizio.

Il tracciamento confluisce in una banalissima "in-memory" cache che conserva una mappatura tra il client IP che ha eseguito l'invocazione, e il numero di invocazioni nei vari "intervalli temporali" che ho stabilito in fase di design. E avere la possibilità di verificare (in ogni istante) se un particolare IP client aveva invocato un certo numero di volte il nostro server nell'ultimo secondo (o minuto, ora, giorno, etc.), vi assicuro che è un'informazione degna di nota.

L'aggiunta di una funzionalità di "negazione del servizio" a fronte di un numero elevato di richieste da parte di un unico client, è la ciliegina sulla torta che ci permette di far fronte ad una consistente percentuale di attacchi mirati al "denial of service", usando solo pochissime righe di codice.

Partiamo dall'utilizzo del nostro filtro di throttling: L'ActionFilter che compie il tracciamento e il conteggio delle richieste è "ThrottleDetectorAttribute"; esso, come la maggior parte degli action filter, è applicabile sia sul controller che sull'action di riferimento. L'incremento dei contatori sarà comunque eseguito per ogni singola action, indipendentemente dai parametri passati alla stessa (se volete qualche cosa di più specifico, è comunque possibile eseguire una customizzazione del comportamento).

Mentre "detector" può essere applicato una sola volta, "ThrottlePolicyAttribute" è applicabile in maniera multipla a seconda delle esigenze funzionali. Il suo significato è auto-esplicativo: genera e registra una policy di restrizione sulla risorsa (action) sulla quale è applicato. Nell'esempio, limita l'uso di tutte le action presenti nel nostro controller "HomeController" ad un massimo di 2 richieste al secondo, 10 richieste al minuto e 1000 richieste al giorno (per ciascuna differente action).

L'enumerazione specificata "ThrottleScope" discrimina se applicare una politica globale ("ThrottleScope.Global", dove il numero di richieste tollerate dalla policy è conteggiato come somma del totale delle richieste provenienti da diversi client), oppure specifica ("ThrottleScope.SingleClient", con il numero di limite di tolleranza specifico per ogni singolo differente indirizzo IP client richiedente).

Ma cosa succede quando una delle policy viene violata? Semplice: il servizio viene negato, e sul client viene ritornato un messaggio di errore di "throttling policy violata", con codice HTTP 503 ("Service Unavailable").

E' possibile eseguire un override sia del metodo di rilevamento di violazione della policy, sia dell'incremento delle richieste in ingresso: tracciare ogni singola interazione può risultare fondamentale per ottenere un monitoraggio (magari grafico) riguardo l'utilizzo del vostro servizio.

Gli action filter descritti sono disponibili nella nuova versione di Chakra.Mvc, scaricabile direttamente da NuGet.
Fateci un giro... ;)

M.

Commenti

Post popolari in questo blog

Cancellazione fisica vs cancellazione logica dei dati

RESTful Stress: misurare le performance di un servizio REST

Load tests, Stress tests e performance di un servizio REST