【课程16】tomcat...共享.xmind47.6KB
【课程16】tomcat...流程.xmind0.6MB
【课程16】tomcat...组件.xmind0.6MB
【课程16】tomcat...架构.xmind0.9MB
【课程16】手写一...mcat.xmind0.1MB
【课程16预习】tom...架构.xmind0.6MB
课程目标:
讲课顺序
tomcat维基百科
Tomcat是由Apache软件基金会下属的Jakarta项目开发的一个Servlet容器,按照Sun Microsystems提供的技术规范,实现了对Servlet和JavaServer Page(JSP)的支持,并提供了作为Web服务器的一些特有功能,如Tomcat管理和控制平台、安全域管理和Tomcat阀等。
由于Tomcat本身也内含了一个HTTP服务器,它也可以被视作一个单独的Web服务器。
http server与tomcat
划重点:
- http server与application server的不一样在哪?
严格的来说,Apache/Nginx 应该叫做「HTTP Server」;而 Tomcat 则是一个「Application Server」,或者更准确的来说,是一个「Servlet/JSP」应用的容器。
一个 HTTP Server 关心的是 HTTP 协议层面的传输和访问控制,所以在 Apache/Nginx 上你可以看到代理、负载均衡等功能。客户端通过 HTTP Server 访问服务器上存储的资源(HTML 文件、图片文件等等)。一个 HTTP Server 始终只是把服务器上的文件如实的通过 HTTP 协议传输给客户端。
对于 Tomcat 来说,就是需要提供 JSP/Sevlet 运行需要的标准类库、Interface 等。为了方便,应用服务器往往也会集成 HTTP Server 的功能,但是不如专业的 HTTP Server 那么强大,所以应用服务器往往是运行在 HTTP Server 的背后,执行应用,将动态的内容转化为静态的内容之后,通过 HTTP Server 分发到客户端。
HTTP服务器本质上也是一种应用程序——它通常运行在服务器之上,绑定服务器的IP地址并监听某一个tcp端口来接收并处理HTTP请求。
Tomcat运行在JVM之上,它和HTTP服务器一样,绑定IP地址并监听TCP端口,同时还包含以下指责:
- 与Servlet程序合作处理HTTP请求——根据HTTP请求生成HttpServletRequest对象并传递给Servlet进行处理,将Servlet中的HttpServletResponse对象生成的内容返回给浏览器。
tomcat的整体架构
整体架构
划重点:
Tomcat中最顶层的容器是Server,代表着整个服务器,从上图中可以看出,一个Server可以包含至少一个Service,用于具体提供服务。
Service主要包含两个部分:Connector和Container。从上图中可以看出 Tomcat 的心脏就是这两个组件,他们的作用如下:
- 1、Connector用于处理连接相关的事,并提供与Request和Response相关的转化;
- 2、Container用于封装和管理Servlet,以及具体处理Request请求;
其他组件说明:
- Jasper:负责jsp页面的解析,jsp属性的验证,同时负责将jsp动态转换为java代码并编译成class。
- Naming:资源管理,负责数据库连接池、EJB、mail等通过JDNI获取的内容。
小结:
(1)Tomcat中只有一个Server,一个Server可以有多个Service,一个Service可以有多个Connector和一个Container;
(2)Server掌管着整个Tomcat的生死大权;
(4)Service 是对外提供服务的;
(5)Connector用于接受请求并将请求封装成Request和Response来具体处理;
(6)Container用于封装和管理Servlet,以及具体处理request请求;
两大组件 --- Connector
划重点:
基本功能
一个Connecter将在某个指定的端口上侦听客户请求,接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 Response 对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理Engine(Container中的一部分),从Engine出获得响应并返回客户。
Tomcat中有两个经典的Connector,
HTTP/1.1 Connector在端口8080处侦听来自客户Browser的HTTP请求,AJP/1.3 Connector在端口8009处侦听其他Web Server(其他的HTTP服务器)的Servlet/JSP请求。
Connector 最重要的功能就是接收连接请求然后分配线程让 Container 来处理这个请求,所以这必然是多线程的,多线程的处理是 Connector 设计的核心。
Connector 中具体是用ProtocolHandler 来处理请求的,不同的ProtocolHandler 代表不同的连接类型,比如, Http11Protocol 使用的是普通Socket 来连接的, Http 11 NioProtocol 使用的是NioSocket 来连接的。
ProtocolHandler 里面有3 个非常重要的组件: Endpoint 、Processor 和Adapter。
- Endpoint用于处理底层Socket 的网络连接,
- AsyncTimeout 用于检查异步request 的超时
- Handler 用于处理接收到的Socket,在内部调用了Processor 进行处理。
- Processor 用于将Endpoint 接收到的Socket 封装成Request,
- Adapter 用于将封装好的Request 交给Container 进行具体处理。
也就是说Endpoint用来实现TCP/IP 协议, Processor 用来实现HTTP 协议, Adapter 将请求适配到Servlet 容器进行具体处理。
优化方向
在使用tomcat时,经常会遇到连接数、线程数之类的配置问题,就是在这里做优化。 可以说,Servlet容器处理请求,是需要Connector进行调度和控制的,Connector是Tomcat处理请求的主干 ,因此Connector的配置和使用对Tomcat的性能有着重要的影响。
优化包括NIO/BIO模式、线程池、连接数等。
优化一:指定protocol -- BIO、NIO、NIO2,APR。
如果没有指定protocol,则使用默认值HTTP/1.1,其含义如下:
- 在Tomcat7中,自动选取使用BIO或APR(如果找到APR需要的本地库,则使用APR,否则使用BIO);
- 在Tomcat8中,自动选取使用NIO或APR(如果找到APR需要的本地库,则使用APR,否则使用NIO)。
优化二:3个参数:acceptCount、maxConnections、maxThreads
当客户端向服务器发送请求时,如果客户端与OS 完成三次握手建立了连接,则OS 将该连接放入accept 队列。
对应三个节点,请求进入accept队列中,请求获取到链接,请求获得线程处理请求。
<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" acceptCount="2" maxConnections="10" maxThreads="2"
connectionTimeout="20000"
redirectPort="8443" />
accept队列的长度;当accept队列中连接的个数达到acceptCount时,队列满,进来的请求一律被拒绝。默认值是100。
acceptCount的设置,与应用在连接过高情况下希望做出什么反应有关系。如果设置过大,后面进入的请求等待时间会很长;如果设置过小,后面进入的请求立马返回connection refused。
Tomcat在任意时刻接收和处理的最大连接数。当Tomcat接收的连接数达到maxConnections时,Acceptor线程不会读取accept队列中的连接;这时accept队列中的线程会一直阻塞着,直到Tomcat接收的连接数小于maxConnections。如果设置为-1,则连接数不受限制。
maxConnections的设置与Tomcat的运行模式有关。
- 如果tomcat使用的是BIO,那么maxConnections的值应该与maxThreads一致;
- 如果tomcat使用的是NIO,那么类似于Tomcat的默认值,maxConnections值应该远大于maxThreads。
请求处理线程的最大数量。默认值是200(Tomcat7和8都是的)。如果该Connector绑定了Executor,这个值会被忽略,因为该Connector将使用绑定的Executor,而不是内置的线程池来执行任务。
优化三:线程池Executor
Executor元素代表Tomcat中的线程池,可以由其他组件共享使用;要使用该线程池,组件需要通过executor属性指定该线程池。
<Executor name="tomcatThreadPool" namePrefix ="catalina-exec-" maxThreads="150" minSpareThreads="4" />
<Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" acceptCount="1000" />
- maxThreads:线程池中最大活跃线程数,默认值200(Tomcat7和8都是)
- minSpareThreads:线程池中保持的最小线程数,最小值是25
- maxIdleTime:线程空闲的最大时间,当空闲超过该值时关闭线程(除非线程数小于minSpareThreads),单位是ms,默认值60000(1分钟)
- threadPriority:线程优先级,默认值5
- namePrefix:线程名字的前缀,线程池中线程名字为:namePrefix+线程编号
linux下的一些有用命令
netstat命令是一个监控TCP/IP网络的非常有用的工具
#查看连接情况
netstat -nat | grep 8080
1、LISTEN
服务侦听中(LISTEN)状态。
2、ESTABLISHED
ESTABLISHED的意思是建立连接。表示两台机器正在通信。
3、CLOSE_WAIT
对方主动关闭连接或者网络异常导致连接中断,这时我方的状态会变成CLOSE_WAIT 此时我方要调用close()来使得连接正确关闭
4、TIME_WAIT
我方主动调用close()断开连接,收到对方确认后状态变为TIME_WAIT。
#获取tomcat的pid
ps -ef | grep tomcat
#查看该进程内有多少个线程
#其中,nlwp含义是number of light-weight process。
ps -o nlwp 32283
#ps -eLo pid ,stat可以找出所有线程,并打印其所在的进程号和线程当前的状态
#两个grep命令分别筛选进程号和线程状态;
#wc统计个数
ps -eLo pid,stat | grep 32283| grep running | wc -l
(pid是32283)
(该进程内有32个线程)
(查看进程的运行情况,SL表示空闲)
(筛选出运行中的线程,结果是0)
两大组件 --- Container
划重点:
- container的每一个组件分别代表着项目的那些部分?
组成部分
Container用于封装和管理Servlet,以及具体处理Request请求,在Connector内部包含了4个子容器
4个子容器的作用分别是:
(1)Engine:引擎,用来管理多个站点,一个Service最多只能有一个Engine;
(2)Host:代表一个站点,也可以叫虚拟主机,通过配置Host就可以添加站点;
(3)Context:代表一个应用程序,对应着平时开发的一套程序,或者一个WEB-INF目录以及下面的web.xml文件;
(4)Wrapper:每一Wrapper封装着一个Servlet;
tomcat的基本工作流程
划重点
如何确定请求由谁处理?
当请求被发送到Tomcat所在的主机时,如何确定最终哪个Web应用来处理该请求呢?
(1)根据协议和端口号选定Service和Engine
Service中的Connector组件可以接收特定端口的请求,因此,当Tomcat启动时,Service组件就会监听特定的端口。在第一部分的例子中,Catalina这个Service监听了8080端口(基于HTTP协议)和8009端口(基于AJP协议)。当请求进来时,Tomcat便可以根据协议和端口号选定处理请求的Service;Service一旦选定,Engine也就确定。
通过在Server中配置多个Service,可以实现通过不同的端口号来访问同一台机器上部署的不同应用。
(2)根据域名或IP地址选定Host
Service确定后,Tomcat在Service中寻找名称与域名/IP地址匹配的Host处理该请求。如果没有找到,则使用Engine中指定的defaultHost来处理该请求。在第一部分的例子中,由于只有一个Host(name属性为localhost),因此该Service/Engine的所有请求都交给该Host处理。
(3)根据URI选定Context/Web应用
这一点在Context一节有详细的说明:Tomcat根据应用的 path属性与URI的匹配程度来选择Web应用处理相应请求,这里不再赘述。
(HTTP请求过程)
Tomcat Server处理一个HTTP请求的过程
1、用户点击网页内容,请求被发送到本机端口8080,被在那里监听的Coyote HTTP/1.1 Connector获得。
2、Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的回应。
3、Engine获得请求localhost/test/index.jsp,匹配所有的虚拟主机Host。
4、Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机),名为localhost的Host获得请求/test/index.jsp,匹配它所拥有的所有的Context。Host匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为“ ”的Context去处理)。
5、path=“/test”的Context获得请求/index.jsp,在它的mapping table中寻找出对应的Servlet。Context匹配到URL PATTERN为*.jsp的Servlet,对应于JspServlet类。
6、构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet()或doPost().执行业务逻辑、数据存储等程序。
7、Context把执行完之后的HttpServletResponse对象返回给Host。
8、Host把HttpServletResponse对象返回给Engine。
9、Engine把HttpServletResponse对象返回Connector。
10、Connector把HttpServletResponse对象返回给客户Browser。
一些相关图
(配置文件server.xml)
(tomcat的组件启动流程图)
手写一个迷你tomcat
代码作者:张丰哲 https://www.jianshu.com/p/dce1ee01fb90
编写一个迷你tomcat
tomcat是一个专门处理servlet的web容器。前提需要了解tomcat的工作原理与流程。
一般容器处理流程是这样的,客户端发起请求,web容器接受请求,处理请求,然后把得到结果返回给客户端。
所以提取要点如下:
因此至少需要以下几个类
以上可以说我们已经设计出了一个超级简单的tomcat,但是这个tomcat有点局限性,只有一个servert, 也就是说,这个tomcat只能处理一个url,为了让服务器能够处理多个servlet,我们必须抽象封装servlet。 请求url与servelt是一一对应关系,所以,我们可以通过一个map把这些关系在启动过程中就初始化加载进来, 因此我们可以封装一个ServletMapping类用于封装映射关系, 然后初始化的话可以放在一个config类中统一管理(真实的tomcat是直接在web.xml配置映射关系) 因此又多了两个类
- ServletMapping 封装url与Servlet的对应关系
- ServletMappingConfig 配置项目的Servlet与Url关系,用于初始化
然后,现在可以通过url找到对应的servlet了,每个Servrt的结构应该一直,因此抽象一个MyServlet类,固定处理的方法是service(); 后面添加业务处理的时候都要继承这个MyServlet抽象类。比如我要加一个HelloWorldServlet,对应url是"/hello", 我就需要显示MyServlet抽象类,编写业务逻辑,然后在ServletMappingConfig中配置映射关系。
现在好了,我编写了很多url对应的servelt,如果找不到url,我应该抛出404错误,这个我们先跳过。 当我获取到url对应的servelt名称之后,现在又有一个问题来了,我如何去运行这个servlet的service()业务代码呢? 同学们可能说直接new HelloWorldServlet().service()不就行了么,这里涉及到一个问题,因为后面业务越来越多,需要的servelt也越来越多, 所以不可能全部通过一个个去判断然后new对象,这时候我们自然想到可以通过反射方式获取servlet实例,然后调用service(); 所以在tomcat的处理请求阶段,我们可以通过反射方式去调用业务逻辑。
至此。需求分析完毕。
具体代码请看具体项目。
为什么要实现session共享
我们使用单台Tomcat的时候不会有共享sesssion的疑虑,只要使用Tomcat的默认配置即可,session即可存储在Tomcat上。
但是随着业务的扩大,增加Tomcat节点构成Tomcat集群大势所趋,分布式带来了增加更大规模并发请求的优势,但是也随之到来了一个问题,每个Tomcat只存储来访问自己的请求产生的session,如果Tomcat-A已经为客户端C创建了会话session,那么Tomcat-B并不知道客户端已与集群中的Tomcat-A产生了会话,在访问时就会为C再创建一份session,如果是基于session的验证会话权限的接口(如用户登录认证后才可访问的数据接口),将会导致在访问集群中不同节点的时候重复认证。session的不共享导致原来的会话管理机制在Tomcat集群中无法工作。
tomcat中的session是如何工作的
深度好文:https://www.cnblogs.com/kismetv/p/7228274.html
tomcat集群环境下实现session共享的几种方式
第一种:粘性session,基于nginx的ip-hash,当前用户的请求都集中定位到一台服务器中,这样单台服务器保存了用户的session登录信息。
第二种:session复制共享:sessionreplication,如tomcat自带session共享,主要是指集群环境下,多台应用服务器之间同步session,使session保持一致,对外透明。
第三种:基于cache DB缓存的session共享,比如基于 memcache/redis缓存的 session 共享。
注意点,只支持tomcat7,tomcat8以上不支持。
实现过程:
第一步:首先导入相关的jar包到tomcat的lib文件夹中,如redis和tomcat关联的jar包,redis的包等。
commons-pool2-2.2.jar0.1MB
tomcat-redis-session-m....0.0.jar21KB
jedis-2.5.2.jar0.3MB
第二步:配置tomcat。打开context.xml,在<Context>标签内添加如下代码,其中,host是redis的ip,port是端口。:
context.xml1.7KB
<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" />
<Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager" host="localhost" port="6379" database="0" maxInactiveInterval="60" />
第三步:接下来就是可以直接把项目放在webapp下面运行项目啦,你会发现,tomcat的session信息已经存到redis中。多个tomcat可以改变端口,然后用nginx做下负载均衡,可以随时切换tomcat,然后session已经共享了。
index.jsp0.6KB
https://pan.baidu.com/s/1IXyXGgkGkHqLNKmyewGF1A