javaweb学习笔记

前言  
做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代码    收藏代码
  1. Timeout 10  
  2. KeepAlive On  
  3. MaxKeepAliveRequests 5  
  4. KeepAliveTimeout 5  
  5.   
  6. <IfModule mpm_prefork_module>  
  7.     ServerLimit       3000  
  8.     StartServers      750  
  9.     MinSpareServers   5  
  10.     MaxSpareServers   100  
  11.     MaxClients        3000  
  12.     MaxRequestsPerChild   10000  
  13. </IfModule>  


(b)workers.properties配置: 

Xml代码    收藏代码
  1. worker.list=loader  
  2.   
  3. worker.loader.type=lb  
  4. #两tomcat负载均衡  
  5. worker.loader.balance_workers=tomcat1,tomcat2  
  6. #不同步session  
  7. worker.loader.sticky_session=false  
  8.   
  9. #负载1  
  10. worker.tomcat1.type=ajp13  
  11. worker.tomcat1.host=localhost  
  12. worker.tomcat1.port=8009  
  13. #负载均衡因子,决定apache分发的比例  
  14. worker.tomcat1.lbfactor=1  
  15. worker.tomcat1.socket_timeout=10#配置超时时间  
  16. worker.tomcat1.connection_pool_timeout=600#配置关闭空闲连接时间  
  17.   
  18. #负载2  
  19. worker.tomcat2.type=ajp13  
  20. worker.tomcat2.host=localhost  
  21. worker.tomcat2.port=8109  
  22. #负载均衡因子,决定apache分发的比例  
  23. worker.tomcat2.lbfactor=1  
  24. worker.tomcat2.socket_timeout=10#配置超时时间  
  25. worker.tomcat2.connection_pool_timeout=600#配置关闭空闲连接时间  


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

Xml代码    收藏代码
  1. <Connector port="8009"   
  2.        enableLookups="false"  
  3.        maxProcessors="0"  
  4.        maxThreads="3000"  
  5.        minSpareThreads="100"  
  6.        maxSpareThreads="200"  
  7.        protocol="AJP/1.3"   
  8.        enableLookups="false"  
  9.        connectionTimeout="600000"  
  10.        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 关于nginx,不了解的请先阅读这里:http://wiki.nginx.org/Main,中文文档:http://wiki.nginx.org/NginxChs 
这是最近比较流行的一个轻量级的web server,在我的同机压力测试实验中,响应能力是apache的3倍以上。功能比apache简单,小巧,最大并发连接能到3万。 

对于nginx的使用,我也是近期才开始,使用的目的是在不增加硬件的条件下提升服务响应能力,以下配置适用环境: 
linux + nginx0.7.64+ tomcat6 

nginx同时是一个很强的反向代理server,可以用来跟tomcat做负载均衡集群,而且配置很简单,不过有个需要注意的地方:目前nginx跟后端server使用http1.0协议,不能keepalive,只能每次重新打开连接。需要关闭后端的keepalive,否则会产生大量后端的timewait状态的socket连接。 

1 nginx代理tomcat关键配置代码如下: 
Xml代码    收藏代码
  1. server {  
  2.     listen          80;  
  3.     server_name     YOUR_DOMAIN;  
  4.     root            /PATH/TO/YOUR/WEB/APPLICATION;  
  5.     location / {  
  6.       index.jsp;  
  7.     }  
  8.     location ~ ^/WEB-INF/* {#禁止访问被保护的目录  
  9.       deny all;  
  10.     }    
  11.     location ~ \.do$ {  
  12.       proxy_pass              http://localhost:8080;#转发给tomcat端口,也可以是任何server。  
  13.       proxy_set_header        X-Real-IP $remote_addr;#转发客户端真实IP  
  14.       proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;  
  15.       proxy_set_header        Host $http_host;  
  16.     }                                                                                                         
  17.    location ~ \.jsp$ {  
  18.       proxy_pass              http://localhost:8080;  
  19.       proxy_set_header        X-Real-IP $remote_addr;  
  20.       proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;  
  21.       proxy_set_header        Host $http_host;  
  22.     }  
  23.     location ~ ^/servlet/* {  
  24.       proxy_pass              http://localhost:8080;  
  25.       proxy_set_header        X-Real-IP $remote_addr;  
  26.       proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;  
  27.       proxy_set_header        Host $http_host;  
  28.     }  
  29.   }  


2 nginx负载均衡核心配置代码: 
Xml代码    收藏代码
  1. http {  
  2.     upstream myproject {  
  3.       server 127.0.0.1:8000 weight=3;  
  4.       server 127.0.0.1:8001;  
  5.       server 127.0.0.1:8002;     
  6.       server 127.0.0.1:8003;  
  7.     }  
  8.    server {  
  9.       listen 80;  
  10.       server_name www.domain.com;  
  11.       location / {  
  12.         proxy_pass http://myproject;#myproject 前面要加http://,否则异常  
  13.       }  
  14.     }  
  15.   }  


3 nginx+tomcat负载均衡简单配置:不用修改任何tomcat配置,只要tomcat开启http1.1协议端口(tomcat默认开启) 
Xml代码    收藏代码
  1. http {  
  2.     upstream myproject {  
  3.       server 127.0.0.1:8000 weight=3;  
  4.       server 127.0.0.1:8001;  
  5.       server 127.0.0.1:8002;     
  6.       server 127.0.0.1:8003;  
  7.     }  
  8.    server {  
  9.       listen 80;  
  10.       server_name www.domain.com;  
  11.       root /PATH/TO/YOUR/WEB/APPLICATION;  
  12.       location / {  
  13.         index.jsp;  
  14.        }  
  15.       location ~ ^/WEB-INF/* {#禁止访问被保护的目录  
  16.            deny all;  
  17.        }   
  18.      location ~ \.do$ {  
  19.         proxy_pass              http://myproject;#转发给tomcat端口,也可以是任何server。  
  20.         proxy_set_header        X-Real-IP $remote_addr;  
  21.         proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;  
  22.         proxy_set_header        Host $http_host;  
  23.       }                                                                                                        
  24.      location ~ \.jsp$ {  
  25.         proxy_pass              http://myproject;#myproject可以任意自定义,但是一定要在前面加http://  
  26.         proxy_set_header        X-Real-IP $remote_addr;  
  27.         proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;  
  28.         proxy_set_header        Host $http_host;  
  29.       }   
  30.      location ~ ^/servlets/* {  
  31.         proxy_pass              http://myproject;  
  32.         proxy_set_header        X-Real-IP $remote_addr;  
  33.         proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;  
  34.         proxy_set_header        Host $http_host;  
  35.       }  
  36.     }  
  37.   }  


4 实际环境复杂配置:tomcat动态负载均衡 + 静态文件 
Xml代码    收藏代码
  1. worker_processes  8;  
  2.   worker_rlimit_nofile 65535;  
  3.   events {  
  4.       use epoll;#开启epoll模式  
  5.       worker_connections  65535;  
  6.   }  
  7.  http {  
  8.       include        mime.types;  
  9.       default_type  application/octet-stream;  
  10.     
  11.      #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '  
  12.      #                 '$status $body_bytes_sent "$http_referer" '  
  13.      #                 '"$http_user_agent" "$http_x_forwarded_for"';  
  14.      #access_log  logs/access.log  main;  
  15.   
  16.      sendfile        on;#linux下强大的静态文件发送功能,一定要开启  
  17.      tcp_nopush     on;  
  18.      tcp_nodelay on;  
  19.   
  20.      #keepalive_timeout  0;  
  21.      keepalive_timeout  5;  
  22.      keepalive_requests 10;  
  23.      server_names_hash_bucket_size 128;   
  24.   
  25.      client_header_timeout  10;  
  26.      client_body_timeout    10;  
  27.      send_timeout           10;  
  28.   
  29.      client_header_buffer_size    1k;  
  30.      large_client_header_buffers  4 4k;  
  31.      client_max_body_size 8m;   
  32.   
  33.      gzip on;#开启gzip,节省带宽  
  34.      gzip_min_length  1100;  
  35.      gzip_buffers     4 8k;  
  36.      gzip_types        text/xml;   
  37.   
  38.      output_buffers   1 32k;  
  39.      postpone_output  1460;  
  40.   
  41.      limit_rate_after 3m;#限速模块,前3M下载时不限速  
  42.      limit_rate 512k; #限速模块  
  43.   
  44.      upstream  statproxy  {#负载均衡模块,核心配置  
  45.         server   127.0.0.1:8080;  
  46.         server   127.0.0.1:8180;  
  47.     }  
  48.   
  49.     server {#纯静态文件访问  
  50.          listen        80;  
  51.          server_name  a.udomain.com;  
  52.          index index.html index.php;  
  53.          root  /PATH/TO/YOUR/WEB/APPLICATIONA;  
  54.          #charset koi8-r;  
  55.          #access_log  logs/host.access.log  main;  
  56.          #error_page  404              /404.html;  
  57.          # redirect server error pages to the static page /50x.html  
  58.          #  
  59.          error_page   500 502 503 504  /50x.html;  
  60.   
  61.          location = /50x.html {  
  62.             root   html;  
  63.          }  
  64.   
  65.          location ~ ^/server-status/ {#服务器状态监控,可以通过:http://a.udomain.com/server-status/访问,需要在编译安装是添加此模块,默认不带。  
  66.              stub_status on;  
  67.              access_log off;  
  68.          }  
  69.      }  
  70.   
  71.      server {#动态负载均衡 + 动态静态分离  
  72.          listen        80;  
  73.          server_name  b.udomain.com;  
  74.          index index.html index.php;  
  75.          root  /PATH/TO/YOUR/WEB/APPLICATIONB;  
  76.          error_page   500 502 503 504  /50x.html;  
  77.          location ~ ^/WEB-INF/* {#禁止访问被保护的目录  
  78.              deny all;  
  79.          }  
  80.          location ~ \.do$ {  
  81.              proxy_pass              http://statproxy;#经负载均衡模块转发  
  82.              proxy_set_header        X-Real-IP $remote_addr;  
  83.              proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;  
  84.              proxy_set_header        Host $http_host;  
  85.              }  
  86.   
  87.          location ~ \.jsp$ {  
  88.              proxy_pass              http://statproxy;  
  89.              proxy_set_header        X-Real-IP $remote_addr;  
  90.              proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;  
  91.              proxy_set_header        Host $http_host;  
  92.          }  
  93.   
  94.          location ~ ^/servlet/* {  
  95.              proxy_pass              http://statproxy;  
  96.              proxy_set_header        X-Real-IP $remote_addr;  
  97.              proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;  
  98.              proxy_set_header        Host $http_host;  
  99.          }   
  100.      }  
  101.   
  102.      server {#动态无负载均衡+静态访问  
  103.         listen        80;  
  104.          server_name  c.udomain.com;  
  105.          index index.html index.php;  
  106.          root  /PATH/TO/YOUR/WEB/APPLICATIONC;  
  107.          #charset koi8-r;  
  108.          #access_log  logs/host.access.log  main;  
  109.          #error_page  404              /404.html;  
  110.          # redirect server error pages to the static page /50x.html  
  111.         #  
  112.         error_page   500 502 503 504  /50x.html;  
  113.          location = /50x.html {  
  114.              root   html;  
  115.          }  
  116.   
  117.          location ~ ^/WEB-INF/* {#禁止访问被保护的目录  
  118.             deny all;  
  119.          }   
  120.   
  121.          location ~ \.jsp$ {  
  122.              proxy_pass              http://localhost:8280;#直接转发  
  123.              proxy_set_header        X-Real-IP $remote_addr;  
  124.              proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;  
  125.              proxy_set_header        Host $http_host;  
  126.          }  
  127.   
  128.          location ~ \.do$ {  
  129.              proxy_pass              http://localhost:8280;  
  130.              proxy_set_header        X-Real-IP $remote_addr;  
  131.              proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;  
  132.              proxy_set_header        Host $http_host;  
  133.          }  
  134.   
  135.          location ~ ^/servlet/* {  
  136.              proxy_pass              http://localhost:8280;  
  137.              proxy_set_header        X-Real-IP $remote_addr;  
  138.              proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;  
  139.              proxy_set_header        Host $http_host;  
  140.          }  
  141.      }  
  142.  }  


5 tomcat端配置: 
默认开启http1.1协议即可。建议使用NIO连接器。关于NIO,可以参考tomcat官方文档:http://tomcat.apache.org/tomcat-6.0-doc/config/http.html 

注意问题: 
nginx+tomcat下: 
request.getRemoteAddr()会取不到正确的ip地址的,但是 
proxy_set_header        X-Real-IP $remote_addr; 
这个配置把正确的ip地址放在了header里了,可以通过: 
String ip = request.getHeader("X-Real-IP"); 
获取到正确的用户ip地址,当然这样会需要修改程序,的确不是个好的办法,不能适用所有场景。 

参考文档: 
nginx文档:http://wiki.nginx.org/Main,中文文档:http://wiki.nginx.org/NginxChs 
nginx+tomcat配置示例:http://wiki.nginx.org/NginxJavaServers 
nginx负载均衡配置示例:http://wiki.nginx.org/NginxLoadBalanceExample 

tomcat nio配置参数文档:http://tomcat.apache.org/tomcat-6.0-doc/config/http.html 

web程序,如果不做压力测试上线,往往会遇到多线程抢锁或者同时修改内存对象和高并发响应缓慢问题。 
所以最好是在上线前做一些压力测试,一个简单的apache自动的压力测试工具还是非常好用的。 
各种配置下的web server的响应能力,可以通过ab来进行压力测试,进而得出一个适合自己系统的配置。毕竟不同的应用场景,配置需求是会不一样的,不太可能通用。 

关于ab的使用,网络上有很多介绍,以下内容摘自网络: 
apache自带的测试工具AB(apache benchmark).在apache的bin目录下。 
格式: 
ab [ -A auth-username:password ] [ -c concurrency ] [ -C cookie-name=value ] [ -d ] [ -e csv-file ] [ -g gnuplot-file ] [ -h ] [ -H custom-header ] [ -i ] [ -k ] [ -n requests ] [ -p POST-file ] [ -P proxy-auth-username:password ] [ -q ] [ -s ] [ -S ] [ -t timelimit ] [ -T content-type ] [ -v verbosity] [ -V ] [ -w ] [ -x <table>-attributes ] [ -X proxy[:port] ] [ -y <tr>-attributes ] [ -z <td>-attributes ] [http://]hostname[:port]/path 

参数: 
-A auth-username:password 
向服务器提供基本认证信息。用户名和密码之间由一个":"隔开,并将被以base64编码形式发送。无论服务器是否需要(即是否发送了401认证需求代码),此字符串都会被发送。 
-c concurrency 
一次产生的请求个数。默认是一次一个。 
-C cookie-name=value 
对请求附加一个"Cookie:"头行。其典型形式是 name=value 的一个参数对。此参数可以重复。 
-d 
不显示"percentage served within XX [ms] table"消息(为以前的版本提供支持)。 
-e csv-file 
产生一个逗号分隔(CSV)文件,其中包含了处理每个相应百分比请求(从1%到100%)所需要的相应百分比时间(以微秒为单位)。由于这种格式已经"二进制化",所以比"gnuplot"格式更有用。 
-g gnuplot-file 
把所有测试结果写入一个"gnuplot"或者TSV(以Tab分隔)文件。此文件可以方便地导入到 Gnuplot, IDL, Mathematica, Excel中。其中的第一行为标题。 
-h 
显示使用方法的帮助信息。 
-H custom-header 
对请求附加额外的头信息。此参数的典型形式是一个有效的头信息行,其中包含了以冒号分隔的字段和值(如:"Accept-Encoding: zip/zop;8bit")。 
-i 
执行HEAD请求,而不是GET 。 
-k 
启用KeepAlive功能,即在一个HTTP会话中执行多个请求。默认不启用KeepAlive功能。 
-n requests 
在测试会话中所执行的请求个数。默认仅执行一个请求,此时其结果不具有意义。 
-p POST-file 
包含了POST数据的文件。 
-P proxy-auth-username:password 
对一个中转代理提供基本认证信息。用户名和密码由一个":"隔开,并将被以base64编码形式发送。无论服务器是否需要(即是否发送了407代理认证需求代码),此字符串都会被发送。 
-q 
如果处理的请求数大于150,ab每处理大约10%或者100个请求时,会在stderr输出一个进度计数。此 -q 标记可以屏蔽这些信息。 
-s 
用于编译中(ab -h 会告诉你)使用了SSL的受保护的https ,而不是http协议的时候。此功能是实验性的,最好不要用。 
-S 
不显示中值和标准偏差值,而且在均值和中值为标准偏差值的1到2倍时,也不显示警告或出错信息。默认时,会显示最小值/均值/最大值等数值。(为以前的版本提供支持) 
-t timelimit 
测试所进行的最大秒数。内部隐含值是"-n 50000"。它可以使对服务器的测试限制在一个固定的总时间以内。默认时,没有时间限制。 
-T content-type 
POST数据时所使用的"Content-type"头信息。 
-v verbosity 
设置显示信息的详细程度,4或更大值会显示头信息,3或更大值可以显示响应代码(404,200等),2或更大值可以显示警告和其他信息。 
-V 
显示版本号并退出。 
-w 
以HTML表格形式输出结果。默认时,它是白色背景的两列宽度的一张表。 
-x <table>-attributes 
设置<table>属性的字符串。此属性被填入<table 这里 > 。 
-X proxy[:port] 
对请求使用代理服务器。 
-y <tr>-attributes 
设置<tr>属性的字符串。 
-z <td>-attributes 
设置<td>属性的字符串。 


参数很多,一般我们用 -c 和 -n 参数就可以了. 例如: 

./ab -c 1000 -n 1000 http://127.0.0.1/index.html
 

这个表示同时处理1000个请求并运行1000次index.php文件。 
#/usr/local/xiaobai/apache2054/bin/ab -c 1000 -n 1000 http://127.0.0.1/index.html.zh-cn.gb2312 
This is ApacheBench, Version 2.0.41-dev <$Revision: 1.121.2.12 $> apache-2.0 
Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 
Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/ 

Benchmarking 127.0.0.1 (be patient) 
Completed 100 requests 
Completed 200 requests 
Completed 300 requests 
Completed 400 requests 
Completed 500 requests 
Completed 600 requests 
Completed 700 requests 
Completed 800 requests 
Completed 900 requests 
Finished 1000 requests 


Server Software: Apache/2.0.54 
//平台apache 版本2.0.54 
Server Hostname: 127.0.0.1 
//服务器主机名 
Server Port: 80 
//服务器端口 

Document Path: /index.html.zh-cn.gb2312 
//测试的页面文档 
Document Length: 1018 bytes 
//文档大小 

Concurrency Level: 1000 
//并发数 
Time taken for tests: 8.188731 seconds 
//整个测试持续的时间 
Complete requests: 1000 
//完成的请求数量 
Failed requests: 0 
//失败的请求数量 
Write errors: 0 

Total transferred: 1361581 bytes 
//整个场景中的网络传输量 
HTML transferred: 1055666 bytes 
//整个场景中的HTML内容传输量 
Requests per second: 122.12 [#/sec] (mean) 
//大家最关心的指标之一,相当于 LR 中的 每秒事务数 ,后面括号中的 mean 表示这是一个平均值 
Time per request: 8188.731 [ms] (mean) 
//大家最关心的指标之二,相当于 LR 中的 平均事务响应时间 ,后面括号中的 mean 表示这是一个平均值 
Time per request: 8.189 [ms] (mean, across all concurrent requests) 
//每个请求实际运行时间的平均值 
Transfer rate: 162.30 [Kbytes/sec] received 
//平均每秒网络上的流量,可以帮助排除是否存在网络流量过大导致响应时间延长的问题 

Connection Times (ms) 
min mean[+/-sd] median max 
Connect: 4 646 1078.7 89 3291 
Processing: 165 992 493.1 938 4712 
Waiting: 118 934 480.6 882 4554 
Total: 813 1638 1338.9 1093 7785 
//网络上消耗的时间的分解,各项数据的具体算法还不是很清楚 

Percentage of the requests served within a certain time (ms) 
50% 1093 
66% 1247 
75% 1373 
80% 1493 
90% 4061 
95% 4398 
98% 5608 
99% 7368 
100% 7785 (longest request) 
//整个场景中所有请求的响应情况。在场景中每个请求都有一个响应时间,其中50%的用户响应时间小于1093 毫秒,60% 的用户响应时间小于1247 毫秒,最大的响应时间小于7785 毫秒 

由于对于并发请求,cpu实际上并不是同时处理的,而是按照每个请求获得的时间片逐个轮转处理的,所以基本上第一个Time per request时间约等于第二个Time per request时间乘以并发请求数 



以上四章,本着工欲善其事,必先利其器的想法,简单总结一下web server和压力测试工具,为web开发,准备好前端环境。 

参考文档: 
apache官方文档:http://httpd.apache.org/docs/2.2/programs/ab.html 

中文文档:http://lamp.linux.gov.cn/Apache/ApacheMenu/programs/ab.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多牛人写的很牛很细致的书来讲具体怎么优化的,需要的时候可以好好去找书来研究一下。 
深入去了解每一个点上的优化,你会发现,互联网的魅力真的是奇妙无穷!


你可能感兴趣的:(javaweb学习笔记)