标签归档:Apache

绕过代理服务器获取客户端IP

一般来说,Web应用和客户端之间隔着很多个代理服务器,无论正向代理还是反向代理。这样就给本地资讯类应用(新闻、天气等)或者统计(审计,追踪等)需求带来了麻烦,因为在应用通过request.getRemoteAddr()方法(以Java为例)往往只能拿到与自己直接通信的设备IP。因此,如果用户通过直接或者间接的正向代理(ISP提供的缓存服务器等)上网,Web应用只会取到正向代理服务器地址;如果Web应用前端部署着Nginx或者Apache之类的反向代理,Web应用只会取到反向代理的地址。

面对这样的限制,代理厂商利用HTTP自定义Header规避了问题。比如Nginx,可以添加以下配置:

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

它向后端应用传递了两个HTTP自定义Header,X-Real-IP和X-Forwarded-For。这两个Header作用类似,都是向后端透传客户端源IP,但是又有一些不同,主要区别在于:

1. X-Forwarded-For已有标准RFC文档(RFC 7239)定义,而X-Real-IP没有。
2. X-Forwarded-For Header格式为X-Forwarded-For: client1, proxy1, proxy2,每级代理都会将与自己直接通信的对端IP追加在X-Forwarded-For中,因此应用还需要额外解析IP列表以获取所需IP。而X-Real-IP只会记录与自己直接通信的对端IP。

由此,反向代理后面的应用服务器可以通过X-Real-IP或者X-Forwarded-For请求头来获取客户端IP:

String xRealIp = request.getHeader("X-Real-IP");
String xForwardIps = request.getHeader("X-Forwarded-For");

另外需要注意的是,HTTP Header是很容易被伪造的,因此,X-Forwarded-For Header中的首个IP也不意味着就一定是客户端源IP。从安全性方面来说,如果应用前不加反向代理,则request.getRemoteAddr()拿到的IP便是可信的,无法伪造;如果加了反向代理,那么X-Real-IP和X-Forwarded-For IP的最后一个IP是可信的,无法伪造。

--EOF--

多级反向代理下的维护页面配置

Nginx的error_page指令可以用来在服务器临时维护时呈现静态维护页面,增强用户体验,但是在稍微复杂的反向代理部署架构中,只有error_page还是不够的。比如:
proxy
Tomcat前端接Apache,Apache前端接Nginx,流量入口在Nginx。这样一个两级的反向代理结构下,如果服务器程序升级,Tomcat重启,Apache检测到Tomcat服务不可用时,向上级返回503,那么用户通过Nginx看到的,也是丑陋的503 Service Temporarily Unavailable页面,试图在Nginx配置中加上error_page 503 /50x.html页面也不会起作用。探其原因,在于error_page指令是用来处理由Nginx产生的错误,在Nginx看来,Apache工作正常,且能返回响应码和正确页面,只不过返回的页面对用户来说是个服务不可用页面。如果是Apache停掉,那么Nginx是能够正确呈现error_page指定的维护页面的。

所以,需要另外在Nginx配置中加入proxy_intercept_errors指令,官方说法是:

Determines whether proxied responses with codes greater than or equal to 300 
should be passed to a client or be redirected to nginx for processing with the 
error_page directive.

设置proxy_intercept_errors为on后,Nginx会解析被代理服务器的响应码,如果出现大于300的响应码,就会调用error_page指令处理,如果error_page指令中没有指定返回的错误码,那么Nginx仍会依样返回被代理服务器的响应。

以下为简化的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server {
 server_name domain.com;
 location / {
  ...
  proxy_pass ...;
  proxy_intercept_errors on;
 } 
 
 error_page 503 /50x.html;
 location = /50x.html {
  root /etc/nginx/maintein;
 }
 #50x.html中引入了同级目录下的static目录中的图片文件。
 location ~ /static {
  root /etc/nginx/maintein;
  rewrite ^(.*)/(static/.*)$ /$2 break;
 }
}

--EOF--

Log4j配置文件热更新

默认情况下Log4j配置修改后要重启才能生效,而产品上线以后不能轻易停服,有时候遇到线上bug需要调整日志级别,或者磁盘空间满了要调整日志输出路径,这时就要用到apache log4j提供的热更新配置文件接口:PropertyConfigurator.configureAndWatch()DOMConfigurator.configureAndWatch()。前者用来读取和热更新log4j.properties文件,后者用来读取和热更新log4j.xml文件。

用法:
1. 在ServletContextListener或者Tomcat LifecycleListener中调用configureAndWatch API:

PropertyConfigurator.configureAndWatch(log4jConfigFilename, 60000);

configureAndWatch API接受两个参数,log4jConfigFilename表示log4j.properties配置文件所在文件,后一个参数表示下次查看配置文件的间隔时间,默认是60秒(FileWatchdog.DEFAULT_DELAY)。程序中调用configureAndWatch API后,Log4j会单独起一个线程定期查看配置文件,如果发现配置文件有过修改,会启用新的配置。

2. 添加jvm shutdown hook,当jvm退出时可以清理现场。

Runtime.getRuntime().addShutdownHook(new Thread() {
    @Override
    public void run() {
        LogManager.shutdown();
    }
});

configureAndWatch API创建的线程不会在应用取消部署(undeploy)的时候自己结束,因此需要添加jvm shutdown hook来清理。

References:
[1]. Changing Log4j logging levels dynamically. http://blogs.justenougharchitecture.com/?p=185.
[2]. Automatically reload log4j configuration in tomcat. http://janvanbesien.blogspot.jp/2010/02/reload-log4j-configuration-in-tomcat.html.

--EOF--

apache+mod_jk+tomcat集群

目的:用apache (Apache/2.2.16 (Debian) )作为前端负载均衡,将客户端的请求反向代理到后端的tomcat集群。
步骤:
1. 在apache mods-enable目录中添加jk.load配置,加载mod_jk模块,指定workers.properties文件位置。
2. 修改apache的VirtualHost配置。加入:

1
2
3
<IfModule mod_jk.c>
JkMount /* myworker
</IfModule>

apache会将此虚拟主机接收到的请求全部反向代理到myworker。这里的myworker是一个虚拟出来的用来处理具体请求的实例名称,可以是一个tomcat实例,也可以是一个tomcat集群。本例中代表tomcat集群,详细信息在workers.properties文件中定义。

3. 修改各个tomcat实例的conf/server.xml文件,指定AJP端口号和jvmRoute,保证不冲突。apache推荐使用AJP协议,因为AJP直接走tcp协议和socket通道,效率高。jvmRoute是server.xml配置文件Engine节点的一个属性值,主要用来session绑定。本例中指定tomcat实例一的AJP端口8010,jvmRoute名称woker1,tomcat实例二的AJP端口8011,jvmRoute名称worker2。

4. 配置wokers.properties。wokers.properties用来定义集群中的tomcat实例,以及负载均衡策略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
worker.list=mywoker
 
worker.worker1.port=8010
worker.worker1.host=localhsot
worker.worker1.type=ajp13 
worker.worker1.lbfactor=2
worker.worker1.connection_pool_size=256
worker.worker1.connection_pool_timeout=600
worker.worker1.socket_keepalive=1
worker.worker1.socket_timeout=300
 
worker.worker2.port=8011
worker.worker2.host=localhost
worker.worker2.type=ajp13
worker.worker2.lbfactor=8
worker.worker2.connection_pool_size=256
worker.worker2.connection_pool_timeout=600
worker.worker2.socket_keepalive=1
worker.worker2.socket_timeout=300
 
worker.mywoker.type=lb
worker.mywoker.sticky_session=false
worker.mywoker.balance_workers=worker1, worker2
worker.mywoker.method=B

wokers.properties文件中的属性值必需以“woker.”开头,worker.list列出可以处理请求的实例列表,mod_jk就是将请求代理到列表中的这些实例,多个实例用逗号分隔。接下的配置格式均为worker.<worker name>.<directive>=<value>的形式定义。<worker name>是内部或外部配置文件中用到的实例名,<directive>代表一个属性,<value>代表属性对应的值。
* type属性的可选值为ajp13, ajp14, jni和lb等,常用ajp13和lb,lb表示集群类型实例,ajp13表示一个通过AJP访问的tomcat实例。
* port和host定义了tomcat实例的socket。
* connection_pool_size属性是AJP协议的tcp连接池大小。
* connection_pool_timeout表示连接池中非活动连接距离被回收的超时时间。
* socket_keepalive属性设为true告诉操作系统定时向tomcat实例发送keepalive心跳包,避免防火墙将连接关闭。
* socket_timeout表示mod_jk与tomcat实例建立socket连接的超时时间。
* lbfactor属性表示本实例在集群中的负载权重,值越大,则mod_jk代理到它的请求数相对越多。
* balance_workers是集群中的tomcat实例列表,用逗号分隔,如果需要绑定session,则这里的实例名称必须与tomcat配置文件中的jvmRoute值相同,同时置sticky_session值为true,这样mod_jk就可以把相同sessionid的请求转发到同一个tomcat实例。
* method属性是负载均衡采用的策略,可选值有R[equest]、S[ession]、 N[ext]、T[raffic]和B[usyness]。本例中设置method=B,该策略会根据tomcat实例的当前负载,分别除以各个实例的lbfactor值,选择值最小的tomcat实例,将请求转发过去。

关于woker.properties文件中的各个参数的更详细介绍参见Tomcat官方文档:『The Apache Tomcat Connector Reference Guide - workers.properties configuration』

--EOF--

ab压力测试

ab命令是apache自带的压力测试工具,全称apache bench。它可以模拟并发访问,得到服务器(或服务接口)的基本抗压情况,例如吞吐率、请求平均响应时间、用户平均响应时间等等。为减少网络延时带来的影响,ab测试最好放在服务器上或者与服务器在相同内网的机器上。

基本用法:

1
ab [options] [http[s]://]hostname[:port]/path

例如:

1
ab -n 30 -c 5 http://127.0.0.1:8080/sync

其中,-n指定请求个数,-c指定并发数,上面的命令表示模拟5个并发线程,向http://127.0.0.1:8080/sync发送30个请求。
其他较常用的参数还包括-H, 用来指定HTTP请求头(包括自定义头)。 -p用来指定POST请求体所在的文件位置。

如果正常执行,ab命令会返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
root$ ab -n 30 -c 5 http://127.0.0.1:8080/sync
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
 
Benchmarking 127.0.0.1 (be patient).....done
 
Server Software:        Apache-Coyote/1.1
Server Hostname:        127.0.0.1
Server Port:            8080
Document Path:          /sync
Document Length:        0 bytes
Concurrency Level:      5
Time taken for tests:   6.021 seconds
Complete requests:      30
Failed requests:        0
Write errors:           0
Total transferred:      3630 bytes
HTML transferred:       0 bytes
Requests per second:    4.98 [#/sec] (mean)
Time per request:       1003.568 [ms] (mean)
Time per request:       200.714 [ms] (mean, across all concurrent requests)
Transfer rate:          0.59 [Kbytes/sec] received
 
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       0
Processing:  1002 1003   0.5   1004    1004
Waiting:     1002 1003   0.5   1003    1004
Total:       1003 1003   0.5   1004    1004
 
Percentage of the requests served within a certain time (ms)
  50%   1004
  66%   1004
  75%   1004
  80%   1004
  90%   1004
  95%   1004
  98%   1004
  99%   1004
 100%   1004 (longest request)

其中,Server Software表示服务器server软件,Server Hostname是主机名,Server Port是主机监听端口号,Document Path是请求路径,Document Length是响应的文档大小,Concurrency Level为并发数,Time taken for tests是本次ab压力测试的总用时,Complete requests和Failed requests分别表示完成的请求数和请求失败次数,Total transferred为HTTP响应的总传输量,包含响应头和响应体,HTML transferred为响应体大小。

Requests per second表示每秒响应的请求数,即服务端的吞吐率,Time per request(mean)表示每个并发用户的请求平均响应时间,Time per request(mean, across all concurrent requests)表示每个请求的平均响应时间。这三个值是衡量服务端性能的重要指标,吞吐率和每个请求的平均响应时间互为倒数关系,每个并发用户的请求平均响应时间在数值上等于Time taken for tests / (Complete requests / Concurrency Level),这是从一个并发用户的角度看拿到的值,Complete requests / Concurrency Level是该用户发出的请求数,Time taken for tests是该用户的总用时。

P.S. 我的测试环境是:Mac OS X/10.8.2, Apache/2.2.22,ApacheBench/2.3,测试过程中会随机返回一些apr_socket_recv: Connection reset by peer (54),请求已经到达服务器并执行完毕,只是客户端读取服务器端返回值出了socket异常。Stackoverflow上有讨论,说是MacOS上老版本Apache的bug,升级到新版本即可。或者在命令行中加-r参数,表示“Don't exit on socket receive errors”,让程序继续跑完。

--EOF--