最近在做开发时,遇到了以下四个问题,总结一下。


1.Nginx+Struts中上传文件的大小限制。

在上传文件时,若文件太大会出现上传失败。我们的Web应用前端代理使用了Nginx,MVC使用了Struts,通过检查,发现在Nginx和Struts中对于上传文件的大小都做了限制。

在Nginx的http模块配置中,client_max_body_size这个参数用于表示http请求body最大值,由于文件也是以二进制形式存储于body中,因此client_max_body_size也限制了上传文件的大小,当上传文件较大时,可适当调大该值。

http {
    ...
    client_max_body_size 20m;
    ...
}

在Struts中,对于上传文件大小也有限制,默认大小为2M,可以在Struts配置文件中修改该默认配置。

<constant name="struts.multipart.maxSize" value="104857600"/>


2.Nginx代理对于请求响应超时时间的设置。

外部应用调用我们提供的http接口时,有时会返回504错误,但查看我们的应用日志,请求是被正确执行了,再查看Nginx配置,发现在Nginx代理配置中,proxy_read_timeout这个参数用于表示Nginx转发请求至实际应用后等待响应的时间,若超过该时间,则向客户端返回超时错误,通过适当调大该值解决问题。


3.同一个应用部署在不同服务器上导致的乱码问题。

为了实现高可用,我们将同一个应用部署在两台服务器上,前端通过Nginx实现负载均衡。启动后,发现应用有一个功能存在乱码问题,该功能是接收客户端传来的GBK编码字符串,并调用另一文件服务接口写到另一个服务器的磁盘中。我们发现该乱码问题只在客户端请求转发至这两台服务器上中的其中一台时存在。

通过检查发现,应用在接收客户端传来的GBK编码字符串时,会调用字符串的getByte()方法,进行解码,而getByte()方法在不指定编码格式时,会使用服务器的默认编码,而这两台服务器中,一台服务器的默认编码是GBK,能正确解码,另一台服务器的默认编码是UTF-8,则不能正确解码。

后来的解决方法是:由于应用是部署在resin容器中,因此修改resin的配置文件,在jvm参数配置中,指定服务器的默认编码为GBK。

<jvm-arg>-Dfile.encoding=GBK</jvm-arg>


4.Web应用负载过高

有一个Java Web应用部署在resin容器中,每隔一端时间,会导致服务器负载过高,但查看后台并没有报错信息,因而以前每次只能通过重启应用来解决该问题。

后来在网上找到一个解决该类问题的方法:

1)使用ps查看应用的进程id;

2)使用“top -H -p 进程id”查看进程中的线程列表,排在前面的线程占CPU较高;

3)使用“jstack 进程id”dump Java进程使用情况;

4)从Java进程dump中找到在2)中排在前面占CPU较高的线程,需要注意的是2)中的线程号是十进制,而3)中的线程号是十六进制,需要做一下转换,我们找到的占CPU较高的线程在dump中如下:

"http--8080-37$65442827" daemon prio=10 tid=0x00002aaabc0f3000 nid=0x2c53 runnable [0x000000004517b000]
java.lang.Thread.State: RUNNABLE
at java.util.HashMap.get(HashMap.java:303)
at com.sohu.cms.auth.impl.AuthenticationImpl.hasAction(AuthenticationImpl.java:170)
at com.sohu.cms.webapp.util.AuthenticationInterceptor.intercept(AuthenticationInterceptor.java:62)

发现问题发生在HashMap,这里我们定义了一个static HashMap对象,在多个客户端发起请求时,会并发访问该HashMap对象时,由于HashMap并不是线程安全的,因而可能会引起问题。我们在网上找到一个问题现象和原因和我们类似的例子:http://shuaijie506.iteye.com/blog/1815213,其中提到问题根本原因是HashMap在resize时会造成死循环。

后来的解决方案是我们使用线程安全的ConcurrentHashMap来替代HashMap。