参考文章:断网故障时Mtop触发tomcat高并发场景下的BUG排查和修复(已被apache采纳)
给这篇文章的排查思路点个赞,排查过程很值得学习
NIO模式背景介绍
Tomcat共有三种连接器模式,BIO/NIO/APR,其中NIO是异步IO模式的简称。在默认的配置下,NIO的模式的实现模型如下:
- Acceptor线程:全局唯一,负责接受请求,并将请求放入Poller线程的事件队列。Accetpr线程在分发事件的时候,采用的Round Robin的方式来分发的
- Poller线程:官方的建议是每个处理器配一个,但不要超过两个,由于现在几乎都是多核处理器,所以一般来说都是两个。每个Poller线程各自维护一个事件队列(无上限),它的职责是从事件队列里面拿出socket,往自己的selector上注册,然后等待selector选择读写事件,并交给SocketProcessor线程去实际处理请求。
- SocketProcessor线程:Ali-tomcat的默认配置是250(参见server.xml里面的maxThreads),它是实际的工作线程,用于处理请求。
一个典型的请求处理过程
nio_model如图所示,是一个典型的请求处理过程。其中绿色代表线程,蓝色代表数据。
- Acceptor线程接受请求,从socketCache里面拿出socket对象(没有的话会创建,缓存的目的是避免对象创建的开销),
- Acceptor线程标记好Poller对象,组装成PollerEvent,放入该Poller对象的PollerEvent队列
- Poller线程从事件队列里面拿出PollerEvent,将其中的socket注册到自身的selector上,
- Poller线程等到有读写事件发生时,分发给SocketProcessor线程去实际处理请求
- SocketProcessor线程处理完请求,socket对象被回收,放入socketCache
到这里总结一下:
- Acceptor线程监听8080端口,发现有tcp请求要过来连接,那么内核在处理完该tcp的三次握手后,就会创建一个完整socket给到用户态。
- Acceptor通过轮询方式,依次把成功建立连接的socket封装成PollerEvent放到PollerEventQueue队列里面,并把该socket注册到Poller的多路复用器Selector里面,感兴趣的时间应该只有READ事件,毕竟Write事件是处理线程直接写到socket的,不走事件驱动的多路复用器。
- 这个时候所有已建立连接的socket都在各个Poller的多路复用器里面,等待着客户端把它们各自的请求参数数据写进来。如果某一个socket的请求数据完全写进来了,那么会触发poller的select()函数返回。这个时候Poller线程会把该socket封装成一个SocketProcessor的Runnable,提交给线程池处理。线程池的线程才是真正执行业务操作的,业务操作完成后,也是由它直接向socket写数据,不再经过事件驱动那么麻烦的处理流程了。
- SocketProcessor处理完请求,竟然还会把socket对象放入socketCache中,搞了个对象池。。。牛逼。
网友评论