Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Gestione delle sessioni in cluster con Memcached

Le principali soluzioni su come gestire le sessioni su sistemi gestiti da un load balancer, dal bilanciamento mirato alle sessioni distribuite con Memcached
Le principali soluzioni su come gestire le sessioni su sistemi gestiti da un load balancer, dal bilanciamento mirato alle sessioni distribuite con Memcached
Link copiato negli appunti

Nel precedente articolo Sessioni PHP: cosa sono come si usano abbiamo visto come le sessioni vengono usate per sopperire alle mancanze del protocollo http nella realizzazione di importanti funzionalità web, come le aree ad accesso limitato o i carrelli d'acquisto online.

In particolare abbiamo visto come PHP gestisce le sessioni: quando un client si connette al server per la prima volta il motore delle sessioni del PHP genera un SID (Session ID) univoco e crea un file con nome riconducibile a quel SID nel quale verranno salvate le "variabili di sessione". Tale SID verrà poi trasmesso al browser tramite un cookie in modo che ogni volta che quel client presenterà una richiesta con quel cookie, il PHP saprà in quale file sono contenute la variabili relative a quella sessione ripristinandole.

All'aumentare delle richieste può accadere che un server diventi insufficiente a soddisfare tutto il carico di lavoro, nasce quindi l'esigenza di scalare le proprie applicazioni su più server in batteria il cosiddetto "load balancing" attraverso l'uso di un apparato di rete detto: "bilanciatore".

Il bilanciatore è generalmente posizionato dietro ad un firewall e, ricevendo tutte le connessioni dal web, le smista verso le "macchine bilanciate" secondo politiche definite dall'utente (es. equa distribuzione del carico su tutte le macchine).

Le macchine bilanciate generalmente hanno queste caratteristiche:

  • Sono tutte configurate allo stesso modo
  • Non sono tenute a sapere d'essere bilanciate
  • Producono risposte che, sia nel protocollo che nel contenuto, sono indistinguibili dalle risposte generate dalle altre macchine bilanciate

E questo perché:

  • Si semplifica l'amministrazione delle singole macchine
  • Si rendere agevole l'aggiunta o la rimozione di ulteriori server alla batteria
  • Si presentare tutta l'infrastruttura al web come se fosse un'unica macchina a rispondere

È con il load balancing che le sessioni diventano un problema; infatti potrebbe accadere che alla prima connessione da parte di un determinato client il cookie di sessione venga generato da una delle macchine bilanciate ed i dati della sessione si troverebbero quindi su di essa. Di conseguenza se la richiesta successiva dello stesso client venisse instradata su un altro server, questo non avrebbe memoria della sessione richiesta attraverso il cookie e ne creerebbe una completamente nuova con lo stesso SID.

Se, per di più, la richiesta successiva del client venisse nuovamente instradata sul server che aveva inizialmente generato quella sessione tale memoria tornerebbe "misteriosamente" disponibile.

Soluzione 2: Accentramento delle sessioni

Volendo trovare una soluzione strutturale che non dipenda da configurazioni di sistema una valida soluzione è quella di accentrare le sessioni in un unico contenitore condiviso come una cartella in file sharing o un database mySQL o un server Memcached .

I vantaggi e gli svantaggi in questo caso dipendono dal tipo di contenitore utilizzato.

File sharing

Vantaggi Svantaggi
  • Di facile utilizzazione, basta cambiare la direttiva session.save_path del PHP.ini impostando il percorso della cartella condivisa
  • Non prevede nessuna modifica al codice degli script
  • Sicuramente dal punto di vista prestazionale non garantisce risultati accettabili a meno di investire in apparecchiature dedicate (e costose)

Database

Vantaggi Svantaggi
  • Permette di monitorare in tempo reale lo stato delle varie sessioni tramite query SQL
  • Prestazioni elevate
  • Occorre introdurre codice PHP per la gestione della sessione (vedremo poi come) che eventualmente si può includere in modo trasparente impostando il parametro auto_prepend_file del PHP.ini
  • Se il DB è lo stesso usato per i dati delle applicazioni occorre però verificarne la capacità sopportare entrambi i carichi di lavoro
  • Può presentare problemi di sicurezza come spiegato nel precedente articolo sugli handler di sessione

Memcached

Vantaggi Svantaggi
  • Decisamente più veloce di tutti in quanto è un servizio di RAM condivisa
  • Non occorre introdurre codice PHP, basta modificare i parametri del PHP.ini
  • Praticamente nessuno

Si potrebbe quindi pensare che l'accentramento delle sessioni in una Memcached sia la soluzione migliore, dal punto di vista delle prestazioni e della facilità di adozioni sicuramente sì. Ma nasconde anche una debolezza architetturale: se il contenitore delle sessioni smette di funzionare nessuna sessione funziona più e quindi niente più login o carrelli

Per completezza di informazione occorre dire che l'handler di sessione nativo di memcache supporta una ridondanza delle sessioni su più server; modificando il PHP.ini in questo modo infatti:

memcache.hash_strategy = consistent
memcache.allow_failover = 1
memcache.session_redundancy=2
session.save_handler=memcache
session.save_path="tcp://192.168.0.1:11211, tcp://192.168.0.2:11211"

Si riesce a far sì che le sessioni vengano salvate contemporaneamente su 192.168.0.1 e 192.168.0.2 ma verranno lette solo da un server scelto dall'handler secondo un peso parametrizzabile dall'utente.

C'è però da dire che se una delle scritture parallele fallisce, i due server risulterebbero disallineati con il non trascurabile svantaggio che un utente potrebbe vedere determinati prodotti nel carrello prima del pagamento ed altri dopo il pagamento, qualora i dati di sessione venissero letti dal server che ha subito l'errore in scrittura.

Soluzione 3: Le sessioni distribuite

Innanzitutto modifichiamo leggeremente le classiche caratteristiche delle macchine bilanciate:

  • Sono tutte configurate allo stesso modo
  • Ogni macchina sa di essere bilanciata e conosce gli IP di tutte le altre macchine bilanciate
  • Il valore del cookie di sessione è in grado di veicolare informazioni sulla macchina che ha generato la sessione

Ovviamente la prima regola è rimasta invariata, sarebbe stata improponibile una soluzione che prevedeva di configurare differentemente ogni server.

I presupposti per la realizzazione di questa soluzione sono che ogni macchina bilanciata:

  • Gestirà le proprie sessioni in autonomamente tramite un proprio servizio di Memcached
  • Sarà identificabile da una lettera
  • Conoscerà le lettere di tutte le altre macchine bilanciate e sarà in grado di dedurne l'IP in funzione della lettera di assegnazione
  • Sarà in grado di generare SID che iniziano sempre con la lettera associata

Grazie a questo escamotage, quando un server riceve una richiesta per la prima volta genererà una sessione nella propria Memcached facendo iniziare il cookie di sessione con la propria lettera di assegnazione.

Ogni volta che riceverà una richiesta con cookie di sessione ne valuterà la prima lettera e, se è la propria userà la Memcached locale, altrimenti si connetterà alla Memcached del server che ha generato quella sessione.

Vantaggi Svantaggi
  • Robustezza, tra i server si genera una connettività a "maglia", in questo modo se un server cade tutti gli altri continuano ad operare salvo il ripristino delle sessioni dei client "orfani"
  • Non esistono sessioni non aggiornate o parzialmente aggiornate, le sessioni sono valide oppure vengono resettate completamente
  • Velocità, derivante dall'uso di Memcached
  • Occorre introdurre codice PHP per la gestione della sessione, ma come detto più volte con la direttiva auto_prepend_file si può fare in modo trasparente

Il codice

Vediamo ora come realizzare un handler di sessioni distribuite, e per farlo estenderemo la classe memcache_mySessionHandler già definita nell'articolo Gestire le sessioni su APC, Memcached e APC

Il primo problema che si pone è come far conoscere ai nostri script la mappatura lettera=>server. In questo caso prenderemo spunto dall'handler nativo memcache e inseriremo l'elenco dei nostri server nel PHP.ini in modo da aumentare la flessibilità e la facilità di configurazione (seppure al rischio di inserire informazioni dedicate nel PHP.ini) quindi, oltre all'auto_prepend_file già discusso nel citato articolo, andremo a modificare session.save_path in questo modo:

session.save_path="a=>192.168.0.1:11211,b=>192.168.0.2,c=>192.168.0.3:22122";

Notare come non ci sia vincolo sulla porta usata dalle varie Memcached, volendo si potrebbero mettere semplicemente gli IP e verrà usata la porta 11211.

Vediamo, nella prossima pagina, il codice completo.

Ecco il codice completo della nostra implementazione.

<?php
class multimemcache_mySessionHandler extends memcache_mySessionHandler
{
    private static $thisid, //conterrà la lettera assegnata a questo server
        $srvs; //conterrà array lettera=>IP(:port)  
    public static function set_handler()
    {
        self::$memcache = new memcache();
        $servers = explode(',', ini_get('session.save_path'));
        foreach ($servers as $server) {
            preg_match('@^([a-z]{1})=>((.+)(:[0-9]+)?)$@U', $server, $match); //estrae i nomi dei server da session.save_path
            if ($match[3] == $_SERVER['LOCAL_ADDR'])
                self::$thisid = $match[1]; //individua l'indirizzo specificato per il server corrente
            self::$srvs[$match[1]] = $match[2]; //costruisce arrai associativo lettera=>IP(:port)
        }
        $sessid = $_COOKIE[session_name()]; //cattura l'id di sessione (se presente) in questa fase session_id() non funziona qundi usiamo direttamente $_COOKIE
        if (!preg_match('/^[' . implode(array_keys(self::$srvs)) . ']{1}[0-9a-f]{32}$/', $sessid) || !self::memconnect(self::$srvs[$sessid{0}])) { //se id di sessione non è accettabile o il server relativo non è raggiungibile genera un nuovo id di sessione locale
            self::gen_id(self::$thisid);
        }
        parent::set_handler();
    }
    /**
     * Effettua connessione a memcache
     * @param string $host
     */
    private static function memconnect($host)
    {
        $host = explode(':', $host, 2); //assumendo che $host contenga un indirizzo nella forma 192.22.22.22:11211 lo scompone
        return @self::$memcache->pconnect($host[0], ($host[1] ? $host[1] : 11211)); //se il secondo parametro nonè valorizzato lo setta alla porta di default del servizio
    }
    /**
     * Genera un cookie di sessione basato su UUID versione 4
     * @param char $me lettera da anteporre alla stringa generata
     * @return void
     */
    private static function gen_id($me)
    {
        self::memconnect(self::$srvs[self::$thisid]); //connette la memcache locale
        do {
            $id = $me . self::UUIDv4(); // calcola SID
        } while (!self::$memcache->add(get_called_class() . '_' . $id, array(), 0, self::$maxlifetime)); //se riesce ad eseguire $memcache->add l’$id appena creato non esiste
        session_id($id); //assegna il SID alla sessione corrente
    }
    /**
     * Calcola un id random secondo l'algoritmo UUID versione 4 una stringa di 32 caratteri a-f0-9
     * @return string
     */
    private static function UUIDv4()
    {
        return sprintf('%04x%04x%04x%04x%04x%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff));
    }
    public static function open($path, $name)
    {
    } //le connessioni vengono eseguite nel set_handler() quindi non fa nulla
}
?>

Il cuore del sistema è il metodo gen_id() che deve sostituire il PHP nella generazione dell'ID di sessione, per cui ci si è affidati ad un algoritmo standard di creazione di ID randomici ma con la verifica effettiva che il SID appena generato non sia già presente nella Memcached, proprio per questo la connessione verso la Memcached non avviene nel metodo open() (che anzi non fa più nulla), ma direttamente quando viene invocato il metodo set_handler().

Questo permette anche di verificare in anticipo l'impossibilità di connettersi a determinate Memcached ed eventualmente annullare il SID generandone uno proprio completamente nuovo.

Naturalmente potrebbe essere vista come un'operazione inutile perché in effetti non c'è garanzia che nello script eseguito la sessione sarà poi effettivamente usata ma i vantaggi citati aumentano l'affidabilità del sistema, oltre al fatto che trattandosi di connessioni persistenti potrebbero avere un impatto quasi nullo sulle prestazioni essendo in molti casi già attive.

Nota sulla sicurezza

Oltre all'eventualità di integrare l'elenco dei server direttamente nell'handler, è sempre possibile modificare questa classe introducendo algoritmi di generazione dei SID più lunghi e con maggior entropia. Ovviamente, ma sarebbe inutile dirlo, le porte usate dalle Memcached debbono essere off-limits per qualunque connessione che transiti dal firewall

Ti consigliamo anche