V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
subtleworks
V2EX  ›  Node.js

请教大家在 NodeJS 里如何处理比较耗时的任务

  •  
  •   subtleworks · 2024-04-10 07:48:56 +08:00 · 2874 次点击
    这是一个创建于 368 天前的主题,其中的信息可能已经有所发展或是发生改变。

    大家好

    我用 Express 搭建了一个服务器 有的请求会处理一些非常耗时的任务 可能会持续几分钟到几十分钟不等

    通常会先返回给前端告知任务正在处理 然后会在请求里调用处理这个任务的方法

    我发现耗时短的任务会很快执行完 但耗时长的就会中断进行 并且如果同时还有其他的任务进来 就都会中断执行

    我对 Express 和 Node.js 的认知都很浅 但描述给 GPT 也是答非所问 所以请教大家如何解决这个问题

    谢谢

    第 1 条附言  ·  2024-04-10 12:52:03 +08:00
    大家如果有相关的书籍和文章可以推荐给我 我应该是缺乏对后端的基本认知==
    newlifeinsc
        1
    newlifeinsc  
       2024-04-10 08:01:56 +08:00
    再额外部署一个服务来处理耗时任务,这个服务就不要接收外部请求了,通过队列来沟通。

    比如你现有项目部署了服务 A ,然后你再把这个项目部署一个服务 B 。A 接收到 http 请求要处理耗时任务,那你通知 B 处理就行了。至于 A 通知 B ,可以用队列。如果你没有队列的服务,那也可以直接从 A 发请求给 B ,通知 B 处理。
    newlifeinsc
        2
    newlifeinsc  
       2024-04-10 08:05:45 +08:00
    至于你文中写的 "耗时长的就会中断进行 并且如果同时还有其他的任务进来 就都会中断执行", 这句意义不明,你这里中断执行是啥意思?

    本身你在执行耗时任务时,程序还是可以接收外部请求的,你难道是期望他不处理这些请求吗?
    bianhui
        3
    bianhui  
       2024-04-10 08:13:07 +08:00
    单线程异步模型啊,你理解为所有操作都会到异步队列里面,所以请求来了,还是你的后台任务的异步操作都可能会乱序,你也不要指望按照期望的顺序执行。
    最好的办法,部署一个 job 系统,前台接收到任务,通过,消息、信号或其他方式像 job 发送执行请求。
    subtleworks
        4
    subtleworks  
    OP
       2024-04-10 08:15:33 +08:00
    @newlifeinsc 就是如果此时又来一个任务 B 那么原先的任务 A 好像也会被打断 疑似有这个现象
    subtleworks
        5
    subtleworks  
    OP
       2024-04-10 08:16:39 +08:00
    @newlifeinsc 感谢回复 这个服务是那种定时的 cron job 吗 还是两码事
    subtleworks
        6
    subtleworks  
    OP
       2024-04-10 08:17:32 +08:00
    @bianhui 感谢回复 如果我想要系统地理解相关的知识 有什么关键词我可以搜索吗?
    newlifeinsc
        7
    newlifeinsc  
       2024-04-10 08:28:04 +08:00
    @subtleworks 任务 A 被打断,说明是它自己本身被打断,一般情况都是在等待 io ,你有没有任务进来实际都在中断后等待。新进来任务 B,如果和任务 A 本身没有业务关系,你就不应该管它。如果你要强制后来的任务 B 一定得在任务 A 完成后才执行,那你就得通过队列来控制。
    ferock
        8
    ferock  
       2024-04-10 08:32:03 +08:00 via iPhone
    可能被服务器 kill 了…云服务器都有这个机制
    subtleworks
        10
    subtleworks  
    OP
       2024-04-10 08:56:49 +08:00
    @newlifeinsc 好的 谢谢解释
    subtleworks
        11
    subtleworks  
    OP
       2024-04-10 08:57:28 +08:00
    @ferock 也有可能 不过我也不知道哈哈
    subtleworks
        12
    subtleworks  
    OP
       2024-04-10 08:57:51 +08:00
    @XCFOX 谢谢分享链接!
    wangtian2020
        13
    wangtian2020  
       2024-04-10 09:09:57 +08:00
    任何一个名字里带“Sync”的 nodejs 自身的 API 都不要使用
    比如当你想使用 fs 的读取文件时不要使用
    ```
    let fs = require('fs')
    fs.readFileSync
    ```
    而是使用
    ```
    let fs = require('fs')
    ;(async () => {
    await fs.promises.readFile
    })()
    ```
    这样子就不会造成阻塞。你 express 的方法参数全部采用 async function 然后用 promise 风格就不会造成阻塞。

    如果一个操作会耗时几分钟的话,那么接口就立即返回,告诉前端“我在做了”,然后让前端每个几秒轮询 express ,问后端“有没有做完呀”做完就拿结果没做完继续等。
    wangtian2020
        14
    wangtian2020  
       2024-04-10 09:15:40 +08:00
    记住,nodejs 里只有一种情况必须阻塞卡死,就是真的在计算,比如以下代码会卡个几秒钟
    ```
    let i = 4000000000
    while (i--) {}
    console.log('我循环完了 4000000000 次');
    ```
    其他所有情况,被阻塞住不能处理新进的请求都是错误写法导致的!
    subtleworks
        15
    subtleworks  
    OP
       2024-04-10 09:22:15 +08:00
    @wangtian2020 好的 谢谢举例
    lzgshsj
        16
    lzgshsj  
       2024-04-10 09:22:20 +08:00
    bullmq
    foam
        17
    foam  
       2024-04-10 09:27:50 +08:00 via Android
    13 楼正解。同步方法会停止事件循环,直到操作完成。所以作为一个应用,任意 io 操作(包括网络,文件读写)都必须用异步方式。
    lisongeee
        18
    lisongeee  
       2024-04-10 10:00:30 +08:00
    请问这个耗时的任务具体是什么,能说得更清楚吗?
    subtleworks
        19
    subtleworks  
    OP
       2024-04-10 10:19:48 +08:00
    @lisongeee 大致可以理解为是不同格式之间的转换
    lisongeee
        20
    lisongeee  
       2024-04-10 10:37:09 +08:00
    我就假设你的任务是将一个大的二进制数据转换到另一个大的二进制数据,有两种方法改进

    - 用 worker ,类似 nodejs 在其它语言的子线程,这样你的主线程就不受到干扰
    - 将你的处理函数改成 async 的,内部将大任务按数据量分成批量小任务,每个小任务用 await new Promise(r=>setTimeout(r)) 隔开,这样你的大任务就不会同步阻塞
    darklinden
        21
    darklinden  
       2024-04-10 10:43:14 +08:00
    * 使用 async 丢命令行异步给其他程序执行
    * napi-rs 写个 node 插件异步执行

    node 本身计算性能极其拉垮, 不要让它干重活, 只搞 io 就行
    subtleworks
        22
    subtleworks  
    OP
       2024-04-10 10:48:20 +08:00
    @lisongeee 好的 谢谢回复
    subtleworks
        23
    subtleworks  
    OP
       2024-04-10 10:51:00 +08:00
    @darklinden 现在确实是 async 然后 call 了另一个方法也是 js 写的异步执行 后面莫名其妙它就会执行到一半停止 因为有看到 progress 停留在中间不再改变 不知道是不是没有用队列的原因
    chenshiforever
        24
    chenshiforever  
       2024-04-10 12:16:27 +08:00
    async 怎么会中断呢,,你用 gpt 检查下你代码逻辑错误
    lee88688
        25
    lee88688  
       2024-04-10 12:28:31 +08:00
    把耗时任务扔到 worker 里面看看呢。node 可以搜一下 worker_thread 文档,创建和使用比较简单。
    subtleworks
        26
    subtleworks  
    OP
       2024-04-10 12:33:43 +08:00
    @lee88688 好的 谢谢 我去看下
    zhangyuang
        27
    zhangyuang  
       2024-04-10 14:29:52 +08:00
    让后台用其他语言写然后用 ffi 来调用 https://github.com/zhangyuang/node-ffi-rs
    libook
        28
    libook  
       2024-04-10 15:42:30 +08:00
    业务上做成异步化,就是用户提交请求之后,服务器记录这个请求到消息队列,然后立即返回给用户处理中的信息,用户此时可以去干别的,过一会可以在特定界面查看人物结果。

    服务上就是一个负责与用户交互的服务,一个消息队列用于记录任务,一个负责从队列里读出任务并进行处理的集群服务。集群服务处理完每个任务就会把结果塞到数据库里供交互服务查询,或者重新塞到另一个消息队列里让下游的服务消费。
    subtleworks
        29
    subtleworks  
    OP
       2024-04-10 17:30:17 +08:00
    @libook 谢谢建议 结合以上其他人的回复 我也觉得应该用队列实现 然后你的回复从集群开始 我没有看懂了
    libook
        30
    libook  
       2024-04-10 18:14:20 +08:00
    @subtleworks #29 队列的概念里有生产者和消费者,就是生产者服务往队列里塞任务,消费者服务从队列里拿任务出来执行。

    我所说的集群也可以是一个服务,就是消费者,考虑到你的任务可能执行时间很长,所以我建议是用很多个服务组成的集群一起消费队列里的任务。

    你去找些消息队列相关的文章看看应该就了解了。
    wanguorui123
        31
    wanguorui123  
       2024-04-10 18:25:30 +08:00
    再开个工作线程
    任务分片
    subtleworks
        32
    subtleworks  
    OP
       2024-04-10 18:28:44 +08:00
    @libook 好的 谢谢解释!
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2690 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 11:16 · PVG 19:16 · LAX 04:16 · JFK 07:16
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.