1- # Table of Contents
2-
1+ # 目录
32 * [ 一、I/O复用模型解读] ( #一、io复用模型解读 )
43 * [ 二、TOMCAT对IO模型的支持] ( #二、tomcat对io模型的支持 )
54 * [ 三、TOMCAT中NIO的配置与使用] ( #三、tomcat中nio的配置与使用 )
98 * [ 七、关于性能] ( #七、关于性能 )
109 * [ 八、总结] ( #八、总结 )
1110
12-
1311本文转自:http://www.sohu.com/a/203838233_827544
1412
1513本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到我的仓库里查看
3735
3836Tomcat的NIO是基于I/O复用来实现的。对这点一定要清楚,不然我们的讨论就不在一个逻辑线上。下面这张图学习过I/O模型知识的一般都见过,出自《UNIX网络编程》,I/O模型一共有阻塞式I/O,非阻塞式I/O,I/O复用(select/poll/epoll),信号驱动式I/O和异步I/O。这篇文章讲的是I/O复用。
3937
40- ![ ] ( http://5b0988e595225.cdn.sohucs.com/images/20171112/0ab0b70be5a64b0e9014de867235da73.png )
41-
38+ ![ ] ( https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104442.png )
4239IO复用.png
4340
4441这里先来说下用户态和内核态,直白来讲,如果线程执行的是用户代码,当前线程处在用户态,如果线程执行的是内核里面的代码,当前线程处在内核态。更深层来讲,操作系统为代码所处的特权级别分了4个级别。
@@ -51,13 +48,12 @@ IO复用.png
5148
5249上面提到的网络事件有连接就绪,接收就绪,读就绪,写就绪四个网络事件。I/O复用主要是通过Selector复用器来实现的,可以结合下面这个图理解上面的叙述。
5350
54- ![ ] ( http://5b0988e595225.cdn.sohucs.com/images/20171112/3a18d371c9c848479ca39d8eb4e57ce4.jpeg )
55-
51+ ![ ] ( https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104504.png )
5652Selector图解.png
5753
5854## 二、TOMCAT对IO模型的支持
5955
60- ![ ] ( http ://5b0988e595225.cdn.sohucs .com/images/20171112/983c122c2a924e01b9f898279e2bd0b5.jpeg )
56+ ![ ] ( https ://java-tutorial.oss-cn-shanghai.aliyuncs .com/20230405104517.png )
6157
6258tomcat支持IO类型图.png
6359
@@ -71,7 +67,7 @@ tomcat从6以后开始支持NIO模型,实现是基于JDK的java.nio包。这
7167
7268## 四、NioEndpoint组件关系图解读
7369
74- ![ ] ( http ://5b0988e595225.cdn.sohucs .com/images/20171112/4e1a924a60974c76ab5115007562e563.jpeg )
70+ ![ ] ( https ://java-tutorial.oss-cn-shanghai.aliyuncs .com/20230405104543.png )
7571
7672tomcatnio组成.png
7773
@@ -87,8 +83,7 @@ LimitLatch是连接控制器,它负责维护连接数的计算,nio模式下
8783
8884## 五、NioEndpoint执行序列图
8985
90- ![ ] ( http://5b0988e595225.cdn.sohucs.com/images/20171112/d69c2ef5110d4706aa7284c616d62927.jpeg )
91-
86+ ![ ] ( https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104621.png )
9287tomcatnio序列图.png
9388
9489在下一小节NioEndpoint源码解读中我们将对步骤1-步骤11依次找到对应的代码来说明。
@@ -99,8 +94,7 @@ tomcatnio序列图.png
9994
10095无论是BIO还是NIO,开始都会初始化连接限制,不可能无限增大,NIO模式下默认是10000。
10196
102- ![ ] ( http://5b0988e595225.cdn.sohucs.com/images/20171112/332338f6d2c8488e9b35cd4fd76f078a.png )
103-
97+ ![ ] ( https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104637.png )
10498** 6.2、步骤解读**
10599
106100下面我们着重叙述跟NIO相关的流程,共分为11个步骤,分别对应上面序列图中的步骤。
@@ -111,46 +105,41 @@ tomcatnio序列图.png
111105
112106Socket,NIO下这里返回的是SocketChannel。
113107
114- ![ ] ( http://5b0988e595225.cdn.sohucs.com/images/20171112/ca01395590944a35b4717b15c984849e.png )
115-
108+ ![ ] ( https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104855.png )
116109** 步骤2** :启动接收线程
117110
118- ![ ] ( http://5b0988e595225.cdn.sohucs.com/images/20171112/e7c40dc6f84b48f4915ca854c2a3b2cc.png )
119111
120112** 步骤3** :ServerSocketChannel.accept()接收新连接
121113
122- ![ ] ( http ://5b0988e595225.cdn.sohucs .com/images/20171112/50ddbfe042514ba29a8bcd606b125f76.jpeg )
114+ ![ ] ( https ://java-tutorial.oss-cn-shanghai.aliyuncs .com/20230405104937.png )
123115
124116** 步骤4** :将接收到的链接通道设置为非阻塞
125117
126118** 步骤5** :构造NioChannel对象
127119
128120** 步骤6** :register注册到轮询线程
129121
130- ![ ] ( http ://5b0988e595225.cdn.sohucs .com/images/20171112/f3f5130bcf7a4348bc9752bd009c1b72 .png)
122+ ![ ] ( https ://java-tutorial.oss-cn-shanghai.aliyuncs .com/20230405104957 .png)
131123
132124** 步骤7** :构造PollerEvent,并添加到事件队列
133125
134- ![ ] ( http ://5b0988e595225.cdn.sohucs .com/images/20171112/65de3c5cebea42c59ff19e8a168cf406 .png)
126+ ![ ] ( https ://java-tutorial.oss-cn-shanghai.aliyuncs .com/20230405105014 .png)
135127
136128** 步骤8** :启动轮询线程
137129
138- ![ ] ( http ://5b0988e595225.cdn.sohucs .com/images/20171112/5c097feaa2324a2da082a81287f9e862 .png)
130+ ![ ] ( https ://java-tutorial.oss-cn-shanghai.aliyuncs .com/20230405105027 .png)
139131
140132** 步骤9** :取出队列中新增的PollerEvent并注册到Selector
141133
142- ![ ] ( http ://5b0988e595225.cdn.sohucs .com/images/20171112/8ac5e80b9aa84f57b4c3f7ecd4e2ea1d .png)
134+ ![ ] ( https ://java-tutorial.oss-cn-shanghai.aliyuncs .com/20230405105043 .png)
143135
144136** 步骤10** :Selector.select()
145137
146- ![ ] ( http://5b0988e595225.cdn.sohucs.com/images/20171112/82d20de2a4614eab9fe14e58234db552.jpeg )
147-
148- ![ ] ( http://5b0988e595225.cdn.sohucs.com/images/20171112/75641a4b8d444f8ab7a033bee9497c2b.png )
138+ ![ ] ( https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105057.png )
149139
140+ ![ ] ( https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105110.png )
150141** 步骤11** :根据选择的SelectionKey构造SocketProcessor提交到请求处理线程
151-
152- ![ ] ( http://5b0988e595225.cdn.sohucs.com/images/20171112/b8ec9bbcbdcd4088841741acb20152f8.png )
153-
142+ ![ ] ( https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105154.png )
154143** 6.3、NioBlockingSelector和BlockPoller介绍**
155144
156145上面的序列图有个地方我没有描述,就是NioSelectorPool这个内部类,是因为在整体理解tomcat的nio上面在序列图里面不包括它更好理解。
@@ -159,13 +148,13 @@ Socket,NIO下这里返回的是SocketChannel。
159148
160149以执行servlet后,得到response,往socket中写数据为例,最终写的过程调用NioBlockingSelector的write方法。代码如下:
161150
162- ![ ] ( http ://5b0988e595225.cdn.sohucs .com/images/20171112/4ac6ac8fdb0a4b24bdcb646a09cbb13e.jpeg )
151+ ![ ] ( https ://java-tutorial.oss-cn-shanghai.aliyuncs .com/20230405105223.png )
163152
164- ![ ] ( http ://5b0988e595225.cdn.sohucs .com/images/20171112/549283c8059b4e3c8798077b654dc3d1 .png)
153+ ![ ] ( https ://java-tutorial.oss-cn-shanghai.aliyuncs .com/20230405105237 .png)
165154
166155也就是说当socket.write()返回0时,说明网络状态不稳定,这时将socket注册OP_WRITE事件到辅Selector,由BlockPoller线程不断轮询这个辅Selector,直到发现这个socket的写状态恢复了,通过那个倒数计数器,通知Worker线程继续写socket动作。看一下BlockSelector线程的代码逻辑:
167156
168- ![ ] ( http ://5b0988e595225.cdn.sohucs .com/images/20171112/626817ca25fc4a439dbf5d23c2c08d0e.jpeg )
157+ ![ ] ( https ://java-tutorial.oss-cn-shanghai.aliyuncs .com/20230405105251.png )
169158
170159使用这个辅Selector主要是减少线程间的切换,同时还可减轻主Selector的负担。
171160
@@ -175,13 +164,9 @@ Socket,NIO下这里返回的是SocketChannel。
175164
176165NIO的优势更在于用少量的线程hold住大量的连接。还有一点,我们在压测的过程中,遇到在NIO模式下刚开始的一小段时间内容,会有错误,这是因为一般的压测工具是基于一种长连接,也就是说比如模拟1000并发,那么同时建立1000个连接,下一时刻再发送请求就是基于先前的这1000个连接来发送,还有TOMCAT的NIO处理是有POLLER线程来接管的,它的线程数一般等于CPU的核数,如果一瞬间有大量并发过来,POLLER也会顿时处理不过来。
177166
178- ![ ] ( http://5b0988e595225.cdn.sohucs.com/images/20171112/7d62f8792e1c41f090d945c755bba6c7.jpeg )
179-
180- 压测1.jpeg
181-
182- ![ ] ( http://5b0988e595225.cdn.sohucs.com/images/20171112/e8ec450685d64e5785db44a0bb60660c.jpeg )
167+ ![ ] ( https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105304.png )
183168
184- 压测2.jpeg
169+ ![ ] ( https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105318.png )
185170
186171## 八、总结
187172
0 commit comments