Tomcat系列文章:http://www.cnblogs.com/f-ck-need-u/p/7576137.htmlphp
tomcat通常只提供动态资源处理功能,而静态资源的请求则交给独立的apache/httpd或nginx来处理。但tomcat与外界通讯的惟一组件是链接器Connector,所以动态请求要转发给tomcat时,须要和Connector通讯。Connector与外界通讯的协议有两种:http/ajp。css
tomcat的Connector组件支持两种协议类型的链接:http、ajp。html
其中http又分为http/1.1和http/2,但由于Servlet的阻塞特性,使得每一个http/2的请求都须要单独的容器线程负责处理,所以目前使用http/2的性能还不好。而AJP又有ajp十二、ajp13和ajp14,其中ajp12的功能太简陋,ajp14又处于试验期,所以目前都适用ajp13,或者称为ajp 1.3。前端
基于IO模型,Connector又分为NIO/NIO2/APR三种类型。java
设置链接器协议的方式以下:nginx
<!-- 定义使用Http协议的链接器,其中"HTTP/1.1"表示自动选择NIO/NIO2/APR -->
<connector port="8080" protocol="HTTP/1.1">
<connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol">
<connector port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol">
<connector port="8080" protocol="org.apache.coyote.http11.Http11AprProtocol">
<!-- 定义使用AJP协议的链接器,其中"AJP/1.3"表示自动选择NIO/NIO2/APR -->
<Connector port="8009" protocol="AJP/1.3"/>
<Connector port="8009" protocol="org.apache.coyote.ajp.AjpNioProtocol"/>
<Connector port="8009" protocol="org.apache.coyote.ajp.AjpNio2Protocol"/>
<Connector port="8009" protocol="org.apache.coyote.ajp.AjpAprProtocol"/>
Tomcat链接器与外界通讯的两种协议中,ajp只能和apache基金会开发的部分项目通讯,例如httpd。要与非apache基金会开发的程序通讯,必须使用http协议。所以,tomcat+nginx时,tomcat链接器的协议类型只能是http。web
对于tomcat+httpd,tomcat端的链接器既可使用http,也可使用ajp协议。但在httpd端,也支持多种通讯模块,最经常使用的是mod_proxy和mod_jk。apache
tomcat+httpd时,几个须要注意的点:vim
所以,若是知足业务需求,建议使用mod_proxy,它配置起来比mod_jk要简便的多,且可定制的功能更多。后端
稍做总结,apache和tomcat通讯的实现方式有3种:
apache:mod_jk <--> tomcat:ajp(虽然能够和http配合,但不建议)
apache:mod_proxy <--> tomcat:ajp或http
下图是httpd/nginx+tomcat在动静分离时一般使用的架构模型。
左边的模型中,nginx/httpd须要处理静态请求,并将动态请求转发给tomcat,同时实现负载均衡。右边的模型中,添加一层反向代理层,不管是静态请求仍是动态请求都负载到各自的服务器组。后文的实验都采用左边的架构模型来完成。
测试环境以下:
[root@xuexi ~]# echo "[nginx]
name=nginx
baseurl=http://nginx.org/packages/centos/6/x86_64/
enable=1
gpgcheck=0" >/etc/yum.repos.d/nginx.repo
[root@xuexi ~]# yum install -y nginx
[root@xuexi ~]# vim /etc/nginx/conf.d/tomcat.conf
upstream tomcat_servers {
server 192.168.100.22:8080 weight=1 max_fails=2 fail_timeout=2;
server 192.168.100.23:8080 weight=2 max_fails=2 fail_timeout=2;
}
server {
listen 80;
server_name 192.168.100.17;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location ~* \.(jsp|jspx|do) {
proxy_pass http://tomcat_servers;
}
}
启动nginx和tomcatA、tomcatB。而后在浏览器中分别访问静态资源和动态资源。
http://192.168.100.17
http://192.168.100.17/index.jsp
其中第二个访问的是动态资源,页面结果以下:
发现这个欢迎页面缺失了不少内容。其实这些缺失的内容都是定义在index.jsp中的静态资源,例如某些图片。之因此会缺失,是由于客户端发送请求给nginx后,nginx将请求转发给tomcat,tomcat翻译index.jsp为java源文件,而后执行该servlet。执行servlet时,将所需响应数据都回应给nginx,包括index.jsp中定义的图片连接标签。当客户端收到这一次的响应数据后,还会继续去请求图片,可是这是静态请求,nginx会本身处理而不会转发给tomcat,但nginx自己不知道图片在何处(tomcat才知道)。所以对于图片部分,nginx将返回404错误,使得客户端显示的页面缺失了一部分。
在生产环境下,不须要担忧这样的二次静态请求缺失问题,由于会将图片等静态数据存放在某个位置,并配置好nginx如何找到这些静态数据。例如,上面的页面中缺失了tomcat.{png,css,gif}等静态文件,将它们从tomcat服务器的$CATALINA_HOME/webapps/ROOT/目录下拷贝到nginx服务器的/usr/share/nginx/html目录(上面配置文件的root指令指定的位置)下,并赋予读取权限。再去访问,就能正确显示图片。
测试环境以下:
使用mod_jk模块和tomcat链接时,tomcat的链接器通常都使用ajp协议类型。
mod_jk不是apache httpd的原生模块,而是相似于第三方模块,所以须要额外编译mod_jk模块到httpd中,就像将php模块添加到httpd中同样。
当前最新稳定版的mod_jk是1.2.42版本。
mod_jk下载地址:http://tomcat.apache.org/download-connectors.cgi。
mod_jk官方手册:http://tomcat.apache.org/connectors-doc/。
httpd要扩展模块须要借助apxs,它是httpd的开发包httpd-devel中工具,因此先要安装httpd-devel。若是是编译安装的httpd,则devel包已经装好,若是是yum安装,则须要额外安装httpd-devel包。
此处为了方便,httpd使用yum安装。因此编译mod_jk的方式以下:
yum -y install httpd httpd-devel tar xf tomcat-connectors-1.2.42-src.tar.gz cd tomcat-connectors-1.2.42-src/native/ ./configure --with-apxs=/usr/bin/apxs --prefix=/usr/local/tomcat/mod_jk make && make install
此处暂先配置httpd与其中一个tomcat(192.168.100.22)链接。后文在说明负载均衡时再引入另外一个tomcat。
先提供一个额外的httpd配置文件。
[root@xuexi ~]# cat /etc/httpd/conf.d/mod_jk.conf
LoadModule jk_module modules/mod_jk.so
JkWorkersFile /etc/httpd/conf.d/workers.properties
JkLogFile logs/mod_jk.log
JkLogLevel debug
######### "JkMount /* TomcatA" will send all request to TomcatA ########
JkMount /*.jsp TomcatA
JkMount /status/* statA
JkUnMount /images/* TomcatA
JkUnMount /css/*.* TomcatA
JkUnMount /css_js/* TomcatA
JkUnMount /*.html TomcatA
JkUnMount /*.js TomcatA
mod_jk的配置文件官方手册:http://tomcat.apache.org/connectors-doc/reference/apache.html。如下是几个经常使用的指令说明。
LoadModule
指令用于装载mod_jk相关模块,除此以外还须要在httpd的配置文件中设置其它一些指令来配置其工做属性。如:JkWorkersFile
用于指定保存了worker相关工做属性定义(见下文)的配置文件。JkLogFile
用于指定mod_jk模块的日志文件。JkLogLevel
用于指定日志级别(info,error,debug),此外还可使用JkRequestLogFormat自定义日志信息格式。JkMount
(格式:JkMount )则用于控制URL与Tomcat workers的对应关系。能够理解为转发请求的意思,例如"/status/*"表示url地址后加上/status/可转发至statA这个worker上。注意,JkMount匹配的URL是相对的。若是JkMount指令放在Location指令中,如<Location /app>
,则JkMount将从/app的后面开始匹配。JkMount和JkUnMount是很重要的指令,mod_jk性能之因此比mod_proxy好,就是由于经过这两个指令能够实现动静分离,使得只将动态请求转发给tomcat。其中JkMount指定要转发给tomcat处理的请求,JkUnMount指定明确不转发给tomcat而是在本地处理的请求。虽然不指定JkUnMount时,也表示不转发给tomcat,但若是有重叠时,则应该指定JkUnMount。例以下面的例子,除了/myapp/下的js文件,其余都转发给tomcat1处理。
JkMount /myapp/* tomcat1
JkUnMount /myapp/*.js tomcat1
对于apache来讲,每个后端Tomcat实例中的engine均可以视做一个worker,而每个worker的地址、Connector的端口等信息都须要在apache端指定以即可以识别并使用这些worker。配置这些信息的文件一般为"workers.properties",其具体路径是使用前面介绍过的JkWorkersFile指定的。在apache启动时,mod_jk会扫描此文件获取每个worker配置信息。如这里使用/etc/httpd/conf.d/workers.properties
。
workers.properties文件通常由两类指令组成:一是mod_jk能够链接的各worker名称列表,二是每个worker的属性配置信息。详细的配置方法见官方手册:http://tomcat.apache.org/connectors-doc/reference/workers.html。
如下是和上述/etc/httpd/conf.d/mod_jk.conf中配置相对应的/etc/httpd/conf.d/workers.properties。
[root@xuexi tomcat]# cat /etc/httpd/conf.d/workers.properties
worker.list=TomcatA,statA
worker.TomcatA.type=ajp13
worker.TomcatA.host=192.168.100.22
worker.TomcatA.port=8009
worker.TomcatA.lbfactor=1
worker.statA.type = status
关于worker的配置,它们分别遵循以下使用语法。
worker.list = <a comma separated list of worker_name>
worker.<worker_name>.<property>=<property value>
其中worker.list指令能够重复指定屡次。worker_name是Tomcat中engine组件中jvmRoute属性的值(jvmRoute能够不指定,此时worker_name仅用于标识worker)。
根据工做机制的不一样,worker有多种不一样的类型,每一个worker都须要指定其类型,即设定woker..type项。常见的类型以下:其中ajp13是默认值。
因为status是状态监控页面,因此应该保证其安全性,能够在httpd的配置文件中加入如下控制列表:
# 注意,必须加上尾随斜线,由于在mod_jk.conf中已经明确了"/status/*"
# For http 2.2
<Location /status/>
Order deny,allow
Deny from all
Allow from 192.168.100.0/24
</Location>
# For http 2.4
<Location /status/>
Requrie ip 192.168.100
</Location>
除了type属性外,worker其它常见的属性有:
除了type属性外,worker其它常见的属性有:
另外,在负载均衡模式中专用的属性还有:
至此,一个基于mod_jk模块与后端名为TomcatA的worker通讯的配置已经完成,重启httpd服务便可生效。
测试:在浏览器中输入
http://192.168.100.17/
http://192.168.100.17/index.jsp
http://192.168.100.17/status/
若是都能获取页面,则表示apache经过mod_jk和tomcat基于ajp协议类型的链接已经成功。
使用mod_jk实现tomcat的负载均衡有一个好处,tomcat上能够禁用http协议(将监听此协议的Connector配置删除便可),防止外界直接经过http请求tomcat。
配置apache,使其支持负载均衡,修改/etc/httpd/conf.d/mod_jk.conf为以下内容:
LoadModule jk_module modules/mod_jk.so
JkWorkersFile /etc/httpd/conf.d/workers.properties
JkLogFile logs/mod_jk.log
JkLogLevel notice
JkMount /*.jsp TomcatLB
JkMount /status/* statA
编辑/etc/httpd/conf.d/workers.properties,修改成以下内容:为测试负载效果,不启用stick_session。
worker.list=TomcatLB,statA
worker.statA.type=status
worker.TomcatLB.type=lb
worker.TomcatLB.sticky_session=false
worker.TomcatLB.balance_workers=TomcatA,TomcatB
worker.TomcatA.type=ajp13
worker.TomcatA.host=192.168.100.22
worker.TomcatA.port=8009
worker.TomcatA.lbfactor=5
worker.TomcatB.type=ajp13
worker.TomcatB.host=192.168.100.23
worker.TomcatB.port=8009
worker.TomcatB.lbfactor=10
在mod_jk负载均衡中,后端tomcat的engine组件须要添加jvmRoute参数,该参数会为当前server实例设置全局唯一标识符,所以每个实例的jvmRoute的值均不能相同,且jvmRoute的值必须等于balance_workers的成员值。对于上面的配置,Engine应该以下设置:此处还修改了name,但这不是要求要修改的。
<!-- 在tomcatA上设置 -->
<Engine name="Standalone" defaultHost="localhost" jvmRoute="TomcatA">
<!-- 在tomcatB上设置 -->
<Engine name="Standalone" defaultHost="localhost" jvmRoute="TomcatB">
为了演示效果,在TomcatA部署一个应用程序test。
[root@xuexi tomcat]# mkdir -p /usr/local/tomcat/webapps/test/WEB-INF/{classes,lib}
添加index.jsp,内容以下:
[root@xuexi tomcat]# cat /usr/local/tomcat/webapps/test/index.jsp
<%@ page language="java" %>
<html>
<head><title>TomcatA</title></head>
<body>
<h1><font color="red">TomcatA </font></h1>
<table align="centre" border="1">
<tr>
<td>Session ID</td>
<% session.setAttribute("abc","abc"); %>
<td><%= session.getId() %></td>
</tr>
<tr>
<td>Created on</td>
<td><%= session.getCreationTime() %></td>
</tr>
</table>
</body>
</html>
在TomcatB一样也部署一个应用程序test。以下:
[root@xuexi tomcat]# mkdir -p /usr/local/tomcat/webapps/test/WEB-INF/{classes,lib}
[root@xuexi tomcat]# cat /usr/local/tomcat/webapps/test/index.jsp
<%@ page language="java" %>
<html>
<head><title>TomcatB</title></head>
<body>
<h1><font color="blue">TomcatB </font></h1>
<table align="centre" border="1">
<tr>
<td>Session ID</td>
<% session.setAttribute("abc","abc"); %>
<td><%= session.getId() %></td>
</tr>
<tr>
<td>Created on</td>
<td><%= session.getCreationTime() %></td>
</tr>
</table>
</body>
</html>
重启httpd、tomcatA、tomcatB对应的服务程序。在浏览器中输入192.168.100.17/test/index.jsp测试负载均衡是否生效。
测试时,轮调两次tomcatB后轮调一次tomcatA。并且能够发现每次轮询时Session ID每次都是变化的,由于没有开启sticky_session,因此session没有进行绑定。
要绑定会话,将worker.properties中的sticky_session设置为true便可。
测试环境以下:
当httpd端采用mod_proxy和tomcat链接时,能够采用ajp或http协议进行链接。
要使用mod_proxy与Tomcat链接,须要apache已经装载mod_proxy、mod_proxy_http、mod_proxy_ajp和proxy_balancer_module(实现Tomcat负载均衡时用到)等模块。使用rpm包安装的httpd通常默认已经启用它们,若是是编译httpd,则在编译选项中加上如下对应几项:
--enable-proxy --enable-proxy-http --enable-proxy-ajp --enable-proxy-balancer
若是是已经编译好的Httpd,则可使用apxs工具,向httpd中添加这几个新模块。添加方法见:httpd添加新模块。
确保proxy相关的模块已经加载了。
[root@xuexi ~]# httpd -M | grep proxy
Syntax OK
proxy_module (shared)
proxy_balancer_module (shared)
proxy_ftp_module (shared)
proxy_http_module (shared)
proxy_ajp_module (shared)
proxy_connect_module (shared)
基于ajp链接协议和tomcat链接时,向httpd添加如下配置文件。若是要基于http协议链接tomcat,将上面配置文件的ajp协议改成http协议,并修改端口便可。
[root@xuexi ~]# cat /etc/httpd/conf.d/ajp.conf
<Location /status>
SetHandler balancer-manager
Proxypass ! # 表示此Location的URL不进行反向代理
Require ip 192.168.100
</Location>
ProxyVia Off
ProxyRequests Off
ProxyPreserveHost Off
ProxyPassMatch "^/(.*\.jsp)$" ajp://192.168.100.22:8009/$1
ProxyPassReverse "^/(.*\.jsp)$" ajp://192.168.100.22:8009/$1
<Proxy *>
Require all granted
</Proxy>
重启httpd。注意,重启前将前面mod_jk实验的配置文件删除掉。
关于如上apache配置的几个指令,解释以下。httpd反向代理的详细内容,可参见:详细分析apache httpd反向代理的用法。
ProxyVia {On|Off|Full|Block}
:用于控制在http首部是否使用"Via:",主要用于在多级代理中控制代理请求的流向。默认为Off,即不启用此功能;On表示每一个请求和响应报文均添加"Via:";Full表示每一个"Via:"行都会添加当前apache服务器的版本号信息;Block表示每一个代理请求报文中的"Via:"都会被移除。ProxyRequests {On|Off}
:是否开启apache正向代理的功能;若是为apache设置了ProxyPass即反向代理,则必须将ProxyRequests设置为Off。ProxyPreserveHost {On|Off}
:若是启用此功能,代理会将用户请求报文中的"Host:"行发送给后端的服务器,而再也不使用ProxyPass指定的服务器IP地址。若是后端一个IP上可能会有多个虚拟主机,则须要开启此项明确转发给哪台虚拟主机,不然就无需打开此功能。ProxyPassReverse
:在反向代理环境中必须使用此指令避免重定向报文绕过proxy服务器,属性设置为ProxyPass同样基本上就能够。ProxyPass [path] !|[url [key=value key=value ...]]
:将后端服务器某URL与当前服务器的某虚拟路径关联起来做为提供服务的路径,path为当前服务器上的某虚拟路径,url为后端服务器上某URL路径。使用此指令时必须将ProxyRequests的值设置为Off。须要注意的是,若是path以"/"结尾,则对应的url也必须以"/"结尾,反之亦然。ProxyPassMatch [regex] !|url [key=value [key=value ...]]
:正则格式的ProxyPass。关于httpd反向代理的负载均衡配置方式,参见详细分析apache httpd反向代理的用法。
在httpd.conf中配置以下内容:
<proxy balancer://TomcatLB>
BalancerMember ajp://192.168.100.38:8009 loadfactor=5
BalancerMember ajp://192.168.100.36:8009 loadfactor=10
</proxy>
ProxyVia Off
ProxyRequests Off
ProxyPreserveHost Off
ProxyPassMatch "^/(.*\.jsp)$" balancer://TomcatLB/$1
ProxyPassReverse "^/(.*\.jsp)$" balancer://TomcatLB/$1
<Proxy *>
Require all granted
</Proxy>
<Location /status>
SetHandler balancer-manager
Proxypass !
Require ip 192.168.100
</Location>
重启httpd并在浏览器中输入192.168.100.17/test/index.jsp
测试,测试时会轮调两次tomcatB再轮调一次tomcatA。
若是要实现session粘滞(绑定),则修改httpd配置文件以下:
<proxy balancer://TomcatLB>
BalancerMember ajp://192.168.100.22:8009 loadfactor=5 route=TomcatA
BalancerMember ajp://192.168.100.23:8009 loadfactor=10 route=TomcatB
ProxySet lbmethod=byrequets
</proxy>
ProxyVia Off
ProxyRequests Off
ProxyPreserveHost Off
ProxyPassMatch "^/(.*\.jsp)$" balancer://TomcatLB/$1 stickysession=JSESSIONID
ProxyPassReverse "^/(.*\.jsp)$" balancer://TomcatLB/$1 stickysession=JSESSIONID
<Proxy *>
Require all granted
</Proxy>
<Location /status>
SetHandler balancer-manager
Proxypass !
Require ip 192.168.100
</Location>
而后分别配置tomcatA和tomcatB的engine组件,分别加上jvmRoute="TomcatA"和jvmRoute="TomcatB"属性。
<Engine name="Catalina" defaultHost="localhost" jvmRoute="TomcatA">
<Engine name="Catalina" defaultHost="localhost" jvmRoute="TomcatB">
重启httpd和tomcatA、tomcatB,而后测试结果,再测试时同一客户端将老是获得同一个结果,不会出现负载均衡。