客户端提高系统扩展性的模式
咋一看来, 客户端是乎对系统的性能和可扩展性影响不大:用户总是需要从服务器端获得一定的信息量来完成计划的事务,比如用户在网上购物时需要查询发现需要购买的商品,在获得一定量的商品信息后才能决定购买行为。但是如果进一步细分就可以发现,客户端的代码不仅影响系统性能, 这种影响还是多方面的。首先不同的网页巡航的路径和页面布局设计,造成用户完成事务需要从服务器端读取的总数据量, 以及一定时间内请求的数据密度分布峰值都不一样,以此对服务器负载的影响也不尽相同; 其次,由于访问行为的可重复性和网站网页布局及部分插件资源的不变性,在客户端使用缓存技术来存储静态资源可以大幅度减少从服务端读取的总信息量; 最后, 因为客户端代码问题造成的网页显示迟缓会反过来影响用户行为,产生额外的信息请求,进而增加了不必要的服务器负载。下面就来讨论影响上述三个方面的网页内容优化的常见模式。
-
减少完成事务所需要的动态数据内容:延迟加载。
-
减少完成事务所需要的静态资源:浏览器缓存。
-
减少用户行为造成的无用请求 :支持渐进式页面生成 。
一。 减少完成事务所需要的动态数据内容:延迟加载。
随着IPhone的风靡,在页面用户体验(UX ) 设计中越来越强调界面简洁对UX的重要性。学术界也展开了相关的研究, 比如许多网站的80%的用户使用网站上提供的功能的20%来完成80%的工作; 我 工作过的一个公司做的研究也得出了类似的结论,公司提供了一套完善和复杂的客户退休投资管理系统,而客户实际只使用其中很小的一部分功能,在需要客户选择时,绝大多数是使用系统提供的缺省值。一些行为心理学的实验结果发现,用户的每一次选择都会带来一定程度上的心理负担。给用
户提供过多选择,用户体验反而比不上选择很少,或者没有提供用户选择的系统。 简洁页面同时也直接减少了需要的动态数据内容量。
实现简洁页面的一个关键策略是对数据内容使用延迟加载 : 数据只有在用户真正要使用时才从服务器端下载。比如对大型表格的分页(Pagination) 查询和显示 是一个常用的技巧。分页技术和排序/过滤 方法结合,使用户感兴趣的内容出现在分页的前几页,避免了系统不必要的加载用户不感兴趣的的数据。
随着Ajax技术的普及和成熟,越来越多的Web2.0网页把延迟加载直接使用在表格上,用户使用表格窗口右边的滑动条滚动来取代翻页,滑动条滚动到或者滑动条停止的位置上开始加载数据内容。和分页技术相比,延迟加载的表格技术避免了分页技术因为要求用户点击换页而带来的查询体验的中断性,提供了更自然的用户体验, 在另一方面因为大量数据加载,可能影响UI的反应速度。
无论分页技术还是延迟加载的表格技术,都有共同的缺点。 1)因为数据不是一次性全部加载, 数据的缓存变得复杂;2) 一些在表格水平的计算没法在客户端进行,比如表格某列的数值总和; 在表格数据支持动态更新时,实现尤为困难。比如股票交易系统中显示客户投资收益的表格,因为计算涉及到股票实时价格,如果客户端不一次性加载客户投资账户中所有股票代码,无法在客户端完成实时的投资收益的计算。
实现简洁页面的设计原则是遵循使用者认知空间中的自然逻辑,在合适的时间地点提供合适的信息,来获得用户体验中的认知流畅性。还有一种常用的延迟加载技术是先下载/显示关于对象/主题的一些概述, 或者句柄;在用户进一步选择后再下载/显示更为详细的结果。 比如网上机票价格查询,首先返回给用户的是一个包含各种机票价格的表格, 用户选择表格中的某一行,浏览器再下载显示该价格所对应的详细航线。
二。 减少完成事务所需要的静态资源:浏览器缓存。
今天的网页包括许多组件,尤其是Web2.0后RIA(丰富互联网应用程序)的普及,网页的组件数目上升很快。通过使用HTTP的Expires和 Cache-control报头,可以要求浏览器缓存这些组件。这就避免了在随后的访问中不必要的HTTP请求。
-
Expires报头
Expires: Mon, 4 Jun 2012 12:00:00 GMT
Expires最常用于静态图像,但它可以而且应该使用在包括脚本,样式表,闪客的所有组件。 使用Expires报头会带来一些额外的开发成本; 因为Expires报头使用特定的日期,它有严格的服务器和客户端之间时钟同步的
要求。此外服务器端必须在到期日期前更新服务器的日期设置。
-
-
Cache-Control报头
-
Cache-Control: max-age=157680000
HTTP/1.1引进了Cache-Control报头来克服Expires的局限性。
Cache-Control 使用max-age指令来指定一个组件的缓存有效期(刷新窗口)。浏览器在缓存有效期之内使用组件缓存的版本,从而避免了额外的HTTP请求。
-
-
Cache-control 和 Expires 联用
-
在不支持HTTP/1.1的浏览器上使用缓存技术,可以同时指定Expires和Cache-Control/max-age。在这种情况下依然存在Expires到期的时钟同步和配置的维护问题; 幸运的是,Apache的mod_expires模块 ( ) 通过ExpiresDefault指令设置类似max-age的相对日期,对域中的所有文件有效:
ExpiresDefault "access plus 5 years"
mod_expires 模块同时 设定Expires和 Cache-Control /max-age指令。
Expires: Mon, 4 Jun 2012 12:00:00 GMT
Cache-Control: max-age=157680000
Expires 的实际值是根据接收到请求时的时间而计算的,上面的例子中永远是5年以后。根据HTTP规范规定,Cache-Control/max-age比Expires报头具有优先权; 这样时钟同步问题就被避免了。
一个HTML文档往往包含了用户请求的动态更新的内容,通常情况下不会设有Expires报头。在理想的情况下,Expires报头设置在页面中的所有组件水平上。如果要针对个别MIME类型则可以使用ExpiresByType:
ExpiresByType text/html "access plus 1 month 15 days 2 hours"
ExpiresByType p_w_picpath/gif "modification plus 5 hours 3 minutes"
如上所述, 提高所有浏览器中的缓存的最佳解决方案是联合使用Expires和ExpiresDefault指令。当从浏览器的缓存中读出文档的所有组件时,响应时间将缩短50%或更多。
-
-
使用Last-modified和ETags
-
HTTP/1.1引入 ETag. ETag是一个可以与WEB资源(Web网页,JSON或XML文档)关联的记号(token)。 ETag提供了Web服务器和浏览器验证缓存组件是否匹配的一种机制。
服务器在HTTP响应头中将ETag传送到客户端,以下是服务器端返回的格式 :
ETag:"50b1c1d4f775c61:df3"
客户端验证组件的更新查询:
If-None-Match : W / "50b1c1d4f775c61:df3"
如果ETag没改变,则返回状态码304, 从而减少主体的回应。
Last-Modified和ETags联合使用,可更精确的利用客户端浏览器的缓存。
1.客户端请求一个页面(A)。 2.服务器返回页面A,并在给A加上一个Last-Modified/ETag。 3.客户端展现该页面,并将页面连同Last-Modified/ETag一起缓存。 4.客户再次请求页面A,并将上次请求时服务器返回的Last-Modified/ETag一起传递给服务器。 5.服务器检查该Last-Modified或ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304和一个空的响应体。
Etag同时也解决了 Last-Modified 无法解决的一些问题。
-
有的时候不希望基于文件的最后修改时间来决定客户端是否需要重新获得文件。 比如一些文件的修改时间会周期性发生了更新,但内容并不改变(仅仅改变的修改时间)
-
有的时候服务器运行环境不能得到精确的文件最后修改时间。
-
使用HTML5离线Web应用程序
HTML5的相关的标准 引入了离线Web应用程序(Offline Web Application)的新模式,帮助Web 浏览器在没有网络连接的情况下运行网页 程序。
-
应用程序缓存
-
当地存储(localStorage)
-
Web SQL和 索引数据库(Indexed Database)
-
在线/离线事件
同时, 通过存储在缓存中的数据, 和在客户端永久存储更多的数据(包括用户会话状态数据,应用程序用于重装和恢复页面的数据), 加速了 网页反应速度, 并可以减少客户端的请求,降低服务器端的负载,提高服务器端应用程序的性能和可扩展性。
这是应用程序缓存清单文件(Cache Manifest File) 的一个例子。这里罗列在CACHE MANIFEST下的三个资源文件foo.css, foo.gif 和foo.js在下载后被缓存,在NETWORK下的文件login.jsp 不被缓存,在任何情况下都需要到服务器端读取。
CACHE MANIFEST# 2012-06-04 v1.0.0/foo.css/foo.gif/foo.jsNETWORK:login.jspFALLBACK:/html/ /offline.html |
IE不支持应用程序缓存。不同浏览器缓存大小上限也不一样,有的浏览器对每个站点的缓存上限设定在5MB。