Nodejs里如何处理比较耗时的任务
大家好
我用 Express 搭建了一个服务器 有的请求会处理一些非常耗时的任务 可能会持续几分钟到几十分钟不等
通常会先返回给前端告知任务正在处理 然后会在请求里调用处理这个任务的方法
我发现耗时短的任务会很快执行完 但耗时长的就会中断进行 并且如果同时还有其他的任务进来 就都会中断执行
我对 Express 和 Node.js 的认知都很浅 但描述给 GPT 也是答非所问 所以请教大家如何解决这个问题
谢谢
Nodejs里如何处理比较耗时的任务
再额外部署一个服务来处理耗时任务,这个服务就不要接收外部请求了,通过队列来沟通。
比如你现有项目部署了服务 A ,然后你再把这个项目部署一个服务 B 。A 接收到 http 请求要处理耗时任务,那你通知 B 处理就行了。至于 A 通知 B ,可以用队列。如果你没有队列的服务,那也可以直接从 A 发请求给 B ,通知 B 处理。
至于你文中写的 “耗时长的就会中断进行 并且如果同时还有其他的任务进来 就都会中断执行”, 这句意义不明,你这里中断执行是啥意思?
本身你在执行耗时任务时,程序还是可以接收外部请求的,你难道是期望他不处理这些请求吗?
单线程异步模型啊,你理解为所有操作都会到异步队列里面,所以请求来了,还是你的后台任务的异步操作都可能会乱序,你也不要指望按照期望的顺序执行。
最好的办法,部署一个 job 系统,前台接收到任务,通过,消息、信号或其他方式像 job 发送执行请求。
就是如果此时又来一个任务 B 那么原先的任务 A 好像也会被打断 疑似有这个现象
感谢回复 这个服务是那种定时的 cron job 吗 还是两码事
感谢回复 如果我想要系统地理解相关的知识 有什么关键词我可以搜索吗?
任务 A 被打断,说明是它自己本身被打断,一般情况都是在等待 io ,你有没有任务进来实际都在中断后等待。新进来任务 B,如果和任务 A 本身没有业务关系,你就不应该管它。如果你要强制后来的任务 B 一定得在任务 A 完成后才执行,那你就得通过队列来控制。
可能被服务器 kill 了…云服务器都有这个机制
好的 谢谢解释
也有可能 不过我也不知道哈哈
谢谢分享链接!
任何一个名字里带“Sync”的 nodejs 自身的 API 都不要使用
比如当你想使用 fs 的读取文件时不要使用<br>let fs = require('fs')<br>fs.readFileSync<br>
而是使用<br>let fs = require('fs')<br>;(async () => {<br> await fs.promises.readFile<br>})()<br>
这样子就不会造成阻塞。你 express 的方法参数全部采用 async function 然后用 promise 风格就不会造成阻塞。
如果一个操作会耗时几分钟的话,那么接口就立即返回,告诉前端“我在做了”,然后让前端每个几秒轮询 express ,问后端“有没有做完呀”做完就拿结果没做完继续等。
记住,nodejs 里只有一种情况必须阻塞卡死,就是真的在计算,比如以下代码会卡个几秒钟<br>let i = 4000000000<br>while (i--) {}<br>console.log('我循环完了 4000000000 次');<br>
其他所有情况,被阻塞住不能处理新进的请求都是错误写法导致的!
好的 谢谢举例
bullmq
13 楼正解。同步方法会停止事件循环,直到操作完成。所以作为一个应用,任意 io 操作(包括网络,文件读写)都必须用异步方式。
请问这个耗时的任务具体是什么,能说得更清楚吗?
大致可以理解为是不同格式之间的转换
我就假设你的任务是将一个大的二进制数据转换到另一个大的二进制数据,有两种方法改进
- 用 worker ,类似 nodejs 在其它语言的子线程,这样你的主线程就不受到干扰
- 将你的处理函数改成 async 的,内部将大任务按数据量分成批量小任务,每个小任务用 await new Promise(r=>setTimeout®) 隔开,这样你的大任务就不会同步阻塞
- 使用 async 丢命令行异步给其他程序执行
* napi-rs 写个 node 插件异步执行
node 本身计算性能极其拉垮, 不要让它干重活, 只搞 io 就行
好的 谢谢回复
现在确实是 async 然后 call 了另一个方法也是 js 写的异步执行 后面莫名其妙它就会执行到一半停止 因为有看到 progress 停留在中间不再改变 不知道是不是没有用队列的原因
把耗时任务扔到 worker 里面看看呢。node 可以搜一下 worker_thread 文档,创建和使用比较简单。
好的 谢谢 我去看下
让后台用其他语言写然后用 ffi 来调用 https://github.com/zhangyuang/node-ffi-rs
业务上做成异步化,就是用户提交请求之后,服务器记录这个请求到消息队列,然后立即返回给用户处理中的信息,用户此时可以去干别的,过一会可以在特定界面查看人物结果。
服务上就是一个负责与用户交互的服务,一个消息队列用于记录任务,一个负责从队列里读出任务并进行处理的集群服务。集群服务处理完每个任务就会把结果塞到数据库里供交互服务查询,或者重新塞到另一个消息队列里让下游的服务消费。
谢谢建议 结合以上其他人的回复 我也觉得应该用队列实现 然后你的回复从集群开始 我没有看懂了
#29 队列的概念里有生产者和消费者,就是生产者服务往队列里塞任务,消费者服务从队列里拿任务出来执行。
我所说的集群也可以是一个服务,就是消费者,考虑到你的任务可能执行时间很长,所以我建议是用很多个服务组成的集群一起消费队列里的任务。
你去找些消息队列相关的文章看看应该就了解了。
再开个工作线程
任务分片
好的 谢谢解释!
在Node.js中处理比较耗时的任务,通常不会直接在事件循环中同步执行,因为这会导致阻塞,影响服务器的性能和响应能力。有几种常见的方法可以处理耗时任务:
-
使用回调函数(Callbacks): 虽然回调函数不是最优雅的方法,但它是一种基本的异步编程技术。
-
Promise: Promise提供了一种更清晰的方式来处理异步操作,避免了回调地狱。
function longRunningTask() { return new Promise((resolve, reject) => { setTimeout(() => { resolve("Task Completed"); }, 5000); // 模拟耗时任务 }); } longRunningTask().then(result => { console.log(result); }).catch(error => { console.error(error); });
-
Async/Await: Async/Await基于Promise,但提供了更同步的代码风格,使得异步代码更易于阅读和理解。
async function runTask() { try { const result = await longRunningTask(); console.log(result); } catch (error) { console.error(error); } } runTask();
-
使用子进程(Child Processes): 对于CPU密集型任务,可以考虑使用
child_process
模块来创建子进程,将任务卸载到子进程中执行。const { exec } = require('child_process'); exec('your-cpu-intensive-task', (error, stdout, stderr) => { if (error) { console.error(`exec error: ${error}`); return; } console.log(`stdout: ${stdout}`); console.error(`stderr: ${stderr}`); });
选择哪种方法取决于你的具体需求和代码风格偏好。