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

node 单线程是怎么应对高并发的场景的?

  •  1
     
  •   leebs · 2022-01-20 17:15:25 +08:00 · 12350 次点击
    这是一个创建于 1040 天前的主题,其中的信息可能已经有所发展或是发生改变。

    node 单线程处理事件请求,一个请求卡住了,后续其他请求都会卡住,用 node 做业务处理,并发高的情况下,岂不是后面的请求可能会一直排队? node 不是单线程分发事件,多线程处理事件嘛?

    43 条回复    2022-06-19 10:24:05 +08:00
    kxuanobj
        1
    kxuanobj  
       2022-01-20 17:21:48 +08:00
    你不要卡住不就行了。。需要改改非要让代码卡住的问题。
    BluesQu
        2
    BluesQu  
       2022-01-20 17:21:56 +08:00
    node 不是单线程 JavaScript 才是单线程 一个请求卡住 暂时没有返回的话 并不影响后续请求的执行
    Leviathann
        3
    Leviathann  
       2022-01-20 17:22:42 +08:00
    卡住就下一个
    anonymousar
        4
    anonymousar  
       2022-01-20 17:25:21 +08:00
    @kxuanobj 楼主所说的卡住 应该是指 需要 cpu 计算的不可避免的那部分吧 类似 redis 中出现的慢查询。

    不太了解 node 但是单线程利用多核可以走 multi process, 也可以搞 one event base per thread
    leebs
        5
    leebs  
    OP
       2022-01-20 17:25:39 +08:00
    @kxuanobj 一个请求处理 1s ,第十个请求岂不是要等个 10s 左右的时间,如果请求没有 IO 要处理的话。
    macy
        6
    macy  
       2022-01-20 17:26:10 +08:00
    node 实际上底层有个 libuv 线程池,看看 node 的时间循环就知道了,只要不是 cpu 密集型,把 cpu 卡住了,就没事。
    Kasumi20
        7
    Kasumi20  
       2022-01-20 17:27:08 +08:00
    主线程处理连接,返回数据,数据的处理交给工作线程去干,一个连接对应一个线程可能比较耗费资源,所以要用协程之内的东西
    Kasumi20
        8
    Kasumi20  
       2022-01-20 17:33:37 +08:00   ❤️ 1
    你可以看看我这个代码,对每个请求都会同步地调用 Youtube-dl ,但是不会卡住别的请求,因为有多线程呀

    https://github.com/develon2015/Youtube-dl-REST/blob/node/index.js#L182
    jorneyr
        9
    jorneyr  
       2022-01-20 17:39:13 +08:00
    @macy 就担心 CPU 计算密集型业务。
    akira
        10
    akira  
       2022-01-20 17:49:39 +08:00
    CPU 计算密集型 放合适的地方啊, 为啥要指望一个语言打天下呢
    for8ever
        11
    for8ever  
       2022-01-20 18:09:17 +08:00
    node 适合 IO 密集,不适合 CPU 密集
    leebs
        12
    leebs  
    OP
       2022-01-20 18:14:05 +08:00
    @for8ever 复杂业务很难定义属于哪一类吧,总不能为了一个小的需求用其他语言做单独实现。
    zonghow
        13
    zonghow  
       2022-01-20 18:26:28 +08:00 via iPhone
    一般都是根据 cpu 线程数 单机多实例部署吧
    makelove
        14
    makelove  
       2022-01-20 18:40:49 +08:00
    node 全异步怎么会卡住?业务很少有 CPU 密集部分,CPU 密集部分也不会放在 web 请求中,在 web 请求中搞 CPU 密集放哪个语言多进程框架都不行毕竟线程也是很有限的。
    mxT52CRuqR6o5
        15
    mxT52CRuqR6o5  
       2022-01-20 18:44:38 +08:00
    [单线程分发事件,多线程处理事件] 多线程指的不是真正的系统层面上的线程,甚至你也可以把句话认为是错误的
    node 那些常用的框架正常开发出来应该都是单核应用
    正常的异步内部逻辑不会卡后续逻辑,如果你现在确实碰到了卡后续请求的问题,可能是使用了同步版本的 IO 调用(导致无法利用 JS 的异步能力),也可能是本身业务 cpu 运算量就大
    简单点的利用多核的方法可以直接用 PM2
    knives
        16
    knives  
       2022-01-20 19:40:24 +08:00
    担心主线程卡住,worker_threads 了解一下: http://nodejs.cn/api/worker_threads.html
    hotsymbol
        17
    hotsymbol  
       2022-01-20 23:36:08 +08:00
    发给下游服务去处理。
    3dwelcome
        18
    3dwelcome  
       2022-01-20 23:46:22 +08:00
    你想一下,领导给你一个人的任务太多,卡住怎么办?

    很简单,把工作给下面的人分下去,人多力量大。

    nodejs 也是同理,当然有些数据库之类的写入,还是尽量用消息队列处理。
    ETiV
        19
    ETiV  
       2022-01-20 23:55:52 +08:00 via iPhone
    需要人灵活地使用工具

    拿电钻去钉钉子是不对的 😂
    LUO12826
        20
    LUO12826  
       2022-01-21 00:47:34 +08:00
    @leebs 如果你这 1s 内 cpu (单核)占满,那确实后面的请求就得排队了。但一个请求要 cpu 实实在在算 1s 的业务,估计也不会用 node 写了。不过 node 也有了 worker_threads ,甚至可以调用 c++模块,一定程度上有利于计算密集型任务。
    pengtdyd
        21
    pengtdyd  
       2022-01-21 01:20:34 +08:00
    你考虑的太多了,你应该思考的是你们公司有这么大的业务量吗
    kuangwinnie
        22
    kuangwinnie  
       2022-01-21 02:03:42 +08:00
    所以 node js 不适合用来做 CPU-intensive 的操作啊;本来就是拿来做 IO intensive 的。
    zeni123
        23
    zeni123  
       2022-01-21 04:38:17 +08:00
    @jorneyr CPU 计算密集型的任何语言都会卡住,需要 worker 来利用多个 CPU
    georgema1982
        24
    georgema1982  
       2022-01-21 05:18:10 +08:00
    一个请求卡住后,她会求助的:Step bro, I'm stuck
    IvanLi127
        25
    IvanLi127  
       2022-01-21 08:06:36 +08:00 via Android
    你会卡住?那你就让这会卡住的部分放在这个线程外面,用其他线程跑就行了。
    Biwood
        26
    Biwood  
       2022-01-21 08:44:20 +08:00 via Android
    event loop 怎么会卡住呢,甚至可以说处理高并发是优势,瓶颈在别的地方,几年前讨论的明明白白,现在这是退化了?
    leebs
        27
    leebs  
    OP
       2022-01-21 09:30:00 +08:00
    @Biwood 业务事件是程序员写的,这是有可能会卡住的。如果某个请求因为某个条件触发了耗时的 cpu 运算,其他请求只能等待,多线程处理的情况下,其他线程是不需要等待的。
    byte10
        28
    byte10  
       2022-01-21 09:35:47 +08:00
    @leebs 我靠。。。你。。你。你绝对需要看我的视频啦少年,首先 nodejs 是 nio 模型,还有基于事件轮训的设计,它不用等待 IO ,所有的 IO 也是一个事件。一个请求过来,接收完请求后,就生成一个事件,这个事件处理完后,就扔出一个 response 事件,事件池就会一直被轮训,一直被处理。nodejs 主要适合 IO 密集型,就是不要那么有很多计算性的代码,也就是你的业务代码不要太复杂就行了。

    NIO 有一个绝对性优势,你可以看看我的视频,有讲解 nodejs 如何无视 IO 时间,做到吞吐量保持一致。https://www.bilibili.com/video/BV1FS4y1o7QB
    yyfearth
        29
    yyfearth  
       2022-01-21 09:41:27 +08:00
    @leebs node 单线程 如果做“CPU 密集的工作”或者“同步的 IO 操作”就会卡住
    这个是一定要避免的 如果没法避免就必须用 worker https://nodejs.org/api/worker_threads.html

    然后保证主线程不卡住就没问题了
    所以 node 做 IO 密集的工作 性能还是可以的

    不够就上多个 node 进程 然后外面做负载均衡就是
    尽量无状态 如果必须要 session 就用 redis
    yyfearth
        30
    yyfearth  
       2022-01-21 09:48:06 +08:00
    @leebs "某个条件触发了耗时的 cpu 运算"
    耗时 CPU 运算目前两种方法,核心概念就是保证主线程不阻塞
    1. 把这部分放到其他子线程或者进程 然后主线程异步处理
    2. 如果上面太难 就把需要长时间 CPU 处理的运算打碎成多个短暂同步运算然后异步处理 保证主线程不要长时间卡住

    对于 2 比如一个运算需要 10s 那么就把它打碎成 1000 个小运算 每个 0.1s 然后中间异步等待
    这样主线程就不会因为一个 10s 的运算而阻塞 10s
    虽热这样一来 原先 10s 的事情可能变成了 15s 或者更久 但是至少不会长时间阻塞其他的请求

    当然 2 这种方法比较时候低并发或者低概率触发的情况
    如果真的高并发情况大概率 还是要用 1 来处理
    monkeyWie
        31
    monkeyWie  
       2022-01-21 10:20:08 +08:00   ❤️ 1
    我服了,CPU 密集和 IO 密集都不懂吗,在 JS 里一切 IO 都是异步的,不可能会卡,除非是 CPU 密集运算
    juzisang
        32
    juzisang  
       2022-01-21 10:44:25 +08:00
    不知道现在前端 SSR 框架算不算 CPU 密集型计算,还有 node 里运行 marked md2html 都会卡一下,多了估计会影响
    keepeye
        33
    keepeye  
       2022-01-21 11:30:15 +08:00
    cpu 密集型就多搞一些进程啊 这是问题吗
    jguo
        34
    jguo  
       2022-01-21 11:31:05 +08:00
    别拿 java 和浏览器 js 的概念去套 node
    libook
        35
    libook  
       2022-01-21 12:01:53 +08:00   ❤️ 2
    Node 是如何应对高并发场景的?答:异步非阻塞。

    JavaScript 的生态根基简单来讲就是语言+API 。
    JavaScript 是一门脚本语言,一门语言要想有实际用途就得有能力调用各个系统,那么就需要各个系统面向 JavaScript 提供 API ,比如你计算了 1+2 ,能得出结果 3 ,但你要想看到这个结果就得让操作系统帮你显示出来,于是操作系统(中间省略很多环节)给 JS 提供了个 console API ,你可以使用 console.log 来(中间省略很多环节)调用操作系统把 3 显示出来。

    所以 Node 不等于 JS ,JS 语言的执行能力只是 Node 的一项子功能而已。

    原生 JavaScript 语言是单线程执行的,但 Node 不是单线程的,Node 为 JS 语言提供了一些 API ,其中大部分都是 IO 相关的 API ,比如网络访问、文件系统访问等。

    Node 有一个假设,就是很多应用场景下 IO 操作的工作量要远远大于计算操作。比如大多 Web 应用服务都是响应网络请求( IO 操作),经过简单的逻辑计算,然后进行数据库请求( IO 操作),那么假设只要 CPU 不闲着,IO 负载很可能会比 CPU 负载先用满。

    Node 如何做到让 CPU 不闲着?答:计算单线程执行,IO 多线程执行(异步),但计算可以不等着 IO 完成(异步非阻塞)。

    不调用任何 API ,纯进行 JS 计算,比如算斐波那契数列,1+2=3,2+3=5……这个只能单线程执行,算 2+3=5 的时候必须等着 1+2 出结果,只不过此时 CPU 并没有闲着而已。
    如果在计算出每一个数字的时候,把数字写到硬盘上,这个写硬盘的操作就是 IO 操作;
    假设没有异步非阻塞机制,应该是这样的:计算 1+2 ,得出 3 ,执行将 3 写入硬盘,等待写入完成,写入完成后计算 2+3……CPU 在等待的时候是闲着的,时间基本浪费在等待将 3 写入硬盘。
    现在 Node 给你了一个能力,就是你可以在向硬盘写入 3 的时候选择不等着它完成,直接继续算 2+3 ,这就相当于有 1 个线程在不停算斐波那契数列,额外还有多个线程帮你把每个结果存硬盘。

    回到题主的场景描述,Node 接收到一个请求之后,如果进行简单逻辑计算后就直接操作数据库( IO 操作)或应答( IO 操作)的话,可以选择不等着 IO 操作完成,继续处理下一个请求,等某个 IO 操作完成了就会回来调用后续的 JS 程序。

    但如果执行的是异常复杂的计算,比如视频转码,如果是在处理请求的线程里做的话,一定会抢占预期用于处理请求的 CPU 时间,导致请求“卡住”。不过你猜怎么着,Node 其实是提供了多线程 API ( Worker threads )和多进程 API ( Child process ),你完全可以像其他语言那样使用多线程和多进程来进行优化。除此之外 Node 还提供了面向 C/C++的 N-API 以及面向很多语言的 WebAssembly ,在需要极端计算性能的场景下不至于完全放弃 JS 技术栈。
    13Sl
        36
    13Sl  
       2022-01-21 12:51:09 +08:00
    在设计业务逻辑时, 一般不会把需要 1s 处理时间得超重型任务的触发时机留给外界触发, 这种一般在设计上就会定时或使用其他方法手动触发.
    node 在除非是在处理纯计算类的代码, 一般都会有时机切换到其他任务处理过程上(比较典型的切换代码标志就是 await), 所以一般来说很难产生像 1 楼所描述的处理请求按顺序等待的情况.
    Austaras
        37
    Austaras  
       2022-01-21 16:20:31 +08:00
    我觉得最好还是诚实一点:应对不了。。。
    zzlatan
        38
    zzlatan  
       2022-01-21 16:22:08 +08:00
    @libook 赞!学习到了。
    KouShuiYu
        39
    KouShuiYu  
       2022-01-21 17:43:36 +08:00
    首先:一个请求卡住了,后续其他请求并不会卡住,触发是第一个卡住的原因是因为( CPU 密集型计算)

    你可以试试
    先访问 http://127.0.0.1:3000/?t=10000 再访问 http://127.0.0.1:3000/?t=0 第二次结果是秒回的

    const http = require('http');
    const { URL } = require('url');
    const { setTimeout } = require('timers/promises');

    const hostname = '127.0.0.1';
    const port = 3000;

    const server = http.createServer(async (req, res) => {
    const t = new URL(`http://${hostname}:${port}${req.url}`).searchParams.get('t');
    // 模拟耗时操作
    await setTimeout(t);

    res.end(`Hello World:${t}`);
    });

    server.listen(port, hostname, () => {
    console.log(`Server running at http://${hostname}:${port}`);
    });
    coseylee
        40
    coseylee  
       2022-03-03 14:25:56 +08:00
    @KouShuiYu 如果将 await setTimeout(t) 换成 while(true) 的话,是无法接受并处理之后的请求。之所以 await setTimeout(t) 不会将主线程占用的原因是它属于定时器,是有特殊的调度处理的,不会占用主线程。
    KouShuiYu
        41
    KouShuiYu  
       2022-03-03 16:27:45 +08:00
    @coseylee 只有不是 CPU 密集型计算(比如从 1 循环加到一亿 )都不会卡着,你可以试试比如说请求、读文件等等

    const http = require('http');
    const {
    URL
    } = require('url');
    const {
    setTimeout
    } = require('timers/promises');

    const hostname = '127.0.0.1';
    const port = 3001;

    function fetch(url) {
    return new Promise((resolve) => {
    http.get(url, (res) => resolve(res));
    })
    }


    const server = http.createServer(async (req, res) => {
    const t = new URL(`http://${hostname}:${port}${req.url}`).searchParams.get('t');
    // 模拟耗时操作
    await fetch(`http://127.0.0.1:3000/?t=${t}`);

    res.end(`Hello World:${t}`);
    });

    server.listen(port, hostname, () => {
    console.log(`Server running at http://${hostname}:${port}`);
    });
    coseylee
        42
    coseylee  
       2022-03-04 09:18:21 +08:00
    @KouShuiYu 是的,这些都是异步操作,会在线程池处理。针对题主所问,如果长时间阻塞主线程,的确无法再接受请求进来。
    Makabaka01
        43
    Makabaka01  
       2022-06-19 10:24:05 +08:00
    如果你没有计算耗时的操作,Node 会在相当短的时间把控制权交给下一个请求,直到你的数据库任务、或是网络请求完成时才会继续阻塞程序
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3535 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 00:47 · PVG 08:47 · LAX 16:47 · JFK 19:47
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.