最近在开发的项目须要承受很高的并发量。综合各类状况,决定使用Apache+Tomcat+JK的方式实现负载均衡,而且做为一个统一的服务还要实现群集(同步Session)。 css
在网上找了不少资料,都是零零散散的,没有一个完整的过程。经过几天的努力,完成了从编译、部署到配置的整个过程,期间也遇到了一些问题。在接下来的文字中将这些过程记录下来,作个笔记同时也分享给你们。 html
gcc、g++和java是必须的,若是运行上述命令提示command not found,则须要安装。具体安装方法这里不作介绍,请参阅相关文档。 java
接下来要准备的是apache服务器、tomcat服务器和JK链接器 mysql
1.下载apache服务器源码包 linux
apache服务器官方没有发布编译好的linux二进制包,只能经过下载源代码,而后本身编译。所以须要先下载源码。 c++
访问网址http://httpd.apache.org/download.cgi,能够看到apache服务器目前放出的版本信息,推荐使用稳定版的release。 web
而后选择Unix版源码: sql
2.下载tomcat服务器源码包 apache
目前tomcat服务器我的仍是以为6.0比较稳定。7.0毕竟是新出的东西,须要必定的生产实践考验才能达到理想的状态。所以这里选择tomcat 6.0。 bootstrap
访问网址http://tomcat.apache.org/download-60.cgi,能够看到目前稳定的版本为6.0.33:
这里强烈建议下载tar.gz格式的压缩包。在Linux下,文件访问有着严格的权限限制。一个文件是否容许以二进制或者脚本的形式执行,彻底取决于其是否拥有执行缺陷,这与Windows识别文件后缀名(.exe、.bat)的方式不一样。zip格式的压缩包中是不保留文件的权限信息的,而tar.gz格式的压缩包是保存有文件的权限信息的。
3.下载JK链接器源码包
做为apache与tomcat链接的桥梁,JK链接器使用C语言编写,与apache紧密结合,做为模块装载到apache服务器中,经过配置实现与特定的tomcat服务器进行通讯,从而实现负载均衡的功能。
访问网址http://tomcat.apache.org/download-connectors.cgi,能够找到最新最稳定的JK链接器版本:
这里仍是推荐下载tar.gz格式的源码。缘由同上。
4.解压
apache服务器、tomcat服务器和JK链接器都已经下载好了,以下图所示:
而后将这三个包都解压出来:
5.编译apache服务器
首先编译apache服务器。在编译以前须要执行其自带的检测配置脚本。对于不一样发行版本的Linux,默认安装的库都有所差异,即使是同一个发行版本,因为用户安装软件的软件不一样,也会致使系统内包含的库有所区别。所以apache做为开源服务器,在编译前须要了解系统的库安装状况,某些模块须要依赖于特定的库,若是这些库不存在,配置脚本将自动忽略这些库的编译。通过检测时候会生成合适的MakeFile文件。这里特别提醒一句,若是直接执行配置脚本,是不会编译额外的模块的,咱们但愿使用额外模块时,须要在运行配置脚本命令后加入参数,让其尽最大可能编译可用的库。关于这方面的介绍能够参阅个人另一篇文章“Linux下编译apache服务器modules文件夹缺乏模块(.so)的问题”(http://blog.csdn.net/chaijunkun/article/details/6977466)。下面进入apache服务器源码目录并执行配置脚本:
加入--with-mpm=worker是修改apache服务器的工做模式。默认模式是prefork。prefork采用预派生子进程方式,用单独的子进程来处理 不一样的请求,进程之间彼此独立。相对于prefork,worker是全新的支持多线程和多进程混合模型的MPM(多路处理模块)。因为使用线程来处理,因此能够处理相对海量的请求,而系统资源的开销要小于基于进程的服务器。可是,worker也使用了多进程,每一个进程又生成多个线程,以得到基于进程服务器的稳定性。
若是配置过程当中出现
这样的错误信息,说明本机没有安装apr运行库,须要下载并安装。访问网址:http://apr.apache.org/download.cgi,下载apr和apr-util:
解压apr和apr-util
进入apr,并编译
生成了MakeFile后直接编译
编译好以后使用root权限安装:
而后使用相似的方法配置apr-util:
编译好以后使用root权限安装:
固然若是你在配置apache服务器编译的时候没有提示缺乏“APR”,请忽略上面关于APR编译的几步。
回到apache服务器源码所在目录,开始编译:
编译过程大概不到十分钟,完成以后使用root权限进行安装
若是不出意外,至此apache就安装成功了。来测试一下:
进入apache服务器的bin目录,并启动服务器:
在本地打开浏览器,访问http://127.0.0.1
若是出现“It Works!”则表示启动成功了
这里要注意一点就是Linux的防火墙问题。若是你的Linux服务器启动了防火墙,本地访问上面的网址是没有问题的,但若是其它计算机访问你的服务器有可能会链接失败。
出现这种状况的缘由是防火墙将入站80端口封锁了。解决方法是将80端口加入到容许列表中:
进入防火墙设置后,若是发现Firewall状态为Enabled,表示防火墙已启用,须要将WWW(HTTP)服务标记为信任,若是须要使用hhtps协议,还要将Secure WWW(HTTPS)服务也标记为信任。以下图所示:
另外,此时若是有其余程序占用80端口也是会影响到apache服务器的,须要确保这个端口没有被占用。
还有我还要补充一点,在Mac OS中按照上述方法安装apache服务器是不行的。开始的时候我不想搭建Linux服务器,想到Mac OS也是类Unix的系统,操做命令什么的都同样,就先在Mac上实验了。结果安装上apache服务器后启动了,每次访问都提示505错误,service temporarily unavailable。通过查阅不少资料和尝试才发现,原来Mac系统中已经自带了apache服务器。具体应用是在“系统设置”中的“共享”功能。这个功能里有“Web共享”方式。其实现时使用的服务器就是apache。它采用的配置文件在/etc/httpd/目录中。这里的配置文件和本身安装的apache服务器配置文件冲突了,所以形成505错误。这一点须要注意。(注:我是用的Mac系统为Mac OS X Lion 10.7.2)
若是你但愿把apache服务器注册为系统服务,让它随着系统启动而启动,则须要在/etc/init.d/目录中创建服务管理脚本,咱们将其命名为httpd:
这一步直接拷贝:
cp /usr/local/apache2/bin/apachectl /etc/rc.d/init.d/apache
vi /etc/rc.d/init.d/apache
在开头的#!/bin/sh 下面加上
#chkconfig: 2345 85 15
6.编译JK链接器
刚刚完成了apache服务器的编译,接下来顺便把JK链接器也编译出来。
进入刚刚解压出来的tomcat-connector目录,再进入native目录。执行配置:
这里须要注意的是配置脚本要添加一个apxs完整路径做为参数。apxs是一个为Apache HTTP服务器编译和安装扩展模块的工具,用于编译一个或多个源程序或目标代码文件为动态共享对象,使之能够用由mod_so提供的LoadModule指令在运行时加载到Apache服务器中。
另外,配置脚本运行时会检查g++所在的目录,若是没有安装g++,则会显示:
请检查是否已经正确安装了c++编译器。
由于实验用的服务器安装的是X86_64版的Red Hat Enterprise Linux Server ,所以要安装以下的包:
libstdc++-devel-4.1.2-46.el5.x86_64.rpm
gcc-c++-4.1.2-46.el5.x86_64.rpm
若是使用rpm命令没法安装,能够在http://szmov.net/centos5464/CentOS/里查找到相应的资源,下载下来安装也是同样的。
配置无误后就能够编译了,执行make命令:
7.JK链接器模块的部署
编译完成后使用ls命令来列出native目录下的全部目录和文件。注意有apache-1.3和apache-2.0两个目录。因为在配置编译的时候指定了apxs工具的位置。配置脚本会根据apxs的反馈结果自动识别目标apache服务器为2.x版本,所以本次编译生成的mod_jk.so模块会放在apache-2.0目录中,apache-1.3目录中是没有mod_jk.so的,这一点请注意。以下所示:
咱们如今将编译好的mod_jk.so拷贝到apache服务器的modules目录中,这个目录是专门用来存放扩展模块的:
至此JK链接器模块就部署完成了,可是还须要配置,具体配置将在下文中详细描述。
8.部署tomcat服务器
因为要在本地开启两个tomcat服务器实例以模拟负载均衡+群集的效果,所以咱们须要将以前解压出来的tomcat复制成两份,进入解压时的目录,重命名解压出来的原始目录为tomcat_server_1,而后复制此目录,副本目录名称为tomcat_server_2:
如今测试tomcat_server_1是否可以正常工做。
测试用例,随便用eclipse建立一个web项目,写一个页面,打成war包,放到tomcat_server_1下的,webapps目录中。而后切换到tomcat_server_1的bin目录下,启动tomcat_server_1:
此处要注意的地方同测试apache服务器是否正常工做时是同样的,须要注意防火墙是否阻塞了tomcat服务器默认采用的8080端口,是否有其余程序占用此端口。
看到没什么问题,咱们先吧tomcat_server_1关闭
9.apache服务器的配置
apache服务器、tomcat服务器和JK链接器都部署完成并能正确执行后就能够开始配置了
用vi或者其它编辑器打开/usr/local/apache2/conf/httpd.conf文件(因为该文件权限属性为rw-r--r--,所以要想修改此文件须要root权限),这就是apache服务器的主配置文件了。
这里我推荐使用图形化的编辑器来编辑它。由于这个文件不少行,若是用文本模式的编辑器编辑我的感受很繁琐。
在有不少LoadModule语句的地方,末尾追加一行
LoadModule jk_module modules/mod_jk.so
而后在写有<IfModule XXXX>的区域追加一行以下配置
<IfModule jk_module>
JkWorkersFile conf/workers.properties
JkMountFile conf/uriworkermap.properties
JkLogFile logs/mod_jk.log
JkLogLevel warn
</IfModule>
下面给出了一个我写的配置。注意配置中有注释的地方。“#”开头的行为注释行。已经去除了原有的配置中的多余注释。
LoadModule表示当apache服务启动时要加载模块 jk_module为模块的别名,后面跟的modules/mod_jk.so就是相对于apache服务器所在目录(/usr/local/apache2/)的模块文件名。
<IfModule jk_module>区域表示当apache服务器加载jk_module(在LoadModule指令中指定的模块别名)模块时所作的配置。
其中:
JkWorkersFile 指定负载均衡服务器的配置文件,文件名为相对于apache服务器所在目录的conf/workers.properties文件
JkMountFile 指定那些请求交由负载均衡服务器来处理,那些由apache服务器来处理,配置文件为相对于apache服务器所在目录的conf/uriworkermap.properties文件
JkLogFile 指定JK链接器的日志输出文件,文件为相对于apache服务器所在目录的logs/mod_jk.log文件
JkLogLevel 指定JK链接器输出日志的级别,级别为warn以上的日志将被输出到日志文件中,可选的值级别由低到高分别为:TRACE DEBUG INFO WARN ERROR FATAL
------------------------------------------------------------------------------------------------------------------------------------------------
<IfModule worker.c>区域表示当apache服务器以worker模式工做时使用的配置。
指令说明:
StartServers:设置服务器启动时创建的子进程数量。由于子进程数量动态的取决于负载的轻重,全部通常没有必要调整这个参数。
ServerLimit:服务器容许配置的进程数上限。只有在你须要将MaxClients和ThreadsPerChild设置成须要超过默认值16个子进程的时候才须要使用这个指令。不要将该指令的值设置的比MaxClients 和ThreadsPerChild须要的子进程数量高。修改此指令的值必须彻底中止服务后再启动才能生效,以restart方式重启动将不会生效。
ThreadLimit:设置每一个子进程可配置的线程数ThreadsPerChild上限,该指令的值应当和ThreadsPerChild可能达到的最大值保持一致。修改此指令的值必须彻底中止服务后再启动才能生效,以restart方式重启动将不会生效。
MaxClients:用于伺服客户端请求的最大接入请求数量(最大线程数)。任何超过MaxClients限制的请求都将进入等候队列。默认值是"400",16 (ServerLimit)乘以25(ThreadsPerChild)的结果。所以要增长MaxClients的时候,你必须同时增长 ServerLimit的值。笔者建议将初始值设为(以Mb为单位的最大物理内存/2),而后根据负载状况进行动态调整。好比一台4G内存的机器,那么初始值就是4000/2=2000。
MinSpareThreads:最小空闲线程数,默认值是"75"。这个MPM将基于整个服务器监视空闲线程数。若是服务器中总的空闲线程数太少,子进程将产生新的空闲线程。
MaxSpareThreads:设置最大空闲线程数。默认值是"250"。这个MPM将基于整个服务器监视空闲线程数。若是服务器中总的空闲线程数太多,子进程将杀死多余的空闲线程。MaxSpareThreads的取值范围是有限制的。Apache将按照以下限制自动修正你设置的值:worker要求其大于等于 MinSpareThreads加上ThreadsPerChild的和。
ThreadsPerChild:每一个子进程创建的线程数。默认值是25。子进程在启动时创建这些线程后就再也不创建新的线程了。每一个子进程所拥有的全部线程的总数要足够大,以即可以处理可能的请求高峰。
MaxRequestsPerChild:设置每一个子进程在其生存期内容许伺服的最大请求数量。到达MaxRequestsPerChild的限制后,子进程将会结束。若是MaxRequestsPerChild为"0",子进程将永远不会结束。将MaxRequestsPerChild设置成非零值有两个好处:能够防止(偶然的)内存泄漏无限进行而耗尽内存;
给进程一个有限寿命,从而有助于当服务器负载减轻的时候减小活动进程的数量。
若是设置为非零值,笔者建议设为10000-30000之间的一个值。
公式:
ThreadLimit >= ThreadsPerChild
MaxClients <= ServerLimit * ThreadsPerChild,而且MaxClients必须是ThreadsPerChild的倍数
MaxSpareThreads >= MinSpareThreads+ThreadsPerChild
------------------------------------------------------------------------------------------------------------------------------------------------
接下来配置上面提到的conf/workers.properties文件和conf/uriworkermap.properties文件:
进入apache服务器的conf目录
创建workers.properties和uriworkermap.properties文件
下面给出我已经配置好的两个文件
worker.list 首先配置了两个worker,一个用于负载均衡,一个用于监视负载均衡状态。别名分别为loadBalanceServers和jk_watcher
而后分别配置位于本机的两个负载均衡服务器
worker.s1.port:第一台负载均衡服务器AJP协议链接器的链接端口,这里配置为8109
worker.s1.host:第一台负载均衡服务器的主机名、域名或者IP地址,这里配置为本机localhost
worker.s1.type:JK模块实现负载均衡采用的是AJP协议1.3版本,所以第一台负载均衡服务器的类型配置为ajp13
worker.s1.lbfactor:第一台负载均衡服务器在整个负载均衡系统中所占的权重,这里配置为10,权重越大,越有可能处理更多的请求,建议给性能好的机器配置更高的权重。
worker.s1.cachesize:apache服务器是多线程的,tomcat可以利用这一优点来维持必定数量的链接做为缓存。根据用户的多少来配置一个合适缓存链接数量有助于提升性能。这里配置为5
s1是第一台负载均衡服务器的别名,这个别名要牢记,由于在接下来的配置中还会用到。
s2做为第二台负载均衡服务器,配置与s1大体相同。区别是AJP协议链接器的链接端口与s1的不一样,这是由于要在同一台物理机上部署两个tomcat服务器的缘故。若是是两台物理机,则能够配置相同的端口,那么host属性就应该不同了。两个tomcat服务器的权重都是10,则两个tomcat服务器将会有相同的处理请求的机会。
worker.loadBalanceServers.type:设置名称为“loadBalanceServers”的worker类型,这里配置为lb,也就是Load Balance负载均衡
worker.loadBalanceServers.balanced_workers:设置名称为“loadBalanceServers”的worker拥有哪些负责负载均衡的服务器实例,这里配置为s1和s2
worker.loadBalanceServers.sticky_session:设置负载均衡是否采用粘性会话。若是该属性设置为true,假设一个请求被s1处理了,下次来源于同一个客户端的请求也将被s1处理。直到s1已经达到最大链接数,JK才会将会话切换到其余服务器上。可是若是恰巧一直负责处理该会话的服务器down掉了,则会话将会丢失,明显的故障现象就是关于session的操做会出现莫名其妙的错误(例如你所运行的应用中用户可能已经登陆了,但忽然在一次访问后莫名其妙地提示没有登陆)。这里配置为false,不启用粘性会话,让服务器都有机会处理请求,提升了系统的稳定性。
worker.jk_watcher.type:设置名称为“jk_watcher”的worker类型,这里配置为status,用于监视各个负载均衡服务器实例的运行状态
# worker.jk_watcher.read_only:设置名称为“jk_watcher”的worker是否为只读。上面已经将这个worker设置为了监控worker,若是设置为只读,就不能对负载均衡服务器参数进行配置了,这里先将这条配置注释掉,默认值为false,表示能够配置参数。
worker.jk_watcher.mount:设置名称为“jk_watcher”的worker(负载均衡服务器实例监视器)的挂载路径,这里配置为/admin/jk。这样就能够经过http://127.0.0.1/admin/jk来访问监视工具了,能够很方便地看到各个负载均衡服务器的工做状况。
worker.retries:这是worker全局的重试次数。在apache服务器启动后,会最多尝试若干次去链接这些负载均衡服务器,若链接不上就认为是down掉了,这里配置为3
下面给出配置,其做用是告诉apache服务器哪些请求由负载均衡服务器处理:
在配置文件中,以“!”开头的条件表示“不要”,“=”表示交给。
所以条件“/*=loadBalanceServers”表示将任何请求交给负载均衡服务器。
条件“!/*.jpg=loadBalanceServers”表示不要将.jpg结尾的请求交给负载均衡服务器
apache服务器接收到一个请求后会按照配置文件中的约束条件一个一个地检查,而后按照最后知足的匹配条件来决定由哪一个worker来处理请求。
个人测试用例中须要输入http://127.0.0.1/TestProject/showInfo.do来查看信息。那么接下来就将这个请求做为示例来解释上面配置文件的工做过程:
通过上面的条件筛选,最符合条件的就是“/*=loadBalanceServers”。所以将请求转给了负载均衡服务器。
试想一下,若是在apache主目录下放置了一个名为a.jpg的图片,访问路径为http://127.0.0.1/a.jpg,请求通过该配置的检查,最后知足的条件就是“!/*.jpg=loadBalanceServers”,不要将.jpg结尾的请求交给负载均衡服务器,所以apache服务本身处理了该请求。
.jpg是静态数据,apache由C语言实现,直接针对系统底层进行IO操做,所以静态性能优良。而tomcat做为Servlet容器,擅长的是J2EE相关业务的解析。所以经过这样配置能够实现应用的“动静态分离”,相互取长补短,优化了性能。相似地也能够将.js、.css和.html等等静态文件按照上述格式填写到uriworkermap.properties配置文件中。
10.tomcat服务器的配置
因为在同一台物理机中部署了两个tomcat服务器实例,所以须要对端口相关的设置特别当心。tomcat服务器的主配置文件server.xml位于conf目录内。为了配置简单,我将最原始server.xml配置文件中的全部注释删除,而后配置好了一个模板,该模板是s1((即tomcat_server_1)的配置文件,以下所示:
配置好s1服务器后再配置s2服务器。按照上面模板中的注释要求,修改相应的端口就能够了。PS:<Cluster ...></Cluster>节点之间部分没必要本身动手敲进去,在tomcat服务器目录的/webapps/docs/cluster-howto.html文件中有这一段文字,拷贝出来贴到server.xml文件中便可。
tomcat6中,单机开发时为了保证GET请求参数采用UTF8编码(用于支持中文参数),在server.xml中会进行了以下设置:
<Connector port="8080"maxThreads="150" minSpareThreads="25"
maxSpareThreads="75" enableLookups="false" redirectPort="8443"
acceptCount="100" debug="99" connectionTimeout="20000"
disableUploadTimeout="true" URIEncoding="UTF-8" />
可是,当使用apache + tomcat 组成群集与负载均衡系统时,apache会将servlet/jsp请求转发给Tomcat。此时是经过AJP协议来转发的,所以对应的请求其实是被转发到Tomcat监听的AJP端口上的,因此这里针对8080的设置天然就无效了。正确的方法是进行下面的设置:
<Connector port="8009"enableLookups="false" redirectPort="8443"
debug="0" protocol="AJP/1.3" URIEncoding="UTF-8" />
须要在群集环境中修改默认URL编码的朋友们在这里须要注意一下
最近刚刚发现tomcat的关闭脚本shutdown.sh有问题,常常不能彻底回收资源。tomcat是基于Java编写的,固然其运行也就脱离不了java的JVM。当执行完shutdown.sh脚本后,tomcat服务器表面上是关闭了,然而JVM并无彻底退出,还在清理并回收资源,若是这个时候当即使用startup.sh进行启动,很容易致使再启动一个新的JVM实例,若是维护次数增多就会致使系统内存耗尽,我今天就经历了以下的错误:
registered the JDBC driver [com.mysql.jdbc.Driver] but failed to unregister it when the web application was stopped.
这条错误发现于logs目录下的catalina.日期.log文件中。不管再怎么启动tomcat都启动不了。后来重启了服务器竟然能够启动了,后来同事说shutdown.sh脚本有问题。因而我通过实验,果真是这样。所以在这里奉劝读者,若是须要从新启动tomcat服务器,除了先执行shutdown.sh脚本外,还应该运行ps ax | grep java 来看看有没有残留的java进程,若是有,使用kill命令将其杀死,这才是正确的关闭tomcat服务器方法。
tomcat 6.0.25之后引入了内存泄露侦测,对于垃圾回收不能处理的对像,它就会作日志。
在tomcat的server.xml文件中,以下配置就是用来作内存泄露侦测的
<!-- Prevent memory leaks due to use of particular java/javax APIs-->
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>;
也幸好有了这个东西才提醒了我正在运行着多个JVM实例。
开启了tomcat服务器以后,就能够开启apache服务器了(注意,顺序很重要!必定要先开tomcat)