Redis
 sql >> Database >  >> NoSQL >> Redis

Memorizzazione nella cache dei tweet utilizzando Node.js, Redis e Socket.io

In questo articolo, creeremo un elenco di tweet in streaming basato su una query di ricerca inserita dall'utente. I tweet verranno recuperati utilizzando l'API di streaming di Twitter, archiviati in un elenco Redis e aggiornati nel front-end utilizzando Socket.io. Utilizzeremo principalmente Redis come livello di memorizzazione nella cache per il recupero dei tweet.

Introduzione

Ecco una breve descrizione delle tecnologie che utilizzeremo:

Redis

Redis è un archivio di strutture dati in memoria open source (con licenza BSD), utilizzato come database, cache e broker di messaggi. Supporta strutture di dati come stringhe, hash, elenchi, set, set ordinati con query di intervallo, bitmap, hyperloglog e indici geospaziali con query raggio.

Node.js

Node.js è una piattaforma basata sul runtime JavaScript di Chrome per creare facilmente applicazioni di rete veloci e scalabili. Node.js utilizza un modello I/O non bloccante basato su eventi che lo rende leggero ed efficiente e quindi perfetto per applicazioni in tempo reale ad alta intensità di dati che vengono eseguite su dispositivi distribuiti.

Express.js

Express.js è un framework Node.js. Puoi creare il server e il codice lato server per un'applicazione come la maggior parte degli altri linguaggi web, ma utilizzando JavaScript.

Socket.IO

Socket.IO è una libreria JavaScript per applicazioni web in tempo reale. Consente la comunicazione bidirezionale in tempo reale tra client Web e server. Ha due parti:una libreria lato client che viene eseguita sul browser e una libreria lato server per Node.js. Entrambi i componenti hanno API quasi identiche.

Heroku

Heroku è una piattaforma cloud che consente alle aziende di creare, distribuire, monitorare e ridimensionare app:è il modo più veloce per passare dall'idea all'URL, aggirando tutti quei grattacapi dell'infrastruttura.

Questo articolo presuppone che tu abbia già Redis, Node.js e Heroku Toolbelt installati sul tuo computer.

Configurazione

- Scarica il codice dal seguente repository: https://github.com/Scalegrid/code-samples/tree/sg-redis-node-socket-twitter-search/node-socket-redis-twitter-hashtags

- Esegui npm install per installare i componenti necessari

- Infine, puoi avviare il server del nodo facendo "node index.js". Puoi anche eseguire "nodemon" che controlla anche le modifiche ai file.

Puoi anche accedere a una versione ospitata di questa app qui: https://node-socket-redis-stream-tweet.herokuapp.com/

Il processo

Ecco una breve descrizione del processo che utilizzeremo per creare l'applicazione demo:

1. Inizieremo accettando una query di ricerca dall'utente. La query può essere citazioni Twitter, hashtag o qualsiasi testo di ricerca casuale.

2. Una volta ottenuta la query di ricerca, la invieremo all'API di streaming di Twitter per recuperare i tweet. Poiché si tratta di uno stream, ascolteremo quando i tweet verranno inviati dall'API.

3. Non appena un tweet viene recuperato, lo memorizzeremo in un elenco Redis e lo trasmetteremo al front-end.

Cosa sono le liste Redis?

Gli elenchi Redis sono implementati tramite elenchi collegati. Ciò significa che anche se hai milioni di elementi all'interno di un elenco, l'operazione di aggiunta di un nuovo elemento in testa o in coda all'elenco viene eseguita in un tempo costante. La velocità di aggiunta di un nuovo elemento con il comando LPUSH all'inizio di un elenco con dieci elementi è la stessa dell'aggiunta di un elemento all'inizio di un elenco con 10 milioni di elementi.

Nella nostra applicazione, memorizzeremo i tweet ricevuti tramite l'API in un elenco chiamato "tweet". Utilizzeremo LPUSH per inserire nell'elenco il tweet appena ricevuto, tagliarlo utilizzando LTRIM che limita la quantità di spazio su disco utilizzato (poiché la scrittura di uno stream può richiedere molto spazio), recuperare l'ultimo tweet utilizzando LRANGE e trasmetterlo a il front-end dove verrà aggiunto all'elenco di streaming.

Che cos'è LPUSH, LTRIM e LRANGE?

Si tratta di un insieme di comandi Redis utilizzati per aggiungere dati a un elenco. Ecco una breve descrizione:

PUSH

Inserire tutti i valori specificati all'inizio dell'elenco memorizzato in chiave. Se la chiave non esiste, viene creata come una lista vuota prima di eseguire le operazioni di push. Quando la chiave contiene un valore che non è un elenco, viene restituito un errore.

redis> LPUSH mylist "world"
(integer) 1

redis> LPUSH mylist "hello"
(integer) 2

redis> LRANGE mylist 0 -1
1) "hello"
2) "world"

LTRIM

Taglia un elenco esistente in modo che contenga solo l'intervallo di elementi specificato. Sia start che stop sono indici a base zero, dove 0 è il primo elemento dell'elenco (la testa), 1 l'elemento successivo e così via.

redis> RPUSH mylist "one"
(integer) 1

redis> RPUSH mylist "two"
(integer) 2

redis> RPUSH mylist "three"
(integer) 3

redis> LTRIM mylist 1 -1
"OK"

redis> LRANGE mylist 0 -1
1) "two"
2) "three"

GRANDE

Restituisce gli elementi specificati dell'elenco archiviato in chiave. Gli offset start e stop sono indici a base zero, dove 0 è il primo elemento dell'elenco (l'inizio dell'elenco), 1 è il successivo e così via.

Questi offset possono anche essere numeri negativi che indicano posizioni dalla fine dell'elenco. Ad esempio, -1 è l'ultimo elemento dell'elenco, -2 il penultimo e così via.

redis> RPUSH mylist "one"
(integer) 1

redis> RPUSH mylist "two"
(integer) 2

redis> RPUSH mylist "three"
(integer) 3

redis> LRANGE mylist 0 0
1) "one"

redis> LRANGE mylist -3 2
1) "one"
2) "two"
3) "three"

Creazione dell'applicazione

La nostra demo richiede sia un front-end che un back-end. Il nostro front-end è una casella di testo piuttosto semplice con un pulsante che verrà utilizzato per avviare lo streaming.

$('body').on('click', '.btn-search', function() {
   $('#tweets_area').empty();
   $(this).text('Streaming...').attr('disabled', true);
   $.ajax({
       url: '/search',
       type: 'POST',
       data: {
           val: $.trim($('.search-txt').val())
       }
   });
});

Abbiamo bisogno di una funzione di supporto per creare un tweet box una volta ricevuto il tweet dal nostro back-end:

 var _buildTweetBox = function(status) {
     var html = '';
     html += '<div class="media tweet-single">';
     html += ' <div class="media-left">';
     html += ' <a href="https://twitter.com/' + status.user.screen_name + '" target="_blank" title="' + status.user.name + '">';
     html += ' <img class="media-object" src="' + status.user.profile_image_url_https + '" alt="' + status.user.name + '" />';
     html += ' </a>';
     html += ' </div>';
     html += ' <div class="media-body">';
     html += ' <h5 class="media-heading"><a href="https://twitter.com/' + status.user.screen_name + '" target="_blank">' + status.user.screen_name + '</a></h5>';
     html += '<p class="tweet-body" title="View full tweet" data-link="https://twitter.com/' + status.user.screen_name + '/status/' + status.id_str + '">' + status.text + '</p>';
     html += ' </div>';
     html += '</div>';
     $('#tweets_area').prepend(html);
     $('#tweets_area').find('.tweet-single').first().fadeIn('slow');
};

Abbiamo anche bisogno di un ascoltatore per interrompere lo streaming e impedire l'aggiunta di altri tweet all'elenco di streaming:

socket.on('stream:destroy', function(status) {
    $('.btn-search').text('Start streaming').removeAttr('disabled');
    $('.alert-warning').fadeIn('slow');
    setTimeout(function() {
       $('.alert-warning').fadeOut('slow');
    }, STREAM_END_TIMEOUT * 1000);
});

Passiamo al lato back-end delle cose e iniziamo a scrivere la nostra /search API.

/**
 * API - Search
 */
app.post('/search', function(req, res, next) {
   _searchTwitter(req.body.val);
   res.send({
       status: 'OK'
   });
});

/**
 * Stream data from Twitter for input text
 *
 * 1. Use the Twitter streaming API to track a specific value entered by the user
 * 2. Once we have the data from Twitter, add it to a Redis list using LPUSH
 * 3. After adding to list, limit the list using LTRIM so the stream doesn't overflow the disk
 * 4. Use LRANGE to fetch the latest tweet and emit it to the front-end using Socket.io
 *
 * @param {String} val Query String
 * @return
 */
var _searchTwitter = function(val) {
   twit.stream('statuses/filter', {track: val}, function(stream) {
   stream.on('data', function(data) {
       client.lpush('tweets', JSON.stringify(data), function() {
           client.ltrim('tweets', 0, TWEETS_TO_KEEP, function() {
              client.lrange('tweets', 0, 1, function(err, tweetListStr) {
                  io.emit('savedTweetToRedis', JSON.parse(tweetListStr[0]));
               });
           });
        });
    });
    stream.on('destroy', function(response) {
        io.emit('stream:destroy');
    });
    stream.on('end', function(response) {
        io.emit('stream:destroy');
    });
    setTimeout(stream.destroy, STREAM_TIMEOUT * 1000);
    });
}

Il codice sopra contiene il nucleo del nostro back-end. Una volta ricevuta una richiesta su /search, avviamo lo stream utilizzando l'API di streaming di Twitter che restituisce un oggetto stream.

twit.stream('statuses/filter', {track: val}, function(stream) {});

Possiamo ascoltare l'oggetto stream per una chiave chiamata "data" che ci invierà un nuovo tweet quando disponibile.

stream.on('data', function(data) {});

L'oggetto "data" contiene il tweet JSON che potrebbe assomigliare a questo (parte della risposta è stata omessa):

{
 "created_at": "Wed Jul 26 08:01:56 +0000 2017",
 "id": 890119982641803300,
 "id_str": "890119982641803264",
 "text": "RT @FoxNews: Jim DeMint: \"There is no better man than Jeff Sessions, and no greater supporter...of [President #Trump's] agenda.\"… ",
 "source": "<a href=\"http://twitter.com/download/android\" rel=\"nofollow\">Twitter for Android</a>",
 "truncated": false,
 "in_reply_to_status_id": null,
 "in_reply_to_status_id_str": null,
 "in_reply_to_user_id": null,
 "in_reply_to_user_id_str": null,
 "in_reply_to_screen_name": null,
 "user": {
 "id": 4833141138,
 "id_str": "4833141138",
 "name": "randy joe davis",
 "screen_name": "randyjoedavis1",
 "location": null,
 "url": null,
 "description": "Conservative Patriot, retired military, retired DOD civilian. cattle farmer, horseman, adventurer. Lovin Life ! GO HOGS !!",
 "protected": false,
 "verified": false,
 "followers_count": 226,
 "friends_count": 346,
 "listed_count": 0,
 "favourites_count": 3751,
 "statuses_count": 1339,
 "created_at": "Sat Jan 30 03:39:16 +0000 2016",
 "utc_offset": null,
 "time_zone": null,
 "geo_enabled": false,
 "lang": "en",
 "contributors_enabled": false,
 "is_translator": false,
 "profile_background_color": "F5F8FA",
 "profile_background_image_url": "",
 "profile_background_image_url_https": "",
 "profile_background_tile": false,
 "profile_link_color": "1DA1F2",
 "profile_sidebar_border_color": "C0DEED",
 "profile_sidebar_fill_color": "DDEEF6",
 "profile_text_color": "333333",
 "profile_use_background_image": true,
 "profile_image_url": "http://pbs.twimg.com/profile_images/883522005210943488/rqyyXlEX_normal.jpg",
 "profile_image_url_https": "https://pbs.twimg.com/profile_images/883522005210943488/rqyyXlEX_normal.jpg",
 "default_profile": true,
 "default_profile_image": false,
 "following": null,
 "follow_request_sent": null,
 "notifications": null
 }
}

Memorizziamo questa risposta in un elenco Redis chiamato "tweet" utilizzando LPUSH:

client.lpush('tweets', JSON.stringify(data), function() {});

Una volta che il tweet è stato salvato, riduciamo l'elenco utilizzando LTRIM per mantenere un numero massimo di tweet (in modo che il nostro spazio su disco non si riempia):

client.ltrim('tweets', 0, TWEETS_TO_KEEP, function() {});

Dopo aver tagliato l'elenco, prendiamo l'ultimo tweet utilizzando LRANGE e lo inviamo al front-end:

client.lrange('tweets', 0, 1, function(err, tweetListStr) {
 io.emit('savedTweetToRedis', JSON.parse(tweetListStr[0]));
});

Poiché si tratta di un'applicazione demo, dobbiamo anche distruggere manualmente lo stream dopo un periodo di tempo specifico in modo che non continui a scrivere sul disco:

stream.on('end', function(response) {
 io.emit('stream:destroy');
});
setTimeout(stream.destroy, STREAM_TIMEOUT * 1000);

E hai finito! Avvia il server utilizzando npm start e goditi l'esperienza di streaming.

Una demo dell'applicazione è disponibile qui: https://node-socket-redis-stream-tweet.herokuapp.com/

Per distribuire questa applicazione su Heroku, controlla i loro documenti:https://devcenter.heroku.com/categories/deployment

L'intero codice sorgente è disponibile anche su GitHub per il fork e il lavoro su: https://github.com/Scalegrid/code-samples/tree/sg-redis-node-socket-twitter-search/node-socket-redis -hashtag-twitter

Come sempre, se costruisci qualcosa di fantastico, scrivici un tweet a riguardo @scalegridio.

Se hai bisogno di aiuto con la gestione e l'hosting per Redis™*, contattaci all'indirizzo [email protected] per ulteriori informazioni.