文档章节

基于Spring MVC的BigPipe演进之路

Float_Luuu
 Float_Luuu
发布于 2016/01/11 23:47
字数 1583
阅读 2433
收藏 15

基于NodeJS的前后端分离时代的到来,传统的后端Freemarker是否可能做得更好呢?这篇文章主要介绍作者基于Spring MVC对BigPipe的实践,下面直接进入正题。

普通方式

对于复杂页面我们常常会将页面分成若干模块,每个模块对应的业务代码耦合相对较少,这样易于开发和维护。为了简化模型,我们举个简单的例子,比如下面这个页面。

这个页面很简单,主要包含模块1和模块2这两个页面,我们假设除了模块1和模块2都是静态DOM,并且假设模块1为获取数据所需要的准备时间是1000ms,模块2为获取数据所需要的准备时间为800ms,那么一次请求过来,我们得到下面这张执行图:

显然,当用户请求这个页面的时候需要至少等待1000ms+800ms=1800ms的白屏时间,这里没有计算其他很少的耗时,原因是当服务器使用contentLength方式来决定是否将内容刷新到客户端的时候,客户端在所有数据准备好之前不会得到任何内容。而chunked编码方式方式允许服务器端分块将内容返回到客户端。

基于Spring MVC的实现

上面提到的模型基于Spring MVC实现只需要一个Controller加上一小块模板就可以表达了。我们姑且叫它OldController:

@Controller
@RequestMapping("old")
public class OldController {

    @RequestMapping("index")
    public String index(Model model, String name){
        model.addAttribute("index", name);
        //返回数据和一个模板统一渲染,然后返回
        model.addAttribute("module1", module1(name));
        model.addAttribute("module2", module2(name));
        return "old/index";
    }

    public String module1(String name){
        try {
            Thread.sleep(1000);//业务逻辑
        } catch (Exception e) { }
        //返回数据
        return name;
    }

    public String module2(String name){
        try {
            Thread.sleep(800);//业务逻辑
        } catch (Exception e) { }
        //返回数据
        return name;
    }
}

Controller的实现很简单,我们姑且用sleep来模拟数据的准备时间,至于模板呢,也很简单:

${index!}

模块1的内容
${module1!}


模块2的内容
${module2!}

三块内容,我们启动服务测试一下:

借助浏览器工具我们看下请求时间相关的数据:

发送请求花了0.22ms,我们关心的是客户端等待时间Waiting(TTFB)为1.89s,内容回传用了2.65ms,总耗时1.92秒。

如何改进呢,我想用下面的图解释一下我对Spring MVC的改进:

这幅图左边是老的Spring MVC渲染方式,右边是新的模型,我们把每个模块对应的业务逻辑提取为一个RequestMapping,让这些RequestMapping并发的去准备数据和渲染,结果用chunked方式刷回客户端。


基于Spring MVC CO的BigPipe方式的实现

在完成了Spring MVC CO这个工具之后我们的这个页面对应的Controller和模板可以像下面这样写:

Controller:

/**
 * @author float.lu
 */
@Controller
@RequestMapping("co")
public class CoController {

    @RequestMapping("index")
    public String index(Model model, String name){
        model.addAttribute("index", name);
        //返回数据和模板,同步返回
        return "co/index";
    }


    //ModelAndView并发执行和渲染
    @RequestMapping("module1")
    public String module1(Model model, String name){
        model.addAttribute("module1", name);
        try {
            //业务逻辑
            Thread.sleep(1000);
        } catch (Exception e) {

        }
        //模块模板
        return "co/module1";
    }

    //ModelAndView并发执行和渲染
    @RequestMapping("module2")
    public String module2(Model model, String name){
        model.addAttribute("module2",name);
        try {
            //业务逻辑
            Thread.sleep(800);
        } catch (Exception e) {

        }
        //模块模板
        return "co/module2";
    }
}

解释下,之前这个页面整体渲染的时候只需要一个RequestMapping,而这个时候我们除了为主页面配置一个RequestMapping之外还需要给每一个模块配置一个RequestMapping,每个RequestMapping的写法和原生Spring MVC的写法并没有什么不同,每个RequestMapping返回的ModelAndView也和原生的Spring MVC写法没有不同。不同的是这些RequestMapping对应的是页面上的模块,和主页面的请求共享一个Request,而不是来源于客户端的真正的Request。下面来看看如何编写主请求对应的模板:

其实很简单:

<#assign co=JspTaglibs["http://www.springframework.org/co"]>

<@co.config timeout=3000/>

${index!}


模块1:
<@co.module mapping="/co/module1"/>


模块2:
<@co.module mapping="/co/module2" />

第一行<#assign co=JspTaglibs["http://www.springframework.org/co"]>是引入JSP标签库,接着<@co.config timeout=3000/>是设置模块的超时时间,如果超过这个时间我们可以放弃这个模块了以便释放连接。<@co.module mapping="/co/module1"/>这句是引入模块,其中/co/module1是和Controller中的RequestMapping对应的。令人兴奋的是我们也支持PathVariable方式传参,但是一个请求上下文我们不能有同名的PathVariable参数。

同时Spring MVC配置文件可能需要改动一下:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc/co"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc/co
        http://www.springframework.org/schema/mvc/spring-mvc-co.xsd">

       <mvc:annotation-driven>
       </mvc:annotation-driven></beans>

仅仅是将MVC的命名空间地址换了一下,其他不需要任何改动。

启动服务器请求一个我们再看看响应时间数据:

从上面的数据我们可以看出来服务器的响应时间变成了13.02ms,中间的1.01s包括第一个响应chunk到最后一个chunk下载完的总时间,而页面的总响应时间为1.04秒,几乎也近处理最慢的那个模块的时间了,而这个时候其实前端的样式准备从13.02ms就已经开始了,而不是让用户一直面对白屏。


关于BigPipe

BigPipe概念很早就出现了,目前业界也有很多种实现,它带来的好处主要是通过长连接方式让先完成的数据返回给用户以优化用户体验,同时减少请求次数,达到本来多次请求合并成一个请求的效果。作者仅仅是在Spring MVC的基础之上实现这一功能,感谢Spring开源的支持。原理落地,没有最好的设计,只有最好的实践,也希望以后能有更多的实践。


项目地址:http://git.oschina.net/floatlu/spring-mvc-co

欢迎一起改进。

© 著作权归作者所有

Float_Luuu
粉丝 219
博文 47
码字总数 104674
作品 0
长宁
高级程序员
私信 提问
加载中

评论(10)

Float_Luuu
Float_Luuu 博主

引用来自“赵安民”的评论

您好,pom中的parent在哪儿获取?
<parent>
<artifactId>flab</artifactId>
<groupId>com.floatlu.lab</groupId>
<version>1.0-SNAPSHOT</version>
</parent>

parent可以删掉哦,没有的
赵安民
您好,pom中的parent在哪儿获取?
<parent>
<artifactId>flab</artifactId>
<groupId>com.floatlu.lab</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
Float_Luuu
Float_Luuu 博主

引用来自“qinerg”的评论

奥,明白了,co 的实现方案是自行实现的呀,我还以为是 spring 官方的呢。
我 check 下代码学习下83

是的,是自己实现的
qinerg
qinerg
奥,明白了,co 的实现方案是自行实现的呀,我还以为是 spring 官方的呢。
我 check 下代码学习下83
qinerg
qinerg
比如我不用 jsp,假设用 velocity 又或 beetl 作为视图展示层,又如何实现呢?
如果学习到 http://www.springframework.org/co 的实现方案,那么我们不论用什么技术都可以实现了。
Float_Luuu
Float_Luuu 博主

引用来自“qinerg”的评论

http://www.springframework.org/co 的实现逻辑是什么呢? 我们能否移植它到其它的模板视图上?

啥意思?要不加个微信聊?
qinerg
qinerg
http://www.springframework.org/co 的实现逻辑是什么呢? 我们能否移植它到其它的模板视图上?
MayDay芋头
MayDay芋头
不错13
Float_Luuu
Float_Luuu 博主

引用来自“飒然”的评论

这个用ajax也可以解决吧
Ajax需要再发一次请求。
飒然
飒然
这个用ajax也可以解决吧
BigPipe为什么可以节省时间?

关于什么是BigPipe,请移步http://baike.baidu.com/view/4601904.htm去查阅一下。 在实现BigPipe的过程中,就对BigPipe到底能省多少时间比较奇怪。 普通的web页面,一般来说是页面生成,网络...

悠悠然然
2013/10/21
252
0
BigPipe:高性能的“流水线技术”网页

原文地址:http://www.facebook.com/note.php?note_id=389414033919 译文地址:http://isd.tencent.com/?p=2419 作者:蒋长浩 Facebook的网站速度做为最关键的公司任务之一。在2009年,我们成...

晨曦之光
2012/03/09
133
0
BigPipe 学习研究

文章转自:http://www.searchtb.com/2011/04/an-introduction-to-bigpipe.html 1. 技术背景 FaceBook页面加载技术 试想这样一个场景,一个经常访问的网站,每次打开它的页面都要要花费6 秒;...

红薯
2011/07/08
1K
3
基于 Struts2 标签的 BigPipe 技术实现

Facebook 介绍了一个名为 BigPipe 的技术,这项技术可使 Facebook 站点的访问速度提升一倍。目前,也有一小部分文章介绍了该技术在 JSP 中的实现,但是这些文章只是基于 Servlet 的理论实现,...

IBMdW
2011/08/29
746
0
bigpipe & bigrender

bigpipe & bigrender的区别有哪些???

kevinlz
2013/03/26
213
0

没有更多内容

加载失败,请刷新页面

加载更多

springboot2.0 maven打包分离lib,resources

springboot将工程打包成jar包后,会出现获取classpath下的文件出现测试环境正常而生产环境文件找不到的问题,这是因为 1、在调试过程中,文件是真实存在于磁盘的某个目录。此时通过获取文件路...

陈俊凯
今天
4
0
BootStrap

一、BootStrap 简洁、直观、强悍的前端开发框架,让web开发更加迅速、简单 中文镜像网站:http://www.bootcss.com 用于开发响应式布局、移动设备优先的WEB项目 1、使用boot 创建文件夹,在文...

wytao1995
今天
9
0
小知识:讲述Linux命令别名与资源文件的区别

别名 别名是命令的快捷方式。为那些需要经常执行,但需要很长时间输入的长命令创建快捷方式很有用。语法是: alias ppp='ping www.baidu.com' 它们并不总是用来缩短长命令。重要的是,你将它...

老孟的Linux私房菜
今天
8
0
《JAVA核心知识》学习笔记(6. Spring 原理)-5

它是一个全面的、企业应用开发一站式的解决方案,贯穿表现层、业务层、持久层。但是 Spring 仍然可以和其他的框架无缝整合。 6.1.1. Spring 特点 6.1.1.1. 轻量级 6.1.1.2. 控制反转 6.1.1....

Shingfi
今天
7
0
Excel导入数据库数据+Excel导入网页数据【实时追踪】

1.Excel导入数据库数据:数据选项卡------>导入数据 2.Excel导入网页数据【实时追踪】:

东方墨天
今天
8
1

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部