lizongbo at 618119.com 工作,生活,Android,前端,Linode,Ubuntu,nginx,java,apache,tomcat,Resin,mina,Hessian,XMPP,RPC

2009年02月18日

调整resin的resin.conf优化access-log输出日志

Filed under: Resin — 标签:, — lizongbo @ 00:25

目前服务器上resin.conf的日志配置一般是:
《stdout -log path=”log/stdout.log” timestamp=”[%Y-%m-%d %H:%M:%S] ” rollover-size=”200mb” /》
《stderr -log path=”log/stderr.log” timestamp=”[%Y-%m-%d %H:%M:%S] ” rollover-size=”200mb” /》
《access -log path=”logs/access.log” rollover-period=”1D” rollover-size=”200mb”/》

在这种情况下,日志始终是先输出到logs/access.log这样的固定文件里,待文件大小满200M的时候,resin先锁住所有写日志操作,将文件的内容复制到文件名带时间戳的文件里,再清空当前日志文件的内容。
这种情况下,既多增加了磁盘io操作,也因为锁机制导致线程容易满,以至resin被webapp监控重启。
以前出现该问题的时候,只简单的通过关闭access-log绕了过去,而最近web网站的resin也出现了重启,
由于web是lvs负载均衡,因此不能通过关闭accesslog来解决问题。

经过查看resin的源代码分析,结合现在业务的实际情况,可以通过调整日志的配置为按时间周期切割来解决问题。
通过分析和实验,将resin的配置调整后大致为:

《stdout -log path-format=”log/stdout.log.%Y%m%d” timestamp=”[%Y-%m-%d %H:%M:%S] ” rollover-period=”1D” /》
《stderr -log path-format=”log/stderr.log.%Y%m%d” timestamp=”[%Y-%m-%d %H:%M:%S] ” rollover-period=”1D” /》
《access -log path-format=”logs/access.log.%Y%m%d_%H” rollover-period=”1h”/》
(默认format是combined格式)
上面的是stdout和stderr按天切割(如果代码里异常输出比较多的话,也可以配制成按小时打,例如有:ex.printStackTrace();),access.log按小时切割。

大致分析过程:

访问高峰期时,日志滚动时候,导致线程锁住了。
JMX监控查看线程堆栈可以得知,可以看出大量的线程都 在执行com.caucho.server.log.AccessLog.log的时候BLOCKED了。
[code]
==============================
“resin-tcp-connection-*:80-5973” Id=41812 in BLOCKED on lock=java.lang.Object@14dca59
owned by resin-tcp-connection-*:80-5545 Id=41588
at com.caucho.server.log.AccessLog.log(AccessLog.java:310)
at com.caucho.server.webapp.WebAppFilterChain.doFilter(WebAppFilterChain.java:206)
at com.caucho.server.dispatch.ServletInvocation.service(ServletInvocation.java:229)
at com.caucho.server.http.HttpRequest.handleRequest(HttpRequest.java:268)
at com.caucho.server.port.TcpConnection.run(TcpConnection.java:389)
at com.caucho.util.ThreadPool.runTasks(ThreadPool.java:507)
at com.caucho.util.ThreadPool.run(ThreadPool.java:433)
at java.lang.Thread.run(Thread.java:619)

[/code]

先下载resin的源代码: http://www.caucho.com/download/resin-3.0.19-src.tar.gz

接下来分析AccessLog的log方法。

可以看到:
if (_isSharedBuffer && ! _isAutoFlush) {
synchronized (_sharedBufferLock) {

而这两个选项的默认值是:_isSharedBuffer=true,_isAutoFlush=false;
(因为resin.conf没有配置对应的属性,例如: shared-buffer=”false” auto-flush=”false”)
而这两个属性在官方文档里并没有直接列出。
http://caucho.com/resin-3.0/config/log.xtp#rollover

(测试了不能设置 auto-flush=”true”,因为没缓冲,还没判断好滚动就直接打到文件了,rollover-size几乎等同失效)

com.caucho.vfs. AbstractRolloverLog.java中:
private static final long DEFAULT_ROLLOVER_CHECK_PERIOD = 600L * 1000L;

private long _rolloverCheckPeriod = DEFAULT_ROLLOVER_CHECK_PERIOD;
滚动日志检测周期默认是10分钟。
而该参数没法在resin.conf中动态配置。

在文件超过指定大小之后,日志滚动的方法为:com.caucho.vfs. AbstractRolloverLog 里的rolloverLog, 这个也是个同步方法:

而执行文件内容的操作是movePathToArchive方法:
而movePathToArchive的做法是:

关键代码是: path.writeToStream(out);和path.truncate()
前一个方法,是表示把当前文件的内容读出来,再写到out里。
后一个方法是,把当前文件内容清空。
而其对应的实现是:
com.caucho.vfs.Path和com.caucho.vfs.FilePath。

从整个代码分析可以得知,resin在按文件大小滚动日志的时候,是检测到当前文件超过200M,就先把缓冲全部写入当前日志文件access.log里,再将access.log的内容读出,再写到 access.log.20090206.1047 这样的文件里,写完之后,再通过truncate利用FileOutputStream的append为false将access.log内容清空。相当于先复制内容到新文件,然后再清空当前文件内容。

而这整个过程中,是同步的,因此在执行复制201M日志的时候,线程全部锁住,而这个时候大量的用户请求正在涌来,导致jmx线程过高且webapp监控告警,于是resin被监控重启。

解决办法:
使用path-format属性替代 path,
例如配置:
《access -log path-format=”logs/access.log.%Y%m%d_%H” rollover-period=”1h”/》
这样就是每小时打一个文件,切换日志输出时候,自动切换,不再产生movePathToArchive操作。
jvm.Log里也没了”Archiving access log to “这样的日志。
Stdout和stderr也是同样处理,有这两个日志的内不会太多,可以配置成按天滚动。
stdout-log元素和stderr-log也有path-format属性。

为什么resin不用File的renameTo通过重命名来切换日志呢,这是因为java的io里,只有所有的流都关闭之后,且操作系统中没有程序对文件持有引用时,文件重命名才能够成功,且renameTo是依赖操作系统的,而这种正在对文件进行读写操作的,执行renameTo无法做到一定成功的。Resin也是因为此,在按文件大小进行滚动的时候,才不得已使用复制文件加清空内容的做法。

2008年05月8日

配置独立运行的jsp预编译ant脚本

Filed under: Java,JAVA IDE,Resin,Tomcat — 标签:, , , , , — lizongbo @ 20:23

配置resin开启预编译jsp,在启动时,resin会自动编译jsp,出错信息在 stdout.log中。

[code]
<web-app id=”/lizongbo” document-directory=”webapps/lizongbo”>
<listener>
<listener-class>com.caucho.jsp.JspPrecompileListener</listener-class>
<init>
<extension>jsp</extension>
<extension>jspx</extension>
<extension>xtp</extension>
</init>
</listener>
</web-app>
[/code]

在开发过程,经常将页面传到resin上,才发现页面编译失败,浪费了不少时间,因此需要一个在本地预编译进行检查的操作。

resin 3.1以前的版本提供命令行方式进行jsp预编译。

完整的命令例子如下
%JAVA_HOME%\bin\java.exe -classpath %JAVA_HOME%\lib\tools.jar;D:\Java\resin-3.0.19\lib\aopalliance.jar;D:\Java\resin-3.0.19\lib\ejb-20.jar;D:\Java\resin-3.0.19\lib\ejb-30.jar;D:\Java\resin-3.0.19\lib\j2eedeploy.jar;D:\Java\resin-3.0.19\lib\jca-15.jar;D:\Java\resin-3.0.19\lib\jms-11.jar;D:\Java\resin-3.0.19\lib\jmx-12.jar;D:\Java\resin-3.0.19\lib\jsdk-24.jar;D:\Java\resin-3.0.19\lib\jstl-11.jar;D:\Java\resin-3.0.19\lib\jta-101.jar;D:\Java\resin-3.0.19\lib\portlet-10.jar;D:\Java\resin-3.0.19\lib\quercus.jar;D:\Java\resin-3.0.19\lib\resin-jdk15.jar;D:\Java\resin-3.0.19\lib\resin.jar;D:\Java\resin-3.0.19\lib\resinboot.jar;D:\Java\resin-3.0.19\lib\script-10.jar;D:\Java\resin-3.0.19\lib\webutil.jar;D:\Java\resin-3.0.19\plugins\resin-ant.jar com.caucho.jsp.JspCompiler -app-dir  E:\lizongbo\workspace\webapp E:\lizongbo\workspace\webapp
使用起来很不方便。(注意classpath里特意加了%JAVA_HOME%\lib\tools.jar,否则会报错,
出错信息如下:
Exception in thread “main” com.caucho.java.JavaCompileException: Resin can’t loa
d com.sun.tools.javac.Main.  Usually this means that the JDK tools.jar is missin
g from the classpath, possibly because of using a JRE instead of the JDK.  You c
an either add tools.jar to the classpath or change the compiler to an external o
ne with <java compiler=’javac’/> or jikes.

java.lang.ClassNotFoundException: com.sun.tools.javac.Main
at com.caucho.java.InternalCompiler.compileInt(InternalCompiler.java:77)
at com.caucho.java.AbstractJavaCompiler.run(AbstractJavaCompiler.java:101)
at java.lang.Thread.run(Thread.java:595)
)

resin 3.1.5及以后的版本终于提供了 ant方式的jspc的atsk,但是却有bug。
[code]
<project name=”test” default=”test” basedir=”.”>
<property name=”resin.home” value=”/usr/local/share/resin”/>

<target name=”test”>
<taskdef name=”resin-jspc”
classname=”com.caucho.ant.Jspc”>
<classpath>
<fileset dir=”${resin.home}”>
<include name=”plugins/resin-ant.jar”/>
<include name=”lib/*.jar”/>
</fileset>
</classpath>
</taskdef>

<resin-jspc rootDirectory=”/home/ferg/ws/dist/my-webapp”/>
</target>

</project>
[/code]
来源: http://wiki.caucho.com/Ant
在该方式下,会遇到提示说contentType重复定义的bug
出错信息如下:
E:\lizongbo\workspace\webapp\WEB-INF\ant\resin-jspc.xml:38:
com.caucho.jsp.JspLineParseException: /618119/test.jsp:6: contentType ‘text/vnd.wap.wml; charset=UTF-8’ conflicts with previous value of contentType ‘text/html; charset=UTF-8’.  Check the .jsp and any included .jsp files for conflicts.

原因是resin 3.1 对带BOM格式的UTF-8编码的jsp识别有问题。
参考: http://anotherbug.blog.chinajavaworld.com/entry/3683/0/

此时改页面是件痛苦的事情,于是想到使用Tomcat的ant jspC task

Tomcat 6.0.16的文件目录已经发生了变化,而官方的jspC的文档还没有更新:
http://tomcat.apache.org/tomcat-6.0-doc/jasper-howto.html

以前写过一篇笔记,基本是copy tomcat的官方文档
参考: http://blog.donews.com/lizongbo/archive/2005/06/06/413985.aspx
而现在主要用resin,因此想要一个可以独立进行jsp预编译的ant脚本,
所以手工清理后制作了个独立的jspc,方便随时预编译.

所用到的jar文件为:D:\Java\apache-tomcat-6.0.16\lib和D:\Java\apache-tomcat-6.0.16\bin 目录下所有jar
[code]
D:\Java\jspc\tomcat6lib 的目录

2008-05-08  20:09    <DIR>          .
2008-05-08  20:09    <DIR>          ..
2008-01-28  23:39            10,368 annotations-api.jar
2008-01-28  23:39            17,530 bootstrap.jar
2008-01-28  23:39            49,240 catalina-ant.jar
2008-01-28  23:39           122,869 catalina-ha.jar
2008-01-28  23:39           228,175 catalina-tribes.jar
2008-01-28  23:39         1,128,229 catalina.jar
2008-01-28  23:39             9,341 commons-daemon.jar
2008-01-28  23:39            27,699 el-api.jar
2008-01-28  23:39           102,129 jasper-el.jar
2008-01-28  23:39         1,395,270 jasper-jdt.jar
2008-01-28  23:39           511,240 jasper.jar
2008-01-28  23:39            72,408 jsp-api.jar
2008-01-28  23:39            85,287 servlet-api.jar
2008-01-28  23:39           742,089 tomcat-coyote.jar
2008-01-28  23:39           197,846 tomcat-dbcp.jar
2008-01-28  23:39            18,980 tomcat-juli.jar
16 个文件      4,718,700 字节
2 个目录    895,870,976 可用字节
[/code]

tomcat-jspc.bat的内容为:
(因为jsp文件太多,消耗内存较大,因此通过 ANT_OPTS=-Xmx512m 指定内存)
[code]
set ANT_OPTS=-Xmx512m
ant -f tomcat-jspc.xml -Dwebapp.name=mywebapp
[/code]
tomcat-jspc.properties的内容为:
[code]
webapp.root=E:/lizongbo/workspace
webapp.name = webapp
[/code]

tomcat-jspc.xml的内容如下:

[code]
<project name=”Webapp Precompilation” default=”all” basedir=”.”>
<property file=”${basedir}/tomcat-jspc.properties”/>
<property name=”tomcatlib.home” value=”${basedir}/tomcat6lib”/>
<property name=”webapp.path” value=”${webapp.root}/${webapp.name}”/>
<description> JSPC Tasks</description>
<taskdef resource=”org/apache/catalina/ant/catalina.tasks”>
<classpath>
<fileset file=”${tomcatlib.home}/tomcat-juli.jar”/>
<fileset file=”${tomcatlib.home}/jasper.jar”/>
<fileset file=”${tomcatlib.home}/jasper-el.jar”/>
<fileset file=”${tomcatlib.home}/el-api.jar”/>
<fileset file=”${tomcatlib.home}/jsp-api.jar”/>
<fileset file=”${tomcatlib.home}/servlet-api.jar”/>
<fileset file=”${tomcatlib.home}/catalina-ant.jar”/>
</classpath>
</taskdef>
<taskdef resource=”org/apache/catalina/ant/jmx/jmxaccessor.tasks”>
<classpath>
<fileset file=”${tomcatlib.home}/catalina-ant.jar”/>
</classpath>
</taskdef>
<typedef
name=”jmxEquals”
classname=”org.apache.catalina.ant.jmx.JMXAccessorEqualsCondition”>
<classpath>
<fileset file=”${tomcatlib.home}/catalina-ant.jar”/>
</classpath>
</typedef>
<typedef
name=”jmxCondition”
classname=”org.apache.catalina.ant.jmx.JMXAccessorCondition”>
<classpath>
<fileset file=”${tomcatlib.home}/catalina-ant.jar”/>
</classpath>
</typedef>
<!– 先把WEB-INF/src下的java文件编译成class –>
<target name=”compilejava”>
<echo>jdk path: ${java.home} </echo>
<echo>webapp path: ${webapp.path} </echo>

<mkdir dir=”${webapp.path}/WEB-INF/classes”/>
<mkdir dir=”${webapp.path}/WEB-INF/lib”/>

<javac destdir=”${webapp.path}/WEB-INF/classes”
optimize=”off”
debug=”on” failonerror=”false”
encoding=”utf-8″ nowarn=”true” target=”1.5″
srcdir=”${webapp.path}/WEB-INF/src”
excludes=”**/*.smap”>
<classpath>
<pathelement location=”${webapp.path}/WEB-INF/classes”/>
<fileset dir=”${webapp.path}/WEB-INF/lib”>
<include name=”*.jar”/>
</fileset>
<pathelement location=”${tomcatlib.home}”/>
<fileset dir=”${tomcatlib.home}”>
<include name=”*.jar”/>
</fileset>
</classpath>
<include name=”**” />
<exclude name=”tags/**” />
</javac>

</target>
<!– 再把jsp页面生成java文件 –>
<target name=”jspc”>
<jasper
validateXml=”false”
uriroot=”${webapp.path}”
webXmlFragment=”${webapp.path}/WEB-INF/generated_web.xml”
outputDir=”${webapp.path}/WEB-INF/jspsrc” />

</target>
<!– 再把jsp的java代码编译成class –>
<target name=”compilejspjava”>

<mkdir dir=”${webapp.path}/WEB-INF/work”/>
<mkdir dir=”${webapp.path}/WEB-INF/lib”/>

<javac destdir=”${webapp.path}/WEB-INF/work”
optimize=”off”
debug=”on” failonerror=”true”
encoding=”utf-8″ nowarn=”true” target=”1.5″
srcdir=”${webapp.path}/WEB-INF/jspsrc”
excludes=”**/*.smap”>
<classpath>
<pathelement location=”${webapp.path}/WEB-INF/classes”/>
<fileset dir=”${webapp.path}/WEB-INF/lib”>
<!– need ignore mywebapp/WEB-INF/lib/servlet.jar 在这里忽略掉webapp lib里的低版本的servlet,以免编译失败–>
<exclude name=”servlet.jar”/>
<include name=”*.jar”/>
</fileset>
<pathelement location=”${tomcatlib.home}”/>
<fileset dir=”${tomcatlib.home}”>
<include name=”*.jar”/>
</fileset>
</classpath>
<include name=”**” />
<exclude name=”tags/**” />
</javac>

</target>

<target name=”all” depends=”compilejava,jspc,compilejspjava,cleanup”>
</target>

<target name=”cleanup”>
<delete includeEmptyDirs=”true”>
<fileset dir=”${webapp.path}/WEB-INF/jspsrc”/>
<fileset dir=”${webapp.path}/WEB-INF/work”/>
</delete>
</target>

</project>
[/code]

2007年10月15日

开张了。

Filed under: Lighttpd,nginx,Resin,Squid,Tomcat,Web Server,数据库 — 标签:, , , , , , , — lizongbo @ 08:10

测试一下,首篇开张, apache ,Tomcat,Squid,Tomcat,Lighttpd,resin,memcached,ngnix,Linux,Mina等等。

« Newer Posts

Powered by WordPress