您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 管理学资料 > 基于Struts2标签的BigPipe技术实现
引言Facebook创立了一项技术名为BigPipe。该技术改善了Facebook用户的用户体验,减少页面加载等待时间,它的原理简单、合理。本文借鉴BigPipe的思想,针对Struts2和JSP技术的特点,实现了单线程与多线程版的BigPipe。两种版本的实现各有优缺点,它们与Facebook的BigPipe不尽相同,其中多线程版的BigPipe实现与Facebook较为类似。单线程与多线程实现方式都可以明显改善用户体验,文章之所以要介绍两种实现,是笔者认为二者都有更加适用的情境,在很多情况下,单线程的使用情况更多、更简单。文章将用实际的使用实例,对两种实现进行详细的介绍。在阅读文章之前,读者最好先了解一下Struts2自定义标签的开发方法、Java的Concurrent多线程框架以及FreeMarker模板引擎,这将帮助你更好的理解文章的BigPipe实现方式。技术简介现在的浏览器,显示网页时需要经历连续的几个步骤,分别是请求网页-服务器端的页面生成-返回全部内容-浏览器渲染,在这一过程中,“服务器的页面生成”到“返回全部内容”阶段,浏览器什么也不做,大部分浏览器就直接显示空白。可想而知,如果页面庞大,那么等待的时间就很长,这很可能导致大量的用户丢失。Facebook提出的BigPipe技术就是为了解决这个问题,它是基于多线程实现,原理大致可以分为以下两点。将一个页面分为多个的PageLet,每个的PageLet实际上就是一个HTML片段,每个PageLet的页面内容由单独的线程生成与处理。由于使用了多线程,PageLet内容的返回顺序无法确定,因此如果将内容直接写回HTML文档内,它的位置是无法确定的,因此需要借助JavaScript将内容插入到正确的位置,因为脚本代码的位置无关紧要。实现了以上两点,最终的效果将是网页中首先出现网页结构和基本的、简单的信息,然后才会在网页的各个PageLet位置出现具体内容,这些PageLet没有按流模型从上到下从左到右出现,而是“并行出现”,加载页面速度加快。从以上的分析,这种技术至少有两种好处。首先出现的结构和基本信息,告诉用户页面正在加载,是有希望的。并行加载的机制使得某个PageLet的缓慢不会影响到别的PageLet的加载。所有的PageLet在同一个HTTP请求内处理。接下来,文章先进行示例程序的展示与分析,给出各种实现方式的对比,然后讲解了基于Struts2的BigPipe标签开发,最后总结了单线程与多线程实现方式的优缺点。示例展示为了让读者对本文所讲内容有一个实际的印象,提升您对该技术的兴趣,本文以一个例子,采用三种实现方式来实现。该例子实现了一个2*3的表格,按从左到右、从上到下的顺序(也就是文档流模型的加载顺序),标明了序号。每个单元格的内容,都使用Thread.sleep方法模拟了加载时间较长的HTML内容。按照文档流顺序,每个单元格的线程等待时间分别是1、2、3、4、5、6秒。我们观察三种实现方式:普通实现、单线程BigPipe、多线程BigPipe,看它们对结果的影响。示例程序在附件部分,它是一个JEEEclipse工程,读者可以到Eclipse官方网站下载JEEEclipse,下载后导入工程。另外运行示例程序需要Tomcat6+的支持。普通方式打开附件,查看WebContent下的normal.jsp源码,如清单1所示。清单1.normal.jsp源码%@pagelanguage=javacontentType=text/html;charset=utf-8pageEncoding=utf-8%%longpstart=System.currentTimeMillis();%tableborder=1width=100%height=500caption普通例子/captiontrtd%longstart=System.currentTimeMillis();Thread.sleep(1000);longseconds=System.currentTimeMillis()-start;%1秒的内容br加载耗时:%=seconds%毫秒;/td//中间的省略//...td%start=System.currentTimeMillis();Thread.sleep(6000);seconds=System.currentTimeMillis()-start;%6秒的内容br加载耗时:%=seconds%毫秒;/td/tr/table%seconds=System.currentTimeMillis()-pstart;%整个页面加载耗费了:%=seconds%毫秒这是一个再普通不过的JSP文件,用Thread.sleep模拟长时间的HTML加载。运行附件工程,打开{yourport}/BigPipeImpl/normal.jsp。接下来等待我们的就是一个很长时间的等待,浏览器一直处于白屏的状态,最终会出现如图1的结果。图1.普通实现方式的结果(查看大图)普通方式的实现缺点明显,从这个例子我们就可以知道,如果你的网页很大,这将直接导致用户无法等待。为了给出更加准确的用户等待时间,使用Firebug的网络监测功能,查看网页的加载时间,结果如图2所示。图2.普通实现的加载时间(查看大图)可以看到,该页面的加载时间是21.02秒,试问有哪个用户会忍受这么长时间的页面空白呢?该实现方式的效果也在预料之内,表格按照文档流的顺序进行加载,也就是按照单元格的编号顺序逐个加载,直到页面全部加载完才一口气写回到浏览器,这样用户必须等待较长的时间。单线程方式普通方式的用户体验很差,要想增强用户体验就可以用到单线程BigPipe技术。单线程的实现方式,本质上与普通方式一样,但是不一样的是它可以将优先级高的区域提前加载,并且可以先将网页结构写回客户端,然后再显示内容,增强用户体验。本文的单线程示例程序,单元格内容的加载顺序是可以编程设置的,不一定需要按照文档流的顺序。由于增加了客户端的JavaScript处理,在总时间上会略微多于普通方式,但是在用户体验效果却远远优于普通方式。当我们编程设置单元格显示顺序按照1-6显示时(后半部分为展示如何设置顺序),打开{yourport}/BigPipeImpl/single.action,效果如图3所示。图3.单元格1-6顺序的单线程加载结果(查看大图)可以看到,打开不久,表格的框架就显示了出来,接下来,就会逐个的显示单元格的内容,其他的单元格则显示加载状态,等到他加载完毕,我们再通过Firebug查看它的加载时间,如图4所示。图4.单元格1-6顺序的单线程加载时间(查看大图)可以看到,网页的加载时间与普通实现方式一样,但是却带来了普通实现方式不可比拟的用户体验,有时候用户只希望网页及时的给他回馈,让用户充满希望。有人说,这用Ajax一样可以实现,但是请再看图4,我们看到,浏览器发出的请求只有一个single.action,再没有别的请求,这大大减轻了服务器端的压力。又有人说,可以在每加载一个内容完毕的时候,执行flush操作。的确,这样可以实现图3的效果,但是,如果我想实现6-1的显示顺序呢,flush就无能为力了,而用单线程BigPipe,却可以通过简单的调整代码顺序,来改变加载顺序,6-1顺序的显示结果如图5所示。图5.单元格6-1顺序的单线程加载结果(查看大图)从上图我们看到,这次的加载顺序,是按照6-1的显示顺序,总时间不变。这个功能很重要,有时候,重要的内容在文档流的后方,而我们想让它显示的优先级变高,那么单线程的实现方式将非常实用。多线程方式不管是单线程还是普通实现方式,它们加载页面所需的总时间没有减少,对于非常大的页面,缩短加载时间才是最重要的,那么就可以使用本文介绍的多线程BigPipe技术了。多线程实现方式与Facebook的实现方式基本一致,在本文的例子中,将每个单元格视为一个PageLet,每个PageLet的内容交给单独的线程进行生成和处理,也就是说,六个PageLet的内容并行处理,无需按照文档流顺序进行处理。我们打开{yourport}/BigPipeImpl/multi.action,我们再次查看页面的内容加载时间,结果如图6所示。图6.多线程实现方式的加载时间(查看大图)看到了吗?总共的加载时间变为了6秒,是不是很神奇,针对本文的例子,提高了3倍多,同时也只在一个请求内完成(另外两个请求是请求JavaScript文件和图片文件的)。而实际上,这个6秒,是加载时间最长的PageLet所需要的时间,因为各个PageLet的加载是并行的,页面加载时间以最晚的那个PageLet为准。本文例子的加载原理如图7所示。图7.多线程BigPipe原理可以看到,六个单元格并行加载,整个页面的加载时间由最长的单元格6决定。按照图7的分析,单元格是按照1-6的顺序显示,同时每个单元格之间相差接近1秒。经验证,单元格显示的顺序的确是1-6,结果如图8所示。图8.多线程显示结果(查看大图)在每个单元格(也就是PageLet)显示出内容的瞬间,Firebug的网络监控部分,就会显示出当时网页所消耗的时间,结果如图9所示。图9.每个PageLet显示的时间可以看到,每个PageLet的显示间隔正好一秒,与图7的分析完全一致。这也证实了多线程加载PageLet的实现是正确的。多种实现方式的对比从以上的示例展示和结果分析,不难看出普通实现方式、单线程BigPipe、多线程BigPipe以及Ajax之间的差异,我们不防用一个表格来展示,对比结果如表1所示,注意:我们使用本文的示例程序作为评价背景,因为对于不同网页有可能出现不同的结果。表1.四种实现方式对比类型请求数服务器端压力用户体验网页加载速度模块加载顺序实现难度普通1小差慢文档流顺序简单Ajax多大好快不确定困难单线程BigPipe1小好慢自定义一般多线程BigPipe1一般(线程池引起)好最快不确定最困难针对本文的例子,给出了上表的评价结果,这些结果并不是绝对的,它是针对网页较大、内容模块较多情况下给出的结果,从中可以很容易看出各个实现方式的差异所在。读者可以从中找到符合自己需求的实现方式。基于Struts2的标签开发根据前面的分析,读者应该可以领略到BigPipe技术的优点了,为了让单线程和多线程版本更加实用,文章结合Struts2的标签技术,开发实现BigPipe技术,这样就可以让Java开发人员可以真正的将该技术用于实际。因此,这部分需要大致讲解一下Struts2自定义标签的开发方法。实现基于Struts2的标签,需要重载两个类,org.apache.struts2.views.jsp.ComponentTagSupport和org.apache.struts2.components.Component,实现ComponentTagSupport类的getBean方法和populateParams方法,getBean方法返回自定义Component的实例,populateParams则是负责将页面传递的参数装配到Component里。在Component类里,需要重写start和end方法(也可以不重写),这两个方法分别代表标签的起始和标签的结束。最后再新建一个tld文件,来配置这个标签,由于篇幅限制,本文对Struts2的标签开发不多加解释,读者可以自行上网搜索相关资料。单线程实现还记得单线程BigPipe的实现效果吗?它可以自定义页面模块的显示顺序。普通的JSP文档,它显示页面的顺序,是按照文档流的顺序,也就是从上到下,从左到右的生成。但是页面中的一些元素,我们希望它早点显示出来,但是往往它又在文档流的后半部分,前半部分耽误了很多时间,这可能直接导致用户因为看不到重要信息而不再等候,离开
本文标题:基于Struts2标签的BigPipe技术实现
链接地址:https://www.777doc.com/doc-2572292 .html