nodejs 解决高并发

在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/');
});
  1. 首先,我们导入了Node.js的内置http模块。

  2. 使用http.createServer()创建一个新的HTTP服务器。这个函数的参数是一个回调函数,每当服务器接收到新的请求时,这个回调函数就会被调用。

  3. 在回调函数中,我们用setTimeout()模拟了一个异步的I/O操作。这个操作用时100毫秒,模拟了例如读取数据库或读取文件这样的耗时操作。

  4. 当这个异步操作完成,我们用res.writeHead(200)设置了HTTP响应的状态码为200,表示请求成功。然后,用res.end(‘Hello Worldn’)结束HTTP响应,并返回内容"Hello Worldn"。

  5. 最后,我们让服务器在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]);
        });
    });
  });

这段代码一步步解析如下:

  1. 使用 superagent 获取测试页面信息。
  2. cheerio 解析出所有需要并发爬取的主题 URL 并存入数组 topicUrls。
  3. 使用 eventproxy 控制这次并发爬取,首先通过 after 方法,监听长度等于 topicUrls.length 的 topic_html 事件,当所有数据爬取成功后执行回调。
  4. 在回调中,用 cheerio 解析每个主题页面的结构,取出需要的数据。
  5. 最后,在 forEach 循环中,针对每个 URL 使用 superagent 发送请求,请求结束后调用 ep.emit 来通知 eventproxy 数据已返回。
  6. 使用如 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: "结束"});  // 在子进程中,发送消息给主进程
  });
}
 
  1. 如果当前进程是主进程(通过 cluster.isMaster 判断),那么创建子进程(使用 cluster.fork()方法),数量等同于 CPU 核数(通过 os.cpus().length 获得)。

  2. 然后在主进程中监听 ‘exit’ 事件,当任何一个工作进程关闭的时候,这个事件会被触发。

  3. 主进程也会监听 ‘message’ 事件,当接收到子进程发送的消息后,主进程会关闭对应的子进程。

  4. 如果当前进程是子进程,那么就处理来自主进程的消息(通过 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 块来根据不同的路径进行不同的转发。

以上就是文章全部内容了,如果喜欢这篇文章的话,还希望三连支持一下,感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小纯洁w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值