优化快速 + 滑动 HTML 渲染时间

Optimize Express + Swig HTML rendering time

本文关键字:时间 HTML 滑动 优化      更新时间:2023-09-26

我遇到了一个关键问题。

我的应用程序体系结构描述如下:

nginx -> Web App (Express/NodeJS) -> API (Jetty/Java) -> MySQL

API 应用程序经过了很好的优化,因此无需在此处提及其性能。(~200 毫秒/频率,100 个频率/秒)

我的网络应用程序:

  1. 表达
  2. 摇摆模板引擎

在执行配置文件日志时,我注意到 Swig 模板引擎的 HTML 渲染时间会阻止 I/O 太长,因此它会大大增加其他待处理请求的等待时间。

对于渲染 1MB 的文本/html 响应,Swig 模板需要 ~250 毫秒。


这是我的压力测试的输出:

$ node stress.js 20
Receive response [0] - 200 - 431.682654ms
Receive response [1] - 200 - 419.248099ms
Receive response [2] - 200 - 670.558033ms
Receive response [4] - 200 - 920.763105ms
Receive response [3] - 200 - 986.20115ms
Receive response [7] - 200 - 1521.330763ms
Receive response [5] - 200 - 1622.569327ms
Receive response [9] - 200 - 1424.500137ms
Receive response [13] - 200 - 1643.676996ms
Receive response [14] - 200 - 1595.958319ms
Receive response [10] - 200 - 1798.043086ms
Receive response [15] - 200 - 1551.028243ms
Receive response [8] - 200 - 1944.247382ms
Receive response [6] - 200 - 2044.866157ms
Receive response [11] - 200 - 2162.960215ms
Receive response [17] - 200 - 1941.155794ms
Receive response [16] - 200 - 1992.213563ms
Receive response [12] - 200 - 2315.330372ms
Receive response [18] - 200 - 2571.841722ms
Receive response [19] - 200 - 2523.899486ms
AVG: 1604.10ms

如您所见,请求越晚,等待时间越长。

当我返回响应代码而不是渲染 HTML 时,通过修改一些代码:

function render(req, res, next, model) {
    return res.status(200).end(); // add this line
    res.render('list', model);
}

压力测试输出更改为:

$ node stress.js 20
Receive response [0] - 200 - 147.738725ms
Receive response [1] - 200 - 204.656645ms
Receive response [2] - 200 - 176.583635ms
Receive response [3] - 200 - 218.785931ms
Receive response [4] - 200 - 194.479036ms
Receive response [6] - 200 - 191.531871ms
Receive response [5] - 200 - 265.371646ms
Receive response [7] - 200 - 294.373466ms
Receive response [8] - 200 - 262.097708ms
Receive response [10] - 200 - 282.183757ms
Receive response [11] - 200 - 249.842496ms
Receive response [9] - 200 - 371.228602ms
Receive response [14] - 200 - 236.945983ms
Receive response [13] - 200 - 304.847457ms
Receive response [12] - 200 - 377.766879ms
Receive response [15] - 200 - 332.011981ms
Receive response [16] - 200 - 306.347012ms
Receive response [17] - 200 - 284.942474ms
Receive response [19] - 200 - 249.047099ms
Receive response [18] - 200 - 315.11977ms
AVG: 263.30ms

我之前尝试过实施一些解决方案,但没有一个可以减少响应时间:

使用节点群集(我的服务器中有 2 个工作线程)

if (conf.cluster) {
    // cluster setup
    var cluster = require('cluster');
    var numCPUs = require('os').cpus().length;
    if (cluster.isMaster) {
        for (var i = 0; i < numCPUs; i++) {
            cluster.fork();
        }
        cluster.on('exit', function(worker, code, signal) {
            console.log('Worker ' + worker.process.pid + ' died');
            // create new worker
            cluster.fork();
        });
    } else {
        rek('server').listen(conf.port, function() {
            console.log('Application started at port ' + conf.port + ' [PID: ' + process.pid + ']');
        });
    }
} else {
    rek('server').listen(conf.port, function() {
        console.log('Application started at port ' + conf.port + ' [PID: ' + process.pid + ']');
    });
}

使用 JXCore 与 16 个线程(最大线程数没有)

jx mt-keep:16 app.js

使用 NGINX 负载平衡

启动 4 个节点进程

$ PORT=3000 forever start app.js
$ PORT=3001 forever start app.js
$ PORT=3002 forever start app.js
$ PORT=3003 forever start app.js

nginx.conf

upstream webapp {
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
    server 127.0.0.1:3003;
}
server {
    listen 80;
    location / {
        proxy_pass http://webapp;
    }
    [...]
}

我认为上述所有解决方案都会提供多个进程/线程,这些进程/线程在执行 HTML 渲染等繁重任务时不会相互阻塞,但结果与我的预期不同:等待时间不会减少。尽管日志显示请求实际上由多个进程/线程提供服务。

我在这里错过了什么点吗?

或者你能告诉我另一种减少等待时间的解决方案吗?

创建集群不会减少响应时间,但它将允许您并行运行响应而不会阻塞 IO。当然,为了正确使用集群,您需要为主节点设置自己的逻辑,以有效地控制工作线程。在没有适当逻辑的情况下添加集群永远不会给您带来任何真正的好处。为了使此操作正确工作,您的主节点需要处理所有传入的请求,并将它们分发给工作线程进行处理。然后,工作人员将结果发送回主节点,由主节点处理其余部分。

我认为您应该检查以下几点:

->避免使用模板缓存阻止 IO

->将模板拆分为多个部分并按需使用负载

->收集数据的时间已经覆盖了一次?

这里真正的答案是缓存。

每次压力时页面是否都要重新呈现?我不认为模板数据每分钟都在变化,如果有的话。

一个解决方案可能是编写 2 个充当"三明治"的中间件,渲染的路由应该具有这样的结构:

  • 请求
  • 从缓存中获取中间件
  • 数据库???
  • 摇摆模板
  • 放入缓存中间件
  • 响应中间件

put-in-cache应该将编译的模板写入一个非常快速的缓存数据库(REDIS 非常适合此),使其在 1 分钟内过期,并使用 url 或其他(更聪明的)机制(如请求它的 userid 或标头)对其进行索引

get-from-cache会在每个 HTTP 请求上查询 REDIS 以获取"索引查询",以防它找到已经编译的模板,它应该res.send