jsp实际上是servlet草稿,在第一次运行时由服务器编译成servlet,以后每次重启就把已经编译好的serlvet载入内存,成为一种系统服务。因此,在重新发布后第一次访问的速度会比较慢,是可以理解的。“比较”的意思就是明显可以感觉到推延,但充其量在第二第三次访问后就会变快,这是 jsp的常识。但近日的几次发布失败,似乎是在挑战这条常识,而结论是直指jsp2.0系统级结构的不合理。
这是第二第三次,在发布时速度极慢,以致于无法完成发布。原因呢?几乎每一个可能性因素都被有根据将它否定,目前仍是未明。最早是在周二第一次发布,在晚上到测试服务器上发布,这是小流量测试服务器,结果到第二天中午几次重启,仍没有完成发布。此前也有过明显的大概几个小时的缓慢记录,但象这样几乎无法完成,却是第一次碰到。要知道,在开发和初步测试时,都只是在第一次刷新时延迟了几秒,不至于象进入死循环一样长久没有反应,甚至发展到内存溢出的程度。
当时对系统的修改程度归纳一下,与前面的系统应用相比,有以下几项变动:一是引入了tagfile代替include小文件;这样可以不考虑路径,并大大增强可配置性;对于不值得为之写class但重用性又挺高,却又稍有差别的presentation代码,的确较 include更佳;第二,是引入了jstl,这是为了规范化,因为懂strutslogic:hanva条件标签的人不多;而且swithcase逻辑的表达,以及多个连续条件的表达,jstl比strutslogic要强。因此,很自然就会怀疑到是这两样东西带来的问题,而且所有没有使用这两样东西的界面都比较地正常。
相比之下,tagfile是问题的核心的可能性更大,但却更加有吸引力。jstl的感觉是有点不妥,它的uri是连到 sun的core地址;而且可轻易使 用原来的logic代码替换。我不明白jstl是如何能够懂得从standard.jar中找到它的tld的,这不符合jsp标签的RFC说明:由taglib指向的tld/xml文件进行标签使用说明;我想它多多少少会与向http://java.sun.com/jstl/core那个网址发请求,得到指示后再转向 stardard.jar中找那个tld文件。所以如果网络上不通或者大量请求拥挤,在编译时就有可能由于网络等待而造成长时间的停顿。
我决定先排除这种情况,使用旧的自已修改的logic系统条件标签更替jstl中的流程控制。结果真怪,才换了一半,就顺风顺水地统统畅顺了。结果显示似乎直指是由于这个jstl需要网络请求才能解析导致编译时等待。为了验证这个想法,我把所有的jstltaglib的directives全部改成 c.tld;和其他的一样,这样就绝无可能再出现网络请求的延时了。而且每次发布升级时都把先前的编译结果(在work目录中)全部删除,砍保全新的编译,也的确是每次都顺风顺水几乎是立刻发布成功。包括昨天下午五点钟的一次发布,几乎是立刻成功,毫无延时了(那时侯的网络访问流量是最大的,最低同时请求达到一百个以上)。也正是由于这样的鼓励,我进一步使用tagfile代替那些重用程度很高的小代码——它们已经让我有点糊涂了,不得不在前台花了大量的时间,接近一半时间了,而正常情况下,presentation 层的编码不应该超过10%的工作比例,还主要是修正页面人员的错误而操作。
但当晚上再修改了一点地方,进行再一次发布时,这时的网络流量是一天最低的时侯,却再次出现了第一次那种情况,由于系统屡屡由于内存溢出而退出服务状态,我把系统设为每小时自动退出一次重启应用一次,那么就算它某个小时退出了,在整个晚上也不会中止的。同时在另一台流量较小的服务器上也同样发布。但结果似乎是一模一样的:两台在线服务器都是到早上仍没有恢复工作;而在我的开发测试的非在线服务器上,就快得出奇。几台服务器的状况是一样的,也是先删除原 work中的文件后再发布,但结果就是不同。唯一的区别就是存在网络请求,但上面的几个结果又把这个设想否定了N次了。
进一步的深入到 work的目录中,发现网上服务器的程序编译有一个怪事:所有的在线实时编译的tagfile文件(实际上是实现 SimpleTagSupport接口的标签的jsp草案)在某一个jsp文件调用后完成一次编译,在另一个同样调用它的jsp文件再调用它(那个文件也要重新编译)时,它会再编译一次,并清空前面的结果:这就不对了!除非tagfile改变,它不应该反复编译。从情况看,很象是由于多个jsp文件同时被请求时,对这个tag文件同时提出编译请求,由于sychronized锁定了,所以出现多重的等待。
这就是说:如果任何一个大型的网站,同时请求访问比较多,而jsp顶级文件也比较多时,几乎是不可能完成发布的:几千个文件会长时间地产生资源争夺;反复死锁。也正是在大型网站逻辑复杂的情况下,才会特别需要使用这种连前台逻辑也是模型建立。这就变成了,普通的jsp程序方式,多顶级模板(jsp文件)的是不适合于大网站的。这是目前最有可能的解释,但同样由于前几次的顺利升级而显得仍然缺乏说服力——需要又增加了一些 tagfile文件。
但无论如何,工作服务器必须恢复正常,而现实情况下是似乎永远无法初始化完毕,而服务器断断续续的响应已经近十个小时了,尽管大部分是在夜晚。万般无奈的情况下我搞了一个怪招:本地的初始化速度快是不是?那我就做了一个自动化的请求,在本地向本地的一台服务器一页页的把需要访问的页面统统访问一次,这样就令本地的jspweb服务器把该编译的统统编译好,然后就把这编译好的文件,(妈呀,原来有几百兆呢!)上载到工作服务器,替换那拖拉机般在缓慢增加的 work目录中的文件;呵呵,行了!
答案仍然是不清晰的,只能说最有可能的因素是两个:
一是由于jsp缺乏顶级模板的功能,所以不得不因为逻辑需要分成若干个文件,当这些文件达到一定程度时,象目前的工程上是几十个连锁子单位每个有十几个顶级文件,再发布到请求相当大的网站上(目前这个网站alea排名最高达2000),就会在发布时由于同时总会有几十个大文件处于同时编译过程中;而导致系统在极限边缘运转;
二:jsp标签协议有漏洞;表现为部分标签在常常在修改后除非删去已经编译好的class,否则系统始终载入旧的未升级的class;更严重的是,对于 tagfile这种jsp化的动态标签,如同上面说的,不同的调用它的jsp文件在进行更新编译时,会重新编译它调用的tagfile;并进行同步锁定。这显示是一个逻辑错误!同时,一旦未完成编译的jsp发布它调用的tagfile.class改变了(实际上是重新编译了),它就重新再次编译,也再次锁定目标的tagfile文件。这样就形成一个实际上的死循环。
这两个因素任何一个都不致命;两个结合在一起,仍不足以致命,但如果再加上第三个因素:大流量请求,就变成致命了:在这样的服务器上难以发布这样的系统,永远的,jsp似乎不能完成最后的编译工作。
解决方法除了象上面这样的无赖手段外,另一个办法就是不删除旧的work文件,而在发现不对时手工清除那些文件,这样可以降低编译负载;但每一次的发布升级就成了一个繁杂的工程了;要过相当长的时间却能确定运行的是新的程序。作为jsp2.0服务器方面,应该检查一下这种bug是如何来的,毕竟如果jsp 只能是给简单的网站使用的话,倒不如赶快关门算了;jsp技术发展上的设计变成了主要满足初学者扫盲,是java技术委员会最弱智的,也是最不及微软的地方。而在这两项之外,要想解决这个问题的办法之一就是减少jsp的顶级模板,多使用条件判断调用不同的jsp文件,这样就可以把需要编译的文件降低一个数量级,减少由于同时编译的文件多发生煞费死锁的可能性。最后就是避免使用tagfile,毕竟这是可以使用SimpleTagSupport直接编写,或者使用include 替代的,虽然不算太方便。
本文地址:http://com.8s8s.com/it/it31571.htm