Java, Redis e fantasia

Utilizziamo alcune librerie per dialogare con Redis e vedere come può essere utilizzato questo famoso sistema di memorizzazione volatile.

Redis è un sistema di memorizzazione key-value, realizzato da Salvatore Sanfilippo (a.k.a. antirez) in C. Attraverso Redis è possibile effettuare salvataggi di informazioni su memoria volatile e grazie alle sue ottime performance possiamo trovarlo impiegato in diversi scenari architetturali. La maggior parte dei key-value store gestiscono i dati come semplici stringhe, mentre Redis ha la possibilità di definire diversi tipi di dato

  • String
  • List (ordinati in base all’inserimento)
  • Set
  • Sorted Set: ogni elemento ha un punteggio e gli elementi possono essere presi in base ad esso
  • Hash: mappe composta da campi con i rispettivi valori.
  • Bitmap: stringhe gestite come un array di bit che possono essere manipolati/contati/resettati
  • HyperLogLogs: struttura dati utilizzata per stimare la cardinalità di un insieme

 

Prima di andare avanti dobbiamo preoccuparci dell’installazione di Redis sulla nostra macchina. Scarichiamo l’ultima versione dal sito ufficiale, esplodiamo il pacchetto tar.gz e poi lanciamo la compilazione e installazione. Di seguito i comandi che possono essere lanciati nella shell per installarlo

wget http://download.redis.io/redis-stable.tar.gz
tar xvzf redis-stable.tar.gz
cd redis-stable
make
make test
make install

A questo punto Redis è installato nel nostro sistema. Possiamo avviarlo con il comando redis-server e successivamente verifichiamo che il server sia in funzione con il seguente test

federico@nowhere:~$ redis-cli

127.0.0.1:6379> ping

PONG

127.0.0.1:6379> set hello world

OK

127.0.0.1:6379> get hello

"world"

127.0.0.1:6379>

Redis è pronto per essere utilizzato.

 

Hello World da Java

Vediamo ora come possiamo connetterci a Redis utilizzando Java e la libreria opensource Jedis. Prendiamo un semplice programma di test all’interno del quale definiamo la seguente dipendenza per importare la libreria

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.8.0</version>
    <type>jar</type>
    <scope>compile</scope>
</dependency>

Quindi passiamo al primo dialogo con Redis attraverso le semplici istruzioni riportate di seguito

package com.javastaff.redistest;

import redis.clients.jedis.Jedis;

public class HelloWorld {
    public static void main(String a[]) {
        Jedis jedis = new Jedis("localhost");
        jedis.set("hello", "world");
        String value = jedis.get("hello");
        System.out.println(value);
    }
}

Come prima cosa abbiamo indicato dove vogliamo collegarci (localhost). In seguito abbiamo settato la chiave hello con il valore world attraverso il comando SET. Dopo aver recuperato lo stesso valore da Redis con il comando GET l’abbiamo stampato. Per sapere nel dettaglio cosa possiamo fare su Redis è presente sulla documentazione ufficiale una lista di tutti i comandi disponibili.

Redis è un key-value store che memorizza le informazioni in memoria. Proprio per questo motivo potrebbe essere una buona scelta per utilizzarlo come cache della nostra applicazione (volendo anche distribuita) e infatti viene spesso messo a confronto con Memcached, altro famoso server pensato per essere utilizzato come cache davanti alla nostra applicazione web.

 

List

Possiamo memorizzare una lista in Redis, associandola ad una chiave come nel caso della stringa. Vediamo quindi come popolare una lista e successivamente stampare i valori presenti

package com.javastaff.redistest;

import redis.clients.jedis.Jedis;

public class ListExample {
    public static void main(String a[]) {
        Jedis jedis = new Jedis("localhost");
        jedis.rpush("hadoop_tools", "Pig");
        jedis.rpush("hadoop_tools", "Sqoop");
        jedis.rpush("hadoop_tools", "Hive");
        jedis.rpush("hadoop_tools", "Oozie");
        jedis.rpush("hadoop_tools", "Zookeeper");

        long lunghezza = jedis.llen("hadoop_tools");
        System.out.println("Numero elementi presenti: "+lunghezza);
        System.out.println("Elementi: ");
        
        for (int i=0;i<lunghezza ;i++ ) {
            String value = jedis.lindex("hadoop_tools",i);
            System.out.println("\t"+value);
        }
        jedis.del("hadoop_tools");
    }
}

Per aggiungere elementi in una lista possiamo utilizzare RPUSH per aggiungere alla fine della lista o LPUSH per aggiungerlo in testa. Attraverso LLEN possiamo sapere la lunghezza della lista ed infine con LINDEX accediamo direttamente al N-esimo elemento della lista.

Per estrarre gli elementi dalla lista possiamo usare RPOP che rimuove l’ultimo elemento dalla lista e LPOP per il primo elemento. Questi comandi ci fanno subito pensare ad una coda FIFO o LIFO, quindi vediamo come realizzarne una utilizzando Redis. La seguente classe espone due metodi di gestione, inserisci e rimuovi, che effettuano le operazioni sulla coda FIFO mentre il metodo main crea una coda e poi la svuota.

package com.javastaff.redistest;

import redis.clients.jedis.Jedis;

public class RedisQueue {
    
    private String QUEUE="provacoda";
    private final Jedis jedis;
    
    public RedisQueue() {
        this.jedis = new Jedis("localhost");
    }
    
    public RedisQueue(String queue,String host) {
        this.QUEUE=queue;
        this.jedis = new Jedis(host);
    }
    
    public RedisQueue(String queue) {
        this.QUEUE=queue;
        this.jedis = new Jedis("localhost");
    }
    
    public static void main(String a[]) {
        RedisQueue queue=new RedisQueue();
        queue.inserisci("Mario Rossi");
        queue.inserisci("Giuseppe Verdi");
        queue.inserisci("Antonio Bianchi");
        String elemento=queue.rimuovi();
        while(elemento!=null) {
            System.out.println("Serviamo il signor: "+elemento);
            elemento=queue.rimuovi();
        }
        queue.distruggi();
    }
    
    public void inserisci(String valore) {
        jedis.lpush(QUEUE, valore);
    }
    
    public String rimuovi() {
        return jedis.rpop(QUEUE);
    }
    
    public void distruggi() {
        jedis.del(QUEUE);
    }
    
}

La coda che abbiamo creato era gestita localmente alla classe, mentre invece con Redis possiamo ovviamente creare una coda che viene alimentata da un parte e letta dall’altra. Di seguito vediamo il codice di un ipotetico producer che popola la coda con dei dati, come se volessimo ad esempio gestire le informazioni relative alle macchine che transitano su una coda del casello

RedisQueue queue=new RedisQueue("CODA_CASELLO");
queue.inserisci("AB1234567CF");

ora prima di passare alla parte del consumer dobbiamo aggiungere un metodo alla classe RedisQueue per rimanere in attesa che qualcuno inserisca qualcosa nella coda, utilizzando il comando BLPOP

public String attendi() {
        return jedis.blpop(0,QUEUE).get(1);
}

e ora il codice del consumer

package com.javastaff.redistest;

public class QueueConsumer {
    public static void main(String a[]) { 
        RedisQueue queue=new RedisQueue("CODA_CASELLO");
        String targa;
        while(true){
          System.out.println("In attesa che passi una macchina...");
          targa = queue.attendi();
          if (targa != null && targa.equals("CHIUDI CASELLO")) {
              System.out.println("Chiudo il casello. Ciao ciao.");
              System.exit(0);
          } else
            System.out.println("Passata una macchina con targa " + targa);
        }
    }
}

 

Insiemi

Passiamo ora alle operazioni sugli insiemi che possono essere gestiti con Redis. Come per le stringhe esistono una serie di comandi che permettono le classiche operazioni insiemistiche. Vedendo i comandi di Redis, tutti quelli che si riferiscono agli insiemi iniziano con il prefisso s di set.

Di seguito sono riportate le operazioni che creano due insiemi attraverso il comando SADD e in seguito si effettuano le operazioni di unione e intersezione rispettivamente con i comandi SUNION e SINTER.

Jedis jedis = new Jedis("localhost");
        
jedis.sadd("insieme1", "1");
jedis.sadd("insieme1", "2");
jedis.sadd("insieme1", "3");
jedis.sadd("insieme1", "4");
        
System.out.println(jedis.smembers("insieme1"));
        
jedis.sadd("insieme2", "2");
jedis.sadd("insieme2", "4");
jedis.sadd("insieme2", "6");
jedis.sadd("insieme2", "8");
        
System.out.println(jedis.smembers("insieme2"));
System.out.println(jedis.sunion("insieme1","insieme2"));
System.out.println(jedis.sinter("insieme1","insieme2"));
        
jedis.del("insieme1");
jedis.del("insieme2");

 

Expire

Tra le altre cose che è possibile fare in Redis, possiamo associare un tempo di vita dell’oggetto che stiamo memorizzando. In questo modo siamo sicuri che Redis renderà disponibile quel valore per un intervallo di tempo definito, cosa molto utile nel caso in cui vogliamo utilizzarlo come una sorta di cache per la nostra applicazione (There are only two hard things in Computer Science: cache invalidation and naming things).

Attraverso il comando EXPIRE definiamo fra quanto tempo Redis rimuoverà la coppia chiave-valore e con TTL possiamo verificare quanto tempo rimane al nostro oggetto. Nel seguente esempio inseriamo un oggetto con EXPIRE settato a 10 secondi e poi verifichiamo la sua vita attraverso il comando TTL

Jedis jedis = new Jedis("localhost");
jedis.set("provaexpire", "test");
jedis.expire("provaexpire", 10);
Long ttl=jedis.ttl("provaexpire");
while (ttl!=-2) {
     System.out.println("La chiave può vivere ancora per "+
                 ttl+" secondi");
     Thread.sleep(1000);
     ttl=jedis.ttl("provaexpire");
}
System.out.println("La chiave non c'è più †");

 

Conclusioni

Ci molte altre cose che possono essere fatte utilizzando Redis, quello che abbiamo visto qui è poco di più del banale Hello World. Sicuramente da approfondire la parte relativa ai cluster, alla persistenza e alle transazioni. Vi consiglio di leggere prima di tutto la documentazione sul sito ufficiale e come introduzione ai vari argomenti anche il libro Redis in Action può aiutare.

Federico Paparoni

Looking for a right "about me"...