最近遇到一个系统登录缓慢的问题。一开始想到的就是删掉一些不需要的逻辑,把js放到底部,<script>标签使用defer属性,以及SQL的优化,建索引等数据库的优化,但是效果还是不是很明显。于是继续对一些看似不必要的逻辑统统删掉,但是总是怕删掉一些逻辑后因为测试不充分造成bug,开始投鼠忌器。光这样下去也不是办法,应该找到最耗时的地方,于是用httpwatch查看从登录到进入使用页面整个过程的详细情况。在一系列的操作中发现有个操作非常耗时,达到4~5秒这样,需要查表,然后循环操作,还要在页面循环组装数据,相当费时。整个页面分为几个iframe,当全部的iframe都加在完成之后,等待页面的遮蔽层才消失显示操作页面。最初的想法是,重要的页面先展示,不让用户在等待页面等待太久,这样用户长时间没有跳转到使用页面就会烦躁。于是就把下面的if条件去掉。
if (document.frames("top_frame").readyState == "complete") {
document.getElementById("loading").style.display = "none";
}
但是我的思路看起来太简单了,这个iframe里面有个用户常使用的地方,如果不等它加载完就退出等待页面这一块区域还是一片空白,用户不能使用。于是干脆把耗时的地方使用异步来加载。首先第一次加载的时候先返回页面,后台不做什么操作,立即返回页面。然后等整个页面都加在后10秒再用ajax去进行耗时的操作。
window.onload = function() {
setInterval(10000, lazyLoadData());
}
function lazyLoadData() {
$.ajax({
//异步请求
});
}
顺便把页面组装数据的操作移到后台一并完成,然后返回json到页面展示,改造后数度比之前快了大概3~4秒。但是整个登录过程相对其他系统来说确实还是慢了一点。接着就上网搜搜一些解决方案。无意中看到一个facebook对页面加载数据优化的解决方案,据说这个方案耗时半年把数据提高了一倍,从5秒编程2.5秒,这个技术较bigpipe,直译过来就叫大管道,挺形象的,呵呵。原理大概看了下,分为单线程和多线程两种模式,单线程模式最终的耗时和没使用bigpipe几乎一样,但是它将页面分块,加载完一块就展示一个块,用户体验比没使用之前好一些。我着重看了下多线程模式的,将一个页面分割为多个块之后,每块并发地向服务器取资源,也是加载完就展示,整个页面加载时间取决于最耗时的那一块,这样串行的步骤编程并行的了,速度当然就快多了。我找了个例子,是用struts2标签实现的。之前也用过struts标签,所以也就当复习一下,呵呵。
标签是这样的:
<b:multiThread pageLetNum="6" bigPipeJSPath="js/bigpipeMulti.js">
</b:multiThread>
在tld文件中这样定义multiThread标签的:
<tag>
<name>multiThread</name>
<tag-class>com.bigpipe.tag.MultiThreadTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>pageLetNum</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>bigPipeJSPath</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
在MultiThreadTag.java的getBean方法中
@Override
public Component getBean(ValueStack vs, HttpServletRequest arg1,
HttpServletResponse arg2) {
CountDownLatch end = new CountDownLatch(Integer.parseInt(pageLetNum));
arg1.setAttribute(COUNT_DOWN, end);
return new MultiThread(vs, arg1, arg2);
}
在MultiThread.java中的start方法和end方法
@Override
public boolean start(Writer writer) {
boolean start = super.start(writer);
try {
writer.write("<script type='text/javascript' src='" + bigPipeJSPath
+ "'></script>");
} catch (IOException e) {
e.printStackTrace();
}
return start;
}
@Override
public boolean end(Writer writer, String body) {
boolean end = super.end(writer, body);
CountDownLatch c = (CountDownLatch)request.getAttribute(MultiThreadTag.COUNT_DOWN);
try {
c.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
return end;
}
虽然有个例子了,但是这个例子只是实现了一个模拟耗时页面的多线程模式的bigpipe的实现,具体用在我的系统中还是要做一些改动的,还在修改中,但是bigpipe这个思路应该还是能很大程度上提高登录速度的。