`
helloqiner
  • 浏览: 24828 次
  • 来自: ...
文章分类
社区版块
存档分类

充分利用多核优势,高效并行渲染页面--改造nutz使其成为支持并行计算的MVC框架

阅读更多

首先我们来看一个场景:

 


       此图为sohu 首页的一部分。

这个页面比较典型,其实只要是复杂一些的页面通常都由许多不同模块的数据内容组成。如该页面就包含了新闻、体育、娱乐、视频等内容。每一个模块都需要单独查询,然后将结果填充到页面的各个部分。

如果由你来实现这个页面,你会怎么写呢?

通常,我们的写法是 (struts2.0)

public String index() {
	//查询新闻
	newsList = service.queryNewsList();
	//查询体育
	sportsList = service.querySportsList();
	//查询视频
	videoList = service.queryVideoList();
	return SUCCESS;
}

 这样是可以完成任务的,但是每一个查询都必须等待上一个查询结束后才能开始。假设查询比较耗时(不考虑缓存的因素):新闻查了20秒,体育查了10秒,视频查了5秒,则总页面至少需要 35 秒后才能打开。

 

如果老板对性能要求较高,且 SQL优化已经起不到帮助作用,那么还有没有其它办法可以提高程序的效率呢?

 

对了,最简单的就是在页面中使用 iframe,将页面拆分成若干个子页面并行加载。这样每个部分执行快慢并不影响页面的整体打开速度,从总体上讲页面的加载速度提高了。但是过多的使用iframe,会带来许多负面影响。如session问题、内存占用问题、样式问题等等,而且页面的可维护性也将变差。因此这并不是一种好的解决方案。

或者也可以采用 ajax 动态获取各个部分并填充页面。但这样大大增加了前端的代码量及实现难度。

 

现在我们的服务器往往都是4核、甚至是8核的了,我们能不能充分利用多核优势在后台并行运算结果并统一渲染页面呢?

思路1:当用户请求到 indexAction 的时候,启动n个子线程分别查询不同模块数据。最终当所有线程处理结束后,合并结果,渲染页面。

使用多线程,就必须解决线程同步、加解锁等等问题。需要大大增加代码量,且要求程序员水平较高。如果一个项目中,程序员水平参差不齐,那么最好别采用这种方案,否则不知哪里的出了问题就会影响整个项目的质量。

 

思路2:让框架去干脏活、累活。当用户请求到 indexAction 的时候,我们利用框架的功能,动态将该请求模拟成为n个子action的请求,并在子action结束时自动合并结果,渲染页面。这样我们就通过框架虚拟出了iframe 的效果,同时避免了页面中使用 iframe 带来的问题,且降低了程序的复杂度。

采用这种方案,原有的项目无需大改,程序员也可以用熟悉的方法去开发,无需关注多线程及数据合并等等问题,可谓一举多得。

现在我们来改造 NutzMVC 框架,使其能支持并行计算功能。

Nutz是一个国产的开源框架,融合了MVCIocAOPDao诸多功能,且设计合理,预留了多处扩展点,用户可以很容易的动态给它增、减功能。因此今天就拿它来开刀。

Nutz地址为:http://code.google.com/p/nutz/ 有兴趣的同学可以去看看。下面的内容假设您已经对 Nutz有所了解。

为了在程序中虚拟出对 action 请求,需要用到 UrlMapping 类的实例。这个类在系统启动时会自动创建,但默认为 private 直接获取不到。因此需要自定义一个加载器,将UrlMapping 实例开放出来。

这个类很简单,只需重载下 NutLoading 即可。

 

 

 

/**
 * 缺省加载器的基础上公开 urlMaping 属性,使 MVC 运行时可以并行执行其它 URL
 * @author Gongqin(gongqin@gmail.com)
 */
public class ProLoading extends NutLoading {
	
	private static UrlMapping urlMaping = null;

	@Override
	public UrlMapping load(NutConfig config) {
		urlMaping = super.load(config);
		return urlMaping;
	}

	/**
	 * 返回 urlMaping 实例
	 * @return
	 */
	public static UrlMapping getUrlMaping() {
		return urlMaping;
	}
}
 

 

 

新增加一个注解,用于标识需要异步请求的子 action 地址

 

/**
 * 声明一个需要异步请求的 url 地址<br/>
 * 例如: 
 * <code>
 * @Asyn({"retb:/b", "retc:/c"})
 * </code> 
 * 对 /b 和 /c 请求的返回值会分别存入 req 的 retb 和 retc 的属性中。
 * 
 * @author Gongqin(gongqin@gmail.com)
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
public @interface Asyn {
	
	/**
	 * 需要异步请求的 url,支持同时进行多个请求<p/> 
	 * 每行的格式为 key:url 。运行完成后会将 key 做为主键存入 req 中去。如果没有写 key,则认为您已经自己把返回值做了处理
	 */
	String[] value();
	
	/**
	 * 异步执行的子action最长处理时间,默认为 30 秒
	 * @return
	 */
	int timeout() default 30;
	
}

 

 

 这里到了今天的核心:定制一个动作链处理器,用于解析注解,并异步执行子 action ,最后合并处理结果。

 

/**
 * 异步执行的动作链处理器
 * @author Gongqin(gongqin@gmail.com)
 */
public class AsynProcessor extends AbstractProcessor {

	/**
	 * 解析 Asyn 注解并异步执行子 action,最后合并处理结果
	 */
	@Override
	public void process(final ActionContext ac) throws Throwable {
		Asyn asyn = ac.getMethod().getAnnotation(Asyn.class);
		// 如果没有异步任务,则处理完毕后直接返回
		if (null == asyn || null == asyn.value() || asyn.value().length == 0) {
			doNext(ac);
			return;
		}

		// 开始处理异步任务
		ExecutorService executor = Executors.newCachedThreadPool();
		List<Future<Pair<Object>>> tasks = new ArrayList<Future<Pair<Object>>>(asyn.value().length);
		for (final String url : asyn.value()) {
			//增加一个异步请求
			tasks.add(executor.submit(new Callable<Pair<Object>>() {
				@Override
				public Pair<Object> call() throws Exception {
					if (url == null)
						return null;
					String key = null;
					String tourl = url;
					int pos = url == null ? 0 : url.indexOf(":");
					if (pos > 0) {
						key = url.substring(0, pos);
						tourl = url.substring(pos + 1);
					}
					ActionContext subAc = new ActionContext();
					subAc.setRequest(new PathInfoRequest(tourl, ac.getRequest())).setResponse(ac.getResponse());

					ActionInvoker ai = ProLoading.getUrlMaping().get(subAc);
					ai.invoke(subAc);
					return new Pair<Object>(key, subAc.getMethodReturn());
				}

			}));
		}

		doNext(ac);

		// 获取其它 action 的返回值,供页面渲染
		for (Future<Pair<Object>> future : tasks) {
			try {
				Pair<Object> ret = future.get(asyn.timeout(), TimeUnit.SECONDS);
				if (ret != null && !Strings.isBlank(ret.getName())) {
					ac.getRequest().setAttribute(ret.getName(), ret.getValue());
				}
			}
			catch (TimeoutException e) {
				//忽略处理超时的任务
			}
		}
	}
}

 

 发起虚拟请求时,要修改req的请求路径。默认的 HttpServletRequest 无法修改其 pathInfo 属性。为了虚拟出多个请求,因此对HttpServletRequest 进行了薄封装,使其允许修改 pathInfo

 

public class PathInfoRequest implements HttpServletRequest {
	//其它方法省略
	private String url;
	public void setPathInfo(String url) {
			this.url = url;
		}
		@Override
		public String getPathInfo() {
			return url;
	}
}

 

 

 

OK,这样就全部搞定了。不复杂吧。下面我们来测试一下。

重新定义一个处理器链的配置文件 default-chains.js,把异步执行的动作链处理器声明进去。

 

 

{
	"default" : {
		"ps" : [
		      "org.nutz.mvc.impl.processor.UpdateRequestAttributesProcessor",
		      "org.nutz.mvc.impl.processor.EncodingProcessor",
		      "org.nutz.mvc.impl.processor.ModuleProcessor",
		      "com.nutz.mvc.AsynProcessor", //自行实现的处理器
		      "org.nutz.mvc.impl.processor.ActionFiltersProcessor",
		      "org.nutz.mvc.impl.processor.AdaptorProcessor",
		      "org.nutz.mvc.impl.processor.MethodInvokeProcessor",
		      "org.nutz.mvc.impl.processor.ViewProcessor"
		      ],
		"error" : 'org.nutz.mvc.impl.processor.FailProcessor'
	}
}

 

 

 写个主模块,把自定义的加载器和处理器链声明进去。

 

/**
 * @author Gongqin(gongqin@gmail.com)
 */
//使用自定义的加载器,暴露 urlMaping
@LoadingBy(ProLoading.class)
@ChainBy(args={"com/nutz/mvc/default-chains.js"})
public class MainModule {
	
	/**
	 * 主 action,请求时自动产生两个子action的请求
	 */
	@At("/a")
	@Asyn({"retb:/b", "retc:/c"})
	@Ok("jsp:a")
	public String a(@Param("a") String str, HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("接收参数a=" + str);
		//模拟耗时操作
		try {
			TimeUnit.SECONDS.sleep(1);
		}catch (InterruptedException e) {}
		return "请求到了 a";
	}
	
	@At("/b")
	@Ok("void")
	public String b(@Param("b") String str) {
		//子 action 也一样可以获取到表单提交的参数
		System.out.println("接收参数b=" + str);
		System.out.println("b处理完成");
		return "请求到了 b";
	}
	
	@At("/c")
	@Ok("void")
	public String c(@Param("c") String str) {
		System.out.println("接收参数c=" + str);
		System.out.println("c处理完成");
		return "请求到了 c";
	}
}

 

 

 

开始进行测试:

 

/**
 * @author Gongqin(gongqin@gmail.com)
 */
public class MvcTest extends AbstractMvcTest {

	@Override
	protected void initServletConfig() {
		servletConfig.addInitParameter("modules", "com.nutz.test.MainModule");
	}

	@Test
	public void test1() throws Throwable {
		//模拟一个 http 请求
		request.setPathInfo("/a");
		request.setParameter("a", "this_is_a");
		request.setParameter("b", "this_is_b");
		request.setParameter("c", "this_is_c");
		request.setMethod("GET");
		servlet.service(request, response);
		
		//验证返回值 
		Assert.assertEquals("obj属性返回不正确",  "请求到了 a", request.getAttribute("obj"));
		Assert.assertEquals("retb属性返回不正确", "请求到了 b", request.getAttribute("retb"));
		Assert.assertEquals("retc属性返回不正确", "请求到了 c", request.getAttribute("retc"));
	}
}

 

 

 

执行后通过日志可以看出,虽然我们只请求了 /a ,但框架自动将此请求分解到了 /b 和 /c 的 action 上,并且 /b 和 /c 比 /a 处理完成时间还早,说明它们是真正的并行执行了。

今后,您只需对action 声明一个注解,即可灵活的并行执行多个子action,最终统一渲染页面啦。

拿开头举的场景的例子来说,则总页面只需20 秒即可打开。

注意,虽然子action还可以再嵌套子 action ,但一定要避免循环嵌套。如果出现循环嵌套的情况,则只能等待处理超时了。默认的超时时间为 30 秒。

 

 

 

 

思路3BigPipe

BigPipe 是 Facebook 提出的前端性能优化方案。2010 年初的时候,Facebook 的前端性能研究小组开始了他们的优化项目,经过了六个月的努力,成功的将个人空间主页面加载耗时由原来的秒减少为现在的2.5 秒。关于它的详细介绍大家可以baidu一下。

它的核心思路是先给浏览器输出页面的主体框架,之后服务器端并行处理不同的pagelet 的内容,一个pagelet 内容生成好了,立刻将其flush 给浏览器。以此来加速页面。这是一种很棒的思路。

在方案2的基础上,我们只需要再做简单的修改即可实现。唯一的区别就是,我们不需要AsynProcessor处理器了,而是要把官方的 org.nutz.mvc.impl.processor.ViewProcessor 重新定制下即可。

具体的实现方法,留待下一讲再行发布。感兴趣的同学可以自行实现下。

  • 大小: 197.1 KB
分享到:
评论
1 楼 mojunbin 2014-08-14  
Nutz没有使用过,但是这种思路是好的,不知道有没有其他实现方式

相关推荐

    nutz搭建的MVC框架

    本篇文章将详细介绍如何利用Nutz搭建MVC框架,并探讨其在实际开发中的优势。 **一、Nutz MVC框架介绍** Nutz MVC是Nutz框架的一部分,它基于Servlet容器运行,通过注解驱动,简化了Java Web应用的开发流程。Nutz ...

    nutz需要的jar包

    `nutz-mvc`包括了`nutz-web`和`nutz-wk`,前者处理Web相关的功能,后者是Nutz的Web容器,提供了Servlet的替代方案,使得开发者可以脱离传统的Servlet容器运行Nutz应用。 在进行Web开发时,我们还需要`nutz-plugin-...

    nutz初使用之MVC HelloWorld (netbeans html,js版)

    - Nutz MVC是Nutz框架的一部分,用于构建Web应用程序,提供了控制器、视图和模型的结构,支持RESTful风格的URL路由。 2. MVC模式: - Model(模型):负责处理业务逻辑,与数据库交互,存储和管理数据。 - View...

    高效,小巧的开源JAVA WEB 开发框架-Nutz (源码,开发文档)

    Mvc 框架 -- Nutz.Mvc Json 解析器 -- Nutz.Json 更多的简化Java语法的函数和类 -- Nutz.Lang 以及 Nutz.Castors 不依赖任何第三方 Jar 包,从而便于程序员建立开发环境,部署,甚至重新编译 Nutz 的源代码。 不幸的...

    nutz-1.b.52.zip

    Nutz, 它是一组轻便小型的框架的集合。 -------------Nutz 可以做什么? Dao -- 针对 JDBC 的薄封装,事务模板,无缓存 Ioc -- JSON 风格的配置文件,声明时切片支持 Mvc -- 注解风格的配置,内置多文件上传功能 ...

    SpringMVC+Nutz框架介绍.pdf

    综上所述,SpringMVC+Nutz框架组合构建的开发平台,以其强大的功能、高度的可定制性和良好的兼容性,为企业级应用开发提供了坚实的基础设施。开发者可以根据项目需求,灵活选择和使用其中的各项技术,以实现高效、...

    nutz框架使用手册.zip

    Nutz框架是一个基于Java语言的轻量级开源框架,它以简单、实用、高效为设计理念,为Java开发者提供了一整套全面的开发解决方案。Nutz框架由一系列子项目组成,包括Nutz IOC(依赖注入)、Nutz DAO(数据访问对象)、...

    nutz框架开发手册

    ### Nutz框架开发手册知识点详解 #### 一、引言 - **背景介绍**:随着Web开发技术的不断发展,Java虽然仍然占据着重要的地位,但其在开发效率方面相较于脚本语言存在一定的劣势。为此,Nutz框架应运而生,旨在通过...

    nutz_redis集成依赖包

    1. **Nutz框架**:Nutz是一个轻量级的Java Web开发框架,它提供了MVC模式,ORM支持,以及丰富的工具类。Nutz的目标是简化开发流程,提高开发效率,使开发者更专注于业务逻辑。 2. **Redis**:Redis是一个高性能的...

    nutz除了ssh框架比较好的框架

    Nutz是一个在中国开源社区活跃的Java Web开发框架,它的出现为开发者提供了另一种选择,特别是对于那些寻求更轻量级、高效且易于上手的框架的开发者。标题提到"Nutz除了SSH框架比较好的框架",这里的SSH指的是Struts...

    nutz 一个使用简单功能强大的mvc框架

    该框架是一个开源项目,可以免费下载, 集成的DAO,MVC,Spring 等框架的功能 不需要其他jar包 学习容易,好用

    Nutz高效开发框架v1.0.5免费版

    Nutz高效开发框架是国内比较独立强大的技术团队开发的轻量级的框架快捷实用工具,由于可以方便地在各种数据库中调用存储数据,所以可以称之为方便程序员通行的项目大厦的手脚架工具。对于从事数据库相关的程序员来说...

    新框架Nutz

    Nutz是一个轻量级的开源Java开发框架,它将数据持久化、反转控制(IoC)和Web框架(MVC)集成为一个功能完备的工具集,其核心设计目标是提供小巧、易于理解和使用的功能,而不依赖于其他第三方jar包。 数据持久化是...

    nutz-1.b.48-manual.pdf 文档

    Nutz的设计目的之一是解决Java在Web开发领域相对于脚本语言而言显得开发效率较低的问题,通过提供一整套简洁、高效的组件和工具,如Dao、Ioc、Mvc、Json、Castors、Lang、Aop、Plugin和Resource,它们独立使用且功能...

    wendal-nutz-master.zip

    这个压缩包“wendal-nutz-master.zip”包含了NUTZ框架的一个示例项目或者学习资源,帮助初学者理解和掌握NUTZ的核心特性。 在深入探讨NUTZ之前,我们先来理解一下什么是框架。框架是一种预定义的软件结构,它规定了...

    nutz的freemarker视图插件

    将Nutz与Freemarker结合,可以实现类似Struts2中对Freemarker模板的支持,使开发者能够利用Freemarker的强大功能来构建动态网页。 首先,让我们深入了解Nutz和Freemarker的基本概念: 1. **Nutz**: - Nutz是一个...

    nutz初使用之MVC HelloWorld (netbeans jsp版)

    Nutz是一个轻量级的Java开发框架,它提供了全面的MVC支持,使得开发者能够高效地构建Web应用。我们将使用NetBeans IDE和JSP来实现这一过程。 首先,我们需要确保已经安装了NetBeans IDE,这是一个强大的Java开发...

    nutz-1.b.49-manual.pdf

    描述:"nutz 框架nutz-1.b.49-manual.pdf,配置简单易懂" 提示我们Nutz框架的配置操作简单明了,易于用户理解和上手。 标签:"nutz" 确定了文档主题是围绕Nutz框架展开。 根据部分内容的详细描述,可以提炼出以下...

    nutz 使用手册 nutz-1.a.33-manual.pdf

    - **核心特点**:Nutz 的核心在于其轻便性和灵活性,使得开发者能够更加专注于业务逻辑而非框架细节。该框架不依赖第三方 jar 包,只需引入 nutz.jar 即可使用全部功能。 #### 二、Nutz 主要组件及其用途 - **Dao*...

Global site tag (gtag.js) - Google Analytics