Redis
 sql >> Base de données >  >> NoSQL >> Redis

Mise en cache des tweets à l'aide de Node.js, Redis et Socket.io

Dans cet article, nous allons créer une liste de diffusion de tweets en fonction d'une requête de recherche saisie par l'utilisateur. Les tweets seront récupérés à l'aide de l'API Streaming de Twitter, stockés dans une liste Redis et mis à jour dans le frontal à l'aide de Socket.io. Nous utiliserons principalement Redis comme couche de mise en cache pour récupérer les tweets.

Présentation

Voici une brève description des technologies que nous utiliserons :

Redis

Redis est un magasin de structure de données en mémoire open source (sous licence BSD), utilisé comme base de données, cache et courtier de messages. Il prend en charge les structures de données telles que les chaînes, les hachages, les listes, les ensembles, les ensembles triés avec des requêtes de plage, les bitmaps, les hyperloglogs et les index géospatiaux avec des requêtes de rayon.

Node.js

Node.js est une plate-forme basée sur l'environnement d'exécution JavaScript de Chrome pour créer facilement des applications réseau rapides et évolutives. Node.js utilise un modèle d'E/S non bloquant piloté par les événements qui le rend léger et efficace, et donc parfait pour les applications en temps réel gourmandes en données qui s'exécutent sur des appareils distribués.

Express.js

Express.js est un framework Node.js. Vous pouvez créer le code serveur et côté serveur pour une application comme la plupart des autres langages Web, mais en utilisant JavaScript.

Socket.IO

Socket.IO est une bibliothèque JavaScript pour les applications Web en temps réel. Il permet une communication bidirectionnelle en temps réel entre les clients Web et les serveurs. Il comporte deux parties :une bibliothèque côté client qui s'exécute sur le navigateur et une bibliothèque côté serveur pour Node.js. Les deux composants ont des API presque identiques.

Héroku

Heroku est une plate-forme cloud qui permet aux entreprises de créer, de diffuser, de surveiller et de faire évoluer des applications. C'est le moyen le plus rapide de passer de l'idée à l'URL, en évitant tous ces problèmes d'infrastructure.

Cet article suppose que Redis, Node.js et Heroku Toolbelt sont déjà installés sur votre machine.

Configuration

- Téléchargez le code à partir du référentiel suivant : https://github.com/Scalegrid/code-samples/tree/sg-redis-node-socket-twitter-search/node-socket-redis-twitter-hashtags

- Exécutez npm install pour installer les composants nécessaires

- Enfin, vous pouvez démarrer le serveur de nœud en faisant « node index.js ». Vous pouvez également exécuter "nodemon" qui surveille également les modifications de fichiers.

Vous pouvez également accéder à une version hébergée de cette application ici : https://node-socket-redis-stream-tweet.herokuapp.com/

Le processus

Voici une brève description du processus que nous allons utiliser pour créer l'application de démonstration :

1. Nous commencerons par accepter une requête de recherche de l'utilisateur. La requête peut être des mentions Twitter, des hashtags ou tout autre texte de recherche aléatoire.

2. Une fois que nous aurons la requête de recherche, nous l'enverrons à l'API Streaming de Twitter pour récupérer les tweets. Comme il s'agit d'un flux, nous écouterons les tweets envoyés par l'API.

3. Dès qu'un tweet est récupéré, nous le stockons dans une liste Redis et le diffusons sur le front-end.

Que sont les listes Redis ?

Les listes Redis sont implémentées via des listes liées. Cela signifie que même si vous avez des millions d'éléments dans une liste, l'opération d'ajout d'un nouvel élément en tête ou en fin de liste est effectuée en temps constant. La vitesse d'ajout d'un nouvel élément avec la commande LPUSH en tête d'une liste de dix éléments est la même que celle de l'ajout d'un élément en tête d'une liste de 10 millions d'éléments.

Dans notre application, nous stockerons les tweets reçus via l'API dans une liste appelée "tweets". Nous utiliserons LPUSH pour pousser le tweet nouvellement reçu dans la liste, le découper à l'aide de LTRIM qui limite la quantité d'espace disque utilisé (car l'écriture d'un flux peut prendre beaucoup d'espace), récupérer le dernier tweet à l'aide de LRANGE et le diffuser à le front-end où il sera ajouté à la liste de diffusion.

Qu'est-ce que LPUSH, LTRIM et LRANGE ?

Il s'agit d'un ensemble de commandes Redis utilisées pour ajouter des données à une liste. Voici une brève description :

LPUSH

Insérez toutes les valeurs spécifiées en tête de la liste stockée dans la clé. Si la clé n'existe pas, elle est créée sous forme de liste vide avant d'effectuer les opérations push. Lorsque la clé contient une valeur qui n'est pas une liste, une erreur est renvoyée.

redis> LPUSH mylist "world"
(integer) 1

redis> LPUSH mylist "hello"
(integer) 2

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

LTRIM

Coupez une liste existante afin qu'elle ne contienne que la plage d'éléments spécifiés. Le début et la fin sont tous deux des index de base zéro, où 0 est le premier élément de la liste (l'en-tête), 1 l'élément suivant et ainsi de suite.

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"

LRANGE

Renvoie les éléments spécifiés de la liste stockée dans la clé. Les décalages de début et de fin sont des index de base zéro, 0 étant le premier élément de la liste (la tête de la liste), 1 étant le suivant, et ainsi de suite.

Ces décalages peuvent également être des nombres négatifs indiquant des positions à partir de la fin de la liste. Par exemple, -1 est le dernier élément de la liste, -2 l'avant-dernier, et ainsi de suite.

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"

Construire l'application

Notre démo nécessite à la fois un front-end et un back-end. Notre interface est une zone de texte assez simple avec un bouton qui sera utilisé pour démarrer le flux.

$('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())
       }
   });
});

Nous avons besoin d'une fonction d'assistance pour créer une boîte de tweet une fois que nous recevons le tweet de notre 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');
};

Nous avons également besoin d'un écouteur pour arrêter le flux et empêcher l'ajout d'autres tweets à la liste de flux :

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);
});

Passons à l'arrière-plan et commençons à écrire notre API /search.

/**
 * 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);
    });
}

Le code ci-dessus contient le cœur de notre back-end. Une fois qu'une demande a été reçue sur /search, nous démarrons le flux à l'aide de l'API de streaming de Twitter qui renvoie un objet de flux.

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

Nous pouvons écouter l'objet stream pour une clé appelée "data" qui nous enverra un nouveau tweet lorsqu'il sera disponible.

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

L'objet "data" contient le tweet JSON qui peut ressembler à ceci (une partie de la réponse a été omise) :

{
 "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
 }
}

Nous stockons cette réponse dans une liste Redis appelée "tweets" en utilisant LPUSH :

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

Une fois le tweet enregistré, nous découpons la liste à l'aide de LTRIM pour conserver un nombre maximum de tweets (afin que notre espace disque ne soit pas plein) :

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

Après avoir découpé la liste, nous récupérons le dernier tweet à l'aide de LRANGE et l'envoyons au frontal :

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

Comme il s'agit d'une application de démonstration, nous devons également détruire manuellement le flux après un certain temps afin qu'il ne continue pas à écrire sur le disque :

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

Et tu as fini! Lancez le serveur à l'aide de npm start et profitez de l'expérience de streaming.

Une démo de l'application est disponible ici : https://node-socket-redis-stream-tweet.herokuapp.com/

Pour déployer cette application sur Heroku, consultez leur documentation :https://devcenter.heroku.com/categories/deployment

L'intégralité du code source est également disponible sur GitHub pour que vous puissiez le bifurquer et y travailler : https://github.com/Scalegrid/code-samples/tree/sg-redis-node-socket-twitter-search/node-socket-redis -twitter-hashtags

Comme toujours, si vous construisez quelque chose de génial, envoyez-nous un tweet @scalegridio.

Si vous avez besoin d'aide pour la gestion et l'hébergement de Redis™*, contactez-nous à [email protected] pour plus d'informations.