sql >> Base de Datos >  >> NoSQL >> Redis

Almacenamiento en caché de tweets usando Node.js, Redis y Socket.io

En este artículo, crearemos una lista de transmisión de tweets basada en una consulta de búsqueda ingresada por el usuario. Los tweets se obtendrán mediante la API de transmisión de Twitter, se almacenarán en una lista de Redis y se actualizarán en el front-end mediante Socket.io. Principalmente usaremos Redis como una capa de almacenamiento en caché para obtener tweets.

Introducción

Aquí hay una breve descripción de las tecnologías que usaremos:

Redis

Redis es un almacén de estructura de datos en memoria de código abierto (con licencia BSD), que se utiliza como base de datos, caché y agente de mensajes. Admite estructuras de datos como cadenas, hashes, listas, conjuntos, conjuntos ordenados con consultas de rango, mapas de bits, hiperloglogs e índices geoespaciales con consultas de radio.

Node.js

Node.js es una plataforma construida sobre el tiempo de ejecución de JavaScript de Chrome para crear fácilmente aplicaciones de red rápidas y escalables. Node.js utiliza un modelo de E/S sin bloqueo y controlado por eventos que lo hace liviano y eficiente y, por lo tanto, perfecto para aplicaciones en tiempo real con uso intensivo de datos que se ejecutan en dispositivos distribuidos.

Express.js

Express.js es un marco de Node.js. Puede crear el servidor y el código del lado del servidor para una aplicación como la mayoría de los otros lenguajes web, pero usando JavaScript.

Socket.IO

Socket.IO es una biblioteca de JavaScript para aplicaciones web en tiempo real. Permite la comunicación bidireccional en tiempo real entre clientes web y servidores. Tiene dos partes:una biblioteca del lado del cliente que se ejecuta en el navegador y una biblioteca del lado del servidor para Node.js. Ambos componentes tienen API casi idénticas.

Héroe

Heroku es una plataforma en la nube que permite a las empresas crear, entregar, monitorear y escalar aplicaciones; es la forma más rápida de pasar de la idea a la URL, evitando todos esos dolores de cabeza de infraestructura.

Este artículo asume que ya tiene Redis, Node.js y Heroku Toolbelt instalados en su máquina.

Configuración

- Descarga el código del siguiente repositorio: https://github.com/Scalegrid/code-samples/tree/sg-redis-node-socket-twitter-search/node-socket-redis-twitter-hashtags

- Ejecute npm install para instalar los componentes necesarios

- Finalmente, puede iniciar el servidor de nodos haciendo "node index.js". También puede ejecutar "nodemon", que también observa los cambios en los archivos.

También puede acceder a una versión alojada de esta aplicación aquí: https://node-socket-redis-stream-tweet.herokuapp.com/

El Proceso

Aquí hay una breve descripción del proceso que usaremos para construir la aplicación de demostración:

1. Empezaremos por aceptar una consulta de búsqueda del usuario. La consulta puede ser menciones de Twitter, hashtags o cualquier texto de búsqueda aleatorio.

2. Una vez que tengamos la consulta de búsqueda, la enviaremos a la API de transmisión de Twitter para buscar tweets. Dado que es una transmisión, estaremos escuchando cuando la API envíe tweets.

3. Tan pronto como se recupere un tweet, lo almacenaremos en una lista de Redis y lo transmitiremos al front-end.

¿Qué son las listas Redis?

Las listas Redis se implementan a través de listas vinculadas. Esto significa que incluso si tiene millones de elementos dentro de una lista, la operación de agregar un nuevo elemento al principio o al final de la lista se realiza en tiempo constante. La velocidad de agregar un nuevo elemento con el comando LPUSH al encabezado de una lista con diez elementos es la misma que agregar un elemento al encabezado de una lista con 10 millones de elementos.

En nuestra aplicación, almacenaremos los tweets recibidos a través de la API en una lista llamada "tweets". Usaremos LPUSH para enviar el tweet recién recibido a la lista, recortarlo usando LTRIM, que restringe la cantidad de espacio en disco utilizado (ya que escribir una secuencia puede ocupar mucho espacio), obtener el último tweet usando LRANGE y transmitirlo a el front-end donde se agregará a la lista de transmisión.

¿Qué es LPUSH, LTRIM y LRANGE?

Estos son un conjunto de comandos de Redis que se utilizan para agregar datos a una lista. Aquí hay una breve descripción:

PULSAR

Inserte todos los valores especificados al principio de la lista almacenada en key. Si la clave no existe, se crea como una lista vacía antes de realizar las operaciones de inserción. Cuando la clave contiene un valor que no es una lista, se devuelve un error.

redis> LPUSH mylist "world"
(integer) 1

redis> LPUSH mylist "hello"
(integer) 2

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

LTRIM

Recorte una lista existente para que contenga solo el rango de elementos especificado. Tanto start como stop son índices basados ​​en cero, donde 0 es el primer elemento de la lista (el encabezado), 1 el siguiente elemento y así sucesivamente.

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"

AMPLIAR

Devuelve los elementos especificados de la lista almacenada en key. Los desplazamientos start y stop son índices basados ​​en cero, siendo 0 el primer elemento de la lista (el encabezado de la lista), 1 el siguiente, y así sucesivamente.

Estos desplazamientos también pueden ser números negativos que indican posiciones desde el final de la lista. Por ejemplo, -1 es el último elemento de la lista, -2 el penúltimo y así sucesivamente.

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"

Construyendo la aplicación

Nuestra demostración requiere un front-end y un back-end. Nuestro front-end es un cuadro de texto bastante simple con un botón que se usará para iniciar la transmisión.

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

Necesitamos una función de ayuda para crear un cuadro de tweet una vez que recibamos el tweet de nuestro 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');
};

También necesitamos un oyente para detener la transmisión y evitar agregar más tweets a la lista de transmisión:

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

Pasemos al lado de back-end de las cosas y comencemos a escribir nuestra 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);
    });
}

El código anterior contiene el núcleo de nuestro back-end. Una vez que se recibe una solicitud en /search, comenzamos la transmisión usando la API de transmisión de Twitter que devuelve un objeto de transmisión.

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

Podemos escuchar el objeto de transmisión en busca de una clave llamada "datos" que nos enviará un nuevo tweet cuando esté disponible.

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

El objeto de "datos" contiene el tweet JSON que puede verse así (se ha omitido parte de la respuesta):

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

Almacenamos esta respuesta en una lista de Redis llamada "tweets" usando LPUSH:

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

Una vez que se ha guardado el tweet, recortamos la lista usando LTRIM para mantener un número máximo de tweets (para que nuestro espacio en disco no se llene):

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

Después de recortar la lista, buscamos el último tweet usando LRANGE y lo emitimos al front-end:

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

Dado que esta es una aplicación de demostración, también debemos destruir manualmente la transmisión después de un tiempo específico para que no siga escribiendo en el disco:

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

¡Y tu estas listo! Encienda el servidor usando npm start y disfrute de la experiencia de transmisión.

Una demostración de la aplicación está disponible aquí: https://node-socket-redis-stream-tweet.herokuapp.com/

Para implementar esta aplicación en Heroku, consulte sus documentos:https://devcenter.heroku.com/categories/deployment

El código fuente completo también está disponible en GitHub para que pueda bifurcarlo y trabajar en él:https://github.com/Scalegrid/code-samples/tree/sg-redis-node-socket-twitter-search/node-socket-redis -twitter-hashtags

Como siempre, si crea algo asombroso, envíenos un tweet al respecto @scalegridio.

Si necesita ayuda con la administración y el alojamiento de Redis™*, comuníquese con nosotros a [email protected] para obtener más información.