Redis
 sql >> Datenbank >  >> NoSQL >> Redis

Zwischenspeichern von Tweets mit Node.js, Redis und Socket.io

In diesem Artikel erstellen wir basierend auf einer vom Nutzer eingegebenen Suchanfrage eine Streaming-Liste mit Tweets. Die Tweets werden über die Streaming-API von Twitter abgerufen, in einer Redis-Liste gespeichert und im Frontend über Socket.io aktualisiert. Wir werden Redis hauptsächlich als Caching-Layer zum Abrufen von Tweets verwenden.

Einführung

Hier ist eine kurze Beschreibung der Technologien, die wir verwenden werden:

Redis

Redis ist ein Open-Source (BSD-lizenzierter) In-Memory-Datenstrukturspeicher, der als Datenbank, Cache und Nachrichtenbroker verwendet wird. Es unterstützt Datenstrukturen wie Strings, Hashes, Listen, Mengen, sortierte Mengen mit Bereichsabfragen, Bitmaps, Hyperloglogs und Geoindizes mit Radiusabfragen.

Node.js

Node.js ist eine Plattform, die auf der JavaScript-Laufzeitumgebung von Chrome zum einfachen Erstellen schneller und skalierbarer Netzwerkanwendungen basiert. Node.js verwendet ein ereignisgesteuertes, nicht blockierendes E/A-Modell, das es leicht und effizient macht und sich somit perfekt für datenintensive Echtzeitanwendungen eignet, die auf verteilten Geräten ausgeführt werden.

Express.js

Express.js ist ein Node.js-Framework. Sie können den serverseitigen und serverseitigen Code für eine Anwendung wie die meisten anderen Websprachen erstellen, jedoch mit JavaScript.

Socket.IO

Socket.IO ist eine JavaScript-Bibliothek für Echtzeit-Webanwendungen. Es ermöglicht eine bidirektionale Kommunikation in Echtzeit zwischen Webclients und Servern. Es besteht aus zwei Teilen:einer clientseitigen Bibliothek, die im Browser ausgeführt wird, und einer serverseitigen Bibliothek für Node.js. Beide Komponenten haben nahezu identische APIs.

Heroku

Heroku ist eine Cloud-Plattform, mit der Unternehmen Apps erstellen, bereitstellen, überwachen und skalieren können – es ist der schnellste Weg von der Idee zur URL und umgeht all diese Infrastrukturprobleme.

In diesem Artikel wird davon ausgegangen, dass Redis, Node.js und Heroku Toolbelt bereits auf Ihrem Computer installiert sind.

Einrichtung

– Laden Sie den Code aus dem folgenden Repository herunter: https://github.com/Scalegrid/code-samples/tree/sg-redis-node-socket-twitter-search/node-socket-redis-twitter-hashtags

- Führen Sie npm install aus, um die erforderlichen Komponenten zu installieren

- Schließlich können Sie den Node-Server starten, indem Sie „node index.js“ ausführen. Sie können auch „nodemon“ ausführen, das auch auf Dateiänderungen achtet.

Sie können auch hier auf eine gehostete Version dieser App zugreifen: https://node-socket-redis-stream-tweet.herokuapp.com/

Der Prozess

Hier ist eine kurze Beschreibung des Prozesses, den wir verwenden werden, um die Demoanwendung zu erstellen:

1. Wir beginnen mit der Annahme einer Suchanfrage des Benutzers. Die Suchanfrage kann aus Twitter-Erwähnungen, Hashtags oder einem beliebigen Suchtext bestehen.

2. Sobald wir die Suchanfrage haben, senden wir sie an die Streaming-API von Twitter, um Tweets abzurufen. Da es sich um einen Stream handelt, hören wir zu, wenn Tweets von der API gesendet werden.

3. Sobald ein Tweet abgerufen wird, speichern wir ihn in einer Redis-Liste und übertragen ihn an das Frontend.

Was sind Redis-Listen?

Redis-Listen werden über Linked Lists implementiert. Das bedeutet, dass selbst wenn Sie Millionen von Elementen in einer Liste haben, das Hinzufügen eines neuen Elements am Anfang oder am Ende der Liste in konstanter Zeit ausgeführt wird. Das Hinzufügen eines neuen Elements mit dem LPUSH-Befehl zum Kopf einer Liste mit zehn Elementen ist genauso schnell wie das Hinzufügen eines Elements zum Kopf einer Liste mit 10 Millionen Elementen.

In unserer Anwendung speichern wir die über die API empfangenen Tweets in einer Liste namens „Tweets“. Wir werden LPUSH verwenden, um den neu empfangenen Tweet in die Liste zu verschieben, ihn mit LTRIM trimmen, was den verwendeten Speicherplatz einschränkt (da das Schreiben eines Streams viel Speicherplatz beanspruchen kann), den neuesten Tweet mit LRANGE abrufen und ihn senden an das Front-End, wo es an die Streaming-Liste angehängt wird.

Was ist LPUSH, LTRIM und LRANGE?

Hierbei handelt es sich um eine Reihe von Redis-Befehlen, die zum Hinzufügen von Daten zu einer Liste verwendet werden. Hier ist eine kurze Beschreibung:

LPUSH

Fügen Sie alle angegebenen Werte am Anfang der Liste ein, die unter key gespeichert ist. Wenn der Schlüssel nicht vorhanden ist, wird er als leere Liste erstellt, bevor die Push-Operationen ausgeführt werden. Wenn der Schlüssel einen Wert enthält, der keine Liste ist, wird ein Fehler zurückgegeben.

redis> LPUSH mylist "world"
(integer) 1

redis> LPUSH mylist "hello"
(integer) 2

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

LTRIM

Kürzen Sie eine vorhandene Liste, sodass sie nur den angegebenen Bereich von Elementen enthält. Sowohl start als auch stop sind nullbasierte Indizes, wobei 0 das erste Element der Liste (der Kopf) ist, 1 das nächste Element und so weiter.

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

Gibt die angegebenen Elemente der unter Schlüssel gespeicherten Liste zurück. Die Offsets start und stop sind nullbasierte Indizes, wobei 0 das erste Element der Liste ist (der Kopf der Liste), 1 das nächste und so weiter.

Diese Offsets können auch negative Zahlen sein, die Positionen vom Ende der Liste angeben. Beispielsweise ist -1 das letzte Element der Liste, -2 das vorletzte und so weiter.

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"

Anwendung erstellen

Unsere Demo erfordert sowohl ein Front-End als auch ein Back-End. Unser Front-End ist ein ziemlich einfaches Textfeld mit einer Schaltfläche, die zum Starten des Streams verwendet wird.

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

Wir brauchen eine Hilfsfunktion, um eine Tweet-Box zu erstellen, sobald wir den Tweet von unserem Back-End erhalten:

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

Wir brauchen auch einen Listener, um den Stream zu stoppen und zu verhindern, dass weitere Tweets zur Streaming-Liste hinzugefügt werden:

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

Lassen Sie uns auf die Back-End-Seite der Dinge wechseln und mit dem Schreiben unserer /search-API beginnen.

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

Der obige Code enthält den Kern unseres Backends. Sobald eine Anfrage bei /search eingegangen ist, starten wir den Stream mithilfe der Streaming-API von Twitter, die ein Stream-Objekt zurückgibt.

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

Wir können das Stream-Objekt auf einen Schlüssel namens „Daten“ abhören, der uns einen neuen Tweet sendet, sobald er verfügbar ist.

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

Das „data“-Objekt enthält den Tweet-JSON, der etwa so aussehen könnte (ein Teil der Antwort wurde weggelassen):

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

Wir speichern diese Antwort in einer Redis-Liste namens „Tweets“ mit LPUSH:

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

Sobald der Tweet gespeichert wurde, trimmen wir die Liste mit LTRIM, um eine maximale Anzahl von Tweets zu behalten (damit unser Speicherplatz nicht voll wird):

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

Nachdem wir die Liste getrimmt haben, holen wir den neuesten Tweet mit LRANGE und geben ihn an das Frontend aus:

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

Da es sich um eine Demoanwendung handelt, müssen wir den Stream nach einer bestimmten Zeit auch manuell zerstören, damit er nicht weiter auf die Festplatte schreibt:

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

Und du bist fertig! Starten Sie den Server mit npm start und genießen Sie das Streaming-Erlebnis.

Eine Demo der Anwendung ist hier verfügbar: https://node-socket-redis-stream-tweet.herokuapp.com/

Informationen zum Bereitstellen dieser Anwendung auf Heroku finden Sie in deren Dokumenten:https://devcenter.heroku.com/categories/deployment

Der gesamte Quellcode ist auch auf GitHub verfügbar, damit Sie forken und daran arbeiten können: https://github.com/Scalegrid/code-samples/tree/sg-redis-node-socket-twitter-search/node-socket-redis -twitter-hashtags

Wie immer, wenn Sie etwas Großartiges bauen, twittern Sie uns darüber @scalegridio.

Wenn Sie Hilfe bei der Verwaltung und dem Hosting für Redis™* benötigen, wenden Sie sich für weitere Informationen an uns unter [email protected].