如何使用socket进行java网络编程(六) - 肥兔子爱豆畜子
时间过得很快,一转眼已是3年后。个人的修炼不能停,接下来准备讨论一下课题:nio, nio2, apr, 符合servlet标准的基于tomcat等底层服务器的框架、如spring mvc, 基于netty底层服务器的异步非阻塞框架webflux。把对io的理解再提升一个层次。
服务器能连多少个连接,和实际能并发处理多少是两回事儿,现在的服务器如果配置过关、系统参数优化得当,是能同时连大量的连接的,比如上百万个。但连上来之后,如何处理就是大问题了。传统的bio采用的策略是一个连接就得分配一个线程去处理这个连接的read() wirte(),有1万个连接连上来就得分1万个线程,这显然扯淡。
nio可以实现各种不同的服务器,不能一概而论的认为使用nio实现的服务器就是如何如何的(比如异步的、比如非阻塞的)。以tomcat为例,1万连接使用200个线程处理,1万个连接连上来后,都注册到selector上,然后监听到来自这些个连接上的read、write事件,由poller线程(tomcat里一般是两个)轮询感知并把有事件发生的连接发给身后的工作线程池处理(比如1万个连接此时有100个连接发生了read事件,那将会有100个工作线程从池里出来去处理read),这样就实现了少量线程处理大量连接这样一个目的。但是我们发现,在工作线程接收了来自poller分过来的连接后,还是要自己去从连接里read(),wirte()的,这个过程是阻塞的。
tomcat在这里并没有利用接收过来的连接的socket.setBlocking(false)这个特性(指注册到selector的做法),而是当前工作线程不断的去循环调用read、write方法,直到读写完,这样整体来看,工作线程是阻塞的。(待确认) https://my.oschina.net/wangxindong/blog/1562957
2021/11/1偶然翻阅发现当时上面的理解和猜想有误:Tomcat NIO线程模型与IO方式分析 – 简书 (jianshu.com) 这里的核心问题在于servlet模型规定read/write body时的io是阻塞的,所以要找到方法使用非阻塞的socket去模拟阻塞io,tomcat用的是CountDownLatch做读写锁实现的,能读写则读写,不能读写就注册事件到BlockingPoller上、然后线程latch.await()阻塞,读写就绪了就由BlockingPoller进行latch.count()唤醒阻塞的线程。
解决办法是来使用select的方法,在读写事件未就绪(中途没数据可读或咱无法向连接写入)的时候让工作线程能够解放出来去搞别的,让工作线程一直不停跑,不阻塞、保持“繁忙”。
这样工作线程就从work thread变成了work loop 。(找到了netty的影子)
2020-9-22 接下来的计划是这样的,第一阶段先得把这个老外的demo吃透:https://github.com/jjenkov/java-nio-server 一个异步非阻塞的小服务器
为此,基本的大的结构是能理解的,细节部分比如buffer、消息的读写还没完全搞清楚,所以先得看这哥们的另一篇文章,
《Java resizable Array》http://tutorials.jenkov.com/java-performance/resizable-array.html
第二阶段可以使用基于以上思想的现成的轮子改造一个已有的应用,提高其性能,io密集型应用:应用网关、日志收集应用。
spring-web
这个模块提供了开发反应式web应用的基础支撑:- 对于服务端的request处理提供了两个级别的支持:
WebHandler
API: 稍高一级的用于请求处理的通用web API,用来构建注解controller和函数式endpoint这类具体的编程模型。- 对于客户端,有一个基本的
ClientHttpConnector
contract 执行具有非阻塞I/O和反应流背压的HTTP请求,以及Reactor Netty 和Reactive Jetty HttpClient的适配器。 而应用层真正使用的上层封装库WebClient 构建在这个基本的contract之上。
- 在客户端和服务端,由codecs 负责request和response的序列化和反序列化
WebFluxConfigurationSupport
这个类里边