﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-table-文章分类-服务器架构</title><link>http://www.blogjava.net/table/category/43545.html</link><description /><language>zh-cn</language><lastBuildDate>Tue, 26 Jan 2010 05:10:43 GMT</lastBuildDate><pubDate>Tue, 26 Jan 2010 05:10:43 GMT</pubDate><ttl>60</ttl><item><title>使用ETags减少Web应用带宽和负载</title><link>http://www.blogjava.net/table/articles/310428.html</link><dc:creator>小卓</dc:creator><author>小卓</author><pubDate>Thu, 21 Jan 2010 10:57:00 GMT</pubDate><guid>http://www.blogjava.net/table/articles/310428.html</guid><wfw:comment>http://www.blogjava.net/table/comments/310428.html</wfw:comment><comments>http://www.blogjava.net/table/articles/310428.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/table/comments/commentRss/310428.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/table/services/trackbacks/310428.html</trackback:ping><description><![CDATA[<h2>介绍 </h2>
<p>最近，大众对于REST风格应用架构表现出强烈兴趣，这表明Web的优雅设计开始受到人们的注意。现在，我们逐渐理解了&#8220;<a href="http://www.w3.org/TR/webarch/">3W架构</a>（Architecture of the World Wide Web）&#8221;内在所蕴含的可伸缩性和弹性，并进一步探索运用其范式的方法。本文中，我们将探究一个可被Web开发者利用的、鲜为人知的工具，不引人注意的&#8220;ETag响应头（ETag Response Header）&#8221;，以及如何将它集成进基于Spring和Hibernate的动态Web应用，以提升应用程序性能和可伸缩性。 </p>
<p>我们将要使用的Spring框架应用是基于&#8220;宠物诊所（petclinic）&#8221;的。下载文件中包含了关于如何增加必要的配置及源码的说明，你可以自己尝试。 </p>
<h2>什么是&#8220;ETag&#8221;？ </h2>
<p>HTTP协议规格说明定义ETag为&#8220;被请求变量的实体值&#8221; （参见<a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html"> http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html</a> —— 章节 14.19）。 另一种说法是，ETag是一个可以与Web资源关联的记号（token）。典型的Web资源可以一个Web页，但也可能是JSON或XML文档。服务器单独负责判断记号是什么及其含义，并在HTTP响应头中将其传送到客户端。 </p>
<h2>ETag如何帮助提升性能？ </h2>
<p>聪明的服务器开发者会把ETags和GET请求的&#8220;If-None-Match&#8221;头一起使用，这样可利用客户端（例如浏览器）的缓存。因为服务器首先产生ETag，服务器可在稍后使用它来判断页面是否已经被修改。本质上，客户端通过将该记号传回服务器要求服务器验证其（客户端）缓存。 </p>
<p>其过程如下： </p>
<ol>
    <li>客户端请求一个页面（A）。
    <li>服务器返回页面A，并在给A加上一个ETag。
    <li>客户端展现该页面，并将页面连同ETag一起缓存。
    <li>客户再次请求页面A，并将上次请求时服务器返回的ETag一起传递给服务器。
    <li>服务器检查该ETag，并判断出该页面自上次客户端请求之后还未被修改，直接返回响应304（未修改——Not Modified）和一个空的响应体。 </li>
</ol>
<p>本文的其余部分将展示在基于Spring框架的Web应用中利用ETag的两种方法，该应用使用Spring MVC。首先我们将使用Servlet 2.3 Filter，利用展现视图（rendered view）的MD5校验和（checksum）以实现生成ETag的方法（<a href="http://bitworking.org/news/150/REST-Tip-Deep-etags-give-you-more-benefits">一个&#8220;浅显的&#8221;ETag实现</a>）。 第二种方法使用更为复杂的方法追踪view中所使用的model，以确定ETag有效性（<a href="http://bitworking.org/news/150/REST-Tip-Deep-etags-give-you-more-benefits">一个&#8220;深入的&#8221;ETag实现</a>）。尽管我们使用的是Spring MVC，但该技术可以应用于任何MVC风格的Web框架。 </p>
<p>在我们继续之前，强调一下这里所展现的是提升动态产生页面性能的技术。已有的优化技术也应作为整体优化和应用性能特性调整分析的一部分来考虑。（见下）。 </p>
<blockquote>
<p><strong>自顶向下的Web缓存</strong> </p>
<p>本文主要涉及对动态生成页面使用HTTP缓存技术。当考虑提升Web应用的性能的时候，应采取一个整体的、自顶向下的方法。为了这一目的，理解HTTP请求经过的各层是很重要的，应用哪些适当的技术取决于你所关注的热点。例如： </p>
<ul>
    <li>将Apache作为Servlet容器的前端，来处理如图片和javascript脚本这样的静态文件，而且还可以使用<a href="http://httpd.apache.org/docs/2.2/mod/core.html#fileetag">FileETag指令</a>创建ETag响应头。
    <li>使用针对javascript文件的优化技术，如将多个文件合并到一个文件中以及压缩空格。
    <li>利用GZip和缓存控制头（Cache-Control headers）。
    <li>为确定你的Spring框架应用的痛处所在，可以考虑使用 <a href="http://www.springframework.org/docs/api/org/springframework/aop/interceptor/JamonPerformanceMonitorInterceptor.html">JamonPerformanceMonitorInterceptor</a>。
    <li>确信你充分利用ORM工具的缓存机制，因此对象不需要从数据库中频繁的再生。花时间确定如何让查询缓存为你工作是值得的。
    <li>确保你<strong>最小化数据库中获取的数据量</strong>，尤其是大的列表。如果每个页面只请求大列表的一个小子集，那么大列表的数据应由其中某个页面一次获得。
    <li>使放入到HTTP session中的数据量最小。这样内存得到释放，而且当将应用集群的时候也会有所帮助。
    <li>使用<strong>数据库明细（database profiling）</strong>工具来查看在查询的时候使用了什么索引，在更新的时候整个表没有被上锁。 </li>
</ul>
<p>当然，应用性能优化的至理名言是：两次测量，一次剪裁（measure twice, cut once）。哦，等等，这是对木工而言的！没错，但是它在这里也很适用！ </p>
</blockquote>
<h2>ETag Filter内容体 </h2>
<p>我们要考虑的第一种方法是创建一个Servlet Filter，它将基于页面（MVC中的&#8220;View&#8221;）的内容产生其ETag 记号。乍一看，使用这种方法所获得的任何性能提升看起来都是违反直觉的。我们仍然不得不产生页面，而且还增加了产生记号的计算时间。然而，这里的想法是减少带宽使用。在大的响应时间情形下，如你的主机和客户端分布在这个星球的两端，这很大程度上是有益的。我曾见过东京办公室使用纽约服务器上托管的应用，其响应时间达到了 350 ms。随着并发用户数的增长，这将变成巨大的瓶颈。 </p>
<h3>代码 </h3>
<p>我们用来产生记号的技术是基于从页面内容计算MD5哈希值。这通过在响应之上创建一个包装器来实现。该包装器使用字节数组来保存所产生的内容，在filter链处理完成之后我们利用数组的MD5哈希值计算记号。 </p>
<p>doFilter方法的实现如下所示。 </p>
<pre> <font color="#0000ff">public void</font> doFilter(ServletRequest req, ServletResponse res, FilterChain chain) <font color="#0000ff">throws</font> <font color="#006699">IOException</font>,<br />
ServletException {<br />
HttpServletRequest servletRequest = (HttpServletRequest) req;<br />
HttpServletResponse servletResponse = (HttpServletResponse) res;<br />
<br />
<font color="#0000ff">ByteArrayOutputStream</font> baos = <font color="#0000ff">new</font> <font color="#0000ff">ByteArrayOutputStream</font>();<br />
ETagResponseWrapper wrappedResponse = new ETagResponseWrapper(servletResponse, baos);<br />
chain.doFilter(servletRequest, wrappedResponse);<br />
<br />
<font color="#0000ff">byte</font>[] bytes = baos.toByteArray();<br />
<br />
<font color="#0000ff">String</font> token = <font color="#800000">'"'</font> + ETagComputeUtils.getMd5Digest(bytes) + '"';<br />
servletResponse.setHeader(<font color="#800000">"ETag"</font>, token); <font color="#006600">// always store the ETag in the header</font><br />
<br />
<font color="#0000ff">String</font> previousToken = servletRequest.getHeader(<font color="#800000">"If-None-Match"</font>);<br />
<font color="#0000ff">if</font> (previousToken != <font color="#0000ff">null</font> &amp;&amp; previousToken.equals(token)) { <font color="#006600">// compare previous token with current one</font><br />
logger.debug(<font color="#800000">"ETag match: returning 304 Not Modified"</font>);<br />
servletResponse.sendError(HttpServletResponse.SC_NOT_MODIFIED);<br />
<font color="#006600">// use the same date we sent when we created the ETag the first time through</font><br />
servletResponse.setHeader(<font color="#800000">"Last-Modified"</font>, servletRequest.getHeader(<font color="#800000">"If-Modified-Since"</font>));<br />
} <font color="#0000ff">else</font>  { 		<font color="#006600">// first time through - set last modified time to now </font><br />
<font color="#0000ff">Calendar</font> cal = <font color="#0000ff">Calendar</font>.getInstance();<br />
cal.set(<font color="#0000ff">Calendar</font>.MILLISECOND, 0);<br />
<font color="#0000ff">Date</font> lastModified = cal.getTime();<br />
servletResponse.setDateHeader(<font color="#800000">"Last-Modified"</font>, lastModified.getTime());<br />
<br />
logger.debug(<font color="#800000">"Writing body content"</font>);<br />
servletResponse.setContentLength(bytes.length);<br />
ServletOutputStream sos = servletResponse.getOutputStream();<br />
sos.write(bytes);<br />
sos.flush();<br />
sos.close();<br />
}<br />
} </pre>
<p><small style="font-weight: bold"><font size="2">清单 1：ETagContentFilter.doFilter</font></small> </p>
<p>你需注意到，我们还设置了Last-Modified头。这被认为是为服务器产生内容的正确形式，因为其迎合了不认识ETag头的客户端。 </p>
<p>下面的例子使用了一个工具类EtagComputeUtils来产生对象所对应的字节数组，并处理MD5摘要逻辑。我使用了javax.security MessageDigest来计算MD5哈希码。 </p>
<pre><font color="#0000ff">public static byte</font>[] serialize(<font color="#006699">Object</font> obj) <font color="#0000ff">throws</font> <font color="#006699">IOException</font> {<br />
<font color="#0000ff">byte</font>[] byteArray = <font color="#0000ff">null</font>;<br />
<font color="#006699">ByteArrayOutputStream</font> baos = <font color="#0000ff">null</font>;<br />
<font color="#006699">ObjectOutputStream</font> out = <font color="#0000ff">null</font>;<br />
try {<br />
<font color="#006600">// These objects are closed in the finally</font>.<br />
baos = <font color="#0000ff">new</font> <font color="#006699">ByteArrayOutputStream</font>();<br />
out = <font color="#0000ff">new</font> <font color="#006699">ObjectOutputStream</font>(baos);<br />
out.writeObject(obj);<br />
byteArray = baos.toByteArray();<br />
} <font color="#0000ff">finally</font> {<br />
<font color="#0000ff">if</font> (out != <font color="#0000ff">null</font>) {<br />
out.close();<br />
}<br />
}<br />
<font color="#0000ff">return</font> byteArray;<br />
}<br />
<br />
<font color="#0000ff">public static</font> String getMd5Digest(<font color="#0000ff">byte</font>[] bytes) {<br />
MessageDigest md;<br />
<font color="#0000ff">try</font> {<br />
md = MessageDigest.getInstance("<font color="#800000">MD5</font>");<br />
} <font color="#0000ff">catch</font> (NoSuchAlgorithmException e) {<br />
<font color="#0000ff">throw new</font> RuntimeException(<font color="#800000">"MD5 cryptographic algorithm is not available."</font>, e);<br />
}<br />
<font color="#0000ff">byte</font>[] messageDigest = md.digest(bytes);<br />
BigInteger number = <font color="#0000ff">new</font> BigInteger(1, messageDigest);<br />
<font color="#006600">// prepend a zero to get a "proper" MD5 hash value</font><br />
StringBuffer sb = <font color="#0000ff">new</font> StringBuffer('<font color="#800000">0</font>');<br />
sb.append(number.toString(16));<br />
<font color="#0000ff">return</font> sb.toString();<br />
}</pre>
<p><strong><small><font size="2">清单 2：ETagComputeUtils</font></small></strong> </p>
<p>直接在web.xml中配置filter。 </p>
<pre>    &lt;filter&gt;<br />
&lt;filter-name&gt;ETag Content Filter&lt;/filter-name&gt;<br />
&lt;filter-class&gt;org.springframework.samples.petclinic.web.ETagContentFilter&lt;/filter-class&gt;<br />
&lt;/filter&gt;<br />
<br />
&lt;filter-mapping&gt;<br />
&lt;filter-name&gt;ETag Content Filter&lt;/filter-name&gt;<br />
&lt;url-pattern&gt;/*.htm&lt;/url-pattern&gt;<br />
&lt;/filter-mapping&gt;</pre>
<p><strong><small><font size="2">清单 3：web.xml中配置filter。</font></small></strong> </p>
<p>每个.htm文件将被EtagContentFilter过滤，如果页面自上次客户端请求后没有改变，它将返回一个空内容体的HTTP响应。 </p>
<p>我们在这里展示的方法对特定类型的页面是有用的。但是，该方法有两个缺点： </p>
<ul>
    <li>我们是在页面已经被展现在服务器之后计算ETag的，但是在返回客户端之前。如果有Etag匹配，实际上并不需要再为model装进数据，因为要展现的页面不需要发送回客户端。
    <li>对于类似于在页脚显示日期时间这样的页面，即使内容实际上并没有改变，每个页面也将是不同的。 </li>
</ul>
<p>下一节，我们将着眼于另一种方法，其通过理解更多关于构造页面的底层数据来克服这些问题的某些限制。 </p>
<h2>ETag拦截器（Interceptor） </h2>
<p>Spring MVC HTTP 请求处理途径中包括了在一个controller前插接拦截器（Interceptor）的能力，因而有机会处理请求。这儿是应用我们ETag比较逻辑的理想场所，因此如果我们发现构建一个页面的数据没有发生变化，我们可以避免进一步处理。 </p>
<p>这儿的诀窍是你怎么知道构成页面的数据已经改变了？为了达到本文的目的，我创建了一个简单的ModifiedObjectTracker，它通过Hibernate事件侦听器清楚地知道插入、更新和删除操作。该追踪器为应用程序的每个view维护一个唯一的号码，以及一个关于哪些Hibernate实体影响每个view的映射。每当一个POJO被改变了，使用了该实体的view的计数器就加1。我们使用该计数值作为ETag，这样当客户端将ETag送回时我们就知道页面背后的一个或多个对象是否被修改了。 </p>
<h3>代码 </h3>
<p>我们就从ModifiedObjectTracker开始吧： </p>
<pre><font color="#0000ff">public interface</font> ModifiedObjectTracker {<br />
<font color="#0000ff">void</font> notifyModified(&gt; <font color="#006699">String</font> entity);<br />
}</pre>
<p>够简单吧？这个实现还有一点更有趣的。任何时候一个实体改变了，我们就更新每个受其影响的view的计数器： </p>
<pre><font color="#0000ff">public void</font> notifyModified(<font color="#006699">String</font> entity) {<br />
<font color="#006600">// entityViewMap is a map of entity -&gt; list of view names</font><br />
<font color="#006699">List</font> views = getEntityViewMap().get(entity);<br />
<br />
<font color="#0000ff">if</font> (views == <font color="#0000ff">null</font>) {<br />
<font color="#0000ff">return</font>; // <font color="#006600">no views are configured for this entity</font><br />
}<br />
<br />
<font color="#0000ff">synchronized</font> (counts) {<br />
<font color="#0000ff">for</font> (<font color="#006699">String</font> view : views) {<br />
<font color="#006699">Integer</font> count = counts.get(view);<br />
counts.put(view, ++count);<br />
}<br />
}<br />
}</pre>
<p>一个&#8220;改变&#8221;就是插入、更新或者删除。这里给出的是侦听删除操作的处理器（配置为Hibernate 3 LocalSessionFactoryBean上的事件侦听器）： </p>
<pre><font color="#0000ff">public class</font> DeleteHandler <font color="#0000ff">extends</font> DefaultDeleteEventListener {<br />
<font color="#0000ff">private</font> ModifiedObjectTracker tracker;<br />
<br />
<font color="#0000ff">public void</font> onDelete(DeleteEvent event) <font color="#0000ff">throws</font> HibernateException {<br />
getModifiedObjectTracker().notifyModified(event.getEntityName());<br />
}<br />
<br />
<font color="#0000ff">public</font> ModifiedObjectTracker getModifiedObjectTracker() {<br />
<font color="#0000ff">return</font> tracker;<br />
}<br />
<font color="#0000ff">public void</font> setModifiedObjectTracker(ModifiedObjectTracker tracker) {<br />
<font color="#0000ff">this</font>.tracker = tracker;<br />
}<br />
}</pre>
<p>ModifiedObjectTracker通过Spring配置被注入到DeleteHandler中。还有一个SaveOrUpdateHandler来处理新建或更新POJO。 </p>
<p>如果客户端发送回当前有效的ETag（意味着自上次请求之后我们的内容没有改变），我们将阻止更多的处理，以实现我们的性能提升。在Spring MVC里，我们可以使用HandlerInterceptorAdaptor并覆盖preHandle方法： </p>
<pre><font color="#0000ff">public final boolean</font> preHandle(HttpServletRequest request, HttpServletResponse response, <font color="#006699">Object</font> handler) <font color="#0000ff">throws</font><br />
ServletException, <font color="#006699">IOException</font> {<br />
<font color="#006699">String</font> method = request.getMethod();<br />
if (!"GET".equals(method))<br />
<font color="#0000ff">return true</font>;<br />
<br />
<font color="#006699">String</font> previousToken = request.getHeader("<font color="#800000">If-None-Match</font>");<br />
<font color="#006699">String</font> token = getTokenFactory().getToken(request);<br />
<br />
<font color="#006600">// compare previous token with current one</font><br />
<font color="#0000ff">if</font> ((token != <font color="#0000ff">null</font>) &amp;&amp; (previousToken != null &amp;&amp; previousToken.equals(<font color="#800000">'"'</font> + token + <font color="#800000">'"'</font>))) {<br />
response.sendError(HttpServletResponse.SC_NOT_MODIFIED);<br />
<font color="#006600">// re-use original last modified timestamp</font><br />
response.setHeader("<font color="#800000">Last-Modified</font>", request.getHeader("<font color="#800000">If-Modified-Since</font>"))<br />
<font color="#0000ff">return false</font>; <font color="#006600">// no further processing required</font><br />
}<br />
<br />
<font color="#006600">// set header for the next time the client calls</font><br />
<font color="#0000ff">if</font> (token != null) { <br />
response.setHeader(<font color="#800000">"ETag"</font>, <font color="#800000">'"'</font> + token + <font color="#800000">'"'</font>);<br />
<br />
<font color="#006600">// first time through - set last modified time to now</font><br />
<font color="#006699">Calendar</font> cal = <font color="#006699">Calendar</font>.getInstance();<br />
cal.set(<font color="#006699">Calendar</font>.MILLISECOND, 0);<br />
<font color="#006699">Date</font> lastModified = cal.getTime();<br />
response.setDateHeader("<font color="#800000">Last-Modified</font>", lastModified.getTime());<br />
}<br />
<br />
<font color="#0000ff">return true</font>;<br />
}</pre>
<p>我们首先确信我们正在处理GET请求（与PUT一起的ETag可以用来检测不一致的更新，但其超出了本文的范围。）。如果该记号与上次我们发送的记号相匹配，我们返回一个&#8220;304未修改&#8221;响应并&#8220;短路&#8221;请求处理链的其余部分。否则，我们设置ETag响应头以便为下一次客户端请求做好准备。 </p>
<p>你需注意到我们将产生记号逻辑抽出到一个接口中，这样可以插接不同的实现。该接口有一个方法： </p>
<pre><font color="#0000ff">public interface</font> ETagTokenFactory {<br />
<font color="#0000ff">String</font> getToken(<font color="#006699">HttpServletRequest</font> request);<br />
}</pre>
<p>为了把代码清单减至最小，SampleTokenFactory的简单实现还担当了ETagTokenFactory的角色。本例中，我们通过简单返回请求URI的更改计数值来产生记号： </p>
<pre><font color="#0000ff">public String</font> getToken(<font color="#006699">HttpServletRequest</font> request) {<br />
<font color="#006699">String</font> view = request.getRequestURI();<br />
<font color="#006699">Integer</font> count = counts.get(view);<br />
<font color="#0000ff">if</font> (count == null) {<br />
<font color="#0000ff">return</font> null;<br />
}<br />
<br />
<font color="#0000ff">return</font> count.toString();<br />
}</pre>
<p>大功告成！ </p>
<h3>会话 </h3>
<p>这里，如果什么也没改变，我们的拦截器将阻止任何搜集数据或展现view的开销。现在，让我们看看HTTP头（借助于<a href="http://livehttpheaders.mozdev.org/">LiveHTTPHeaders</a>），看看到底发生了什么。下载文件中包含了配置该拦截器的说明，因此owner.htm&#8220;能够使用ETag&#8221;： </p>
<p>我们发起的第一个请求说明该用户已经看过了这个页面： </p>
<pre>----------------------------------------------------------  <br />
<strong>http://localhost:8080/petclinic/owner.htm?ownerId=10  </strong><br />
<br />
GET /petclinic/owner.htm?ownerId=10 HTTP/1.1<br />
Host: localhost:8080<br />
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4<br />
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5<br />
Accept-Language: en-us,en;q=0.5<br />
Accept-Encoding: gzip,deflate<br />
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7<br />
Keep-Alive: 300<br />
Connection: keep-alive<br />
Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8<br />
X-lori-time-1: 1182364348062<br />
If-Modified-Since: Wed, 20 Jun 2007 18:29:03 GMT<br />
If-None-Match: "-1"<br />
<br />
HTTP/1.x 304 Not Modified<br />
Server: Apache-Coyote/1.1<br />
Date: Wed, 20 Jun 2007 18:32:30 GMT</pre>
<p>我们现在应该做点修改，看看ETag是否改变了。我们给这个物主增加一个宠物： </p>
<pre>----------------------------------------------------------<br />
<strong>http://localhost:8080/petclinic/addPet.htm?ownerId=10<br />
<br />
</strong> GET /petclinic/addPet.htm?ownerId=10 HTTP/1.1<br />
Host: localhost:8080<br />
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4<br />
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5<br />
Accept-Language: en-us,en;q=0.5<br />
Accept-Encoding: gzip,deflate<br />
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7<br />
Keep-Alive: 300<br />
Connection: keep-alive<br />
Referer: http://localhost:8080/petclinic/owner.htm?ownerId=10<br />
Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8<br />
X-lori-time-1: 1182364356265<br />
<br />
HTTP/1.x 200 OK<br />
Server: Apache-Coyote/1.1<br />
Pragma: No-cache<br />
Expires: Thu, 01 Jan 1970 00:00:00 GMT<br />
Cache-Control: no-cache, no-store<br />
Content-Type: text/html;charset=ISO-8859-1<br />
Content-Language: en-US<br />
Content-Length: 2174<br />
Date: Wed, 20 Jun 2007 18:32:57 GMT<br />
----------------------------------------------------------<br />
<strong>http://localhost:8080/petclinic/addPet.htm?ownerId=10  </strong><br />
<br />
POST /petclinic/addPet.htm?ownerId=10 HTTP/1.1<br />
Host: localhost:8080<br />
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4<br />
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5<br />
Accept-Language: en-us,en;q=0.5<br />
Accept-Encoding: gzip,deflate<br />
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7<br />
Keep-Alive: 300<br />
Connection: keep-alive<br />
Referer: http://localhost:8080/petclinic/addPet.htm?ownerId=10<br />
Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8<br />
X-lori-time-1: 1182364402968<br />
Content-Type: application/x-www-form-urlencoded<br />
Content-Length: 40<br />
name=Noddy&amp;birthDate=1000-11-11&amp;typeId=5<br />
HTTP/1.x 302 Moved Temporarily<br />
Server: Apache-Coyote/1.1<br />
Pragma: No-cache<br />
Expires: Thu, 01 Jan 1970 00:00:00 GMT<br />
Cache-Control: no-cache, no-store<br />
Location: http://localhost:8080/petclinic/owner.htm?ownerId=10<br />
Content-Language: en-US<br />
Content-Length: 0<br />
Date: Wed, 20 Jun 2007 18:33:23 GMT</pre>
<p>因为对addPet.htm我们没有配置任何已知ETag，也没有设置头信息。现在，我们再一次查看id为10的物主。注意ETag这时是1： </p>
<pre>----------------------------------------------------------<br />
<strong>http://localhost:8080/petclinic/owner.htm?ownerId=10<br />
<br />
</strong> GET /petclinic/owner.htm?ownerId=10 HTTP/1.1<br />
Host: localhost:8080<br />
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4<br />
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5<br />
Accept-Language: en-us,en;q=0.5<br />
Accept-Encoding: gzip,deflate<br />
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7<br />
Keep-Alive: 300<br />
Connection: keep-alive<br />
Referer: http://localhost:8080/petclinic/addPet.htm?ownerId=10<br />
Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8<br />
X-lori-time-1: 1182364403109<br />
If-Modified-Since: Wed, 20 Jun 2007 18:29:03 GMT<br />
If-None-Match: "-1"<br />
<br />
HTTP/1.x 200 OK<br />
Server: Apache-Coyote/1.1<br />
Etag: "1"<br />
Last-Modified: Wed, 20 Jun 2007 18:33:36 GMT<br />
Content-Type: text/html;charset=ISO-8859-1<br />
Content-Language: en-US<br />
Content-Length: 4317<br />
Date: Wed, 20 Jun 2007 18:33:45 GMT</pre>
<p>最后，我们再次查看id为10的物主。这次我们的ETag命中了，我们得到一个&#8220;304未修改&#8221;响应： </p>
<pre>----------------------------------------------------------<br />
http://localhost:8080/petclinic/owner.htm?ownerId=10<br />
<br />
GET /petclinic/owner.htm?ownerId=10 HTTP/1.1<br />
Host: localhost:8080<br />
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4<br />
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5<br />
Accept-Language: en-us,en;q=0.5<br />
Accept-Encoding: gzip,deflate<br />
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7<br />
Keep-Alive: 300<br />
Connection: keep-alive<br />
Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8<br />
X-lori-time-1: 1182364493500<br />
If-Modified-Since: Wed, 20 Jun 2007 18:33:36 GMT<br />
If-None-Match: "1"<br />
<br />
HTTP/1.x 304 Not Modified<br />
Server: Apache-Coyote/1.1<br />
Date: Wed, 20 Jun 2007 18:34:55 GMT</pre>
<p>我们已经利用HTTP缓存节约了带宽和计算时间！ </p>
<p><strong>细粒度印记（The Fine Print）</strong>：实践中，我们可以通过以更细粒度的跟踪对象变化来获得更大的功效，例如使用对象id。然而，这种使修改对象关联到view上的想法高度依赖应用程序的整体数据模型设计。这里的实现（ModifiedObjectTracker）是说明性的，有意为更多的探索提供想法。它并不是旨在生产环境中使用（比如它在簇中使用还不稳定）。一个可选的更深的考虑是使用数据库触发器来跟踪变化，让拦截器访问触发器所写入的表。 </p>
<h2>结论 </h2>
<p>我们已经看了两种使用ETag减少带宽和计算的方法。我希望本文已为你当下或将来基于Web的项目提供了精神食粮，并正确评价在底层利用ETag响应头的做法。 </p>
<p>正如牛顿（Isaac Newton）的名言所说：&#8220;如果说我看得更远，那是因为我站在巨人的肩膀上。&#8221;REST风格应用的核心是简单、好的软件设计、不要重新发明轮子。我相信随着使用量和知名度的增长，针对基于Web应用的REST风格架构有益于主流应用开发的迁移，我期盼着它在我将来的项目中发挥更大的作用。 </p>
<img src ="http://www.blogjava.net/table/aggbug/310428.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/table/" target="_blank">小卓</a> 2010-01-21 18:57 <a href="http://www.blogjava.net/table/articles/310428.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>20 种提升网页速度的技巧</title><link>http://www.blogjava.net/table/articles/310426.html</link><dc:creator>小卓</dc:creator><author>小卓</author><pubDate>Thu, 21 Jan 2010 10:48:00 GMT</pubDate><guid>http://www.blogjava.net/table/articles/310426.html</guid><wfw:comment>http://www.blogjava.net/table/comments/310426.html</wfw:comment><comments>http://www.blogjava.net/table/articles/310426.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/table/comments/commentRss/310426.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/table/services/trackbacks/310426.html</trackback:ping><description><![CDATA[<blockquote minmax_bound="true">您希望加快网页的加载速度吗？了解如何通过缩短加载时间来改善拨号上网用户的浏览体验，在某些情形下，加载时间最多可缩短 80%。</blockquote><start minmax_bound="true"  RESERVED FOR FUTURE USE INCLUDE FILES>&lt; include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters &gt; <end minmax_bound="true"  RESERVED FOR FUTURE USE INCLUDE FILES>
<p minmax_bound="true"><strong minmax_bound="true"><a name="N1004C" minmax_bound="true"><span class="atitle" minmax_bound="true">引言</span></a></strong></p>
<p minmax_bound="true">不 是所有人都能够使用高速 Internet 连接。即使每个人都能够使用高速网络，也会因为各种各样的原因使您的 Web 应用程序看起来运行缓慢。在这个宽带速度不断提高的时代，您应当关注一下页面加载时间。将珍贵的页面加载时间缩短几秒，将更加珍贵的请求和响应时间缩短几 毫秒。您将为访问者创造一种更好的体验。</p>
<p minmax_bound="true">阅读完本文之后，您将能够较好地了解网页加载时间优化的基本知识。您还能够使用工具和知识更好地识别和判断加载缓慢的页面部分和瓶颈。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="prereqs" minmax_bound="true"><span class="atitle" minmax_bound="true">先决条件</span></a></strong></p>
<p minmax_bound="true">在 理想情况下，您应该安装了 Mozilla Firefox。您还应该大体了解 Web 开发。本文涉及的主题并不复杂，但是如果您了解超文本标记语言（Hypertext Markup Language，HTML）、层叠样式表（Cascading Style Sheet，CSS）以及 &#8482; 编程语言等主题，那么在学习本文时将更加得心应手。不需要使用集成开发环境（IDE），只需使用您喜爱的编辑器。</p>
<p minmax_bound="true">您必须在浏览器中启用了 JavaScript。另外，要学习与 Firebug 和 YSlow 相关的内容，您需要安装 Firefox Web 浏览器。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="broadband" minmax_bound="true"><span class="atitle" minmax_bound="true">假设您没有宽带</span></a></strong></p>
<p minmax_bound="true">许多人通过某种形式的宽带连接访问 Internet，这些形式可能是 DSL、网线、光纤或其他方法。但是，无法使用这类技术的用户不得不使用拨号连接。您一定已经忘记拨号上网是什么感觉了，但您可以试着回想一下网页逐行加载时的情形。</p>
<table cellspacing="0" cellpadding="0" width="40%" align="right" border="0" minmax_bound="true">
    <tbody minmax_bound="true">
        <tr minmax_bound="true">
            <td width="10" minmax_bound="true"><img style="width: 10px; height: 1px; maxwidth: 600px; minmaxwidth: 10px; minmaxheight: 1px" height="1" alt="" src="http://www.ibm.com/i/c.gif" width="10" minmax_bound="true" /></td>
            <td minmax_bound="true">
            <table cellspacing="0" cellpadding="5" width="100%" border="1" minmax_bound="true">
                <tbody minmax_bound="true">
                    <tr minmax_bound="true">
                        <td bgcolor="#eeeeee" minmax_bound="true">
                        <p minmax_bound="true">2006 Organization for Economic Cooperation and Development (OECD) 报告将<em minmax_bound="true">宽带</em> 定义为数据下载速率大于 256 kbit/s 的连接。美国 Federal Communications Commission (FCC) 目前将宽带定义为大于 768 kbit/s 速率的所有连接方式。</p>
                        </td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<p minmax_bound="true">幸 运的是，这些可怜的人们现在已经能够获得一些帮助。您可以通过缩短加载页面的时间来改善他们的体验。但是，拨号连接并不是降低加载和响应速度的惟一原因。 许多 Web 设计人员错误地认为高速 Internet 连接的到来会使网站性能优化变得没有必要。这种观点是不对的。例如，过去使用桌面软件执行的许多任务现在可以在线执行。在 Web 应用程序中获得像桌面软件那样的高速响应体验非常困难，因此性能优化非常重要。幸运的是，一些工具和最佳实践可用于缩短响应和加载时间，提供更加流畅的体 验。</p>
<table cellspacing="0" cellpadding="0" width="40%" align="right" border="0" minmax_bound="true">
    <tbody minmax_bound="true">
        <tr minmax_bound="true">
            <td width="10" minmax_bound="true"><img style="width: 10px; height: 1px; maxwidth: 600px; minmaxwidth: 10px; minmaxheight: 1px" height="1" alt="" src="http://www.ibm.com/i/c.gif" width="10" minmax_bound="true" /></td>
            <td minmax_bound="true">
            <table cellspacing="0" cellpadding="5" width="100%" border="1" minmax_bound="true">
                <tbody minmax_bound="true">
                    <tr minmax_bound="true">
                        <td bgcolor="#eeeeee" minmax_bound="true">
                        <p minmax_bound="true">根据 Birds-Eye 的 2007 宽带统计显示，美国境内 25% 的人没有 Internet 连接，53% 的人拥有宽带，21% 的人仍然在使用拨号连接。</p>
                        </td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<p minmax_bound="true"><strong minmax_bound="true"><a name="toolsessentials" minmax_bound="true"><span class="atitle" minmax_bound="true">基本工具</span></a></strong></p>
<p minmax_bound="true">对于所有与优化相关的任务，您必须使用工具来诊断瓶颈和识别问题。现在在 Web 开发中使用最广泛的两个工具是 Firebug 和 YSlow，它们都是开源、免费的 Firefox 插件。</p>
<p minmax_bound="true"><a name="firebug" minmax_bound="true"><span class="smalltitle" minmax_bound="true">Firebug</span></a></p>
<p minmax_bound="true">Firebug（参见 <a href="http://www.ibm.com/developerworks/cn/web/wa-speedweb/?ca=drs-tp4608#resources" minmax_bound="true">参考资料</a>）是最流行的 Firefox 扩展之一，该应用程序能够使 Web 开发人员的工作更加轻松。它包含许多非常有用的功能，比如：</p>
<ul minmax_bound="true">
    <li minmax_bound="true">JavaScript 调试
    <li minmax_bound="true">JavaScript 命令行
    <li minmax_bound="true">监视 JavaScript 性能和跟踪 <code minmax_bound="true">XmlHttpRequests</code>
    <li minmax_bound="true">登录 Firebug 控制台
    <li minmax_bound="true">跟踪
    <li minmax_bound="true">检查 HTML 元素和动态编辑 HTML 代码
    <li minmax_bound="true">动态编辑 CSS 文档 </li>
</ul>
<p minmax_bound="true"><a name="yslow" minmax_bound="true"><span class="smalltitle" minmax_bound="true">YSlow</span></a></p>
<p minmax_bound="true">YSlow（参见 <a href="http://www.ibm.com/developerworks/cn/web/wa-speedweb/?ca=drs-tp4608#resources" minmax_bound="true">参考资料</a>）分析网页，并根据 Yahoo! 起草的高性能网站规则（参见 <a href="http://www.ibm.com/developerworks/cn/web/wa-speedweb/?ca=drs-tp4608#resources" minmax_bound="true">参考</a>），告诉您网页加载缓慢的原因。YSlow 是一个与 Firebug 集成的 Firefox 插件，因此您需要首先安装 Firebug，然后才能安装和使用 YSlow。</p>
<p minmax_bound="true"><a name="installfirebug" minmax_bound="true"><span class="atitle" minmax_bound="true">安装 Firebug</span></a></p>
<p minmax_bound="true">两个 Firefox 扩展的安装过程都非常简单。要安装 Firebug，执行以下步骤：</p>
<ol minmax_bound="true">
    <li minmax_bound="true">打开 Firefox，转到 <a href="http://www.getfirebug.com/" minmax_bound="true">Firebug 主页</a>。
    <li minmax_bound="true">安装最新版的 Firebug。
    <li minmax_bound="true">如果 Firefox 配置为阻止弹出窗口，单击 <strong minmax_bound="true">Allow</strong> 允许打开安装窗口。否则，单击 <strong minmax_bound="true">Install Now</strong>。
    <li minmax_bound="true">重启 Firefox。 </li>
</ol>
<p minmax_bound="true">您现在可以从 Tools 菜单访问 Firebug。可以在新窗口或现有窗口中打开 Firebug（参见 <a href="http://www.ibm.com/developerworks/cn/web/wa-speedweb/?ca=drs-tp4608#fig1" minmax_bound="true">图 1</a>）。</p>
<p minmax_bound="true"><br minmax_bound="true" />
<a name="fig1" minmax_bound="true"><strong minmax_bound="true">图 1. Firefox 起始页的 Firebug HTML 和 Style 视图</strong></a><br minmax_bound="true" />
<img style="width: 500px; height: 231px; maxwidth: 600px; minmaxwidth: 500px; minmaxheight: 231px" height="231" alt="Firebug 视图" src="http://www.ibm.com/developerworks/cn/web/wa-speedweb/firebuginstalled.jpg" width="500" minmax_bound="true" /></p>
<p minmax_bound="true"><a name="installyslow" minmax_bound="true"><span class="atitle" minmax_bound="true">安装 YSlow</span></a></p>
<p minmax_bound="true">安装 Firebug 之后，接下来安装 YSlow。为此，执行以下步骤：</p>
<ol minmax_bound="true">
    <li minmax_bound="true">打开 Firefox，然后转到 <a href="http://developer.yahoo.com/yslow" minmax_bound="true">YSlow 主页</a>。
    <li minmax_bound="true">安装插件，然后重启 Firefox。
    <p minmax_bound="true"><strong minmax_bound="true">注意：</strong>与许多其他 Firefox 扩展不同，YSlow 不会自动启动。必须首先激活它。</p>
    <li minmax_bound="true">要激活 YSlow，在状态栏右键单击其图标，然后单击 <strong minmax_bound="true">Autorun</strong>。 </li>
</ol>
<p minmax_bound="true"><a href="http://www.ibm.com/developerworks/cn/web/wa-speedweb/?ca=drs-tp4608#fig2" minmax_bound="true">图 2</a> 显示了 YSlow 性能分析的结果。</p>
<p minmax_bound="true"><br minmax_bound="true" />
<a name="fig2" minmax_bound="true"><strong minmax_bound="true">图 2. Firefox 起始页的 YSlow 性能分析</strong></a><br minmax_bound="true" />
<img style="width: 500px; height: 233px; maxwidth: 600px; minmaxwidth: 500px; minmaxheight: 233px" height="233" alt="YSlow 性能分析" src="http://www.ibm.com/developerworks/cn/web/wa-speedweb/yslow-performancetab.jpg" width="500" minmax_bound="true" /></p>
<p minmax_bound="true"><a name="commonsense" minmax_bound="true"><span class="atitle" minmax_bound="true">常识：牢记设计规则</span></a></p>
<p minmax_bound="true">令人惊讶的是简单的设计规则通常会被忽视，最终产生未经优化的、下载缓慢的网页。牢记以下规则，页面的加载速度将会更快。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="goodstructure" minmax_bound="true"><span class="atitle" minmax_bound="true">使用良好的结构</span></a></strong></p>
<p minmax_bound="true">可扩展 HTML (XHTML) 具有许多优势，但是其缺点也很明显。XHTML 可能使您的页面更加符合标准，但是它大量使用标记（强制性的 <code minmax_bound="true">&lt;start&gt;</code> 和 <code minmax_bound="true">&lt;end&gt;</code> 标记），这意味着浏览器要下载更多代码。所以，事情都有两面性，尝试在您的网页中使用较少的 XHTML 代码，以减小页面大小。</p>
<p minmax_bound="true">如果您确实不得不使用 XHTML，试着尽可能对它进行优化。例如，删除空格并采用严格的 XHTML 编码实践，提高下载和解析速度。要严格执行 XHTML Strict 规则，向文档中添加以下 <code minmax_bound="true">doctype</code> 语句：</p>
<table cellspacing="0" cellpadding="0" width="50%" border="0" minmax_bound="true">
    <tbody minmax_bound="true">
        <tr minmax_bound="true">
            <td class="code-outline" minmax_bound="true">
            <pre class="displaycode" minmax_bound="true">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"      <br minmax_bound="true" />
            "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;<br minmax_bound="true" />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<p minmax_bound="true">&nbsp;</p>
<p minmax_bound="true">XHTML 1.0 Strict 与 Strict HTML 4.01 是等效的，包含的属性和元素没有出现在 HTML 4.01 规范的反对内容中。记住，有两个标记能够在 XHTML Transitional 中使用，但不能在 XHTML Strict 中使用，例如：</p>
<ul minmax_bound="true">
    <li minmax_bound="true"><code minmax_bound="true">&lt;center&gt;</code>
    <li minmax_bound="true"><code minmax_bound="true">&lt;font&gt;</code>
    <li minmax_bound="true"><code minmax_bound="true">&lt;iframe&gt;</code>
    <li minmax_bound="true"><code minmax_bound="true">&lt;strike&gt;</code>
    <li minmax_bound="true"><code minmax_bound="true">&lt;u&gt;</code> </li>
</ul>
<p minmax_bound="true">&nbsp;</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="overload" minmax_bound="true"><span class="atitle" minmax_bound="true">不要使布局超载</span></a></strong></p>
<p minmax_bound="true">在博客 （和新的站点）流行起来之前，让页面水平滚动甚至垂直滚动被认为是糟糕的实践。页面越小，越难以（但并不是不可能）完好地填充屏幕。现在，对于博客和内容 驱动的网站，不时可以看到几百 Kb 大小的长页面。是的，您需要填充更多空间，但是这并不意味着您必须使用大的背景图像、大量表格或者许多内容来填充。坚持简约原则：少即是多。页面中充斥着 各种类型的图像、视频、广告等，这大大违背实用性原则，因此，在增加页面的内容时请三思。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="imagestext" minmax_bound="true"><span class="atitle" minmax_bound="true">不要使用图像来表示文本</span></a></strong></p>
<p minmax_bound="true">我们很少会控制字体在不同浏览器中的显示方式，与字体不同的是，图像总是精确地按照其设计方式来显示。但这不能当作使用图像来表示文本的借口。</p>
<p minmax_bound="true">使 用图像表示文本的最常见示例就是在导航栏中。美观的按钮更加具有吸引力，但是它们的加载速度很慢。此外，图像仍然不能由搜索引擎直接索引，因此，使用图像 进行导航不利于搜索引擎优化（search engine optimization，SEO）。当无需图像就可以通过大量 CSS 技巧创建漂亮的按钮时，绝不使用图像来表示文本。</p>
<p minmax_bound="true">一种适用于 CSS 样式的特定导航类型就是选项卡式导航，如 <a href="http://www.ibm.com/developerworks/cn/web/wa-speedweb/?ca=drs-tp4608#fig3" minmax_bound="true">图 3</a> 所示。</p>
<p minmax_bound="true"><br minmax_bound="true" />
<a name="fig3" minmax_bound="true"><strong minmax_bound="true">图 3. 选项卡式导航</strong></a><br minmax_bound="true" />
<img style="width: 500px; height: 33px; maxwidth: 600px; minmaxwidth: 500px; minmaxheight: 33px" height="33" alt="选项卡式导航" src="http://www.ibm.com/developerworks/cn/web/wa-speedweb/tabs.jpg" width="500" minmax_bound="true" /></p>
<p minmax_bound="true">除了体积较小之外，这种实现导航的方式也更加符合 Web 标准。</p>
<table cellspacing="0" cellpadding="0" width="40%" align="right" border="0" minmax_bound="true">
    <tbody minmax_bound="true">
        <tr minmax_bound="true">
            <td width="10" minmax_bound="true"><img style="width: 10px; height: 1px; maxwidth: 600px; minmaxwidth: 10px; minmaxheight: 1px" height="1" alt="" src="http://www.ibm.com/i/c.gif" width="10" minmax_bound="true" /></td>
            <td minmax_bound="true">
            <table cellspacing="0" cellpadding="5" width="100%" border="1" minmax_bound="true">
                <tbody minmax_bound="true">
                    <tr minmax_bound="true">
                        <td bgcolor="#eeeeee" minmax_bound="true">
                        <p minmax_bound="true">遵循统称为 <em minmax_bound="true">Web 标准</em> 的一组最佳实践的网站具有精简性、可访问性、搜索引擎友好性、适用性和基于 CSS 的特征。</p>
                        </td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<p minmax_bound="true"><a href="http://www.ibm.com/developerworks/cn/web/wa-speedweb/?ca=drs-tp4608#list1" minmax_bound="true">清单 1</a> 和 <a href="http://www.ibm.com/developerworks/cn/web/wa-speedweb/?ca=drs-tp4608#list2" minmax_bound="true">清单 2</a> 中的代码以纯 CSS/XHTML 的形式实现基于选项卡的导航功能。</p>
<p minmax_bound="true"><br minmax_bound="true" />
<a name="list1" minmax_bound="true"><strong minmax_bound="true">清单 1. 基于选项卡导航的 CSS 文档</strong></a></p>
<table cellspacing="0" cellpadding="0" width="50%" border="0" minmax_bound="true">
    <tbody minmax_bound="true">
        <tr minmax_bound="true">
            <td class="code-outline" minmax_bound="true">
            <pre class="displaycode" minmax_bound="true">    #nav {<br minmax_bound="true" />
            float:left;<br minmax_bound="true" />
            width:100%;<br minmax_bound="true" />
            background:#E7E5E2;<br minmax_bound="true" />
            font-size:95%;<br minmax_bound="true" />
            line-height:normal;<br minmax_bound="true" />
            border-bottom:1px solid #54545C;<br minmax_bound="true" />
            }<br minmax_bound="true" />
            #nav ul {<br minmax_bound="true" />
            margin:0;<br minmax_bound="true" />
            padding:10px 10px 0 50px;<br minmax_bound="true" />
            list-style:none;<br minmax_bound="true" />
            }<br minmax_bound="true" />
            #nav li {<br minmax_bound="true" />
            display:inline;<br minmax_bound="true" />
            margin:0;<br minmax_bound="true" />
            padding:0;<br minmax_bound="true" />
            }<br minmax_bound="true" />
            #nav a {<br minmax_bound="true" />
            float:left;<br minmax_bound="true" />
            background:url("tableftK.gif") no-repeat left top;<br minmax_bound="true" />
            margin:0;<br minmax_bound="true" />
            padding:0 0 0 4px;<br minmax_bound="true" />
            text-decoration:none;<br minmax_bound="true" />
            }<br minmax_bound="true" />
            #nav a span {<br minmax_bound="true" />
            float:left;<br minmax_bound="true" />
            display:block;<br minmax_bound="true" />
            background:url("tabrightK.gif") no-repeat right top;<br minmax_bound="true" />
            padding:5px 15px 4px 6px;<br minmax_bound="true" />
            color:#FFF;<br minmax_bound="true" />
            }<br minmax_bound="true" />
            /* Commented Backslash Hack hides rule from IE5-Mac \*/<br minmax_bound="true" />
            #nav a span {float:none;}<br minmax_bound="true" />
            /* End IE5-Mac hack */<br minmax_bound="true" />
            #nav a:hover span {<br minmax_bound="true" />
            color:#FFF;<br minmax_bound="true" />
            background-position:100% -42px;<br minmax_bound="true" />
            }<br minmax_bound="true" />
            #nav a:hover {<br minmax_bound="true" />
            background-position:0% -42px;<br minmax_bound="true" />
            }<br minmax_bound="true" />
            #nav a:hover span {<br minmax_bound="true" />
            background-position:100% -42px;<br minmax_bound="true" />
            }<br minmax_bound="true" />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<p minmax_bound="true"><br minmax_bound="true" />
<a name="list2" minmax_bound="true"><strong minmax_bound="true">清单 2. 基于选项卡导航的 HTML 代码</strong></a></p>
<table cellspacing="0" cellpadding="0" width="50%" border="0" minmax_bound="true">
    <tbody minmax_bound="true">
        <tr minmax_bound="true">
            <td class="code-outline" minmax_bound="true">
            <pre class="displaycode" minmax_bound="true">&lt;div id="nav"&gt;<br minmax_bound="true" />
            &lt;ul&gt;<br minmax_bound="true" />
            &lt;li&gt;&lt;a href="#" title="Link 1"&gt;&lt;span&gt;Link 1&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;<br minmax_bound="true" />
            &lt;li&gt;&lt;a href="#" title="Link 2"&gt;&lt;span&gt;Link 2&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;<br minmax_bound="true" />
            &lt;li&gt;&lt;a href="#" title="Link 3"&gt;&lt;span&gt;Link 3&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;<br minmax_bound="true" />
            &lt;li&gt;&lt;a href="#" title="Longer Link Text"&gt;&lt;span&gt;Longer Link Text&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;<br minmax_bound="true" />
            &lt;li&gt;&lt;a href="#" title="Link 5"&gt;&lt;span&gt;Link 5&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;<br minmax_bound="true" />
            &lt;/ul&gt;<br minmax_bound="true" />
            &lt;/div&gt;<br minmax_bound="true" />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<p minmax_bound="true"><strong minmax_bound="true"><a name="cookie" minmax_bound="true"><span class="atitle" minmax_bound="true">检查 cookie 使用情况</span></a></strong></p>
<p minmax_bound="true">cookie 可能是很小的文件，但是浏览器仍然需要下载它们。较大的 cookie 所需的下载时间更长，进而增加了浏览器加载网页的时间。正因为如此，尽可能缩小 cookie 来最小化对浏览器响应时间的影响非常重要。</p>
<p minmax_bound="true">此外，设置一个较早的 <code minmax_bound="true">expire</code> 日期或者根本不设置 <code minmax_bound="true">expire</code> 日期，会缩短响应时间。要在 PHP 语言中设置 cookie 的 <code minmax_bound="true">expire</code> 日期，使用以下代码：</p>
<table cellspacing="0" cellpadding="0" width="50%" border="0" minmax_bound="true">
    <tbody minmax_bound="true">
        <tr minmax_bound="true">
            <td class="code-outline" minmax_bound="true">
            <pre class="displaycode" minmax_bound="true">&lt;?php <br minmax_bound="true" />
            $expire = 2592000 + time(); <br minmax_bound="true" />
            // Add 30 day&#8217;s to the current time <br minmax_bound="true" />
            setcookie(userid, &#8220;123rrw3&#8221;, $expire);<br minmax_bound="true" />
            ?&gt;<br minmax_bound="true" />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<p minmax_bound="true">&nbsp;</p>
<p minmax_bound="true">这段代码设置 cookie <code minmax_bound="true">userid</code>，并将 <code minmax_bound="true">expire</code> 日期设置为自当前日期之后 30 天。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="jojs" minmax_bound="true"><span class="atitle" minmax_bound="true">不要包含不必要的 JavaScript 代码，尽可能将其外部化</span></a></strong></p>
<p minmax_bound="true">与 cookie 类似，JavaScript 文件的下载也需要时间，这不可避免地会降低整个页面的加载速度。因此，明智地使用 JavaScript（仅在真正必要时才使用）并优化脚本的大小和速度。</p>
<p minmax_bound="true">缩 短 JavaScript 下载时间的另一种方式是使用外部文件，而不是包含脚本内联。这种方法也适用于 CSS，因为浏览器会缓存外部化的文本，而（在 HTML 页面自身中）以内联方式编码的 CSS 或 JavaScript 每次都会随 HTML 一起加载。要通过在 HTML 中引用 CSS 和 JavaScript 代码来外部化它们，可以使用具有以下形式的代码：</p>
<table cellspacing="0" cellpadding="0" width="50%" border="0" minmax_bound="true">
    <tbody minmax_bound="true">
        <tr minmax_bound="true">
            <td class="code-outline" minmax_bound="true">
            <pre class="displaycode" minmax_bound="true">&lt;link href="/stylesheets/myStyle.css" media="all" rel="Stylesheet" type="text/css" /&gt;<br minmax_bound="true" />
            &lt;script src="/javascripts/myJavascript.js" type="text/javascript"&gt;&lt;/script&gt;<br minmax_bound="true" />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<p minmax_bound="true">&nbsp;</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="avoidtables" minmax_bound="true"><span class="atitle" minmax_bound="true">尽可能避免使用表格</span></a></strong></p>
<p minmax_bound="true">表 格被用作网页的主要构建块，但是作为页面布局元素，使用表格现在被认为是糟糕的做法。有时候，您必须使用表格（并且它们被认为是显示表格数据的出色实 践）。如果是这样，明确地指定表格单元格、行和列的宽度和高度，否则，浏览器必须执行许多操作来计算如何显示它们，这会降低页面加载速度：</p>
<table cellspacing="0" cellpadding="0" width="50%" border="0" minmax_bound="true">
    <tbody minmax_bound="true">
        <tr minmax_bound="true">
            <td class="code-outline" minmax_bound="true">
            <pre class="displaycode" minmax_bound="true">&lt;td width="50px" height="10px"&gt;...&lt;/td&gt;<br minmax_bound="true" />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<p minmax_bound="true">&nbsp;</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="remove" minmax_bound="true"><span class="atitle" minmax_bound="true">删除任何不必要的元素</span></a></strong></p>
<p minmax_bound="true">可能这是所有技巧中最显而易见的一个，但是它也是最容易忘记的一个技巧。我曾经提到过 &#8220;少即是多&#8221;：这不仅是为了真正吸引更广泛的用户，还意味着需要下载和处理的东西更少。如果您真正需要在网页上放置许多内容，考虑将网页分为 2 个、3 个或更多的独立页面。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="tips" minmax_bound="true"><span class="atitle" minmax_bound="true">一些优化网页的技巧</span></a></strong></p>
<p minmax_bound="true">可以使用许多方法来优化您的网页，包括压缩 JavaScript 文件，使用超文本传输协议（Hypertext Transfer Protocol，HTTP）压缩，以及设置图像大小。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="compressjs" minmax_bound="true"><span class="atitle" minmax_bound="true">压缩和缩小 JavaScript 文件</span></a></strong></p>
<p minmax_bound="true">JavaScript 文件可能非常大，这意味着在某些情形中，它们的下载时间可能比所有其他组件下载时间之和还长。解决此问题的一种方法是压缩 JavaScript 文件。您可以使用 GNU zip (gzip) 来完成此任务，因为许多浏览器都支持这种压缩算法。</p>
<p minmax_bound="true">另一种替代方法是缩小文件。这种方法删除代码中所有不必要的字符，比如制表符（tab）、新行和空格。它删除代码中的注释和空白，进一步缩小文件大小。外部和内部样式表都可以缩小。两种最流行的缩小工具是 JSMin 和 YUI Compressor</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="httpcompression" minmax_bound="true"><span class="atitle" minmax_bound="true">使用 HTTP 压缩，并始终使用小写的 div 和类名</span></a></strong></p>
<p minmax_bound="true">可以使用 HTTP 压缩来减少服务器与浏览器之间的通信量。可以在 Apache 中配置 HTTP 压缩（.htaccess 文件），或者可以将其包含到页面中（对于 PHP，可以使用一个 <code minmax_bound="true">HTTP_ACCEPT_ENCODING</code> 选项）。但是请注意：不是所有浏览器都支持压缩。即使是支持压缩的浏览器，压缩和解压缩都会加重处理器的负载。要在 Apache 中启用地毯式（blanket）压缩（即压缩所有文本和 HTML），使用以下命令：</p>
<table cellspacing="0" cellpadding="0" width="50%" border="0" minmax_bound="true">
    <tbody minmax_bound="true">
        <tr minmax_bound="true">
            <td class="code-outline" minmax_bound="true">
            <pre class="displaycode" minmax_bound="true">AddOutputFilterByType DEFLATE text/html text/plain text/xml<br minmax_bound="true" />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<p minmax_bound="true">&nbsp;</p>
<p minmax_bound="true">另外，考虑一下您想要压缩的内容。图像、音乐和视频在创建时已经进行了压缩，因此您可以将压缩对象限制为 HTML、CSS 和 JavaScript 文件。</p>
<p minmax_bound="true">另一种减少压缩工作的技巧是使用小写形式的 <code minmax_bound="true">&lt;div&gt;</code> 元素和类名。由于大小写敏感性，并且使用的是无损压缩，<code minmax_bound="true">&lt;header&gt;</code> 与 <code minmax_bound="true">&lt;Header&gt;</code> 不同，它们被压缩为两个不同的标记。在下面的例子中，对于压缩程序来说，<code minmax_bound="true">Important</code> 类与 <code minmax_bound="true">important</code> 类是不同的，这意味着对于压缩程序，它们表示不同的对象，因此被分别压缩为两段不同的文本。</p>
<table cellspacing="0" cellpadding="0" width="50%" border="0" minmax_bound="true">
    <tbody minmax_bound="true">
        <tr minmax_bound="true">
            <td class="code-outline" minmax_bound="true">
            <pre class="displaycode" minmax_bound="true">&lt;div class="Important"&gt;read this!&lt;/div&gt;<br minmax_bound="true" />
            &lt;div class="important"&gt;This will cost you some valuable load time&lt;/div&gt;<br minmax_bound="true" />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<p minmax_bound="true">&nbsp;</p>
<p minmax_bound="true">留意细节似乎无关紧要。但是当您优化文件时，所有细微的细节都应考虑在内。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="setimagesize" minmax_bound="true"><span class="atitle" minmax_bound="true">设置图像大小</span></a></strong></p>
<p minmax_bound="true">与表格单元格、行和列一样，当您未明确设置图像大小时，浏览器需要执行计算来显示图像，这会降低处理速度。此外，在某些情形下，图像大小的计算结果可能不正确，因此图像会发生变形。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="cssimage" minmax_bound="true"><span class="atitle" minmax_bound="true">将 CSS 图像映射用于装饰功能</span></a></strong></p>
<p minmax_bound="true">使用图像映射代替多个图像，这是另一种缩短加载时间的方式，因为同时下载图像的各个独立部分能够加快整个页面的下载进度。或者，您可以使用某种名为 <em minmax_bound="true">CSS sprites</em> 的工具。CSS sprites 可帮助减少 HTTP 请求的数量。一个图像可以包含装饰或布置页面所需的所有图像元素。您使用 CSS 来选择（通过调用某些位置和维度）用于特定元素的映射。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="delayload" minmax_bound="true"><span class="atitle" minmax_bound="true">尽可能延迟脚本加载</span></a></strong></p>
<p minmax_bound="true">我 在前面 提到过，移除完全不需要的 JavaScript 代码能够加快加载和处理速度。但是如果代码已经非常精简并且必须在页面中包含 JavaScript 代码的话，该怎么办？</p>
<p minmax_bound="true">在 这种情形下，一种提升页面下载速度的潜在方式是将脚本放在页面的底部，使页面加载更迅速。通常，浏览器只能（从同一个域）下载不超过两个并行对象，如果一 个对象是一段 JavaScript 代码，那么在该脚本下载完之前，其他页面组件的下载将会暂停。如果将 JavaScript 代码放在页面底部，（在大多数情况下）它将在最后下载，这时所有其他组件都已下载完。</p>
<p minmax_bound="true">使用 Firebug 扩展跟踪加载缓慢的文件，我敢打赌您的 JavaScript 文件是下载最慢的文件。压缩 JavaScript 文件会有所帮助，但是仅仅这样可能还不够。您可以使用以下代码片段延迟 JavaScript 的加载：</p>
<table cellspacing="0" cellpadding="0" width="50%" border="0" minmax_bound="true">
    <tbody minmax_bound="true">
        <tr minmax_bound="true">
            <td class="code-outline" minmax_bound="true">
            <pre class="displaycode" minmax_bound="true">var delay = 5;<br minmax_bound="true" />
            setTimeout("heavy();", delay * 1000);<br minmax_bound="true" />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<p minmax_bound="true">&nbsp;</p>
<p minmax_bound="true">这段代码将对 <code minmax_bound="true">heavy()</code> 方法的调用延迟了 5 秒。您可以将这段代码与下面的技巧结合使用来延迟整个 JavaScript 文件的加载。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="loadondemand" minmax_bound="true"><span class="smalltitle" minmax_bound="true">按需加载 JavaScript 文件</span></a></strong></p>
<p minmax_bound="true">要按需加载 JavaScript，使用 import() 函数，如 <a href="http://www.ibm.com/developerworks/cn/web/wa-speedweb/?ca=drs-tp4608#list3" minmax_bound="true">清单 3</a> 所示。</p>
<p minmax_bound="true"><br minmax_bound="true" />
<a name="list3" minmax_bound="true"><strong minmax_bound="true">清单 3. import() 函数</strong></a></p>
<table cellspacing="0" cellpadding="0" width="50%" border="0" minmax_bound="true">
    <tbody minmax_bound="true">
        <tr minmax_bound="true">
            <td class="code-outline" minmax_bound="true">
            <pre class="displaycode" minmax_bound="true">				<br minmax_bound="true" />
            function $import(src){<br minmax_bound="true" />
            var scriptElem = document.createElement('script');<br minmax_bound="true" />
            scriptElem.setAttribute('src',src);<br minmax_bound="true" />
            scriptElem.setAttribute('type','text/javascript');<br minmax_bound="true" />
            document.getElementsByTagName('head')[0].appendChild(scriptElem);<br minmax_bound="true" />
            }<br minmax_bound="true" />
            <br minmax_bound="true" />
            // import with a random query parameter to avoid caching<br minmax_bound="true" />
            function $importNoCache(src){<br minmax_bound="true" />
            var ms = new Date().getTime().toString();<br minmax_bound="true" />
            var seed = "?" + ms; <br minmax_bound="true" />
            $import(src + seed);<br minmax_bound="true" />
            }<br minmax_bound="true" />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<p minmax_bound="true">&nbsp;</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="verifyload" minmax_bound="true"><span class="smalltitle" minmax_bound="true">验证函数加载</span></a></strong></p>
<p minmax_bound="true">也可以验证一个函数是否被加载，如果没有，加载 JavaScript 文件。为此，使用 <a href="http://www.ibm.com/developerworks/cn/web/wa-speedweb/?ca=drs-tp4608#list4" minmax_bound="true">清单 4</a> 中的代码。</p>
<p minmax_bound="true"><br minmax_bound="true" />
<a name="list4" minmax_bound="true"><strong minmax_bound="true">清单 4. 验证函数是否被加载</strong></a></p>
<table cellspacing="0" cellpadding="0" width="50%" border="0" minmax_bound="true">
    <tbody minmax_bound="true">
        <tr minmax_bound="true">
            <td class="code-outline" minmax_bound="true">
            <pre class="displaycode" minmax_bound="true">				<br minmax_bound="true" />
            if (myfunction){<br minmax_bound="true" />
            // The function has been loaded<br minmax_bound="true" />
            }<br minmax_bound="true" />
            else{ // Function has not been loaded yet, so load the javascript.<br minmax_bound="true" />
            $import('http://www.yourfastsite.com/myfile.js');<br minmax_bound="true" />
            }<br minmax_bound="true" />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<p minmax_bound="true">&nbsp;</p>
<p minmax_bound="true"><strong minmax_bound="true">注意：</strong>可以使用 <code minmax_bound="true">defer</code> 属性，但不是所有浏览器（包括 Firefox）都支持它。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="optimizecss" minmax_bound="true"><span class="atitle" minmax_bound="true">优化 CSS 文件</span></a></strong></p>
<p minmax_bound="true">如 果经过适当优化和维护，CSS 文件不一定很大。例如，具有很多独立类的 CSS 文件会影响下载速度。与 JavaScript 文件一样，您需要优化 CSS 文件，使其包含所需的所有内容，同时保持合理的大小。另外，使用外部文件代替内联定义来适应浏览器的缓存机制。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="cdn" minmax_bound="true"><span class="atitle" minmax_bound="true">使用内容分布网络</span></a></strong></p>
<p minmax_bound="true">内容分 布网络（Content-distribution network，CDN）是另一种缩短下载时间的好方法。当您将静态图像放在 Internet 上的许多服务器上时，用户能够从离他们最近的服务器下载这些图像。此外，大多数 CDN 都在快速服务器上运行，因此无论服务器的加载速度如何，其响应速度都比小型的超载服务器快。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="multipledomains" minmax_bound="true"><span class="atitle" minmax_bound="true">对资源使用多个域来增加连接</span></a></strong></p>
<p minmax_bound="true">CDN 的另一个优势是它们是独立的域。因为您的浏览器将并发连接的数量限制到一个单一的域，因此无论何时加载一个页面，都很容易占满所有线程。因此，到其他资产 的连接被延迟了。然而，您的浏览器能够打开新线程或到其他域的连接，这样，从另一个域加载的任何资产都可以与其他所有资产同时加载。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="gears" minmax_bound="true"><span class="atitle" minmax_bound="true">在合适的时候使用 Google Gears</span></a></strong></p>
<p minmax_bound="true">使用 Google Gears（参见 <a href="http://www.ibm.com/developerworks/cn/web/wa-speedweb/?ca=drs-tp4608#resources" minmax_bound="true">参考资料</a>） 是避免用户反复下载同一内容的另一种好方法。Gears 允许用户离线访问 Web 应用程序，但是也允许将页面元素持久化到用户的计算机上。因此，频繁加载但未进行更新的内容可以存储在 Gears 数据库中，该数据库是一个 SQLite3 关系数据库管理系统。对同一内容的所有 <code minmax_bound="true">next</code> 请求都可以从数据库（而不是服务器）直接加载。</p>
<p minmax_bound="true">安装 Gears 之后，获取 gears_init.js 文件，以便轻松访问 <a href="http://code.google.com/apis/gears/gears_init.js" minmax_bound="true">Gears 工厂和应用程序编程接口（API）</a>，将其保存为 <em minmax_bound="true">gears_init.js</em>，通过以下方式在您的代码中引用它：</p>
<table cellspacing="0" cellpadding="0" width="50%" border="0" minmax_bound="true">
    <tbody minmax_bound="true">
        <tr minmax_bound="true">
            <td class="code-outline" minmax_bound="true">
            <pre class="displaycode" minmax_bound="true">&lt;script type="text/javascript" src="gears_init.js"&gt;&lt;/script&gt;<br minmax_bound="true" />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<p minmax_bound="true">&nbsp;</p>
<p minmax_bound="true">要确定是否已安装 Gears，使用 <a href="http://www.ibm.com/developerworks/cn/web/wa-speedweb/?ca=drs-tp4608#list5" minmax_bound="true">清单 5</a> 中的代码。</p>
<p minmax_bound="true"><br minmax_bound="true" />
<a name="list5" minmax_bound="true"><strong minmax_bound="true">清单 5. 确定是否已安装 Gears</strong></a></p>
<table cellspacing="0" cellpadding="0" width="50%" border="0" minmax_bound="true">
    <tbody minmax_bound="true">
        <tr minmax_bound="true">
            <td class="code-outline" minmax_bound="true">
            <pre class="displaycode" minmax_bound="true">				<br minmax_bound="true" />
            &lt;script&gt;<br minmax_bound="true" />
            if (!window.google || !google.gears) {<br minmax_bound="true" />
            location.href = "http://gears.google.com/?action=install&amp;message=&lt;welcome message&gt;" <br minmax_bound="true" />
            + "&amp;return=&lt;return url&gt;";<br minmax_bound="true" />
            }<br minmax_bound="true" />
            &lt;/script&gt;<br minmax_bound="true" />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<p minmax_bound="true">&nbsp;</p>
<p minmax_bound="true">如果未安装 Gears，代码将向您提供下载 Gears 的 URL。</p>
<p minmax_bound="true">当所有元素都通过验证并且 Gears 已安装之后，您可以测试 Gears 的极其有用的数据库功能，使用 <a href="http://www.ibm.com/developerworks/cn/web/wa-speedweb/?ca=drs-tp4608#list6" minmax_bound="true">清单 6</a> 中的 JavaScript 代码。</p>
<p minmax_bound="true"><br minmax_bound="true" />
<a name="list6" minmax_bound="true"><strong minmax_bound="true">清单 6. 测试数据库功能</strong></a></p>
<table cellspacing="0" cellpadding="0" width="50%" border="0" minmax_bound="true">
    <tbody minmax_bound="true">
        <tr minmax_bound="true">
            <td class="code-outline" minmax_bound="true">
            <pre class="displaycode" minmax_bound="true">				<br minmax_bound="true" />
            &lt;script type="text/javascript"&gt;<br minmax_bound="true" />
            var db = google.gears.factory.create('beta.db');<br minmax_bound="true" />
            db.open('database-test');<br minmax_bound="true" />
            db.execute('create table if not exists Test' +<br minmax_bound="true" />
            ' (Phrase text, Timestamp int)');<br minmax_bound="true" />
            db.execute('insert into Test values (?, ?)', ['Monkey!', new Date().getTime()]);<br minmax_bound="true" />
            var rs = db.execute('select * from Test order by Timestamp desc');<br minmax_bound="true" />
            <br minmax_bound="true" />
            while (rs.isValidRow()) {<br minmax_bound="true" />
            alert(rs.field(0) + '@' + rs.field(1));<br minmax_bound="true" />
            rs.next();<br minmax_bound="true" />
            }<br minmax_bound="true" />
            rs.close();<br minmax_bound="true" />
            &lt;/script&gt;<br minmax_bound="true" />
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<p minmax_bound="true">&nbsp;</p>
<p minmax_bound="true">这段代码在您的计算机或服务器上创建一个本地数据库 <em minmax_bound="true">db。</em>如果表 <em minmax_bound="true">Test</em> 不存在，则创建一个，然后插入测试数据（<em minmax_bound="true">Monkey!</em> 和时间）。代码从数据库获取数据，并在浏览器中以警告的形式呈现出来。</p>
<p minmax_bound="true">想像一下可能发生的结果！</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="png" minmax_bound="true"><span class="atitle" minmax_bound="true">使用 PNG 格式的图像</span></a></strong></p>
<p minmax_bound="true">Graphic Interchange Format (GIF) 和 Joint Photographic Experts Group (JPEG) 图像格式都已过时了：Portable Network Graphic (PNG) 是未来流行的格式。当然，您可以说 GIF 和 JPEG 已经消亡，或者 PNG 没有任何缺陷，但是所有事物都有各自的优缺点，PNG 以最佳的文件大小提供了出色的质量。因此，如果进行选择的话，应该尽可能使用 PNG 图像。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="shortajax" minmax_bound="true"><span class="atitle" minmax_bound="true">保持 Ajax 调用简短、准确</span></a></strong></p>
<p minmax_bound="true">当 统称为 Asynchronous JavaScript + XML (Ajax) 的技术在两年前出现时，这些技术为处理页面请求和响应提供了一种革命性方法。然而，拨号用户可能从来没机会体验其真正的优势，因为在许多情形下，Ajax 需要在浏览器与服务器之间大量通信。因此，如果您能够保持 Ajax 调用简短和准确，可以避免用户花费无止尽的时间来等待元素刷新或响应。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="oneajax" minmax_bound="true"><span class="atitle" minmax_bound="true">进行一次较大的 Ajax 调用并在本地处理客户机数据</span></a></strong></p>
<p minmax_bound="true">如 果不能进行简短的 Ajax 调用，或者如果这些调用不能提供期望的结果，可以考虑一种替代方法：进行一次大的 Ajax 调用来获取所需的一切内容，然后让客户机在本地处理数据。通过这种方式，客户机只需等待一次（获取传入的数据），但是在此之后（当浏览器与服务器之间没有 必要通信时），处理速度将更快。当然，还有大量 Ajax 优化技术，本教程无法一一列出。如果想要了解关于 Ajax 的更多信息，请查看 <a href="http://www.ibm.com/developerworks/cn/web/wa-speedweb/?ca=drs-tp4608#resources" minmax_bound="true">参考资料</a>。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="sandbox" minmax_bound="true"><span class="atitle" minmax_bound="true">在沙箱中测试代码</span></a></strong></p>
<p minmax_bound="true">还有一个经常被遗忘的常用技巧。尽管清醒的 Web 开发人员通常会在启动应用程序之前对其进行测试，但是有时候测试会使他们不那么重视维护任务，或者新功能添加得太快，并且未经过充分考虑或测试。结果，余下的脚本减缓了应用程序的速度。</p>
<p minmax_bound="true">如 果您添加一项新功能，可以首先在沙箱里（完全脱离了应用程序的其余部分）进行测试，查看它作为单个函数的行为。通过这种方式，您可以反复检查，并分析性能 和响应时间，无需考虑 Web 应用程序的其余部分。然后，当新功能的行为符合预期时，可以将其引入到应用程序的其余部分中，运行其他测试，保证功能本身的行为符合预期。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="analyze" minmax_bound="true"><span class="atitle" minmax_bound="true">分析站点代码</span></a></strong></p>
<p minmax_bound="true">在许多场景中，自我反省是一个不错的建议。幸运的是，在开发过程中，我们可以使用工具来帮助反省，并尽可能客观地进行实践。像 JSLint 这样的工具的价值是无法衡量的，尽管其站点宣称它 &#8220;可能令您备受挫折&#8221;，因为它向您提供了所有的潜在代码缺陷，这些缺陷不但使调试更加困难，而且可能导致更长的响应时间。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="jslint" minmax_bound="true"><span class="atitle" minmax_bound="true">使用 JSLint 检查 JavaScript 代码中的错误或糟糕的编码实践</span></a></strong></p>
<p minmax_bound="true">您 不需要像完美主义者那样追求完美无缺的 JavaScript 代码。但是，许多开发人员没有认真对待代码分析，通常在开发过程中跳过了这个步骤。不幸的是，错误和糟糕的编码实践不仅不太专业，而且可能减缓应用程序的 速度。当浏览器忙于应付错误和糟糕的编码实践时，加载不仅需要更多时间，还会导致难以调试的错误。</p>
<p minmax_bound="true">因此，如果想要获得良好的代码，可以考虑使用代码分析工具。有许多不同的工具可供使用，但是最适合 JavaScript 语言的工具非 JavaScript Lint 莫属，它也叫做 JSLint。也可以使用 Firebug，但是 JSLint 更加正式，它包含在 YSlow 中。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="orphans" minmax_bound="true"><span class="atitle" minmax_bound="true">检查孤立的文件和丢失的图像</span></a></strong></p>
<p minmax_bound="true">检 查孤立的文件和丢失的图像是一种明智之举。大部分 Web 开发人员都会检查错误的文件引用，但是这里仍然需要说明一下。丢失的文件容易引起各种问题，因为它们会导致 &#8220;The image/page cannot be displayed&#8221; 之类的错误消息。但是在网页速度优化方面，它们具有更大的缺陷：当浏览器寻找丢失的或孤立的文件时，它会消耗资源，这不可避免地会导致页面处理速度变慢。 因此，请检查孤立或丢失的文件，包括拼写错误的文件名。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="YSlow" minmax_bound="true"><span class="atitle" minmax_bound="true">YSlow 扩展</span></a></strong></p>
<p minmax_bound="true">YSlow Firebug 扩展使主观的网页分析日渐被淘汰。YSlow 使用 Yahoo! 起草的面向高性能网站的权威规则，分析网页并告诉您它们变慢的原因。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="analyzepgs" minmax_bound="true"><span class="atitle" minmax_bound="true">使用 YSlow 分析网页</span></a></strong></p>
<p minmax_bound="true">YSlow 是一个相对较小但非常有用的 Firefox 扩展。当启动 YSlow 时，该扩展在浏览器的下半部分中打开，如 <a href="http://www.ibm.com/developerworks/cn/web/wa-speedweb/?ca=drs-tp4608#fig4" minmax_bound="true">图 4</a> 所示。</p>
<p minmax_bound="true"><br minmax_bound="true" />
<a name="fig4" minmax_bound="true"><strong minmax_bound="true">图 4. Firefox 中的 YSlow 扩展 </strong></a><br minmax_bound="true" />
<img style="width: 500px; height: 249px; maxwidth: 600px; minmaxwidth: 500px; minmaxheight: 249px" height="249" alt="Firefox 中的 YSlow 扩展" src="http://www.ibm.com/developerworks/cn/web/wa-speedweb/PerformanceView.jpg" width="500" minmax_bound="true" /></p>
<p minmax_bound="true">图 4 显示了 Performance 视图，可以在其中看到 YSlow 如何评估您的网页的性能，还能够看到该扩展检测到的问题。单击列表中的一个链接将打开一个页面，其中解释了相应的错误。如果存在可以改进的页面组件，YSlow 会给出改进建议。</p>
<p minmax_bound="true">在 Inspect 视图中，如 <a href="http://www.ibm.com/developerworks/cn/web/wa-speedweb/?ca=drs-tp4608#fig5" minmax_bound="true">图 5</a> 所示，您可以逐一分析元素来剖析页面。Inspect 视图的一个最有用的功能是，当您在页面上移动鼠标指针时，它会自动刷新，因此您无需通过滚动代码内容来查找需要检查的行。</p>
<p minmax_bound="true"><br minmax_bound="true" />
<a name="fig5" minmax_bound="true"><strong minmax_bound="true">图 5. Firefox 中的 YSlow Inspect 视图</strong></a><br minmax_bound="true" />
<img style="width: 500px; height: 298px; maxwidth: 600px; minmaxwidth: 500px; minmaxheight: 298px" height="298" alt="YSlow Inspect 视图" src="http://www.ibm.com/developerworks/cn/web/wa-speedweb/InspectView.jpg" width="500" minmax_bound="true" /></p>
<p minmax_bound="true">从 Stats 视图的名称可以猜测到，它（如 <a href="http://www.ibm.com/developerworks/cn/web/wa-speedweb/?ca=drs-tp4608#fig6" minmax_bound="true">图 6</a> 所示）显示与当前页面有关的统计数据。这些数据包括空的和主要的缓存和 cookie。</p>
<p minmax_bound="true"><br minmax_bound="true" />
<a name="fig6" minmax_bound="true"><strong minmax_bound="true">图 6. Firefox 中的 YSlow Stats 视图</strong></a><br minmax_bound="true" />
<img style="width: 500px; height: 350px; maxwidth: 600px; minmaxwidth: 500px; minmaxheight: 350px" height="350" alt="YSlow Stats 视图" src="http://www.ibm.com/developerworks/cn/web/wa-speedweb/StatsView.jpg" width="500" minmax_bound="true" /></p>
<p minmax_bound="true">Components 视图（如 <a href="http://www.ibm.com/developerworks/cn/web/wa-speedweb/?ca=drs-tp4608#fig7" minmax_bound="true">图 7</a> 所示）列出了当前页面上的组件。显示的与每个组件有关的数据包括文件类型和路径、页面过期时间以及 HTTP 响应报头。单击一个组件可以将其打开，以供查看。单击一个列标题可以按升序或降序对表进行排序。</p>
<p minmax_bound="true"><br minmax_bound="true" />
<a name="fig7" minmax_bound="true"><strong minmax_bound="true">图 7. YSlow Components 视图</strong></a><br minmax_bound="true" />
<img style="width: 500px; height: 239px; maxwidth: 600px; minmaxwidth: 500px; minmaxheight: 239px" height="239" alt="YSlow Components 视图" src="http://www.ibm.com/developerworks/cn/web/wa-speedweb/ComponentsView.jpg" width="500" minmax_bound="true" /></p>
<p minmax_bound="true">YSlow 是一个较小的、有用的扩展，可以在提高页面加载速度方面为您提供许多帮助。如果您以前未使用过它，那么现在应该使用了。</p>
<p minmax_bound="true"><strong minmax_bound="true"><a name="summary" minmax_bound="true"><span class="atitle" minmax_bound="true">结束语</span></a></strong></p>
<p minmax_bound="true">优化网页的加载速度并不复杂。实际上，您通常可以轻而易举地实现速度优化。如果遵循本文中介绍的技巧以及 Web 开发最佳实践，那么无需采用其他措施就可以提高页面的加载速度。</p>
<p minmax_bound="true">将 大量页面优化技巧收集到一起很简单，我希望本文的资源具有一定的价值。但是，如果您认为速度优化技巧只有这里列出的这些，那么您将惊奇地发现远远不止这 些。但是，即使您仅遵循这 20 多个技巧，您的页面的加载速度也会更快，您的用户也会更惬意 —— 无论他们通过拨号还是专用的宽带上网。</p>
<img src ="http://www.blogjava.net/table/aggbug/310426.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/table/" target="_blank">小卓</a> 2010-01-21 18:48 <a href="http://www.blogjava.net/table/articles/310426.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>J2EE集群原理 II</title><link>http://www.blogjava.net/table/articles/309199.html</link><dc:creator>小卓</dc:creator><author>小卓</author><pubDate>Tue, 12 Jan 2010 09:52:00 GMT</pubDate><guid>http://www.blogjava.net/table/articles/309199.html</guid><wfw:comment>http://www.blogjava.net/table/comments/309199.html</wfw:comment><comments>http://www.blogjava.net/table/articles/309199.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/table/comments/commentRss/309199.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/table/services/trackbacks/309199.html</trackback:ping><description><![CDATA[<h3><a href="http://koda.javaeye.com/blog/321484"><span class="hilite1">J2EE</span>集群原理 II</a></h3>
<div class="blog_content"><span style="font-size: medium"><span style="color: darkred"><strong>二. JNDI集群</strong></span></span> <br />
Jndi集群对于EJB也是非常重要的，因为几乎所有的EJB都是从JNDI调用开始的 <br />
<strong>I. 共享全局JNDI树</strong> <br />
Weblogic和JBOSS都使用一个全局的、共享的、分布在整个集群系统的JNDI树，对象被绑定到全局上下文，使用<span class="hilite3">ip</span>多播方式拷贝JNDI数据 <br />
<img alt="" src="http://dl.javaeye.com/upload/attachment/71891/abb9c0a0-b1e6-38a6-b6cf-653a8341d910.gif" /> aaaaa<br />
<br />
图十四：全局共享JNDI <br />
集群中的每个节点都有自己的命名服务器，并且自动保存其他所有节点的JNDI数据，因此这种结构具有高度可靠性 <br />
实际中，集群JNDI树主要有两个用途：一是用于部署，你只需要将某个EJB部署到一台服务器上，系统会自动将其拷贝到其他节点。二是在运行中你可以用JNDI存取自己的对象，这些自定义对象同样会被自动拷贝到其他节点。 <br />
<strong>II.独立的JNDI树</strong> <br />
Sun JES, IBM Websphere和其他厂商使用的是独立的JNDI树，各个节点拥有自己独立的JNDI，而不会关心其他节点的JNDI。但这并不意味着它们不能实现集群，关键是每台服务器上的配置要相同，应用也要相同，这样的话通过代理agent就能实现高性能的集群了 <br />
<img alt="" src="http://koda.javaeye.com/upload/attachment/71893/f06a7bb2-a596-3905-a98a-06bb41aad2a2.gif" /> <br />
Sun JES 和 IBM Websphere都在每个节点处安装了一个agent，由console负责协调各个agent <br />
但这种方式不支持动态绑定运行时生成的对象，设计者的理由是：JNDI本身就是作为管理外部资源的中间层，运行时对象绑定不在JNDI的职责范围内，如果真的有这种需要，可以使用LDAP或者具有HA功能的数据库。Sun和IBM都有自己的LDAP实现 <br />
<br />
<strong>III. 中央型JNDI树</strong> <br />
少数厂商采用中央型的JNDI，所有节点都去一个某个指定的JNDI服务器上获取资源，当然这对于客户端来说是透明的。不过这种方式会大大增加安装和管理的复杂性，因此被大部分厂商放弃 <br />
<br />
<strong>怎样开始连接JNDI呢</strong> <br />
使用JNDI，你必须知道提供JNDI服务的域名或<span class="hilite3">IP</span>以及端口，在全局和独立型JNDI树中，都存在着多个JNDI服务器，客户端要连接哪一个呢，负载均衡和失败恢复又是如何实现的呢？ <br />
<br />
技术上来说，可以在客户端和JNDI服务器之间放一个硬件或软件实现的负载均衡器，来实现上述要求，不过大部分厂商都有更加简单的方法： <br />
<br />
* SUN JES 和JBOSS 能够令&#8220;java.naming.provider.url&#8221;这一JNDI属性支持用逗号分隔多个地址，例如，&#8220;java.naming.provider.url=server1:1100,server2:1100,server3:1100,server4:1100&#8221;，客户端会逐个搜索，直到找到一个可用的为止 <br />
<br />
* JBOSS还支持自动找寻功能，即令&#8220;java.naming.provider.url&#8221;属性为空，客户端会自动以网络多播的方式寻找一个JNDI服务器作为查询的起始点 <br />
<br />
<br />
<span style="font-size: medium"><span style="color: darkred"><strong>三. EJB集群原理</strong></span></span> <br />
EJB衍生自分布式计算技术，服务器组件或富客户端都能够以标准协议（RMI/IIOP）调用远程的EJB，并且RMI/IIOP技术能够屏蔽底层网络，使得EJB的调用对客户透明化 <br />
<br />
<img alt="" src="http://koda.javaeye.com/upload/attachment/71894/d9ba37bd-0e84-3b13-a6e4-aa899eeeea3d.gif" /> <br />
<div style="text-align: center">图：EJB调用原理</div>
<br />
<br />
如图，客户端不是直接和EJB打交道，而是通过stub来代理。Stub负责利用RMI找到远程的EJB并进行调用。EJB的调用过程分为下面三步（EJB2.0） <br />
<br />
1.从JNDI中查找EJBHOME stub <br />
2.利用home stub创建一个EJB Object stub <br />
3.利用EJB Object stub调用EJB的方法 <br />
<br />
在JNDI查找时，可以利用上文说到的方法进行负载均衡；各厂商也会有自己专门的技术对stub的使用进行负载均衡，一般有以下三种方式： <br />
<br />
<strong>Smart stub</strong> <br />
<br />
我们知道，stub可以通过JNDI来获得和创建，甚至可以直接下载相应的class文件来在运行时动态生成，而无须在客户端本地的classpath中声明 <br />
<br />
<img alt="" src="http://koda.javaeye.com/upload/attachment/71897/375637dc-0b06-3d70-926f-a24efc3ae205.gif" /> <br />
<div style="text-align: center">图十七：smart stub</div>
<br />
<br />
如图十七，Weblogic 和JBOSS通过在stub的代码中嵌入特殊的代码来实现集群，这个stub的确相当smart，它包含了所有能够访问的集群服务器节点、内置了专门的集群算法来决定把请求发给何处，甚至当集群的拓扑有变时（比如增加了节点），能动态改变自身来适应新的结构，无须手工干预 <br />
Smart stub有以下几个好处： <br />
<br />
1.节省大量服务器资源 <br />
2.由于负载均衡是由客户端处理，因此可以防止在服务器端放置负载均衡器而可能导致的单点失败 <br />
3.Stub可以动态下载安装，意味着无须手工维护 <br />
<br />
<strong>IIOP Runtime Library</strong> <br />
<br />
Sun JES采用另一种方法，它直接修改客户端的IIOP运行时库 <br />
<img alt="" src="http://koda.javaeye.com/upload/attachment/71899/89307338-fa0f-3bf7-90cf-f3ff159de214.gif" /> <br />
<div style="text-align: center">图十八：IIOP Runtime</div>
<br />
如图十八，sun直接把负载均衡逻辑转移到了IIOP库中，从而使stub能够保持&#8220;小而轻&#8221;，同时由于IIOP是底层库，能够更加有效地获取和使用JVM提供的资源。但正是由于底层库有所不同，使得JES与其他<span class="hilite1">J2EE</span>服务器打交道时可能会出现一些问题 <br />
<br />
<strong>Interceptor Proxy</strong> <br />
<br />
IBM Websphere使用Location Service Daemon (LSD)作为一个拦截器代理来实现EJB集群 <br />
<img alt="" src="http://koda.javaeye.com/upload/attachment/71901/c7a81f0a-6b4d-3593-8d7d-9ce68949d27c.gif" /> <br />
Figure 19: Interceptor Proxy <br />
<br />
使用这种机制，stub中包含至LSD的路由信息，而不是直接去找服务器节点，这样LSD就能获得所有请求并进行负载均衡了，不过这样会大大增加管理和维护的成本 <br />
<br />
<strong>EJB的集群支持</strong> <br />
调用EJB方法的过程中我们要和两个stub打交道，一个是home stub，另一个是object stub，因此可以在两个层面上实现负载均衡和失败恢复 <br />
<br />
1.EJBHOME STUB的集群实现 <br />
由于EJBHOME STUB本身不包括任何客户端信息，无论从哪个服务器上获得的EJBHOME STUB都是一样的，因此当客户端调用EJBHOME STUB的create等方法时，就能利用一些负载均衡的算法选择合适的服务器节点 <br />
2.EJBOBJECT STUB的集群实现 <br />
<br />
EJBOBJECT STUB包含业务接口，而且其本身也能够含有集群节点的信息，但也不是所有的方法调用都能够进行负载均衡式的路由，得看EJB的类型是什么 <br />
无状态会话bean最容易实现负载均衡了，因为它本身不包含特定的客户信息 <br />
<br />
有状态会话bean略有不同，因为它本身包含客户端的会话信息，因此有状态bean的集群实现本质上和HTTP session 无异，一般情况下客户端的stub都是一直与某个节点上的EJB组件打交道的，除非中途出问题了才会将请求转发到备用的节点上 <br />
<br />
实体bean本质上也是无状态的。表面上看可以采用和无状态会话bean一样的方式集群，但实际上很少厂商会对实体bean做集群。因为实体bean一般都是被其他会话bean调用的，因此通常都使用本地接口通讯，实在没有集群的必要 <br />
<br />
<span style="font-size: medium"><span style="color: darkred"><strong>四.JMS和数据库连接的集群</strong></span></span> <br />
<br />
目前一些数据库产品已经可以集群了，你可以部署成多份，每个节点之间可以同步。但是JDBC本质上是有状态连接，和底层的socket紧密绑定。当某个JDBC连接突然中断了，与之相关的对象也就费了，因此很难对JDBC集群。Weblogic使用一种JDBC multipool，可以在JDBC断开情况下，方便地进行重新连接 <br />
<br />
JMS的负载均衡和失败恢复只在JMS broker上有实现，很少有厂商在JMS destination 的消息上实现了负载均衡 <br />
<br />
<span style="font-size: medium"><span style="color: darkred"><strong>五.关于<span class="hilite1">J2EE</span>集群的神话</strong></span></span> <br />
<span style="color: olive"><strong>失败恢复能够防止所有错误吗？——错！</strong></span> <br />
JBOSS的文档花了整整一章的内容来提醒你：&#8220;你真的需要HTTP SESSION复制吗&#8221;，有时候不用失败恢复也能以经济的方式获得高可靠性。更何况，失败恢复并没有你想象中的那么可靠 <br />
<br />
你也许认为失败恢复能够在节点宕机时保护你的session数据，但你得清楚，这种保障是有代价的。 <br />
<br />
回想一下作者在定义&#8220;失败恢复&#8221;时，前提条件是&#8220;在两个方法调用之间&#8221;！也就是说只有在第一个方法成功返回之后、第二个方法调用开始之前，失败恢复才能起作用 <br />
<br />
假设某个方法处理到一半时服务器不幸挂掉了，客户端一般只能收到错误消息。除非你这个方法恰好是&#8220;idempotent&#8221;的（即多次调用的结果都能保持一致，不会对环境造成任何改变，比如getter方法），那么有些聪明的负载均衡器会尝试找其他节点去调用这个方法 <br />
<br />
所以说&#8220;idempotent&#8221;这个概念非常重要，因为客户端压根不知道是在什么地方失败的，而如果这个方法不是idempotent，那么系统就可能处于一种不一致的状态，很危险 <br />
<br />
你也许认为事务性的方法就是idempotent的，毕竟事务可以回滚。但其实事务远远不能涵盖整个远程调用的范围，比如服务器成功执行某事务性方法后，返回结果的过程中网络崩溃了呢？ <br />
在比较严格的系统设计中，你根本不能令所有方法都idempotent，你能做的就是利用failover，尽可能减少错误而不是彻底杜绝错误。以某个网上商城为例：每台服务器都同时处理100个用户请求，当某台服务器崩溃时，如果没有失败恢复，那么你会得罪一百个用户；而如果有失败恢复，可能只有不到20个用户会发飙。你自己要权衡： <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.是得罪一百个客户还是得罪20个客户？ <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.有没有失败恢复的服务器的价格差别 <br />
<br />
<span style="color: olive"><strong>独立的应用程序可以无缝地迁移到分布式平台上？——错！</strong></span> <br />
这只是某些厂商的广告而已，不可轻信。如果是大型系统，那么设计之初就应该全盘考虑集群可能造成的影响. <br />
<br />
<strong>HTTP&nbsp; SESSION</strong> <br />
正如前文所述，HTTP SESSION的集群会受到你使用的服务器的诸多限制。首先是可序列化的要求，在很多MVC架构中，session被用来存储一些不可序列化的对象（比如servlet context）；其次，序列化、特别是数据库方式的序列化非常耗费资源，因此要集群就尽量不要用session存储大对象。如果是内存拷贝的方式，那么就要考虑内存的限制和交叉引用的问题（交叉引用请参考前文）；最后，你在集群环境下改变session的属性时，必须使用&#8220;setAttribute&#8221;方法，而在单机环境下却没这个限制。这么做的主要原因是让你的应用服务器能够检测到属性改变，及时地备份已修改的属性 <br />
<br />
<strong>缓存</strong> <br />
大部分流行的应用服务器都会使用缓存来提升性能，但缓存一般都是为单机环境设计的。集群环境下如果使用缓存，那么缓存之间的拷贝花费的性能将比缓存带来的好处还多，适得其反 <br />
<br />
<strong>静态变量</strong> <br />
有一种很流行的<span class="hilite1">J2EE</span>设计模式&#8220;singleton（单体）&#8221;，是使用静态变量的，这在单机环境下适用，但到了集群就不行了，道理很简单，每个JVM都有自己的静态对象，&#8220;单体&#8221;也就失去意义了。比如要统计在线用户数时，经常用一个静态变量来存储，但是在集群环境下这种方法显然失去作用。要使用静态对象，最好把它放到一个数据库中，才能实现全局的&#8220;单体&#8221; <br />
<br />
<strong>外部资源</strong> <br />
尽管<span class="hilite1">J2EE</span>规范并不推荐，但外部IO操作还是有用的，比如用来存放用户上传的文件。在集群中，服务器是不能通过另一台服务器把本地文件直接存放到别的机器上的，那么还是要依靠数据库来统一存放文件，或者你可以使用SAN这种集中式文件仓库 <br />
<br />
<strong>专有服务</strong> <br />
有些专有服务是只限于单机环境的，比如定时器服务（timer）；再比如某些事件触发类型的服务，如初始化服务，邮件提醒服务也属于此列。这些服务一般针对一个特定条件只发生一次，拿去集群没什么意义。JBOSS的&#8220;clustered singleton facility&#8221;就是用来保证所有服务器运行且只运行一次某种服务 <br />
<br />
<span style="color: olive"><strong>分布式系统比并列式系统更灵活？——未必</strong></span> <br />
<br />
尽管EJB生来就是为了分布式、解耦合，但很多框架认为把EJB层和web层放在一起也未尝不是好事，或许更好 <br />
<img alt="" src="http://koda.javaeye.com/upload/attachment/71903/b47a7a24-a11b-309b-94db-6bc9bb9a24d4.gif" /> <br />
<div style="text-align: center">图20：分布式架构</div>
<br />
如图20，负载均衡器先将请求分发到适当服务器的web层，web层进一步判断调用何处的EJB，这等于是做了两次的负载均衡和失败恢复。很多人认为这种设计并不好： <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1. 首先，第二次的转发根本没有必要，每个服务器都有自己的web和ejb层，比起只调用内部的ejb，web层调用其他ejb层没有任何的好处 <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2. 第二次的失败恢复也是没有必要的，大部分厂商都把web容器和ejb容器实现为在同一个jvm上跑，那样一旦web容器挂了，ejb容器很难独善其身 <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3. 损失性能，这个不必解释了，大量的服务器之间的调用对性能肯定有影响。 <br />
<br />
实际上，大部分厂商都让web容器优先选择同一个服务器上的ejb容器，这种方式称作&#8220;并列式&#8221;，是分布式的一种特例，如下图 <br />
<img alt="" src="http://koda.javaeye.com/upload/attachment/71904/573d34a3-8e77-360b-9611-09cd48afe0f4.gif" /> <br />
<div style="text-align: center">图21：并列式</div>
<br />
<br />
这样引出一个有趣的问题：既然都放一块了，为什么不直接用ejb的本地接口呢？虽然本地接口可以提高性能，但它却把web和ejb紧耦合在一起了，负载均衡机制也就没有办法对其进行优化，特别是当你要变成分布式的时候 <br />
<br />
此外很不幸的是，在集群环境下本地接口的使用经常受到限制，因为有本地接口的ejb通常是不可序列化的。所以有些应用服务器，比如sun JES，对本地接口的ejb做了特殊处理使其可以被序列化，从而保存到诸如session中去 <br />
<br />
还有一个问题是，既然大部分情况下并列式的性能都比较好，那还要分布式干什么呢？其实在某些情况下还非得用分布式不可： <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1. 除了web容器，富客户端也要调用ejb组件 <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2. Web容器和ejb容器所处的安全级别不同，它们物流上也被放在不同的地方，中间加上一个防火墙 <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3.Ejb和web层的极度不对称。指的是复杂度的不对称，比如ejb层有大规模的运算，那就放在一台高级的服务器上，而web容器只需放在一台pc上即可. <br />
<br />
<span style="font-size: medium"><span style="color: darkred"><strong>六.结论</strong></span></span> <br />
集群与单机环境是有很大不同的，各厂商的集群实现也不同。最好在项目开始就考虑到集群，使得你的系统更有扩展性，根据自身的需求选择最合适的应用服务器，并且选购第三方软件时也要考虑到对集群的支持。最后，合适的架构可以让你受益于集群而不是让集群成为你的噩梦 <br />
<br />
<br />
关于原作者 <br />
<br />
<div class="quote_title">引用</div>
<div class="quote_div">Wang Yu presently works for GPE group of Sun Microsystems as a Java technology engineer and technology architecture consultant. His duties include supporting local ISVs, evangelizing and consulting on important Java technologies such as <span class="hilite1">J2EE</span>, EJB, JSP/Servlet, JMS, Web services technologies. He can be reached at yu.wang@sun.com.</div>
</div>
<img src ="http://www.blogjava.net/table/aggbug/309199.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/table/" target="_blank">小卓</a> 2010-01-12 17:52 <a href="http://www.blogjava.net/table/articles/309199.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>