I'm planning to set up a group of NodeJS application servers running Socket.io on EC2, and I'd like to use the Elastic Load Balancer to spread load between them. I know ELB doesn't support Websockets out of the box, but I can use the setup described here in Scenario 2.
As described in the blog post, though, I notice that this setup offers no session affinity or source IP info:
We can not have Session Affinity nor X-Forward headers with this setup
because ELB is not parsing the HTTP messages, so its impossible to
match the cookies to ensure Session Affinity nor Inject special
X-Forward headers.
Will Socket.io still work under these circumstances? Or is there another way to have a set of Socket.io app servers behind a load balancer with SSL?
EDIT: Tim Caswell talks about doing this already here. Are there any posts explaining how to set this up? Again there's no session stickiness here, but things seem to be working fine.
As an aside, are sticky sessions actually necessary with websockets? Does information travel as new and separate requests or is there only one request + connection that all the information moves along?
Socket.io does not work out of the box even with a TCP ELB because it makes two HTTP requests before upgrading the connection to websockets.
The first connection is used to establish protocol, since socket.io supports more than just websockets.
GET /socket.io/1/?t=1360136617252 HTTP/1.1
User-Agent: node-XMLHttpRequest
Accept: */*
Host: localhost:9999
Connection: keep-alive
HTTP/1.1 200 OK
Content-Type: text/plain
Date: Wed, 06 Feb 2013 07:43:37 GMT
Connection: keep-alive
Transfer-Encoding: chunked
47
xX_HbcG1DN_nufWddblv:60:60:websocket,htmlfile,xhr-polling,jsonp-polling
0
The second request is used to actually upgrade the connection:
GET /socket.io/1/websocket/xX_HbcG1DN_nufWddblv HTTP/1.1
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: MTMtMTM2MDEzNjYxNzMxOA==
Host: localhost:9999
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 249I3zzVp0SzEn0Te2RLp0iS/z0=
You can see in the above example that xX_HbcG1DN_nufWddblv is a shared key between requests. This is the problem. ELBs do round-robin routing, meaning the upgrade request hits a server than did not participate in the initial negotiation. As such, the server has no idea who the client is.
In-memory stateful data is the enemy of load-balancing. Thankfully, socket.io supports using Redis to store the data instead. If you share your redis connection with multiple servers, they essentially share the sessions of all clients.
See the socket.io wiki page for details on setting up Redis.
You can now use the new application load balancer recently launched by AWS.
Just replace the ELB(now called Classic load balancer) with the ALB (Application load balancer) and enable sticky sessions.
ALB supports Web sockets. This should do the trick.
https://aws.amazon.com/blogs/aws/new-aws-application-load-balancer/
http://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html
As I mentioned in the post, we only use ELB to ssl terminate and load-balance across a cluster of http-proxy servers that do support websockets. ELB doesn't talk to the websocket servers directly. The HTTP proxy cluster handles looking up the right socket.io server to connect to ensuring session stickiness.
When you run a server in a cloud that has a load-balancer/reverse proxy, routers etc, you need to configure it to work properly, especially when you scale the server to use multiple instances.
One of the constraints Socket.io, SockJS and similar libraries have is that they need to continuously talk to the same instance of the server. They work perfectly well when there is only 1 instance of the server.
When you scale your app in a cloud environment, the load balancer (Nginx in the case of Cloud Foundry) will take over, and the requests will be sent to different instances causing Socket.io to break.
To help in such situations, load balancers have a feature called 'sticky sessions' aka 'session affinity'. The main idea is that if this property is set, then after the first load-balanced request, all the following requests will go to the same server instance.
In Cloud Foundry, cookie-based sticky sessions are enabled for apps that set the cookie jsessionid.
Note: jsessionid is the cookie name commonly used to track sessions in Java/Spring applications. Cloud Foundry is simply adopting that as the sticky session cookie for all frameworks.
So, all the apps need to do is to set a cookie with the name jsessionid to make socket.io work.
app.use(cookieParser); app.use(express.session({store:sessionStore, key:'jsessionid', secret:'your secret here'}));
So these are the steps:
Express sets a session cookie with name jsessionid.
When socket.io connects, it uses that same cookie and hits the load balancer
The load balancer always routes it to the same server that the cookie was set in.
If you are using Application Load Balancer then Sticky session settings is at target group level
Related
When I start my API server on local it serves http/1.1 but I found that when it's deployed on VPS and set up with Cloudflare, the browser shows the protocol is http/3. So between the clients and Cloudflare is http/3 and Cloudflare and VPS is http/1.1 is this correct? That means http/3 is served only by DNS, my server is still plain http/1.1 and I need to migrate it to http/2 to be truly supported http/2. (I'm using node so it'll be a switch from http to http2 module)
When your web application / web API is behind Cloudflare, Cloudflare acts as a reverse proxy. This means that there are two "legs" of the connection:
From the end user's client (browser / mobile phone etc...) to Cloudflare
From Cloudflare to your origin server (in your case a VPS)
From a user point of view, they see leg (1) so it is quite easy to enable HTTP/2 or HTTP/3 (see documentation) even if your origin server does not support them. This is what you see in the browser when testing, depending on your configuration in the Cloudflare Dashboard.
For leg (2), only HTTP/1.1 is currently supported (as noted also in this Support KB). You can still optimize the setup of that leg by using features such as Argo Smart Routing or Argo Tunnel,
Update Jun 2022: HTTP/2 to the origin server is now supported and can be enabled in the dashboard. See here for more details.
I am trying to setup websocket servers behind a load balancer. At first, I used the socket.io library. But I found that it requires sticky session when used behind a load balancer.
According to this website, it sends multiple requests to perform handshake and establish a connection. If the requests are sent to different servers, the connection will fail.
After further study, i found that other websocket server library like SockJS also have the same problem. They all require sticky session to work behind a load balancer.
Now I am checking the websocket library ws. But I could not find any example of using it behind load balancer.
Does the ws library requires sticky session to work?
Is there any other websocket library that can work without sticky session behind a load balancer?
Is there a specific reason why you can't / don't want to rely on sticky sessions?
If you want to distribute socket connections across multiple hosts you are going to need some solution, and sticky sessions is a perfectly good one.
The socket.io page on using multiple nodes you link to even describes a way to implement the solution, "by routing clients based on their originating address" via NginX. Have you tried this and found that it doesn't work?
There is also a very good article on Horizontally Scaling Node.js and WebSockets with Redis which describes solving the exact issue you have with sticky sessions and automatic failover.
I am using express with node and nginx as a reverse proxy. I'd like to know how to take advantage of http/2 with nginx to serve static content, with all other requests being forwarded to the express API.
At the moment, my express server is being served via http/1 and nginx is accepting http/2 connections, and forwarding them to express. How do I set up nginx so that it uses http/2 to serve everything in my statics folder, but forwards all requests to the API as http1?
I will break your questions into two parts:
How to take advantages of http/2.0 to serve static files from nginx?
How to setup nginx to send http/1.1 request to the backend server in case where nginx act as a reverse proxy?
Answer 1:
For the case of serving static files the major performance benefit can come from using the multiplexing feature of the http/2.0 protocol.
Multiplexing enhances the pipelining feature introduced in http/1.1 and overcomes the problem of HOL blocking. With multiplexing you can use the same underlying TCP connection to load multiple resources in parallel using one http connection. You should also consider the stream prioritisation to assign priority to the resource which you want to load first on the page otherwise loading of some of the critical resources can be delayed since all the resources will contend for same multiplexed connection.
Answer 2:
Sending http/1.1 request to the backend server is the default behaviour. So if you have already configured nginx to use http/2.0 you do not have to do anything special to proxy http/1.1 request to your backend. This is because nginx does not support http/2.0 in proxy module as of now. Refer to this ticket. Also, please check this digital ocean tutorial which will guide you to setup nginx with http/2.0 configured on ubuntu 16.04.
I have a standard LAMP EC2 instance set-up running on Amazon's AWS. Having also installed Node.js, socket.io and Express to meet the demands of live updating, I am now at the stage of load balancing the application. That's all working, but my sockets aren't. This is how my set-up looks:-
--- EC2 >> Node.js + socket.io
/
Client >> ELB --
\
--- EC2 >> Node.js + socket.io
[RDS MySQL - EC2 instances communicate to this]
As you can see, each instance has an installation of Node and socket.io. However, occasionally Chrome debug will 400 the socket request returning the reason {"code":1,"message":"Session ID unknown"}, and I guess this is because it's communicating to the other instance.
Additionally, let's say I am on page A and the socket needs to emit to page B - because of the load balancer these two pages might well be on a different instance (they will both be open at the same time). Using something like Sticky Sessions, to my knowledge, wouldn't work in that scenario because both pages would be restricted to their respective instances.
How can I get around this issue? Will I need a whole dedicated instance just for Node? That seems somewhat overkill...
The issues come up when you consider both websocket traffic (layer 4 -ish) and HTTP traffic (layer 7) moving across a load balancer that can only inspect one layer at a time. For example, if you set the ELB to load balance on layer 7 (HTTP/HTTPS) then websockets will not work at all across the ELB. However, if you set the ELB to load balance on layer 4 (TCP) then any fallback HTTP polling requests could end up at any of the upstream servers.
You have two options here. You can figure out a way to effectively load balance both HTTP and websocket requests or find a way to deterministically map requests to upstream servers regardless of the protocol.
The first one is pretty involved and requires another load balancer. A good walkthrough can be found here. It's worth noting that when that post was written HAProxy didn't have native SSL support. Now that this is the case it might be possible to just remove the ELB entirely, if that's the route you want to go. If that's the case the second option might be better.
Otherwise you can use HAProxy on its own (or a paid version of Nginx) to implement a deterministic load balancing mechanism. In this case you would use IP hashing since socket.io does not provide a route-based mechanism to identify a particular server like sockjs. This would use the first 3 octets of the IP address to determine which upstream server gets each request so unless the user changes IP addresses between HTTP polls then this should work.
The solution would be for the two(or more) node.js installs to use a common session source.
Here is a previous question on using REDIS as a common session store for node.js How to share session between NodeJs and PHP using Redis?
and another
Node.js Express sessions using connect-redis with Unix Domain Sockets
I am checking node-http-proxy and nodejs-proxy to build a DIY reverse proxy/load balancer in Node.js. After coding a small version, I setup 2 WEBrick servers for the same Rails app so I could load balance (round robin) between them. However each HTTP request is sent to one or another server which is very inefficient since the loading process of CSS and Javascript files from the home page is performed with more than 25 GET requests.
I tried to play a bit with socket events but I didn't get anywhere because by default it uses keep-alive connections (possibly this is why nginx just support http/1.0).
Ok, so I am wondering how can my proxy send a block of HTTP requests (for instance loading a webpage entirely, etc) to only one server so I could send the next block to another server.
You need to consider stickiness or session persistence. This will ensure future connections after the first connection inbound will get 'stuck' to the chosen server for the duration of the session or until the persistence connection times out.