Sticky load balancing(粘性负载平衡)
If you plan to distribute the load of connections among different processes or machines, you have to make sure that requests associated with a particular session id connect to the process that originated them.
This is due to certain transports like XHR Polling or JSONP Polling relying on firing several requests during the lifetime of the “socket”. Failing to enable sticky balancing will result in the dreaded:
如果计划在不同的进程或机器之间分配连接负载,则必须确保与特定会话ID关联的请求连接到发起它们的进程。
这是由于某些传输(例如XHR轮询或JSONP轮询)在“套接字”的生存期内依赖于触发多个请求而导致的。 无法启用粘性平衡将导致可怕的后果:
1 | Error during WebSocket handshake: Unexpected response code: 400 |
Which means that the upgrade request was sent to a node which did not know the given socket id, hence the HTTP 400 response.
To illustrate why this is needed, consider the example of emitting an event to all connected clients:
这意味着升级请求已发送到不知道给定套接字ID的节点,因此不知道HTTP 400响应。
为了说明为什么需要这样做,请考虑向所有连接的客户端发出事件的示例:
1 | io.emit('hi', 'all sockets'); |
Chances are that some of those clients might have an active bi-directional communication channel like WebSocket
that we can write to immediately, but some of them might be using long-polling.
If they’re using long polling, they might or might not have sent a request that we can write to. They could be “in between” those requests. In those situations, it means we have to buffer messages in the process. In order for the client to successfully claim those messages when he sends his request, the easiest way is for him to connect to be routed to that same process.
As noted above, WebSocket
transport do not have this limitation, since the underlying TCP connection is kept open between the client and the given server. That’s why you might find some suggestions to only use the WebSocket
transport:
可能其中一些客户端可能具有活动的双向通信通道,例如WebSocket,我们可以立即写入该通道,但是其中一些客户端可能正在使用长轮询。
如果他们使用长时间轮询,那么他们可能已经发送或未发送我们可以写入的请求。 它们可能在这些请求之间。 在这种情况下,这意味着我们必须在流程中缓冲消息。 为了使客户端在发送请求时成功声明这些消息,最简单的方法是将其连接到同一进程。
如上所述,WebSocket传输没有此限制,因为底层TCP连接在客户端和给定服务器之间保持打开状态。 因此,您可能会发现一些建议仅使用WebSocket传输:
1 | const client = io('https://io.yourhost.com', { |
Both means that there is NO FALLBACK to long-polling when the websocket connection cannot be established, which is in fact one of the key feature of Socket.IO. In that case, you should maybe consider using raw WebSocket, or a thin wrapper like robust-websocket.
To achieve sticky-session, there are two main solutions:
- routing clients based on their originating address
- routing clients based on a cookie
两者都意味着当无法建立Websocket连接时,不会回退到长轮询,这实际上是Socket.IO的关键功能之一。 在这种情况下,您可能应该考虑使用原始WebSocket或像robust-websocket这样的薄包装器。
为了实现粘性会话,有两种主要解决方案:
根据客户端的原始地址路由
根据Cookie路由客户端
NginX configuration
Within the http { }
section of your nginx.conf
file, you can declare a upstream
section with a list of Socket.IO process you want to balance load between:
在nginx.conf文件的http {}部分中,可以声明一个上游部分,其中包含要平衡负载的Socket.IO进程列表:
1 | http { |
Notice the ip_hash
instruction that indicates the connections will be sticky.
Make sure you also configure worker_processes
in the topmost level to indicate how many workers NginX should use. You might also want to look into tweaking the worker_connections
setting within the events { }
block.
请注意ip_hash指令,该指令指示连接将处于粘性状态。
确保您还在最顶层配置了worker_processes,以指示NginX应该使用多少个worker。 您可能还想研究一下在事件{}块中对worker_connections设置的调整。
Apache HTTPD configuration
1 | Header add Set-Cookie "SERVERID=sticky.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED |
HAProxy configuration
1 | Reference: http://blog.haproxy.com/2012/11/07/websockets-load-balancing-with-haproxy/ |
Using Node.JS Cluster(使用Node.JS集群)
Just like NginX, Node.JS comes with built-in clustering support through the cluster
module.
Fedor Indutny has created a module called sticky session that ensures file descriptors (ie: connections) are routed based on the originating remoteAddress
(ie: IP). Please note that this might lead to unbalanced routing, depending on the hashing method.
You could also assign a different port to each worker of the cluster, based on the cluster worker ID, and balance the load with the configuration that you can find above.
就像NginX一样,Node.JS通过集群模块提供了内置的集群支持。
Fedor Indutny创建了一个名为“粘性会话”的模块,该模块可确保文件描述符(即:连接)基于始发的remoteAddress(即:IP)进行路由。 请注意,这可能会导致路由不平衡,具体取决于哈希方法。
您还可以根据集群工作程序ID为集群的每个工作程序分配不同的端口,并通过上面的配置平衡负载。
Passing events between nodes(在节点之间传递事件)
Now that you have multiple Socket.IO nodes accepting connections, if you want to broadcast events to everyone (or even everyone in a certain room) you’ll need some way of passing messages between processes or computers.
The interface in charge of routing messages is what we call the Adapter
. You can implement your own on top of the socket.io-adapter (by inheriting from it) or you can use the one we provide on top of Redis: socket.io-redis:
现在,您已经拥有多个接受连接的Socket.IO节点,如果您想向所有人(甚至是某个房间中的所有人)广播事件,您将需要某种方式在进程或计算机之间传递消息。
负责路由消息的接口就是我们所说的适配器。 您可以在socket.io-adapter上实现自己的继承(通过继承),也可以使用我们在Redis之上提供的实现:socket.io-redis:
1 | const io = require('socket.io')(3000); |
Then the following call:
1 | io.emit('hi', 'all sockets'); |
will be broadcast to every node through the Pub/Sub mechanism of Redis.
Note: sticky-session is still needed when using the Redis adapter.
If you want to pass messages to it from non-socket.io processes, you should look into “Sending messages from the outside-world”.
将通过Redis的发布/订阅机制广播到每个节点。
注意:使用Redis适配器时仍然需要粘性会话。
如果要从non-socket.io进程向其传递消息,则应查看“从外界发送消息”。
Caught a mistake? Edit this page on GitHub