`

java web开发总结(一) :前言 & 我的server配置升级路线图

阅读更多
前言
做java开发也有4年了,回头看看,自己也到了一个需要总结积累,寻求突破的时候了。
我在前面的博文里有提到我的工作经历,这里再提一下我所经历的项目:一直在做web开发,从前端的html,javascript到后端的java程序,数据库;系统从最小几百访问量,到百万,千万访问量,亿级访问量。从单机到大规模分布式部署,由windows到linux。这四年中遇到的各种问题和解决,给我很多经验。体验得越多,越觉得互联网的伟大,个人力量的渺小,和自己离真正技术牛人的差距。技术无止境,唯有不断前行。

打算写一系列的文章,来总结自己的非计算机专业菜鸟在互联网开发中的成长历程。 
不多不多提一句,做技术的,很多时候都太闷了,缺少交流,我也是如此,第一次在javaeye发文,希望借着文章来增进自己的交流。 
总结过去,增进交流,认识不足,学习前进。这四点就是这系列文章的目的。 

虽然在清华有很多遗憾,但是对清华的校训还是刻骨铭心,一直引为座右铭: 
天行健,君子以自强不息;地势坤,君子以厚德载物。 

愿与诸君共勉。 

我的web server配置升级路线图 

做java web开发,最前端跟用户打交道的,就是服务器和server了。掌握这些东西,会对开发高效的程序有很大的帮助。 
不得不说,软件更换升级,带来的硬件成本的节省,就是一种能力。相信在中小公司,这种能力会是非常值得重视的。 
不断提升单机服务能力,挖掘硬件效率是非常值得去做得一件事。从中收获到的,是程序员的快感,欣喜若狂的快感。 

总结一下这些年我的系统升级的路线: 
1 windows xp + tomcat 
2 windows 2003 + tomcat 
3 windows 2003 + 单apache + 单tomcat 
4 windows 2003 + apache + tomcat负载均衡 
5 windows 2003 + apache + tomcat负载均衡 + 多机dns轮询 
6 ubuntu + apache + tomcat负载均衡 
7 ubuntu + nginx + tomcat均衡 

不得不感叹一下最原始开发的时候,直接就是台式机做服务器,xp系统加tomcat就上线了。最牛逼的时候,是在服务器上用myeclipse启动server,然后debug异常。 
这就是小公司,无技术积累,一切都是原始起步的痛。不过跟随系统成长的成长曲线,非常适合我这个菜鸟。 
言归正传,继续来说前端server配置: 

最早的配置是tomcat默认配置,能服务就OK。然后慢慢的访问量大了以后,就不得不调查研究tomcat的优化配置。访问量到了一定级别以后,就开始研究apache+tomcat动静分离。动静分离以后tomcat还是响应不过来,就继续研究apache+多tomcat的负载均衡。再后来接触到ubuntu后,才知道linux下,web程序跟windows的差距,简直就是一个天上,一个地下。再后来接触到nginx后,发现在某些方面,apache也是老态龙钟,无法比拟。这就是技术力量,技术的魔法,不得不为创造出这些技术的前辈们所折服。 

这些年的经验:系统增长到比较高的并发的时候,一定要有人研究配置,系统配置、server配置,不断地跟随系统的成长而进行优化。 

一些并发数据对比: 
服务器配置8G内存,2.0GHz 8核CPU: 
windows 2003下,apache配置到3000线程,是以前配置的极限了。 
改成linux后,压力测试下apache到5000并发。 
再把apache换成nginx后,轻松突破1.5万并发。 
当然,linux下还有一个很重要的问题,必须修改内核参数才能提高系统并发。 

目前系统下单机每日千万级别的访问量是非常轻松的(当然还跟应用本身有关)。


具体的配置对比在下一篇web server配置里再详细说。




引用
目前总结出来的一些优化配置:
配置适用环境 linux + apache2.2(prefork模式) + jk1.2.26 + tomcat6

(a)apache端需要配置的核心参数:apache/conf/httpd.conf


Xml代码
Timeout 10  
KeepAlive On  
MaxKeepAliveRequests 5  
KeepAliveTimeout 5  
 
<IfModule mpm_prefork_module> 
    ServerLimit       3000  
    StartServers      750  
    MinSpareServers   5  
    MaxSpareServers   100  
    MaxClients        3000  
    MaxRequestsPerChild   10000  
</IfModule> 

Timeout 10
KeepAlive On
MaxKeepAliveRequests 5
KeepAliveTimeout 5

<IfModule mpm_prefork_module>
    ServerLimit       3000
    StartServers      750
    MinSpareServers   5
    MaxSpareServers   100
    MaxClients        3000
    MaxRequestsPerChild   10000
</IfModule>

(b)workers.properties配置:

Xml代码
worker.list=loader 
 
worker.loader.type=lb 
#两tomcat负载均衡  
worker.loader.balance_workers=tomcat1,tomcat2  
#不同步session  
worker.loader.sticky_session=false 
 
#负载1  
worker.tomcat1.type=ajp13 
worker.tomcat1.host=localhost 
worker.tomcat1.port=8009 
#负载均衡因子,决定apache分发的比例  
worker.tomcat1.lbfactor=1  
worker.tomcat1.socket_timeout=10#配置超时时间  
worker.tomcat1.connection_pool_timeout=600#配置关闭空闲连接时间  
 
#负载2  
worker.tomcat2.type=ajp13 
worker.tomcat2.host=localhost 
worker.tomcat2.port=8109 
#负载均衡因子,决定apache分发的比例  
worker.tomcat2.lbfactor=1 
worker.tomcat2.socket_timeout=10#配置超时时间  
worker.tomcat2.connection_pool_timeout=600#配置关闭空闲连接时间 

worker.list=loader

worker.loader.type=lb
#两tomcat负载均衡
worker.loader.balance_workers=tomcat1,tomcat2
#不同步session
worker.loader.sticky_session=false

#负载1
worker.tomcat1.type=ajp13
worker.tomcat1.host=localhost
worker.tomcat1.port=8009
#负载均衡因子,决定apache分发的比例
worker.tomcat1.lbfactor=1
worker.tomcat1.socket_timeout=10#配置超时时间
worker.tomcat1.connection_pool_timeout=600#配置关闭空闲连接时间

#负载2
worker.tomcat2.type=ajp13
worker.tomcat2.host=localhost
worker.tomcat2.port=8109
#负载均衡因子,决定apache分发的比例
worker.tomcat2.lbfactor=1
worker.tomcat2.socket_timeout=10#配置超时时间
worker.tomcat2.connection_pool_timeout=600#配置关闭空闲连接时间

(c)tomcat/conf/server.xml配置:

Xml代码
<Connector port="8009"   
       enableLookups="false" 
       maxProcessors="0" 
       maxThreads="3000" 
       minSpareThreads="100" 
       maxSpareThreads="200" 
       protocol="AJP/1.3"   
       enableLookups="false" 
       connectionTimeout="600000" 
       redirectPort="8443" /> 

<Connector port="8009"
       enableLookups="false"
       maxProcessors="0"
       maxThreads="3000"
       minSpareThreads="100"
       maxSpareThreads="200"
       protocol="AJP/1.3"
       enableLookups="false"
       connectionTimeout="600000"
       redirectPort="8443" />

优化配置核心关键:
(一)prefork模式下(其他模式下不适用),apache需要优化的主要参数:
    ServerLimit       3000
    StartServers      750
    MinSpareServers   5
    MaxSpareServers   100
    MaxClients        3000
    MaxRequestsPerChild   10000

首先来看看apache各个参数的意义(引号里引用的是官方文档的描述):
(1)ServerLimit和MaxClients  服务器最大同时响应请求数
这个就是你当前配置的apache最大的并发响应数,对应的是apache的进程数,两个参数同时修改,MaxClients不得大于ServerLimit参数。
ServerLimit的大小,取决于你系统的资源,每个apache进程默认占用2M内存,基本可以按照这个公式来计算:最大内存*80%/2M=ServerLimit

(2)StartServers  750   启动时默认启动的进程数
这个参数默认是5,因为apache会通过自动启动新进程来增加响应服务的进程数,这个值不做调整的也是可以的,会由默认的5增加到满足服务的进程数,但是会出现开始启动时的卡住。
小启动参数有一个好处:就是可以让传递后后端tomcat的压力缓慢增加上来,而不是一下子增加压力。可以把这个调整到当前服务最大的并发数,当前服务最大并发连接数,可以通过监控apache进程个数:ps -ef | grep httpd | wc -l 来获得。不用调得太大,否则是无谓增加apache通过jk去跟tomcat建立的连接。

注意:apache进程跟tomcat建立连接后,不会释放此连接,会一直保持连接,直到timeout,如果没有timeout时间,就会永久连接。timeout的设置,会在后面jk配置里说明。

所以不要一次启动太多的apache进程,只启动足够用的进程即可。其他增加的流量,apache会自动调整进程数,直到MaxClients参数限定的范围。

(3)MinSpareServers 5   最小空闲进程
MinSpareServers指令设置空闲子进程的最小数量。所谓空闲子进程是指没有正在处理请求的子进程。如果当前空闲子进程数少于MinSpareServers ,那么Apache将以第一秒一个,第二秒两个,第三秒四个,按指数递增个数的速度产生新的子进程。
(4)MaxSpareServers 10  最大空闲进程
MaxSpareServers指令设置空闲子进程的最大数量。所谓空闲子进程是指没有正在处理请求的子进程。如果当前有超过MaxSpareServers数量的空闲子进程,那么父进程将杀死多余的子进程。

可以调整这两个参数,但是这两个参数的值不能设得太大,否则apache进程太多,会导致对应开启的tomcat进程也会很多。
官网上关于这两个参数都有这么句话:“将此参数设的太大通常是一个坏主意。”
在一台压力大(并发访问2800)的服务器上,MaxSpareServers这个值设置的是200。
设置了这个值的好处是不会有太多的空闲的进程在消耗资源,同时减少apache和tomcat的连接端口。
关闭空闲apache进程的同时,会释放jk连接,同时释放tomcat连接数,进而减少系统资源消耗。

(5)MaxRequestsPerChild   10000
"MaxRequestsPerChild指令设置每个子进程在其生存期内允许伺服的最大请求数量。到达MaxRequestsPerChild的限制后,子进程将会结束。如果MaxRequestsPerChild为"0",子进程将永远不会结束。
将MaxRequestsPerChild设置成非零值有两个好处:
    * 可以防止(偶然的)内存泄漏无限进行,从而耗尽内存。
    * 给进程一个有限寿命,从而有助于当服务器负载减轻的时候减少活动进程的数量。
注意
对于KeepAlive链接,只有第一个请求会被计数。事实上,它改变了每个子进程限制最大链接数量的行为。"

也就是说实际上这个时候子进程最大连接数等于MaxRequestsPerChild*MaxKeepAliveRequests
所以在开启KeepAlive后,需要同时设置MaxRequestsPerChild和MaxRequestsPerChild,确保每个apache进程在服务一定请求数后会关闭,重新开启新的子进程,避免apache进程异常导致的内存泄露和资源占用。

(6)Keep-Alive
默认:ON
发送的请求,在MaxRequestsPerChild里面只算一个,不管这个连接发送了多少个请求。

(7)MaxKeepAliveRequests
默认:100
"一个建立好的Keep-Alive连接,允许发送的请求的个数。一旦建立连接,要么就是个数达到了断开,要么就是等KeepAliveTimeout时间到了断开连接。
MaxKeepAliveRequests指令限制了当启用KeepAlive时,每个连接允许的请求数量。如果将此值设为"0",将不限制请求的数目。我们建议最好将此值设为一个比较大的值,以确保最优的服务器性能。"

这个数字的设置,必须考虑在一个时间段内,同一个用户访问你的服务会发多少请求。要结合KeepAliveTimeout参数来考虑。
举个例子,用户需要间隔时间不大于KeepAliveTimeout的时间内,连续请求10个文件,那么这个参数就应该设置成10,如果用户在连续时间里不断请求访问,则这个数值得设置得更多。否则就重新建立连接下载。一旦用户连续进行了10个请求后,并且这个用户肯定在完成这些请求后的5秒内不会再请求,甚至要在之后的很长时间后请求,那么这个KeepAliveTimeout时间就可以设置得很短,以便尽早断开这种用户,把资源让个其他用户。


(8)KeepAliveTimeout
默认:5
"在一个建立好的Keep-Alive连接上,在MaxKeepAliveRequests个数未满的情况下,等待下一个请求的时间。"

如果有请求到达,那么apache等待IO响应的timeout时间时间开始生效,timeout时间没等到响应,连接被断开;如果KeepAliveTimeout时间内,没有请求到达,连接就被断开。
具体设置可以参考配合MaxKeepAliveRequests参数。同时这个参数又受TimeOut参数影响,在一次成功连接中,TimeOut时间内没有等到响应,也会断开连接。


(9)TimeOut
默认:300
"TimeOut指令用于设置Apache等待以下三种事件的时间长度:

   1. 接受一个GET请求耗费的总时间。
   2. POST或PUT请求时,接受两个TCP包之间的时间。
   3. 应答时TCP包传输中两个ACK包之间的时间。

我们计划在发展里程中,逐步把它们分别变得更易配置。计时器在1.2版本之前的默认值为1200,而现在已经设置为300了,但对于绝大多数情况来说仍是足够的。没有把它默认值设的更小的原因在于代码里还有点问题:有时发送一个包之后,计时器没有复位。”


(二 )调整jk参数配置和tomcat设置
jk连接主要给每个worker添加了两个参数:
 
worker.tomcat1.type=ajp13
worker.tomcat1.host=localhost
worker.tomcat1.port=8109
worker.tomcat1.lbfactor=1
worker.tomcat1.socket_timeout=10
worker.tomcat1.connection_pool_timeout=600


首先来了解一下这两个配置参数:
worker.tomcat1.socket_timeout=10
worker.tomcat1.connection_pool_timeout=600

socket_timeout默认为0
设置JK与远程服务器的Socket连接超时秒数,如果超出此秒数则产生一个错误,并再次重试。如果为0,JK会一直等下去。在连接出现异常的时候,尽快关闭连接,从而保证无用的socket会被回收。

connection_pool_timeout:默认值0,单位秒
在连连池中维护的非活动连接连续多少秒后被释放。如果为0,则不释放。详细可以参考tomcat官方文档,jk在处理连接的时候,是用一个线程池建立跟tomcat的连接,并且把所有连接都放在连接池。如果这个值为0的时候,它就不会释放任何已经空闲的连接,也就是说这个连接池在某个瞬间压力很大的话,被撑大了就不会小下来。所以这个值一定不能使用默认的0,必须修改。官方推荐是10分钟,也就是600秒。一旦设置了这个值,就一定要注意:必 须同时设置对应的tomcat的ajp连接器配置里的connectionTimeout参数大小跟它完全一致!同时必须注意这两个参数单位不一样:jk connection_pool_timeout单位是秒,而tomcat connectionTimeout单位是毫秒。

来解释一下为什么必须设置两个参数完全一致:这两个参数的意思是在连接空闲多长时间后关闭空闲连接(不包括活动的连接)。想象一下这边apache jk模块开启了一个连接A,连接到tomcat的B,这个连接被固化了,保持在连接池里。这个时候,如果A突然关闭,导致的结果是B返回的数据不知道给谁,tomcat会抛出socket异常;如果B突然关闭,则A突然失去连接,无法进行程序响应,会返回错误给用户。这就是所谓的半连接,一半是开的,另 外一半却关闭了,并且这个时候还不知道对方已经关了,程序会一直单方面开启着自己的这一半,导致资源浪费。所以必须设置两个的空闲关闭时间一致,这样两边同时开启,在空闲时间等待到了相同的时间后,同时关闭。这样jk和tomcat建立的连接在空闲时会慢慢变小,从而保证资源不被浪费,空闲的socket被关闭回收。


调整tomcat配置参数
minSpareThreads="100"
maxSpareThreads="200"
connectionTimeout="600000"

空闲线程不用太多,减少系统负载。同时设置 connectionTimeout和jk的connection_pool_timeout一致,保证空闲连接的同时回收。高并发情况下,这个时间可以设置得短一些,比如:20000 ms

此配额的优点是:
apache空闲线程少,同时产生的通过jk和tomcat建立的永久连接也少,并且和tomcat的连接能在空闲一定时间后自动回收。占用系统socket连接少。

可以通过以下命令对比查看各状态的socket连接:
查看当前tcp/ip连接状态:netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
查看tcp/ip连接数:cat /proc/net/sockstat


参考文档链接:
apache2.2文档:

prefork模块说明:
中文:http://lamp.linux.gov.cn/Apache/ApacheMenu/mod/prefork.html
英文:http://httpd.apache.org/docs/2.2/mod/prefork.html
各配置参数说明:
中文:http://lamp.linux.gov.cn/Apache/ApacheMenu/mod/mpm_common.html
英文:http://httpd.apache.org/docs/2.2/mod/mpm_common.html

tomcat6参数说明文档:http://tomcat.apache.org/tomcat-6.0-doc/config/ajp.html

jk workers.properties
参数说明文档:http://tomcat.apache.org/connectors-doc/reference/workers.html



如何进行系统优化的思考
引用
前两天跟同事讨论,说到高并发系统如何做优化,提到这个问题,他说他有些茫然,有点不知道该如何下手。
我想了想这几年做的各种系统优化工作,正好也简单总结一下,总结起来就是:一个核心,N种手段。
一个核心就是:多、快、准。
N种手段就要围绕上面的核心做的各种处理。

上面这个核心字多点说也就是:更多用户访问、更短响应时间、数据正确性。
优化的过程,我的想法就是先顺藤摸瓜,沿着一个请求发生的路径一路看过去,测量一下每个点上消耗的时间,会发现很多消耗时间多的点,都是值得你去优化的地方。然后再考虑在每个点上发生了拥挤导致响应时间变长了又该怎么解决。
当然也不需要一上来就全面优化,连影响最小的地方也不放过。先优化对你的性能影响最大的地方,效果是最好的,解决主要矛盾才是关键。不同的情况下,会有不同的优化方式。

简单地来看一个浏览器用户访问的流程:
浏览器->服务器->返回结果显示
这么简单地看,可能想得到的优化手段很少,常见的可能就是优化sql,加快数据库处理;加个缓存,加快返回;使用静态文件,减少动态计算。

细分开来看每一个步骤:
1 浏览器发起一个请求,如果本地有缓存会请求本地缓存文件,没有缓存会请求服务器。所以这里就有一个优化点:需要把常用的css和js文件独立成独立的静态文件,一次加载以后,后面直接加载本地缓存。另外IE浏览器内核在请求图片下载时会限制一次只能同时从同一个域名下载两个文件,这里又有优化点,分散图片存储的域名。使用静态文件,减少计算的同时增加本地缓存的使用,减少请求。静态化是常见的一种优化手段。

2 浏览器真实发起请求服务器时,首先被请求到的是服务器的操作系统层,那么服务器的操作系统对外界连接的响应能力,就是你需要了解的东西了。比如linux的内核参数的调整如何影响最大连接数,简单的一个例子就是在一个默认最大文件句柄数只有1024的服务器上,超过这个压力的时候,你如何优化你的程序,也都没有意义。入口只有那么窄,你得把口给扩开。熟悉服务器的性能,调优系统内核也是一个必要的手段。

3 系统层再把连接交给你的server做处理的时候,server的配置这个时候也相当重要。比如apache的最大连接数,tomcat的最大连接数。对server的配置调优很影响性能。比如tomcat在处理静态文件上的能力比apache要差很多,所以在apache+tomcat的负载均衡就能很好地进行动静请求的分离,提高响应速度。又比如tomcat新版本里的NIO技术又比普通IO性能好上不少。对server的了解,要保持跟踪最新动态。

4 server再把数据交给你的程序处理的时候,就到了考验你编程能力的时候了。你得对你的程序的执行效率非常清楚。必须保证每个响应都在尽量短的时间内执行成功。还有比较常见的一些对不常更新的数据使用内存缓存来加快访问。内存永远是最快的。这方面的优化也有非常非常多的事情可以去做,而且跟你的编程息息相关。

5 程序处理的时候,数据库连接池的使用,连接池大小的配置,连接池性能的优化,sql语句的优化,等等都可能影响你的程序的效率,这些地方永远是值得关注的。当然,优秀的算法在这个地方是少不了的。一个好的业务逻辑设计,可能极大提升你的程序性能。对数据库操作的调优也是一个永远的话题。

6 数据传递到数据库进行保存和查询的时候,你就必须对你的数据库的使用有所了解,知道数据库本身的哪些配置可以优化从而带来性能的提升。一个简单的例子就是在内存足够大的时候,增大mysql的内存缓存就可以极大提升它的响应速度。

7 现在server把数据返回给用户了,那么返回的数据的大小又同样影响着结果的显示速度。尽量减小数据的大小,比如开启apache的gzip就能极大压缩常见的静态文件,可以保证用户更快完出数据的下载,同时节省你的服务器使用带宽,老板一定会很高兴的。

8 用户下次访问的时候,同样面临一个优化的方式:是利用上次跟服务器建立好的连接再次通讯呢?还是重现跟服务器建立连接?这就是在server端做配置要考虑的一个问题,在低并发下,保持跟用户建立的socket连接,并且让用户通过这个连接来多次访问,可以提高速度。但是在高并发下,大量这种建立好的连接就意味着其他用户失去了进来的机会。所以这个是需要权衡的。一般情况下最好可以预估一下一个用户可能在多长的时间里连续发起多少个请求,然后可以把用户断开,把资源用来服务其他用户。

9 ajax技术也是在减少大请求,使用更小的局部数据更新来代替整个页面的刷新,加快用户的响应速度,结合静态化能完美改善性能。

这是对一个用户的访问的时候的考虑,然后就要考虑多用户情况的问题(有些是上面提到过的):
1 操作系统对多用户访问时的一些限制的优化
2 server的并发量的优化
3 多用户并发下,更多地要仔细考虑程序在数据操作的并发上的问题。比如对象的锁,数据库的锁,事务,等待处理的数据的排队方式等等。你需要知道读写分离的好处,应该隔离不同操作间的等待。另外并发带来的锁等待问题需要极大地关注,往往不是在内存就是在数据库里,发生着大量并发锁等待,导致你的程序缓慢。
4 对数据库的锁的机制必须深入了解,比如mysql不同引擎的带来的锁表和行级锁对性能的影响。同时要在自己的逻辑处理上要控制好不同用户同时操作的问题,时刻要绷紧这个弦。多数据集群,读写分离等等机制也是需要深入了解的。

以上是我一些简单的零碎总结,并不全面,也不细致,主要想表达一个思考的过程。不在于你都学会了什么,发现问题,研究问题,解决问题的能力,才是最重要的。
其实每一个点上,基本上你都可以找到N多牛人写的很牛很细致的书来讲具体怎么优化的,需要的时候可以好好去找书来研究一下。
深入去了解每一个点上的优化,你会发现,互联网的魅力真的是奇妙无穷!


http://hillside.iteye.com/blog/580639
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics