有一个问题想问问大家, 就是 Netty 是异步 I/O,也就是不会在 I/O 的时候不会阻塞,但是 I/O 本身还是耗时的操作,操作系统还是应该需要耗时处理, 效率的提升到底实在什么地方

2019-11-17 11:27:03 +08:00
 iXingo
我就是想不通的地方就是, 所以 Netty 高效的地方就是连接和分发吗,真正 I/O 耗时的处理还是需要底层操作系统慢慢处理, 是这样吗
7691 次点击
所在节点    Java
56 条回复
outoftimeerror
2019-11-18 01:06:32 +08:00
业务操作阻塞了,netty 用了也没什么意义,比如你每个请求过来再用 jdbc 操作数据库,那效率依然不高。
除非你全部非阻塞,就像 vert.x 用了异步的方式 vertx-jdbc-client 操作数据库。
全部异步的难点在于,很多业务其实是需要同步的,而且代码写起来反人类,当然函数式编程倒是提供了优雅的解决方案:future monad。
纯异步反人类,java 的线程太重,所以我觉得更实用的解决方案是 goroutine+channel。
by73
2019-11-18 01:17:54 +08:00
我所知道的(碍于知识水平可能有误),阻塞与非阻塞是针对 I/O 传输到内存这部分而言,阻塞 I/O 在传输时全程需要 CPU 干预;非阻塞 I/O 在传输时 CPU 可以干别的事,直到像 DMA 这样的控制器给 CPU 发个完成的中断,再调用到操作系统的中断处理程序。

而同步和异步则是针对进程 /线程而言的,假如调用 Files.readAllLines(),那么当前线程就会处于挂起状态,直到上面的阻塞 /非阻塞 I/O 把数据传递到内存之后才被恢复,这个过程其实对 CPU 没有影响,只是对你的线程有影响;异步就是当前线程不再等待 I/O 传输的完成,只是发起一个 I/O 请求和请求完之后要做的事情,然后交给另一个线程去负责(这样阻塞也就只会阻塞另外那个线程,不会影响当前线程的执行)。异步本质上可以理解为多线程。

所以综上,回答题主的问题,非阻塞 I/O 提升的时间主要是数据传输到内存的这段 CPU 时间,这样 CPU 就能处理其他的进程;另外,异步 I/O 的价值在于“小而多”的请求,典型例子就是 HTTP,因为 I/O 特别多,如果每个线程都要去等待这些 I/O 完成,肯定会浪费其他非 I/O 的时间,将这些 I/O 交给另一个线程,自己就“解放了”;而对于 I/O 密集的任务来说,异步 I/O 没啥大优势,当 I/O 占很大一部分时,非 I/O 部分的运行时间就可以忽略了,基本上都是在排队阻塞、传输、调回调函数,整个系统的并发度也就降到了跟同步差不多的水平(大家都是在等 I/O )。

(注意这里的所有解释都是针对 I/O 传输的过程,因为等待 I/O 到来这件事必须要等待,像 Socket.accept() 或者 listen() 都是要强行阻塞的)

(熬夜过程可能脑子不太清醒,见谅,也希望能指出错误)
by73
2019-11-18 01:45:21 +08:00
@by73 噢对了,Java/Netty 的 NIO 指的是 New I/O,具体而言指的是异步 I/O,而不是 Non-blocking,因为 I/O 阻塞的话语权不在它们那。
des
2019-11-18 08:36:07 +08:00
打个比方,好比如双十一代购
同步阻塞就是,你买了东西,等快递到了再去做其他事情,等待的时候啥也不做
非阻塞就是,你买了东西,你自己过一会去看一下到了没,过一会看一下
异步就是,买了东西,人家短信通知你,你可以做其他事情去。其中还有水平和边缘触发的区别,也很简单,一个是你有快递了通知你一下,另一个是你有快递了一直通知你
至于省出来的时间当然是,你在那里瞎等的时间或者自己去查状态的时间。
什么,你说自己查状态也不费时间?那假如你有 1w 个呢,每一个都要查一遍呢?

另外这是建立在,你的工作本身就是处理这些快递的前提下的,也就是 io 密集,类似 curd (怎么感觉像是在黑 curd

如果是 cpu 密集,那就完全不一样了。
你需要花很多时间去处理相关的东西(与 io 无关的),比方说,快递到了要组装好才能再给代购的人,这期间你是没法处理其他快递的

所以问题就很明显了,省下来时间就是在那里干等白白流逝的时间,本来你还能接更多单的。
至于说人家快递发的慢,那你也没办法是不是(指 io 本身慢以及 io 处理对方慢),但是你还是能服务到更多人是不是?


同步阻塞的另一种方案是,开多个线程去等,就好比限购,你只能找多个人一起买,然后每个人还是慢慢等
这个好像在客户端程序开发多见一点,毕竟异步写起来还是比较复杂的
des
2019-11-18 08:40:32 +08:00
@des
最近死亡搁浅看多了,第一个想到的就是快递
可能有些东西不够准确,但是基本就是这些原理了
如果说的有不对的地方,希望大家能指正
micean
2019-11-18 10:25:05 +08:00
@iXingo

阻塞的业务放在放在业务线程,别放在 io 线程
w0000
2019-11-18 10:28:17 +08:00
对于这种优化应该从两种情况来看,第一种是少量连接,每次请求的数据多,第二种是大量连接,每次请求的数据量小。
netty 对第二种情况的优化是比较大的,用少量线程去轮训处理连接,但是对第一种情况是没有优化的,甚至会有轮训的性能损失。
qiyuey
2019-11-18 10:53:14 +08:00
配合 Kotlin Coroutines 或者 Reactive 效果更佳
iXingo
2019-11-18 12:40:05 +08:00
@by73 感谢您的回答, 还有其实异步回调函数就是把原本主线程等待 I/O 连接 /传输过程之后的操作放在其他线程上, 其实本质上就是多线程的一种操作,是否可以这么理解
iXingo
2019-11-18 12:42:14 +08:00
@w0000 请问 netty 对于大量连接和数据量也大的情况,其实是可以并发处理连接,但是数据量受限于操作系统和物理限制,也是没法做更大的优化,是否可以这么理解
iXingo
2019-11-18 12:51:44 +08:00
@des 如果按照您这样说的, Netty 做的就是包装了 JDK 的 NIO 方法, 相当于一个菜鸟驿站的快递接收窗口(可能不恰当), 原先你要需要对每个快递进行等待, 现在你每次只要和这个窗口进行对接, 你每次让他帮你查询一下(底层调用一下 select()方法), 你知道有哪些快递已经到了, 你就只要对已经到达的快递进行处理. 可以这么理解吗
by73
2019-11-18 13:18:27 +08:00
@iXingo 对,本质上就是让另一个线程去处理。至少 java.nio.channels 是这样做的,如果你带了一个回调函数,那么

> The completion handler for an I/O operation initiated on a channel bound to a group is guaranteed to be invoked by one of the pooled threads in the group.

(摘自 JavaDoc ),就是说你的回调函数会在 I/O 结束之后被异步处理的线程调用。
a570295535
2019-11-18 13:35:19 +08:00
@des 写的很不错👍
iXingo
2019-11-18 15:29:02 +08:00
@by73 好的,感谢
iIli1iIliIllLiL
2019-11-22 14:05:49 +08:00
需要了解下异步与同步非阻塞的区别
mazai
2019-12-02 19:03:24 +08:00
可以去看下 Reactor 模式,看完以后你就会明白了,且 netty 对 NIO 进行了改进和增强,以及封装了一些协议,实现了 zero-copy 等,这就是他强大的地方。吊打其他同类型框架。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://tanronggui.xyz/t/620346

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX