在Node.js中解决高并发的问题,可以采取以下几种策略:
1. 异步非阻塞I/O: Node.js的异步非阻塞I/O模型让其在处理高并发请求时具有优势。在接收到请求后,Node.js可以立即接受其他请求,而不需要等待当前请求的I/O操作完成,从而提高并发处理能力。
// 引入http模块
const http = require('http');// 创建一个 HTTP 服务器
const server = http.createServer((req, res) => {// 模拟异步的I/O操作,比如数据库查询或文件读取setTimeout(() => {// 服务器向客户端返回消息res.writeHead(200);res.end('Hello Worldn');}, 100);
});// 监听端口
server.listen(8000, () => {console.log('Server is running at http://localhost:8000/');
});
-
首先,我们导入了Node.js的内置http模块。
-
使用http.createServer()创建一个新的HTTP服务器。这个函数的参数是一个回调函数,每当服务器接收到新的请求时,这个回调函数就会被调用。
-
在回调函数中,我们用setTimeout()模拟了一个异步的I/O操作。这个操作用时100毫秒,模拟了例如读取数据库或读取文件这样的耗时操作。
-
当这个异步操作完成,我们用res.writeHead(200)设置了HTTP响应的状态码为200,表示请求成功。然后,用res.end(‘Hello Worldn’)结束HTTP响应,并返回内容"Hello Worldn"。
-
最后,我们让服务器在8000端口上监听,当服务器开始运行后,会在控制台打印出"Server is running at http://localhost:8000/"。
这是Node.js如何以异步非阻塞的方式来处理高并发请求的一种典型方式。尽管JavaScript是单线程的,但Node.js的这种机制可以让它在接收到新请求的同时,继续处理其他的请求,不需要等待当前请求的I/O操作完成,从而能够处理大量并发请求。
2. 事件驱动: Node.js的事件驱动模型可以将CPU密集型操作和I/O操作分离,让CPU和I/O可以并行工作,从而提高处理效率。
const http = require('http');
const events = require('events');
const emitter = new events.EventEmitter();// 创建一个HTTP服务器
const server = http.createServer((req, res) => {// 异步事件触发和监听emitter.on('data_received', () => {res.writeHead(200);res.end('Hello Worldn');});// 模拟异步I/O操作,比如数据库查询或文件读取setTimeout(() => {emitter.emit('data_received');}, 100);
});// 监听端口
server.listen(8000, () => {console.log('Server is running at http://localhost:8000/');
});
-
首先,我们使用Node.js内置的http模块创建了一个HTTP服务器,它会对初始的每个请求建立一个新的事件监听器。
-
我们定义了一个事件监听器,它会在’data_received’事件发生时向客户端返回HTTP响应。这里的事件可以模拟如数据库查询、文件读取等异步I/O操作。
-
用setTimeout模拟了一个异步操作,它在100毫秒后触发’data_received’事件,此事件被监听并执行了响应的返回。此时,可以接受其他的请求,因此是非阻塞的。
-
服务器监听8000端口运行,当服务器开始运行后,会在控制台输出’Server is running at http://localhost:8000/'。
这就是Node.js使用事件驱动模型处理高并发请求的基本方式。
3. 使用中间件控制并发: 可以使用如eventproxy、async等中间件对并发进行控制
// 导入所需模块
var eventproxy = require('eventproxy');
var superagent = require('superagent');
var cheerio = require('cheerio');
var url = require('url');// 目标网站
var cnodeUrl = 'https://test.org/';superagent.get(cnodeUrl).end(function (err, res) {if (err) { return console.error(err); }var topicUrls = [];var $ = cheerio.load(res.text);$('#topic_list .topic_title').each(function (idx, element) {var $element = $(element);var href = url.resolve(cnodeUrl, $element.attr('href'));topicUrls.push(href);});// 得到一个 eventproxy 实例var ep = new eventproxy();// 命令 ep 重复监听 topicUrls.length 次(在这里也就是 40 次)topic_html 事件再行动ep.after('topic_html', topicUrls.length, function (topics) {topics = topics.map(function (topicPair) {var topicUrl = topicPair[0];var topicHtml = topicPair[1];var $ = cheerio.load(topicHtml);return ({title: $('.topic_full_title').text().trim(),href: topicUrl,comment1: $('.reply_content').eq(0).text().trim(),});});console.log('final:');console.log(topics);});topicUrls.forEach(function (topicUrl) {superagent.get(topicUrl).end(function (err, res) {console.log('fetch ' + topicUrl + ' successful');ep.emit('topic_html', [topicUrl, res.text]);});});});
这段代码一步步解析如下:
- 使用 superagent 获取测试页面信息。
- cheerio 解析出所有需要并发爬取的主题 URL 并存入数组 topicUrls。
- 使用 eventproxy 控制这次并发爬取,首先通过 after 方法,监听长度等于 topicUrls.length 的 topic_html 事件,当所有数据爬取成功后执行回调。
- 在回调中,用 cheerio 解析每个主题页面的结构,取出需要的数据。
- 最后,在 forEach 循环中,针对每个 URL 使用 superagent 发送请求,请求结束后调用 ep.emit 来通知 eventproxy 数据已返回。
- 使用如 async.mapLimit 或 async.queue 等函数来限制并发数量时,可以确保任何时刻只有一定数量的函数在执行。一旦一个函数执行完成,async 就会从队列中取出下一个任务开始执行,从而保证避免了 “饥饿” 问题,即无法得到处理的请求等待过久。
限制并发不仅能防止应用用尽系统资源,还能使应用处于一个我们可以更好管理和理解的状态,提高系统的稳定性和效率。
4. 服务器集群: Node.js提供了cluster模块,可以创建一个主进程和多个工作进程,主进程接收到请求后将其分发给工作进程处理,从而实现负载均衡。
在 Node.js 中,我们可以通过 cluster 模块实现服务器集群化。以下是一个使用 cluster 创建主进程和多个工作进程的代码示例:
var cluster = require("cluster");
var numCPUs = require("os").cpus().length;if (cluster.isMaster) {let primes = [];for (let i = 0; i < numCPUs; i++) {const worker = cluster.fork();worker.send({}); // 向子进程发送数据}cluster.on("exit", function(worker) {console.log("进程" + worker.process.pid + "结束");});cluster.on("message", function(worker) {worker.kill(); // 当主进程收到消息时,关闭子进程});} else {process.on("message", (msg) => {process.send({data: "结束"}); // 在子进程中,发送消息给主进程});
}
-
如果当前进程是主进程(通过 cluster.isMaster 判断),那么创建子进程(使用 cluster.fork()方法),数量等同于 CPU 核数(通过 os.cpus().length 获得)。
-
然后在主进程中监听 ‘exit’ 事件,当任何一个工作进程关闭的时候,这个事件会被触发。
-
主进程也会监听 ‘message’ 事件,当接收到子进程发送的消息后,主进程会关闭对应的子进程。
-
如果当前进程是子进程,那么就处理来自主进程的消息(通过 process.on(‘message’) 方法),并在处理完成后向主进程发送消息。
5. 利用反向代理服务器: 使用像Nginx这样的反向代理服务器,可以帮助分发请求,提供静态文件服务,缓存内容等,从而减轻Node.js服务器的压力。
Nginx 可以充当反向代理服务器,将客户端请求转发到后端的多个服务器上,并将响应返回给客户端。Nginx的工作原理如下:
- 客户端发送请求:当客户端(例如浏览器)需要访问某个Web服务时,它会向Nginx服务器发送HTTP请求。这个请求包括了要访问的URL和其他相关信息。
- Nginx接收请求:Nginx服务器接收到客户端发送的请求后,会解析请求中的URL和其他信息。
- 转发请求:Nginx根据配置规则,将接收到的客户端请求转发给后端的Web服务器。这个过程可能是基于轮询、加权轮询、IP hash等负载均衡策略来选择的。
- 后端服务器处理请求:后端的Web服务器接收到Nginx转发过来的请求后,会处理这个请求,并生成相应的响应。
- 返回响应:后端服务器将处理后的响应返回给Nginx服务器。Nginx服务器可能会对这个响应进行一些处理,例如缓存、压缩等。
- Nginx转发响应:Nginx将后端服务器返回的响应转发给客户端。这样,客户端就得到了它需要的数据。
在整个过程中,Nginx作为反向代理服务器,起到了一个中间人的角色。它接收客户端的请求,转发给后端服务器,再将后端服务器的响应返回给客户端。这样,客户端并不知道它实际上访问的是哪个后端服务器,从而实现了隐藏后端服务器、负载均衡、缓存加速等功能。同时,由于Nginx的高性能和稳定性,它可以处理大量的并发请求,提高整个Web服务的性能和可靠性。
Nginx反向代理配置
一个示例配置,用于将请求反向代理到本地的 8080 端口:
http {server {listen 80;server_name localhost; # 你的域名location / {proxy_pass http://127.0.0.1:8080;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;}}
}
这个配置文件的含义是:当用户访问你的服务器(假设你的服务器 IP 是 yourserverip)的 80 端口时,Nginx 会将请求转发到本地的 8080 端口。
- listen 80 表示 Nginx 监听 80 端口。
- location / 表示匹配所有 URI 的 location 块。就是说,这个 location 块会处理所有发送到 Nginx 服务器的请求,不管 URI 是什么。
- proxy_pass http://127.0.0.1:8080 表示将所有请求转发到本地的 8080 端口,proxy_pass 就是用来设置反向代理的目标服务器地址和端口。
- proxy_set_header 用于设置转发请求的 HTTP 头部信息。
以下是对 proxy_set_header 的一些解释:
- proxy_set_header Host $host; 这个指令用于设置 HTTP 请求头部中的 Host 字段,将客户端请求中的 Host 头部信息传递给后端服务器。这样后端服务器就能知道客户端请求的原始 Host 信息。
- proxy_set_header X-Real-IP $remote_addr; 这个指令用于设置 HTTP 头部 X-Real-IP字段,将客户端的真实 IP 地址传递给后端服务器。这有助于后端服务器获取客户端的真实 IP 地址。
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 这个指令用于设置 HTTP 头部 X-Forwarded-For字段,将客户端的原始 IP 地址列表传递给后端服务器。这个头部通常用于记录经过的代理服务器的 IP 地址,以便后端服务器跟踪请求的来源。
需要注意的是,当请求到达 Nginx 时,Nginx 会根据 location 块的配置顺序和匹配规则来决定使用哪个 location 块来处理请求。你可以使用多个 location 块来根据不同的路径进行不同的转发。
以上就是文章全部内容了,如果喜欢这篇文章的话,还希望三连支持一下,感谢!