如何使用Node.js创建负载均衡服务器
如果您的网站或应用程序没有收到很多请求,您不必使用负载均衡,但当它变得非常受欢迎并开始接收大量的流量时,您的底层服务器可能无法处理它。因为单个NodeJS服务器无法灵活处理非常大量的流量。
添加更多机器可以解决这个问题。但为了将流量分配到您所有的应用服务器,需要一个负载均衡器。
负载均衡器: 负载均衡器充当站在您的应用服务器前面的交通警察,将客户端请求路由到能够满足这些请求的所有服务器,并以最大化速度和容量利用率的方式确保没有一个服务器超负荷工作,从而可能降低性能。
如何设置负载均衡服务器?
1. 使用集群模块: NodeJS有一个内置模块叫做 集群模块 ,可以利用多核系统的优势。使用这个模块,您可以为系统的每个核心启动NodeJS实例。主进程在一个端口上监听以接受客户端请求,并使用一些智能的方式分发给工作进程。因此,使用这个模块,您可以利用系统的工作能力。
下面的示例涵盖了使用和不使用集群模块的性能差异。
不使用集群模块:
确保您已安装了express和crypto模块,使用以下命令:
npm install express crypto
index.js
const { generateKeyPair } = require('crypto');
const app = require('express')();
// API endpoint
// Send public key as a response
app.get('/key', (req, res) => {
generateKeyPair('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
cipher: 'aes-256-cbc',
passphrase: 'top secret'
}
}, (err, publicKey, privateKey) => {
// Handle errors and use the
// generated key pair.
res.send(publicKey);
})
})
app.listen(3000, err => {
err ?
console.log("Error in server setup") :
console.log('Server listening on PORT 3000')
});
使用以下命令运行 index.js 文件:
node index.js
输出: 在终端屏幕上会看到以下输出:
Server listening on PORT 3000
现在打开浏览器并访问 http://localhost:3000/key , 你将看到以下输出:
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwAneYp5HlT93Y3ZlPAHjZAnPFvBskQKKfo4an8jskcgEuG85KnZ7/16kQw2Q8/7Ksdm0sIF7qmAUOu0B773X1BXQ0liWh+ctHIq/C0e9eM1zOsX6vWwX5Y+WH610cpcb50ltmCeyRmD5Qvf+OE/CBqYrQxVRf4q9+029woF84Lk4tK6OXsdU+Gdqo2FSUzqhwwvYZJJXhW6Gt259m0wDYTZlactvfwhe2EHkHAdN8RdLqiJH9kZV47D6sLS9YG6Ai/HneBIjzTtdXQjqi5vFY+H+ixZGeShypVHVS119Mi+hnHs7SMzY0GmRleOpna58O1RKPGQg49E7Hr0dz8eh6QIDAQAB
-----END PUBLIC KEY-----
上述代码监听端口3000,并将作为响应发送公钥。生成RSA密钥是耗费CPU资源的工作。这里仅有一个NodeJS实例在单个核心上工作。为了查看性能,我们使用了 autocannon 工具来测试服务器,如下所示:
上图显示,在连续连接500个并发连接持续10秒时,服务器能够响应2000个请求。平均每秒的请求次数为190.1。
使用Cluster模块:
const express = require('express');
const cluster = require('cluster');
const { generateKeyPair } = require('crypto');
// Check the number of available CPU.
const numCPUs = require('os').cpus().length;
const app = express();
const PORT = 3000;
// For Master process
if (cluster.isMaster) {
console.log(`Master {process.pid} is running`);
// Fork workers.
for (let i = 0; i{worker.process.pid} died`);
});
}
// For Worker
else {
// Workers can share any TCP connection
// In this case it is an HTTP server
app.listen(PORT, err => {
err ?
console.log("Error in server setup") :
console.log(`Worker ${process.pid} started`);
});
// API endpoint
// Send public key
app.get('/key', (req, res) => {
generateKeyPair('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
cipher: 'aes-256-cbc',
passphrase: 'top secret'
}
}, (err, publicKey, privateKey) => {
// Handle errors and use the
// generated key pair.
res.send(publicKey);
})
})
}
运行以下命令使用 index.js 文件:
node index.js
输出: 在终端屏幕上我们将看到以下输出:
Master 16916 is running
Worker 6504 started
Worker 14824 started
Worker 20868 started
Worker 12312 started
Worker 9968 started
Worker 16544 started
Worker 8676 started
Worker 11064 started
现在打开您的浏览器并转到 http://localhost:3000/key , 您将看到以下输出:
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzxMQp9y9MblP9dXWuQhf sdlEVnrgmCIyP7CAveYEkI6ua5PJFLRStKHTe3O8rxu+h6I2exXn92F/4RE9Yo8EOnrUCSlqy9bl9qY8D7uBMWir0I65xMZu3rM9Yxi+6gP8H4CMDiJhLoIEap+d9Czr OastDPwI+HF+6nmLkHvuq9X5aORvdiOBwMooIoiRpHbgcHovSerJIfQipGs74IiR 107GbpznSUxMIuwV1fgc6mAULuGZl+Daj0SDxfAjk8KiHyXbfHe5stkPNOCWIsbAtCbGN0bCTR8ZJCLdZ4/VGr+eE0NOvOrElXdXLTDVVzO5dKadoEAtzZzzuQId2P/z JwIDAQAB
-----END PUBLIC KEY-----
上面的NodeJS应用程序在我们系统的每个核心上启动。主进程接受请求并分发给所有工作进程。在这种情况下执行的操作如下所示:
以上图片显示了当服务器以500个并发连接运行10秒时,可以响应5000个请求。平均请求/秒为162.06秒。
因此,使用集群模块可以处理更多的请求。但是,有时这还不够,如果这是您的情况,那么您的选择是水平扩展。
2. 使用 Nginx: 如果您的系统有多个应用服务器需要响应,并且您需要将客户端请求分布到所有服务器上,那么您可以巧妙地使用 Nginx 作为代理服务器。Nginx 位于服务器池的前端,并且使用一些智能的方式分配请求。
在以下示例中,我们在不同的端口上有4个相同的 NodeJS 应用程序实例,您也可以使用另一个服务器。
文件名为 index.js
const app = require('express')();
// API endpoint
app.get('/', (req,res)=>{
res.send("Welcome to GeeksforGeeks !");
})
// Launching application on several ports
app.listen(3000);
app.listen(3001);
app.listen(3002);
app.listen(3003);
现在在您的计算机上安装 Nginx,并在 /etc/nginx/conf.d/ 目录下新建一个名为 your-domain.com.conf 的文件,并在该文件中添加以下代码。
upstream my_http_servers {
# httpServer1 listens to port 3000
server 127.0.0.1:3000;
# httpServer2 listens to port 3001
server 127.0.0.1:3001;
# httpServer3 listens to port 3002
server 127.0.0.1:3002;
# httpServer4 listens to port 3003
server 127.0.0.1:3003;
}
server {
listen 80;
server_name your-domain.com www.your-domain.com;
location / {
proxy_set_header X-Real-IP remote_addr;
proxy_set_header Hosthttp_host;
proxy_pass http://my_http_servers;
}
}
3. 使用Express Web Server:
使用Express Web服务器有很多优势。如果你熟悉NodeJS,可以根据以下示例自己实现基于Express的负载均衡器。
步骤1:
创建一个空的NodeJS应用程序。
mkdir LoadBalancer
cd LoadBalancer
npm init -y
步骤2: 安装所需的依赖项,如 ExpressJS,axios 和 Concurrently ,使用以下命令:
npm i express axios
npm i concurrently -g
步骤3: 创建两个文件 config.js 用于负载均衡服务器和 index.js 用于应用服务器。
文件名:config.js
const express = require('express');
const path = require('path');
const app = express();
const axios = require('axios');
// Application servers
const servers = [
"http://localhost:3000",
"http://localhost:3001"
]
// Track the current application server to send request
let current = 0;
// Receive new request
// Forward to application server
const handler = async (req, res) =>{
// Destructure following properties from request object
const { method, url, headers, body } = req;
// Select the current server to forward the request
const server = servers[current];
// Update track to select next server
current === (servers.length-1)? current = 0 : current++
try{
// Requesting to underlying application server
const response = await axios({
url: `{server}{url}`,
method: method,
headers: headers,
data: body
});
// Send back the response data
// from application server to client
res.send(response.data)
}
catch(err){
// Send back the error message
res.status(500).send("Server error!")
}
}
// Serve favicon.ico image
app.get('/favicon.ico', (req, res
) => res.sendFile('/favicon.ico'));
// When receive new request
// Pass it to handler method
app.use((req,res)=>{handler(req, res)});
// Listen on PORT 8080
app.listen(8080, err =>{
err ?
console.log("Failed to listen on PORT 8080"):
console.log("Load Balancer Server "
+ "listening on PORT 8080");
});
这里,文件名是index.js
const express = require('express');
const app1 = express();
const app2 = express();
// Handler method
const handler = num => (req,res)=>{
const { method, url, headers, body } = req;
res.send('Response from server ' + num);
}
// Only handle GET and POST requests
// Receive request and pass to handler method
app1.get('*', handler(1)).post('*', handler(1));
app2.get('*', handler(2)).post('*', handler(2));
// Start server on PORT 3000
app1.listen(3000, err =>{
err ?
console.log("Failed to listen on PORT 3000"):
console.log("Application Server listening on PORT 3000");
});
// Start server on PORT 3001
app2.listen(3001, err =>{
err ?
console.log("Failed to listen on PORT 3001"):
console.log("Application Server listening on PORT 3001");
});
说明: 上面的代码使用两个Express应用程序,一个监听3000端口,另一个监听3001端口。独立的负载均衡进程应该在这两个端口之间交替选择,将一次请求发送到3000端口,下一次请求发送到3001端口,然后再将下一次请求发送回3000端口。
第4步: 在你的项目文件夹中打开命令提示符,并使用concurrently同时运行两个脚本。
concurrently "node config.js" "node index.js"
输出:
我们将在控制台上看到以下输出:
现在,打开浏览器并转到 http://localhost:8080/ ,并进行几个请求,我们将看到以下输出: