Redis flush when connection ends - javascript

I'm developing a clustered NodeJS application that uses a single Redis server to share data with multiple application instances.
The problem is when 1 of the application instances (using NodeJS) crash or reload, the keys are retained in Redis. I need a strategy to handle application an application crash/disconnection without duplicating data.
Is there a way to flush the Redis cache when the connection is lost/ended?

Not entirely clear what you mean by connection ending, but to clear your Redis instance, go into your terminal and type node to open the node repl and set up your Redis client like so:
> const redis = require('redis');
> const redisUrl = 'redis://127.0.0.1:6379';
> const client = redis.createClient(redisUrl);
> client.flushall()

You could add a TTL to all your keys and update the TTL before the keys expire. If one of your applications goes down, it won't renew its keys and after the TTL, they'll be gone.

Related

Will Mongo DB Connection closes or expires automatically

I have written a Queue Trigger Azure Function App(Node JS) where on each queue trigger data will be inserted into MongoDB. I am creating MongoClient above function level and re-using same MongoClient for all the Triggers
if(mongoClient.topology.isConnected())
//Use Same Connection
else //Creating new client
mongoClient = await mongoDB.MongoClient.connect();
Sometimes on my mongo db cluster i am getting error connections to your cluster(s) have exceeded i dont understand is it because i am keeping connection open for too long? will connection automatically expire after sometime? Is it good to keep Client Connection above function level and reuse it? Can some one suggest please.
If i do open and close connection at function level then i am getting another error in function Cannot use Session that has ended
If you've deployed the Function App in Consumption Plan, then the number of outbound connections is limited (~600/instance) but you'll get the connections exceeded when you exceed the limit.
I would suggest enabling the Application Insights on the Function App to track the requests time, response time and other metrics that helps to troubleshoot more.
Is it good to keep Client Connection above function level and reuse it?
Yes, you can keep client connections above function level and reuse them instead of creating new connection whatever the client connection it is Http Client, Document Client or Database client.
Do not create a new client with every function invocation.
Do create a single, static client that every function invocation can use.
Consider creating a single, static client in a shared helper class if different functions use the same service.
Refer to MSFT Doc of Azure Functions Client Connections regarding the best practices when managing the client connections in Function Instances.

handle websocket connections on multiple instances

I need to understand the concept of handling websockets on multiple instances so that it could be shared across all the instances. For e.g I have three nodes running which is being connected by the load balancer. On receiving the data which needs to be emit on the specific socket. My initial idea was to create a hashmap or json objects to holds the websocket connections. But I realize that this couldn't be done in this way as it will be only specific to its particular instance. If I will receive the data on any of the instances then most of the data will not be going to emit on that websocket connection as it doesn't know on which instance the websocket is created. Is there a good way to handle websocket connections so that it could be shared on all instances. My ideas was to use redis or postgres sql database because they are shared among all the instances.
Also I have tried the postgres solution to store the websocket connection but when I save the connection it says
TypeError: Converting circular structure to JSON
Is there some good solution to handle websocket connections that could be shared among all instances. If database is a good solution how can I resolve
TypeError: Converting circular structure to JSON
I guess the thing you are looking for are Adapters, and it's being documented relatively well in the official socket.io documentation (/v4/adapter/).
When scaling to multiple Socket.IO servers, you will need to replace the default in-memory adapter by another implementation, so the events are properly routed to all clients.
You have several official adapters you can choose from :
Redis adapter
MongoDB adapter
Postgres adapter
Cluster adapter
Here is an example using the MongoDB Adapter :
npm install #socket.io/mongo-adapter mongodb
const { Server } = require("socket.io");
const { createAdapter } = require("#socket.io/mongo-adapter");
const { MongoClient } = require("mongodb");
// DATABASE CONNECTION
const DB = "mydb";
const COLLECTION = "socket.io-adapter-events";
const mongoClient = new MongoClient("mongodb://localhost:27017/?replicaSet=rs0", {
useUnifiedTopology: true,
});
const io = new Server();
const main = async () => {
await mongoClient.connect();
try {
await mongoClient.db(DB).createCollection(COLLECTION, {
capped: true,
size: 1e6
});
} catch (e) {
// collection already exists
}
const mongoCollection = mongoClient.db(DB).collection(COLLECTION);
// HERE COMES THE IMPORTANT PART :)
// CREATE MONGO DB ADAPTER AND USE IT
io.adapter(createAdapter(mongoCollection));
io.listen(3000);
}
main();
The adapter then will take care of the rest. Every packet that is sent to multiple clients (e.g. io.to("room1").emit() or socket.broadcast.emit()) will be
sent to all matching clients connected to the current server
inserted in a MongoDB capped collection, and received by the other Socket.IO servers of the cluster

What is the right way to manage connections to mongoDB, using node?

I'm using node.js and mongoDB. Right now, for my test app, the connection to the db is in the main node file, but I guess this is a wrong practice.
What I want/need: a secure way (i.e. not storing password on files users can access) to connect to the db just when needed.
For example: I want several admin pages (users, groups, etc..). Each page should connect to the db, find some data, and display it. It also have a form for adding a document to the db and a delete option.
I thought maybe to create some kind of a connection function - send it what you want to do (add, update, find, delete), to where (collection name) and whatever it needs. But I can't just include this function, because then it'll reveal the password to the db. So what can I do?
Thanks!
I'm going to answer your question bit by bit.
Right now, for my test app, the connection to the db is in the main node file
This is fine, though you might want to put it in a separate file for easier reuse. NodeJS is a continuesly running process, so in theory you could serve all of your HTTP responses using the same connection to the database. In practice you'd want to create a connection pool, but the Mongodb driver for NodeJS already does this automatically.
Each page should connect to the db, find some data, and display it.
When you issue a query on the MongoDB driver, it will automatically use a connection from its internal connection pool, as long as you gave it the credentials when your application was starting up.
What I want/need: a secure way (i.e. not storing password on files users can access) to connect to the db just when needed.
I would advice to keep your application configuration (any variables that depend on the environment in which the app is running) in a separate file which you don't commit to your VCS. A module like node-config can help a great deal with that.
The code you will end up with, using node-config, is something like:
config/default.json:
{
"mongo": null
}
This is the default configuration file which you commit.
config/local.json:
{
"mongo": "mongo://user:pass#host:port/db"
}
The local.json should be ignored by your VCS. It contains secret sauce.
connection.js:
var config = require('config');
var MongoClient = require('mongodb').MongoClient;
var cache;
module.exports = function(callback){
if(cache){
return callback(cache);
}
MongoClient.connect(config.get('mongo'), function(err, db){
if(err){
console.error(err.stack);
process.exit(1);
}
cache = db;
callback(db);
});
}
An incomplete example of how you might handle reusing the database connection. Note how the configuration is gotten using config.get(*). An actual implementation should have more robust error handling and prevent multiple connections from being made. Using Promises would make all that a lot easier.
index.js:
var connect = require('./connection');
connect(function(db){
db.find({whatever: true})
});
Now you can just require your database file anywhere you want, and reuse the same database connection, which handles pooling for you and you don't have your passwords hard-coded anywhere.

Cassandra connections best practice

I'm using Node JS with Cassandra and I wonder what the best way to interact. I have multiple modules that interact with Cassandra and I want to know if it's better to
keep a single connection for all the modules
set a connection for each module, or if the best is to;
connect to Cassandra each time I have a request.
This web application uses Cassandra for most of the requests.
I would recommend you to use the DataStax Node.js driver for Cassandra, it features connection pooling and transparent failover, you only need to execute your queries and it will handle the rest for you.
var cassandra = require('cassandra-driver');
var client = new cassandra.Client({
contactPoints: ['host1', 'host2'],
keyspace: 'ks1'
});
var query = 'SELECT email, last_name FROM user_profiles WHERE key=?';
//the driver will handle connection pool and failover
client.execute(query, ['guy'], function(err, result) {
assert.ifError(err);
console.log('User profile email ' + result.rows[0].email);
});
Disclaimer: I'm an active developer of the project
I'd pool connections and recycle them rather than going with one of the options you listed. That way you don't need to destroy already created connections. The only thing I'd be weary of is having too large a pool, so make sure you set a sensible threshold.
Something like this:
no connections are available in pool
create connection (add it back once finished using it)
connections are available in pool
fetch connection from pool
Reasons for choosing a pool rather than a hardcoded number:
keep a single connection for all the modules - This will be a bottleneck unless you are running a single threaded app and you aren't
set a connection for each module - You need to provide us with more context. This might a good approach based on how threaded each module is.
connect to Cassandra each time I have a request - Building connections isn't cheap (code below), so don't discard them!
.
Cluster cluster = Cluster.builder().addContactPoints("localhost").build();
long start = System.currentTimeMillis();
Session session = cluster.connect();
System.out.println(String.format("Took %s ms", System.currentTimeMillis() - start));
Output: 490 ms.

Node.js http-proxy drops websocket requests

Okay, I've spent over a week trying to figure this out to no avail, so if anyone has a clue, you are a hero. This isn't going to be an easy question to answer, unless I am being a dunce.
I am using node-http-proxy to proxy sticky sessions to 16 node.js workers running on different ports.
I use Socket.IO's Web Sockets to handle a bunch of different types of requests, and use traditional requests as well.
When I switched my server over to proxying via node-http-proxy, a new problem crept up in that sometimes, my Socket.IO session cannot establish a connection.
I literally can't stably reproduce it for the life of me, with the only way to turn it on being to throw a lot of traffic from multiple clients to the server.
If I reload the user's browser, it can then sometimes re-connect, and sometimes not.
Sticky Sessions
I have to proxy sticky sessions as my app authenticates on a per-worker basis, and so it routes a request based on its Connect.SID cookie (I am using connect/express).
Okay, some code
This is my proxy.js file that runs in node and routes to each of the workers:
var http = require('http');
var httpProxy = require('http-proxy');
// What ports the proxy is routing to.
var data = {
proxyPort: 8888,
currentPort: 8850,
portStart: 8850,
portEnd: 8865,
};
// Just gives the next port number.
nextPort = function() {
var next = data.currentPort++;
next = (next > data.portEnd) ? data.portStart : next;
data.currentPort = next;
return data.currentPort;
};
// A hash of Connect.SIDs for sticky sessions.
data.routes = {}
var svr = httpProxy.createServer(function (req, res, proxy) {
var port = false;
// parseCookies is just a little function
// that... parses cookies.
var cookies = parseCookies(req);
// If there is an SID passed from the browser.
if (cookies['connect.sid'] !== undefined) {
var ip = req.connection.remoteAddress;
if (data.routes[cookies['connect.sid']] !== undefined) {
// If there is already a route assigned to this SID,
// make that route's port the assigned port.
port = data.routes[cookies['connect.sid']].port;
} else {
// If there isn't a route for this SID,
// create the route object and log its
// assigned port.
port = data.currentPort;
data.routes[cookies['connect.sid']] = {
port: port,
}
nextPort();
}
} else {
// Otherwise assign a random port, it will/
// pick up a connect SID on the next go.
// This doesn't really happen.
port = nextPort();
}
// Now that we have the chosen port,
// proxy the request.
proxy.proxyRequest(req, res, {
host: '127.0.0.1',
port: port
});
}).listen(data.proxyPort);
// Now we handle WebSocket requests.
// Basically, I feed off of the above route
// logic and try to route my WebSocket to the
// same server regular requests are going to.
svr.on('upgrade', function (req, socket, head) {
var cookies = parseCookies(req);
var port = false;
// Make sure there is a Connect.SID,
if (cookies['connect.sid'] != undefined) {
// Make sure there is a route...
if (data.routes[cookies['connect.sid']] !== undefined) {
// Assign the appropriate port.
port = data.routes[cookies['connect.sid']].port;
} else {
// this has never, ever happened, i've been logging it.
}
} else {
// this has never, ever happened, i've been logging it.
};
if (port === false) {
// this has never happened...
};
// So now route the WebSocket to the same port
// as the regular requests are getting.
svr.proxy.proxyWebSocketRequest(req, socket, head, {
host: 'localhost',
port: port
});
});
Client Side / The Phenomena
Socket connects like so:
var socket = io.connect('http://whatever:8888');
After about 10 seconds on logging on, I get this error back on this listener, which doesn't help much.
socket.on('error', function (data) {
// this is what gets triggered. ->
// Firefox can't establish a connection to the server at ws://whatever:8888/socket.io/1/websocket/Nnx08nYaZkLY2N479KX0.
});
The Socket.IO GET request that the browser sends never comes back - it just hangs in pending, even after the error comes back, so it looks like a timeout error. The server never responds.
Server Side - A Worker
This is how a worker receives a socket request. Pretty simple. All workers have the same code, so you think one of them would get the request and acknowledge it...
app.sio.socketio.sockets.on('connection', function (socket) {
// works... some of the time! all of my workers run this
// exact same process.
});
Summary
That's a lot of data, and I doubt anyone is willing to confront it, but i'm totally stumped, don't know where to check next, log next, whatever, to solve it. I've tried everything I know to see what the problem is, to no avail.
UPDATE
Okay, I am fairly certain that the problem is in this statement on the node-http-proxy github homepage:
node-http-proxy is <= 0.8.x compatible, if you're looking for a >=
0.10 compatible version please check caronte
I am running Node.js v0.10.13, and the phenomena is exactly as some have commented in github issues on this subject: it just drops websocket connections randomly.
I've tried to implement caronte, the 'newer' fork, but it is not at all documented and I have tried my hardest to piece together their docs in a workable solution, but I can't get it forwarding websockets, my Socket.IO downgrades to polling.
Are there any other ideas on how to get this implemented and working? node-http-proxy has 8200 downloads yesterday! Sure someone is using a Node build from this year and proxying websockets....
What I am look for exactly
I want to accomplish a proxy server (preferrably Node) that proxies to multiple node.js workers, and which routes the requests via sticky sessions based on a browser cookie. This proxy would need to stably support traditional requests as well as web sockets.
Or...
I don't mind accomplishing the above via clustered node workers, if that works. My only real requirement is maintaining sticky sessions based on a cookie in the request header.
If there is a better way to accomplish the above than what I am trying, I am all for it.
In general I don't think node is not the most used option as a proxy server, I, for one use nginx as a frontend server for node and it's a really great combination. Here are some instructions to install and use the nginx sticky sessions module.
It's a lightweight frontend server with json like configuration, solid and very well tested.
nginx is also a lot faster if you want to serve static pages, css. It's ideal to configure your caching headers, redirect traffic to multiple servers depending on domain, sticky sessions, compress css and javascript, etc.
You could also consider a pure load balancing open source solution like HAProxy. In any case I don't believe node is the best tool for this, it's better to use it to implement your backend only and put something like nginx in front of it to handle the usual frontend server tasks.
I agree with hexacyanide. To me it would make the most sense to queue workers through a service like redis or some kind of Message Query system. Workers would be queued through Redis Pub/Sub functionality by web nodes(which are proxied). Workers would callback upon error, finish, or stream data in realtime with a 'data' event. Maybe check out the library kue. You could also roll your own similar library. RabbitMQ is another system for similar purpose.
I get using socket.io if you're already using that technology, but you need to use tools for their intended purpose. Redis or a MQ system would make the most sense, and pair great with websockets(socket.io) to create realtime, insightful applications.
Session Affinity(sticky sessions) is supported through Elastic LoadBalancer for aws, this supports webSockets. A PaaS provider(Modulus) does this exactly. Theres also satalite which provides sticky sessions for node-http-proxy, however I have no idea if it supports webSockets.
I've been looking into something very similar to this myself, with the intent of generating (and destroying) Node.js cluster nodes on the fly.
Disclaimer: I'd still not recommend doing this with Node; nginx is more stable for the sort of design architecture that you're looking for, or even more so, HAProxy (very mature, and easily supports sticky-session proxying). As #tsturzl indicates, there is satellite, but given the low volume of downloads, I'd tread carefully (at least in a production environment).
That said, since you appear to have everything already set up with Node, rebuilding and re-architecting may be more work than it's worth. Therefore, to install the caronte branch with NPM:
Remove your previous http-node-proxy Master installation with npm uninstall node-proxy and/or sudo npm -d uninstall node-proxy
Download the caronte branch .zip and extract it.
Run npm -g install /path/to/node-http-proxy-caronte
In my case, the install linkage was broken, so I had to run sudo npm link http-proxy
I've got it up and running using their basic proxy example -- whether or not this resolves your dropped sessions issue or not, only you will know.

Categories

Resources