问题现象:
最近遇到一个很奇怪的线上问题:线上的管理后台web应用,经常在跑了一段时间后,访问出现404。
之前查过好几次日志,并没有报错或是其他异常,重新部署后又能正常访问了。直到最近又出现了,遂决定彻底查一下原因。
观察到的现象是:
1.管理页面无法正常访问,显示404。如上图。
2.访问该应用的restful接口,可以正常返回。
原因定位
通过现象可见,该应用并没有彻底down掉,因为接口还可以正常访问。所以排除了内存泄漏等导致应用挂掉的原因。
直观感觉是可能真的是找不到这个页面文件了,所以才会404。抱着这个思路试图找到jetty查找index.html文件的位置,即jetty将应用war包解压的位置。
查看jetty官方文档找到下面这一段:
By default, Jetty will create this directory inside the directory named by the java.io.tmpdir System property. You can instruct Jetty to use a different parent directory by setting the context attribute org.eclipse.jetty.webapp.basetempdir to the name of the desired parent directory. The directory named by this attribute must exist and be writeable.
翻译一下就是jetty默认使用java.io.tmpdir这一系统变量的路径作为解压路径。
可以使用如下命令查看系统属性:
java -XshowSettings:properties -version
部分输出如下:
# java -XshowSettings:properties -version
Property settings:
awt.toolkit = sun.awt.X11.XToolkit
file.encoding = UTF-8
file.encoding.pkg = sun.io
file.separator = /
java.awt.graphicsenv = sun.awt.X11GraphicsEnvironment
java.awt.printerjob = sun.print.PSPrinterJob
java.class.path = .
java.class.version = 52.0
java.endorsed.dirs = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.20-3.b26.el6.x86_64/jre/lib/endorsed
java.ext.dirs = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.20-3.b26.el6.x86_64/jre/lib/ext
/usr/java/packages/lib/ext
java.home = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.20-3.b26.el6.x86_64/jre
java.io.tmpdir = /tmp
或者在java代码中使用如下语句获取系统属性:
System.getProperty("java.io.tmpdir")
可以看到linux系统下默认为/tmp目录。遂去/tmp目录查看,并没有发现跟jetty解压相关的文件夹。猜想可能是/tmp目录定期清理时把相关的文件夹删掉了,导致解压出来的页面相关文件找不到。
查看文件tmpwatch查看清理周期:
vim /etc/cron.daily/tmpwatch
#! /bin/sh
flags=-umc
/usr/sbin/tmpwatch "$flags" -x /tmp/.X11-unix -x /tmp/.XIM-unix \
-x /tmp/.font-unix -x /tmp/.ICE-unix -x /tmp/.Test-unix \
-X '/tmp/hsperfdata_*' 10d /tmp
/usr/sbin/tmpwatch "$flags" 30d /var/tmp
for d in /var/{cache/man,catman}/{cat?,X11R6/cat?,local/cat?}; do
if [ -d "$d" ]; then
/usr/sbin/tmpwatch "$flags" -f 30d "$d"
fi
done
可以看出来清理周期为30天,基本跟问题复现的周期吻合。
解决问题
找到问题了,来看怎么解决。jetty官方文档给出几种设置解压路径的方法:
通过xml文件配置:
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
<Set name="contextPath">/test</Set>
<Set name="war">foo.war</Set>
<Call name="setAttribute">
<Arg>org.eclipse.jetty.webapp.basetempdir</Arg>
<Arg>/home/my/foo</Arg>
</Call></Configure>
通过java代码配置:
WebAppContext context = new WebAppContext();
context.setContextPath("/test");
context.setWar("foo.war");
context.setAttribute("org.eclipse.jetty.webapp.basetempdir", "/tmp/foo");
或者还有一种最简单的方法,在jetty安装的根目录下建一个叫work的文件夹,jetty就会使用该文件夹作为解压的路径了。我使用的是这种方法。
Mostly for backward compatibility, from Jetty 9.1.1 onwards, it is possible to create a directory named "work" in the ${jetty.base} directory. If such a directory is found, it is assumed you want to use it as the parent directory for all of the temporary directories of the webapps in ${jetty.base}.
新建完work目录后,重新部署应用,会发现该目录下jetty自动创建的解压文件夹,文件夹下有页面相关的文件。
到此,问题的原因和解决方案都有了,问题也算解决了。
更进一步
仔细想一想,还有一个问题没有解决。为什么应用解压的文件夹被删除,页面文件肯定找不到了,但是跟页面无关的接口却正常工作呢?
是否是jetty把静态资源放在上述的解压文件夹下,class等文件放在别的地方了呢。
我们都知道,jetty和tomcat要部署war包都只要把war包放在根目录的webapps目录下。查看原始的war包文件,内容如下。
可以看到war包原始内容跟work下解压出的内容完全一致。其中WEB-INF目录下包括了class、lib引入的jar包等文件夹,符合servlet规范要求。
1)/WEB-INF/
专门的Servlet API定义文件夹,通常存储和Web应用相关但不为外部访问的任何东西。
如果你有内容被你的Web应用内部访问,但不会被web浏览器直接地访问,你就应该把他们放在这里。
2)/WEB-INF/web.xml
必须的部署描述符,用于定义你的Web应用的各种行为。
3)/WEB-INF/classes/
Web应用的java classes文件放置目录。
4)/WEB-INF/lib/
JAR文件放置的目录。
此时尝试把work目录下解压出来的文件夹删除,依然是页面404无法访问,接口可以。
继续查看jetty部署相关章节文档,并没有找到相关的描述,只能大胆的猜测:
1.class文件、jar包等内容,jetty是在解压后就加在到内存中的,jvm在运行时,并不会访问之前解压出来的文件,所以删除没有影响。
2.html、css等静态资源,当有页面访问时,是从之前解压的文件夹中读取的,所以删除会报404异常。
如有不同看法,欢迎指正。
参考:
http://www.eclipse.org/jetty/documentation/current/ref-temporary-directories.html
http://www.eclipse.org/jetty/documentation/current/configuring-deployment.html
http://blog.csdn.net/u011479200/article/details/79200788
https://stackoverflow.com/questions/26459904/reading-java-system-properties-from-command-line