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

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

  •  1
     
  •   leebs · 119 天前 · 10178 次点击
    这是一个创建于 119 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

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

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

    nodejs 也是同理,当然有些数据库之类的写入,还是尽量用消息队列处理。
    ETiV
        19
    ETiV  
       119 天前 via iPhone
    需要人灵活地使用工具

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

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

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

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

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

    当然 2 这种方法比较时候低并发或者低概率触发的情况
    如果真的高并发情况大概率 还是要用 1 来处理
    monkeyWie
        31
    monkeyWie  
       118 天前   ❤️ 1
    我服了,CPU 密集和 IO 密集都不懂吗,在 JS 里一切 IO 都是异步的,不可能会卡,除非是 CPU 密集运算
    juzisang
        32
    juzisang  
       118 天前
    不知道现在前端 SSR 框架算不算 CPU 密集型计算,还有 node 里运行 marked md2html 都会卡一下,多了估计会影响
    keepeye
        33
    keepeye  
       118 天前
    cpu 密集型就多搞一些进程啊 这是问题吗
    jguo
        34
    jguo  
       118 天前
    别拿 java 和浏览器 js 的概念去套 node
    libook
        35
    libook  
       118 天前   ❤️ 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  
       118 天前
    在设计业务逻辑时, 一般不会把需要 1s 处理时间得超重型任务的触发时机留给外界触发, 这种一般在设计上就会定时或使用其他方法手动触发.
    node 在除非是在处理纯计算类的代码, 一般都会有时机切换到其他任务处理过程上(比较典型的切换代码标志就是 await), 所以一般来说很难产生像 1 楼所描述的处理请求按顺序等待的情况.
    Austaras
        37
    Austaras  
       118 天前
    我觉得最好还是诚实一点:应对不了。。。
    zzlatan
        38
    zzlatan  
       118 天前
    @libook 赞!学习到了。
    KouShuiYu
        39
    KouShuiYu  
       118 天前
    首先:一个请求卡住了,后续其他请求并不会卡住,触发是第一个卡住的原因是因为( 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  
       77 天前
    @KouShuiYu 如果将 await setTimeout(t) 换成 while(true) 的话,是无法接受并处理之后的请求。之所以 await setTimeout(t) 不会将主线程占用的原因是它属于定时器,是有特殊的调度处理的,不会占用主线程。
    KouShuiYu
        41
    KouShuiYu  
       77 天前
    @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  
       76 天前
    @KouShuiYu 是的,这些都是异步操作,会在线程池处理。针对题主所问,如果长时间阻塞主线程,的确无法再接受请求进来。
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1400 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 18:14 · PVG 02:14 · LAX 11:14 · JFK 14:14
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.