大家好
我用 Express 搭建了一个服务器 有的请求会处理一些非常耗时的任务 可能会持续几分钟到几十分钟不等
通常会先返回给前端告知任务正在处理 然后会在请求里调用处理这个任务的方法
我发现耗时短的任务会很快执行完 但耗时长的就会中断进行 并且如果同时还有其他的任务进来 就都会中断执行
我对 Express 和 Node.js 的认知都很浅 但描述给 GPT 也是答非所问 所以请教大家如何解决这个问题
谢谢
1
newlifeinsc 206 天前
再额外部署一个服务来处理耗时任务,这个服务就不要接收外部请求了,通过队列来沟通。
比如你现有项目部署了服务 A ,然后你再把这个项目部署一个服务 B 。A 接收到 http 请求要处理耗时任务,那你通知 B 处理就行了。至于 A 通知 B ,可以用队列。如果你没有队列的服务,那也可以直接从 A 发请求给 B ,通知 B 处理。 |
2
newlifeinsc 206 天前
至于你文中写的 "耗时长的就会中断进行 并且如果同时还有其他的任务进来 就都会中断执行", 这句意义不明,你这里中断执行是啥意思?
本身你在执行耗时任务时,程序还是可以接收外部请求的,你难道是期望他不处理这些请求吗? |
3
bianhui 206 天前
单线程异步模型啊,你理解为所有操作都会到异步队列里面,所以请求来了,还是你的后台任务的异步操作都可能会乱序,你也不要指望按照期望的顺序执行。
最好的办法,部署一个 job 系统,前台接收到任务,通过,消息、信号或其他方式像 job 发送执行请求。 |
4
subtleworks OP @newlifeinsc 就是如果此时又来一个任务 B 那么原先的任务 A 好像也会被打断 疑似有这个现象
|
5
subtleworks OP @newlifeinsc 感谢回复 这个服务是那种定时的 cron job 吗 还是两码事
|
6
subtleworks OP @bianhui 感谢回复 如果我想要系统地理解相关的知识 有什么关键词我可以搜索吗?
|
7
newlifeinsc 206 天前
@subtleworks 任务 A 被打断,说明是它自己本身被打断,一般情况都是在等待 io ,你有没有任务进来实际都在中断后等待。新进来任务 B,如果和任务 A 本身没有业务关系,你就不应该管它。如果你要强制后来的任务 B 一定得在任务 A 完成后才执行,那你就得通过队列来控制。
|
8
ferock 206 天前 via iPhone
可能被服务器 kill 了…云服务器都有这个机制
|
9
XCFOX 206 天前 1
|
10
subtleworks OP @newlifeinsc 好的 谢谢解释
|
11
subtleworks OP @ferock 也有可能 不过我也不知道哈哈
|
12
subtleworks OP @XCFOX 谢谢分享链接!
|
13
wangtian2020 206 天前
任何一个名字里带“Sync”的 nodejs 自身的 API 都不要使用
比如当你想使用 fs 的读取文件时不要使用 ``` let fs = require('fs') fs.readFileSync ``` 而是使用 ``` let fs = require('fs') ;(async () => { await fs.promises.readFile })() ``` 这样子就不会造成阻塞。你 express 的方法参数全部采用 async function 然后用 promise 风格就不会造成阻塞。 如果一个操作会耗时几分钟的话,那么接口就立即返回,告诉前端“我在做了”,然后让前端每个几秒轮询 express ,问后端“有没有做完呀”做完就拿结果没做完继续等。 |
14
wangtian2020 206 天前
记住,nodejs 里只有一种情况必须阻塞卡死,就是真的在计算,比如以下代码会卡个几秒钟
``` let i = 4000000000 while (i--) {} console.log('我循环完了 4000000000 次'); ``` 其他所有情况,被阻塞住不能处理新进的请求都是错误写法导致的! |
15
subtleworks OP @wangtian2020 好的 谢谢举例
|
16
lzgshsj 206 天前
bullmq
|
17
foam 206 天前 via Android
13 楼正解。同步方法会停止事件循环,直到操作完成。所以作为一个应用,任意 io 操作(包括网络,文件读写)都必须用异步方式。
|
18
lisongeee 206 天前
请问这个耗时的任务具体是什么,能说得更清楚吗?
|
19
subtleworks OP @lisongeee 大致可以理解为是不同格式之间的转换
|
20
lisongeee 206 天前
我就假设你的任务是将一个大的二进制数据转换到另一个大的二进制数据,有两种方法改进
- 用 worker ,类似 nodejs 在其它语言的子线程,这样你的主线程就不受到干扰 - 将你的处理函数改成 async 的,内部将大任务按数据量分成批量小任务,每个小任务用 await new Promise(r=>setTimeout(r)) 隔开,这样你的大任务就不会同步阻塞 |
21
darklinden 206 天前
* 使用 async 丢命令行异步给其他程序执行
* napi-rs 写个 node 插件异步执行 node 本身计算性能极其拉垮, 不要让它干重活, 只搞 io 就行 |
22
subtleworks OP @lisongeee 好的 谢谢回复
|
23
subtleworks OP @darklinden 现在确实是 async 然后 call 了另一个方法也是 js 写的异步执行 后面莫名其妙它就会执行到一半停止 因为有看到 progress 停留在中间不再改变 不知道是不是没有用队列的原因
|
24
chenshiforever 206 天前
async 怎么会中断呢,,你用 gpt 检查下你代码逻辑错误
|
25
lee88688 206 天前
把耗时任务扔到 worker 里面看看呢。node 可以搜一下 worker_thread 文档,创建和使用比较简单。
|
26
subtleworks OP @lee88688 好的 谢谢 我去看下
|
27
zhangyuang 206 天前
让后台用其他语言写然后用 ffi 来调用 https://github.com/zhangyuang/node-ffi-rs
|
28
libook 206 天前
业务上做成异步化,就是用户提交请求之后,服务器记录这个请求到消息队列,然后立即返回给用户处理中的信息,用户此时可以去干别的,过一会可以在特定界面查看人物结果。
服务上就是一个负责与用户交互的服务,一个消息队列用于记录任务,一个负责从队列里读出任务并进行处理的集群服务。集群服务处理完每个任务就会把结果塞到数据库里供交互服务查询,或者重新塞到另一个消息队列里让下游的服务消费。 |
29
subtleworks OP @libook 谢谢建议 结合以上其他人的回复 我也觉得应该用队列实现 然后你的回复从集群开始 我没有看懂了
|
30
libook 206 天前
@subtleworks #29 队列的概念里有生产者和消费者,就是生产者服务往队列里塞任务,消费者服务从队列里拿任务出来执行。
我所说的集群也可以是一个服务,就是消费者,考虑到你的任务可能执行时间很长,所以我建议是用很多个服务组成的集群一起消费队列里的任务。 你去找些消息队列相关的文章看看应该就了解了。 |
31
wanguorui123 206 天前
再开个工作线程
任务分片 |
32
subtleworks OP @libook 好的 谢谢解释!
|