坚持看完本文,我将从 CPU 执行流程开始,逐步探索 I/O 模型、回调、响应式编程、协程对高并发的意义。
一、CPU 执行流程程序的运行离不开操作系统,操作系统调度程序给 CPU 执行。CPU 负责运算,CPU 很快,一般来说,它能很快给出结果。
CPU 不停地算啊算,整个流程运转完美。但突然,他发现数据没了[思考]。这些数据要么在硬盘,要么在网络上的其他设备。CPU 只得停下来休息,并向总线(Bus)发出命令:“把数据给我取来!”,这就是 I/O(Input/Output) 等待。
CPU 现在就没事做了,能不能找点“算数”工作给它,让它能把实力发挥出来。
二、让 CPU 忙一点(I/O 模型)假设你看到这里手机突然卡了,手机刚买不敢强行重启,但又想看下面的内容,你有哪些选择?
我简单列了一下:
方案名
方案内容
阻塞 I/O
Blocking I/O
坚持不懈,盯着手机等它恢复
朴素非阻塞 I/O
Non-Blocking I/O
把手机放到旁边,去读书,不时看下恢复了没
I/O 复用
I/O MultiPlex
把手机交给网友盯着,不时问下网友,好了再去拿手机
事件驱动 I/O
Event-Driven I/O
手机恢复了自动给你发微信(你提前装了APP),再去拿手机
异步 I/O
Async I/O
手机恢复了自动飞到你手上(手机会魔法[鼓掌])
上面的方案也是 CPU 在 I/O 时能做的 5 种选择,即I/O 模型。
I/O 有两个基本步骤:
查询 I/O状态(Query I/O Status)检查其他设备是不是把数据送来了,有数据了才能拉数据。
从操作系统拉数据到程序(Fetch Data)阻塞 I/O 和朴素非阻塞 I/O 都完整地做了这两步,又是查状态又是拉数据,效率低。I/O 复用和事件驱动 I/O 则引入了“第三者”来管理 I/O 状态,I/O 复用的帮手能一次性管理大量的 I/O 请求,但却不必事件 I/O 更好。事件 I/O 有“绝招”,不需要程序自己去查状态 OK 了吗,I/O 好了能直接通知程序来拉数据。异步 I/O 则是真正的“扫地僧”,I/O 的任务自己全做完,数据“喂到嘴里”。
回到主题。CPU 这么快,要让它发挥全部实力,I/O 这样的慢操作就不能等(要非阻塞 I/O),而是 I/O 完成了自动“回去调用”(Callback)后续的业务逻辑。
三、回调与 I/O现在有一个需求,调用 Bing 接口搜索关键字并把结果渲染出来,用 JavaScript 实现:
响应式编程通过链式调用(Method Chaining)把处理逻辑前后串联起来,调用操作本身能包含逻辑判断,不再需要函数内嵌函数这种不直观的语法形态。但是响应式编程和传统的编程范式有很多区别,掌握它有较高的学习成本呢[捂脸]。
五、百分百资源利用率回到主题,做到 100% 资源利用率具体需要什么?“木桶效应”说得好,性能的上限取决于性能的短板。我们分情况来看:
对于计算密集型应用,CPU 统筹计算资源。短板就在 CPU,它的利用率长期在 100%。不换硬件,就没有优化的空间。
对于 I/O 密集型应用,程序和外部环境有太多的关联沟通,对 I/O 的依赖很高。但 I/O 相比 CPU 慢太多了,通常是 I/O 满载,CPU 却只用了七七八八。I/O 是瓶颈,但在分布式环境下,主机间沟通存在天生的不确定性,I/O 速度是肯定赶不上 CPU。
仔细分析 I/O 密集应用的执行流程。应用进程以线程作为代码逻辑执行的基本单元,但为了避免维护线程本身消耗过大,线程的数量会有固定的上限。这就是导致在阻塞 I/O 下,如果 I/O 过多(高并发),大部分线程就会处于 I/O 等待状态。没有线程,CPU 也没法工作。虽然利用率不高,但却无法处理新请求。
非阻塞 I/O 就是用来解决这个问题,把慢操作异步化。只要计算任务量还符合 I/O 密集型,总有线程资源能处理新的请求。
根据闲鱼的实践(详情见资料5),在高并发环境下,异步化能在响应时间降低 50% 的同时,使系统的吞吐量提升约 30%,而且 CPU 在 100% 满载前还能一直处理请求。作为对比,同步 I/O 在线程资源耗尽后,CPU 利用率只达到约 75%,程序也出现不可用的情况。
在高并发环境下,使用同步 I/O 的程序,线程资源很快就会耗尽。再多的请求就会诱导 CPU “停转”,使程序不能处理新请求,程序崩溃。有没有例外呢?能不能假设这样一种可能,线程可以没有上限一直新建,并且不会带来过大的线程维护成本。
能!协程就基本解决了这个问题。线程消耗大的主要原因是在于需要操作系统内核进行高成本的线程调度。但是协程把这些调度工作交给了用户程序本身,调度成本的下降带来了不设上限的“线程”。
协程正在走向我们,把我们从高难度的异步响应式编程拉出来。Go 语言有 Goroutine,JDK 21 也正式发布了虚拟线程。在不久的将来,高并发编程不再依赖这些“套娃”理念,编程又能回归到淳朴时代。
六、参考资料A brief history of select(2) — Idea of the dayJS线程和UI线程是同一个线程吗? - Sebastian·S·Pan - 博客园 (cnblogs.com)GitHub - AsyncHttpClient/async-http-client: Asynchronous Http and WebSocket Client library for Javareactor-core/docs/asciidoc/reactiveProgramming.adoc at main · reactor/reactor-core · GitHubRxJava在闲鱼系统吞吐量提升上的实践 - 掘金 (juejin.cn)相关教程
2024-06-15
2023-11-17
2024-09-29
2023-12-13
2024-10-11
2024-01-10
2024-10-30
2024-04-23
2024-06-09
2024-03-25
2024-11-18
2024-11-17
2024-11-17
2024-11-17
2024-11-16
2024-11-16
copyright © 2012-2024 纯净基地 m.czlaby.com 版权声明