How to instantiate Multiple Redis Connections for Publish Subscribe (node.js + node_redis) - node.js

Scenario
Using node_redis to build a simple Redis Pubish Subscribe (chat) example: https://github.com/nelsonic/hapi-socketio-redis-chat-example (with Hapi.js and Socket.io)
We have created a node module redis_connection.js in our project ( see: http://git.io/vqaos ) to instantiate the Redis connection because we don't want to be repeating the code which connects (to RedisCloud) multiple times:
var redis = require('redis');
var url = require('url');
var redisURL = url.parse(process.env.REDISCLOUD_URL);
var redisClient = redis.createClient(redisURL.port, redisURL.hostname,
{no_ready_check: true});
redisClient.auth(redisURL.auth.split(":")[1]);
module.exports = redisClient;
Which we then use like this:
var redisClient = require('./redis_connection.js');
// Confirm we are able to connect to RedisCloud:
redisClient.set('redis', 'working', redisClient.print);
redisClient.get('redis', function (err, reply) {
console.log('RedisCLOUD is ' +reply.toString());
});
This works fine for normal GET/SET operations with Redis,
but when we try to instantiate multiple connections to Redis (e.g: one to publish, another to subscribe and a third just to GET/SET keys/values) we get an error:
Issue
We are seeing the following error:
Error: Connection in subscriber mode, only subscriber commands may be used
What are we doing wrong?
Full code at the point where we see this issue: http://git.io/vqa6y
Note
We tried to dig through existing SO Q/A on this, e.g:
Publish subscribe with nodejs and redis(node_redis)
Redis publish/subscribe: see what channels are currently subscribed to
how to use the redis publish/subscribe
but did not find a solution that exactly matched our situation...
(any suggestions/help much appreciated!)

Not tested, but too long for a comment.
Try to define another redis connection module, one for your regular usage and a second one solely for your pubsub subscriptions usage.
Add a redis_pubsub_connection.js to your project:
var redis = require('redis');
var url = require('url');
var redisURL = url.parse(process.env.REDISCLOUD_URL);
var redisPubSubClient = redis.createClient(redisURL.port, redisURL.hostname,
{no_ready_check: true});
redisPubSubClient.auth(redisURL.auth.split(":")[1]);
module.exports = redisPubSubClient;
And change your publish.js require statement to:
var redis = require('./redis_pubsub_connection'); // RedisCloud

redis-connection node.js module
In the interest of keeping this re-useable across our projects we wrote a (mini) node.js module to initialize Redis connections: https://github.com/dwyl/redis-connection
The code is simple and tested and takes care of authentication if required.
(not copy-pasting the module here to avoid duplication)
see: https://github.com/dwyl/redis-connection/blob/master/index.js
Usage:
Install from NPM
npm install redis-connection --save
Use in your script
var redisClient = require('redis-connection')();
redisClient.set('hello', 'world');
redisClient.get('hello', function (err, reply) {
console.log('hello', reply.toString()); // hello world
});
Publish Subscribe
var redisClient = require('redis-connection')(); // Publisher
var redisSub = require('redis-connection')('subscriber');
redisSub.subscribe("chat:messages:latest", "chat:people:new");
For a working example see: https://github.com/dwyl/hapi-socketio-redis-chat-example
The advantage is that we can re-use the same redisClient across multiple files in the same project without creating new connections (the single or pub/sub connection is cached and re-used)
Credit: We borrowed ideas from several places so have up-voted all the answers. But ultimately we wrote a slightly different solution so we have shared it with everyone on NPM/GitHub. Thanks again everyone!

If you want to supply regular connection and a sub one and you want to ensure you only have one of each across the application than you could use a combination of the two solutions that includes the notion of a singleton, something like this:
var subConnection, con;
var createConnection = module.exports.createConnection = function(){
var redis = require('redis');
var url = require('url'); var redisURL = url.parse(process.env.REDISCLOUD_URL);
var redisClient = redis.createClient(redisURL.port, redisURL.hostname, {no_ready_check: true});
redisClient.auth(redisURL.auth.split(":")
return redisClient;
}
module.exports.getSubConnection = function(){
if (!subConnection)
subConnection = createConnection();
return subConnection
}
module.exports.getConnection = function(){
if (!con)
con = createConnection();
return con
}
}
Repeat for the oher two connection types and call it like
var con = require('./redis_connection.js').getConnection();

The problem is that your redis client creation code is being cached by requires so you reuse the same connection again and again. Instead of returning the connection in your redis_connection module, you could return a function:
module.exports = function(){
var redis = require('redis');
var url = require('url'); var redisURL = url.parse(process.env.REDISCLOUD_URL);
var redisClient = redis.createClient(redisURL.port, redisURL.hostname, {no_ready_check: true});
redisClient.auth(redisURL.auth.split(":")
return redisClient;
}
And then call it like so:
var redisClient = require('./redis_connection.js')();

Related

How can we use two separate redis DB client in single nodejs app

I want to have more than one Redis client object as multiple DB instance like DB0 (O INDEX REDIS DB).
Currently, I'm using
let RedisClient = require("./redis");
RedisClient.select(1, function (err, res) {
// any operation here
});
so RedisClient is having DB1 instance. what is the best way to deal with multiple db if we want to use another DB2 ?
Use redis.createClient() twice
const redis = require("redis");
const client_1 = redis.createClient(REDIS_1_HOST, REDIS_PORT_1);
const client_2 = redis.createClient(REDIS_2_HOST, REDIS_PORT_2);
see: https://www.npmjs.com/package/redis#rediscreateclient

Heroku Node.js RedisCloud Redis::CannotConnectError on localhost instead of REDISCLOUD_URL

When i try to connect my Nodsjs application to RedisCloud on Heroku I am getting the following error
Redis::CannotConnectError: Error connecting to Redis on 127.0.0.1:6379 (ECONNREFUSED)
I have even tried to directly set the redis URL and port in the code to test it out as well. But still, it tried to connect to the localhost on Heroku instead of the RedisCloud URL.
const {Queue} = require('bullmq');
const Redis = require('ioredis');
const conn = new Redis(
'redis://rediscloud:mueSEJFadzE9eVcjFei44444RIkNO#redis-15725.c9.us-east-1-4.ec2.cloud.redislabs.com:15725'
// Redis Server Connection Configuration
console.log('\n==================================================\n');
console.log(conn.options, process.env.REDISCLOUD_URL);
const defaultQueue = () => {
// Initialize queue instance, by passing the queue-name & redis connection
const queue = new Queue('default', {conn});
return queue;
};
module.exports = defaultQueue;
Complete Dump of the Logs https://pastebin.com/N9awJYL9
set REDISCLOUD_URL on .env file as follows
REDISCLOUD_URL =redis://rediscloud:password#hostname:port
import * as Redis from 'ioredis';
export const redis = new Redis(process.env.REDISCLOUD_URL);
I just had a hard time trying to find out how to connect the solution below worked for me.
Edit----
Although I had been passed the parameters to connect to the Redis cloud, it connected to the local Redis installed in my machine. Sorry for that!
I will leave my answer here, just in case anyone need to connect to local Redis.
let express = require('express');
var redis = require('ioredis');
pwd = 'your_pwd'
url = 'rediss://host'
port = '1234'
redisConfig = `${url}${pwd}${port}`
client = redis.createClient({ url: redisConfig })
client.on('connect', function() {
console.log('-->> CONNECTED');
});
client.on("error", function(error) {
console.error('ERRO DO REDIS', error);
});
Just wanted to post my case in case someone has the same problem like me.
In my situation I was trying to use Redis with Bull, so i need it the url/port,host data to make this happened.
Here is the info:
https://devcenter.heroku.com/articles/node-redis-workers
but basically you can start your worker like this:
let REDIS_URL = process.env.REDISCLOUD_URL || 'redis://127.0.0.1:6379';
//Once you got Redis info ready, create your task queue
const queue = new Queue('new-queue', REDIS_URL);
In the case you are using local, meaning 'redis://127.0.0.1:6379' remember to run redis-server:
https://redis.io/docs/getting-started/

Can't receive redis data from socket io

I'm building a realtime visualization using redis as pubsub messenger between python and node. There's a python script always running which sets a redis hash with hmset. That side of the app is working fine, if I enter the following example command: "HGETALL 'sellers-80183917'" in a redis client I end up getting the proper data.
The problem is in the js side. I'm using socketio and redis nodejs libraries to listen to the redis instance and publish the results online through a d3js viz.
I run the following code with node:
var express = require('express');
var app = express();
var redis = require('redis');
app.use(express.static(__dirname + '/public'));
var http = require('http').Server(app);
var io = require('socket.io')(http);
var sredis = require('socket.io-redis');
io.adapter(sredis({ host: 'localhost', port: 6379 }));
redisSubscriber = redis.createClient(6379, 'localhost', {});
redisSubscriber.on('message', function(channel, message) {
io.emit(channel, message);
});
app.get('/sellers/:seller_id', function(req, res){
var seller_id = req.params.seller_id;
redisSubscriber.subscribe('sellers-'.concat(seller_id));
res.render( 'seller.ejs', { seller:seller_id } );
});
http.listen(3000, '127.0.0.1', function(){
console.log('listening on *:3000');
});
And this is the relevant part of the seller.ejs file that's receiving the user requests and outputting the viz:
var socket = io('http://localhost:3000');
var stats;
var seller_key = 'sellers-'.concat(<%= seller %>);
socket.on(seller_key, function(msg){
stats = [];
console.log('Im in');
var seller = $.parseJSON(msg);
var items = seller['items'];
for(item in items) {
var item_data = items[item];
stats.push({'title': item_data['title'], 'today_visits': item_data['today_visits'], 'sold_today': item_data['sold_today'], 'conversion_rate': item_data['conversion_rate']});
}
setupData(stats);
});
The problem is that the socket_on() method never receives anything and I don't see where the problem is as everything seems to be working fine besides this.
I think that you might be confused as to what Pub/Sub in Redis actually is. It's not a way to listen to changes on hashes; you can have a Pub/Sub channel called sellers-1, and you can have a hash with the key sellers-1, but those are unrelated to each other.
As documented here:
Pub/Sub has no relation to the key space.
There is a thing called keyspace notifications that can be used to listen to changes in the key space (through Pub/Sub channels); however, this feature isn't enabled by default because it'll take up more resources.
Perhaps an easier method would be to publish a message after the HMSET, so any subscribers would know that the hash got changed (they would then retrieve the hash contents themselves, or the published message would contain the relevant data).
This brings us to the next possible issue: you only have one subscriber connection, redisSubscriber.
From what I understand from the Node.js Redis driver, calling .subscribe() on such a connection would remove any previous subscriptions in favor of the new one. So if you were previously subscribed to the sellers-1 channel and subscribe to sellers-2, you wouldn't be receiving messages from the sellers-1 channel anymore.
You can listen on multiple channels by either passing an array of channels, or by passing them as a arguments:
redisSubscriber.subscribe([ 'sellers-1', 'sellers-2', ... ])
// Or:
redisSubscriber.subscribe('sellers-1', 'sellers-2', ... )
You would obviously have to track each "active" seller subscription. Either that, or create a new connection for each subscription, which also isn't ideal.
It's probably a better idea to have a single Pub/Sub channel on which all changes would get published, instead of a separate channel for each seller.
Finally: if your seller id's aren't hard to guess (for instance, if it's based on an incremental integer value), it would be trivial for someone to write a client that would make it possible to listen in on any seller channel they'd like. It might not be a problem, but it is something to be aware of.

How to connect multiple sockets to sails in test

I have a messaging controller setup in sails.js and want to test it with multiple clients to see if the pub/sub code works. I set up a test file
var socketIOClient = require('socket.io-client');
var sailsIOClient = require('sails.io.js');
var socket1 = socketIOClient;
var client1 = sailsIOClient(socket1);
var socket2 = socketIOClient;
var client2 = sailsIOClient(socket2);
var socket3 = socketIOClient('http://localhost:1337', {'force new connection': true});
var client3 = sailsIOClient(socket2);
...
client1.socket.get... works and says it is subscribed.
client1.socket.post... works and posts a message to the DB.
So I want to test that a client can receive the notification when a new message is posted. However, when I post from either client1 or client2, it posts from both. Essentially, they are linked to the same socket object or something like that, but I don't know where. So I want to connect multiple sockets, and I've tried variations like socket3 and client3, but get the following problem:
client3.socket.get... and client3.socket.post... and other variations (forceNew, multiplexing, etc.) each hang up and don't resolve.
Example of hang up:
sails.log('posting...');
client3.socket.post('/v1.0/messaging', data, function(body, JWR){
sails.log('posted');
done();
});
Only posting... is logged in this way, but posted is logged if using client1 or client2.
My question:
How can I connect multiple clients to my sails api to test if my pub/sub controller works?
I can't test it right now, but you could try this
var socketIOClient = require('socket.io-client');
var sailsIOClient = require('sails.io.js');
// Instantiate the socket client (`io`)
var io = sailsIOClient(socketIOClient);
// prevents socket to connect with it's default origin
io.sails.autoConnect = false
// Ask the client to create two socket connections
var socket1 = io.sails.connect('http://localhost:1337');
var socket2 = io.sails.connect('http://localhost:1337');
// Test it
socket1.get(url, data, cb)
socket1.post(url, data, cb)
socket2.get(url, data, cb)
socket2.post(url, data, cb)
// If you want to configure and use the "eager" instance
io.sails.url = 'http://localhost:1337';
io.socket.get(url, data, cb)
This way, you would create several SailsSocket instance instead of using the "eager" instance.
When you use sails.io.js in a browser, io.socket contains the socket instance (called "eager instance" in the comments) which will automatically try to connect using the host that the js file was served from. io.sails.connect() allows you to create other instances.
The correct syntax for actual version of socket.io should be
//first socket
var socket1 = socketIOClient('http://localhost:1337', {'forceNew: true});
//second socket
var socket2 = socketIOClient('http://localhost:1337', {'forceNew: true});
See socket.io docs http://socket.io/blog/socket-io-1-2-0/#

How to associate properties to socket.io object in Redis Store?

I am working on a complex game with Nodejs and Socket.io, where I need to store socket.io objects in the memory and also assign properties to the socket object ( say a name , a counter of some action from the socket , etc )
In the code below, I have shown an example of what I am trying to achieve. I store all the sockets in an array and also have another array which stores the name property of the socket.
At any time if I get a request for the name, I can just pick the name from the array in the memory.
But now I have too many users and I need to load-balance my application across multiple servers. So I cant store objects and properties in the memory. I need to store them in a Database.
I am planning to use Redis. This link tells how to use Redis Store for sockets -
https://github.com/LearnBoost/Socket.IO/wiki/Configuring-Socket.IO
But how do I associate my other properties ( say name etc ) to the socket object in the Redis Store ? If there is some new ways to achieve this , please let me know also.
var socket_array = new Array();
var socket_name_array = new Array();
var io = require('socket.io').listen(80);
io.sockets.on('connection', function (socket) {
socket_array.push(socket);
var i = socket_array.indexOf(socket);
var name = generate_random_name();
socket_name_array[i]= name;
socket.on('get_name', function (data) {
var i = socket_array.indexOf(socket);
var name= socket_name_array[i]
socket.emit('socket_name' , {name :name } );
});
});
function generate_random_name(){
var random_string;
//code
return random_string;
}
Yes, if you want to load balance socket.io servers you will have to use a store like redisstore.
However now you should not use "socket_name_array" + events to maintain data consistent across your server.
Setup
var redis = require('redis'),
var pub = redis.createClient(port, host),
var sub = redis.createClient(port, host),
var client = redis.createClient(port, host);
io.configure(function(){
io.set('store', new RedisStore({
redisPub: pub,
redisSub : sub,
redisClient : client
}));
});
Usage
io.sockets.on('connection', function (socket) {
var name = generate_random_name();
socket.set('name', name); // store it in redis and forward this to other socket.io servers
// On another server, if you want to retrieve this value from this socket just do:
socket.get('name', function(err, name){
// don't forget err. handling
console.log(name);
});
});

Resources