table

          使用ETags減少Web應(yīng)用帶寬和負(fù)載

          介紹

          最近,大眾對(duì)于REST風(fēng)格應(yīng)用架構(gòu)表現(xiàn)出強(qiáng)烈興趣,這表明Web的優(yōu)雅設(shè)計(jì)開始受到人們的注意。現(xiàn)在,我們逐漸理解了“3W架構(gòu)(Architecture of the World Wide Web)”內(nèi)在所蘊(yùn)含的可伸縮性和彈性,并進(jìn)一步探索運(yùn)用其范式的方法。本文中,我們將探究一個(gè)可被Web開發(fā)者利用的、鮮為人知的工具,不引人注意的“ETag響應(yīng)頭(ETag Response Header)”,以及如何將它集成進(jìn)基于Spring和Hibernate的動(dòng)態(tài)Web應(yīng)用,以提升應(yīng)用程序性能和可伸縮性。

          我們將要使用的Spring框架應(yīng)用是基于“寵物診所(petclinic)”的。下載文件中包含了關(guān)于如何增加必要的配置及源碼的說明,你可以自己嘗試。

          什么是“ETag”?

          HTTP協(xié)議規(guī)格說明定義ETag為“被請(qǐng)求變量的實(shí)體值” (參見 http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html —— 章節(jié) 14.19)。 另一種說法是,ETag是一個(gè)可以與Web資源關(guān)聯(lián)的記號(hào)(token)。典型的Web資源可以一個(gè)Web頁,但也可能是JSON或XML文檔。服務(wù)器單獨(dú)負(fù)責(zé)判斷記號(hào)是什么及其含義,并在HTTP響應(yīng)頭中將其傳送到客戶端。

          ETag如何幫助提升性能?

          聰明的服務(wù)器開發(fā)者會(huì)把ETags和GET請(qǐng)求的“If-None-Match”頭一起使用,這樣可利用客戶端(例如瀏覽器)的緩存。因?yàn)榉?wù)器首先產(chǎn)生ETag,服務(wù)器可在稍后使用它來判斷頁面是否已經(jīng)被修改。本質(zhì)上,客戶端通過將該記號(hào)傳回服務(wù)器要求服務(wù)器驗(yàn)證其(客戶端)緩存。

          其過程如下:

          1. 客戶端請(qǐng)求一個(gè)頁面(A)。
          2. 服務(wù)器返回頁面A,并在給A加上一個(gè)ETag。
          3. 客戶端展現(xiàn)該頁面,并將頁面連同ETag一起緩存。
          4. 客戶再次請(qǐng)求頁面A,并將上次請(qǐng)求時(shí)服務(wù)器返回的ETag一起傳遞給服務(wù)器。
          5. 服務(wù)器檢查該ETag,并判斷出該頁面自上次客戶端請(qǐng)求之后還未被修改,直接返回響應(yīng)304(未修改——Not Modified)和一個(gè)空的響應(yīng)體。

          本文的其余部分將展示在基于Spring框架的Web應(yīng)用中利用ETag的兩種方法,該應(yīng)用使用Spring MVC。首先我們將使用Servlet 2.3 Filter,利用展現(xiàn)視圖(rendered view)的MD5校驗(yàn)和(checksum)以實(shí)現(xiàn)生成ETag的方法(一個(gè)“淺顯的”ETag實(shí)現(xiàn))。 第二種方法使用更為復(fù)雜的方法追蹤view中所使用的model,以確定ETag有效性(一個(gè)“深入的”ETag實(shí)現(xiàn))。盡管我們使用的是Spring MVC,但該技術(shù)可以應(yīng)用于任何MVC風(fēng)格的Web框架。

          在我們繼續(xù)之前,強(qiáng)調(diào)一下這里所展現(xiàn)的是提升動(dòng)態(tài)產(chǎn)生頁面性能的技術(shù)。已有的優(yōu)化技術(shù)也應(yīng)作為整體優(yōu)化和應(yīng)用性能特性調(diào)整分析的一部分來考慮。(見下)。

          自頂向下的Web緩存

          本文主要涉及對(duì)動(dòng)態(tài)生成頁面使用HTTP緩存技術(shù)。當(dāng)考慮提升Web應(yīng)用的性能的時(shí)候,應(yīng)采取一個(gè)整體的、自頂向下的方法。為了這一目的,理解HTTP請(qǐng)求經(jīng)過的各層是很重要的,應(yīng)用哪些適當(dāng)?shù)募夹g(shù)取決于你所關(guān)注的熱點(diǎn)。例如:

          • 將Apache作為Servlet容器的前端,來處理如圖片和javascript腳本這樣的靜態(tài)文件,而且還可以使用FileETag指令創(chuàng)建ETag響應(yīng)頭。
          • 使用針對(duì)javascript文件的優(yōu)化技術(shù),如將多個(gè)文件合并到一個(gè)文件中以及壓縮空格。
          • 利用GZip和緩存控制頭(Cache-Control headers)。
          • 為確定你的Spring框架應(yīng)用的痛處所在,可以考慮使用 JamonPerformanceMonitorInterceptor
          • 確信你充分利用ORM工具的緩存機(jī)制,因此對(duì)象不需要從數(shù)據(jù)庫中頻繁的再生。花時(shí)間確定如何讓查詢緩存為你工作是值得的。
          • 確保你最小化數(shù)據(jù)庫中獲取的數(shù)據(jù)量,尤其是大的列表。如果每個(gè)頁面只請(qǐng)求大列表的一個(gè)小子集,那么大列表的數(shù)據(jù)應(yīng)由其中某個(gè)頁面一次獲得。
          • 使放入到HTTP session中的數(shù)據(jù)量最小。這樣內(nèi)存得到釋放,而且當(dāng)將應(yīng)用集群的時(shí)候也會(huì)有所幫助。
          • 使用數(shù)據(jù)庫明細(xì)(database profiling)工具來查看在查詢的時(shí)候使用了什么索引,在更新的時(shí)候整個(gè)表沒有被上鎖。

          當(dāng)然,應(yīng)用性能優(yōu)化的至理名言是:兩次測(cè)量,一次剪裁(measure twice, cut once)。哦,等等,這是對(duì)木工而言的!沒錯(cuò),但是它在這里也很適用!

          ETag Filter內(nèi)容體

          我們要考慮的第一種方法是創(chuàng)建一個(gè)Servlet Filter,它將基于頁面(MVC中的“View”)的內(nèi)容產(chǎn)生其ETag 記號(hào)。乍一看,使用這種方法所獲得的任何性能提升看起來都是違反直覺的。我們?nèi)匀徊坏貌划a(chǎn)生頁面,而且還增加了產(chǎn)生記號(hào)的計(jì)算時(shí)間。然而,這里的想法是減少帶寬使用。在大的響應(yīng)時(shí)間情形下,如你的主機(jī)和客戶端分布在這個(gè)星球的兩端,這很大程度上是有益的。我曾見過東京辦公室使用紐約服務(wù)器上托管的應(yīng)用,其響應(yīng)時(shí)間達(dá)到了 350 ms。隨著并發(fā)用戶數(shù)的增長(zhǎng),這將變成巨大的瓶頸。

          代碼

          我們用來產(chǎn)生記號(hào)的技術(shù)是基于從頁面內(nèi)容計(jì)算MD5哈希值。這通過在響應(yīng)之上創(chuàng)建一個(gè)包裝器來實(shí)現(xiàn)。該包裝器使用字節(jié)數(shù)組來保存所產(chǎn)生的內(nèi)容,在filter鏈處理完成之后我們利用數(shù)組的MD5哈希值計(jì)算記號(hào)。

          doFilter方法的實(shí)現(xiàn)如下所示。

           public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
          ServletException {
          HttpServletRequest servletRequest = (HttpServletRequest) req;
          HttpServletResponse servletResponse = (HttpServletResponse) res;

          ByteArrayOutputStream baos = new ByteArrayOutputStream();
          ETagResponseWrapper wrappedResponse = new ETagResponseWrapper(servletResponse, baos);
          chain.doFilter(servletRequest, wrappedResponse);

          byte[] bytes = baos.toByteArray();

          String token = '"' + ETagComputeUtils.getMd5Digest(bytes) + '"';
          servletResponse.setHeader("ETag", token); // always store the ETag in the header

          String previousToken = servletRequest.getHeader("If-None-Match");
          if (previousToken != null && previousToken.equals(token)) { // compare previous token with current one
          logger.debug("ETag match: returning 304 Not Modified");
          servletResponse.sendError(HttpServletResponse.SC_NOT_MODIFIED);
          // use the same date we sent when we created the ETag the first time through
          servletResponse.setHeader("Last-Modified", servletRequest.getHeader("If-Modified-Since"));
          } else { // first time through - set last modified time to now
          Calendar cal = Calendar.getInstance();
          cal.set(Calendar.MILLISECOND, 0);
          Date lastModified = cal.getTime();
          servletResponse.setDateHeader("Last-Modified", lastModified.getTime());

          logger.debug("Writing body content");
          servletResponse.setContentLength(bytes.length);
          ServletOutputStream sos = servletResponse.getOutputStream();
          sos.write(bytes);
          sos.flush();
          sos.close();
          }
          }

          清單 1:ETagContentFilter.doFilter

          你需注意到,我們還設(shè)置了Last-Modified頭。這被認(rèn)為是為服務(wù)器產(chǎn)生內(nèi)容的正確形式,因?yàn)槠溆狭瞬徽J(rèn)識(shí)ETag頭的客戶端。

          下面的例子使用了一個(gè)工具類EtagComputeUtils來產(chǎn)生對(duì)象所對(duì)應(yīng)的字節(jié)數(shù)組,并處理MD5摘要邏輯。我使用了javax.security MessageDigest來計(jì)算MD5哈希碼。

          public static byte[] serialize(Object obj) throws IOException {
          byte[] byteArray = null;
          ByteArrayOutputStream baos = null;
          ObjectOutputStream out = null;
          try {
          // These objects are closed in the finally.
          baos = new ByteArrayOutputStream();
          out = new ObjectOutputStream(baos);
          out.writeObject(obj);
          byteArray = baos.toByteArray();
          } finally {
          if (out != null) {
          out.close();
          }
          }
          return byteArray;
          }

          public static String getMd5Digest(byte[] bytes) {
          MessageDigest md;
          try {
          md = MessageDigest.getInstance("MD5");
          } catch (NoSuchAlgorithmException e) {
          throw new RuntimeException("MD5 cryptographic algorithm is not available.", e);
          }
          byte[] messageDigest = md.digest(bytes);
          BigInteger number = new BigInteger(1, messageDigest);
          // prepend a zero to get a "proper" MD5 hash value
          StringBuffer sb = new StringBuffer('0');
          sb.append(number.toString(16));
          return sb.toString();
          }

          清單 2:ETagComputeUtils

          直接在web.xml中配置filter。

              <filter>
          <filter-name>ETag Content Filter</filter-name>
          <filter-class>org.springframework.samples.petclinic.web.ETagContentFilter</filter-class>
          </filter>

          <filter-mapping>
          <filter-name>ETag Content Filter</filter-name>
          <url-pattern>/*.htm</url-pattern>
          </filter-mapping>

          清單 3:web.xml中配置filter。

          每個(gè).htm文件將被EtagContentFilter過濾,如果頁面自上次客戶端請(qǐng)求后沒有改變,它將返回一個(gè)空內(nèi)容體的HTTP響應(yīng)。

          我們?cè)谶@里展示的方法對(duì)特定類型的頁面是有用的。但是,該方法有兩個(gè)缺點(diǎn):

          • 我們是在頁面已經(jīng)被展現(xiàn)在服務(wù)器之后計(jì)算ETag的,但是在返回客戶端之前。如果有Etag匹配,實(shí)際上并不需要再為model裝進(jìn)數(shù)據(jù),因?yàn)橐宫F(xiàn)的頁面不需要發(fā)送回客戶端。
          • 對(duì)于類似于在頁腳顯示日期時(shí)間這樣的頁面,即使內(nèi)容實(shí)際上并沒有改變,每個(gè)頁面也將是不同的。

          下一節(jié),我們將著眼于另一種方法,其通過理解更多關(guān)于構(gòu)造頁面的底層數(shù)據(jù)來克服這些問題的某些限制。

          ETag攔截器(Interceptor)

          Spring MVC HTTP 請(qǐng)求處理途徑中包括了在一個(gè)controller前插接攔截器(Interceptor)的能力,因而有機(jī)會(huì)處理請(qǐng)求。這兒是應(yīng)用我們ETag比較邏輯的理想場(chǎng)所,因此如果我們發(fā)現(xiàn)構(gòu)建一個(gè)頁面的數(shù)據(jù)沒有發(fā)生變化,我們可以避免進(jìn)一步處理。

          這兒的訣竅是你怎么知道構(gòu)成頁面的數(shù)據(jù)已經(jīng)改變了?為了達(dá)到本文的目的,我創(chuàng)建了一個(gè)簡(jiǎn)單的ModifiedObjectTracker,它通過Hibernate事件偵聽器清楚地知道插入、更新和刪除操作。該追蹤器為應(yīng)用程序的每個(gè)view維護(hù)一個(gè)唯一的號(hào)碼,以及一個(gè)關(guān)于哪些Hibernate實(shí)體影響每個(gè)view的映射。每當(dāng)一個(gè)POJO被改變了,使用了該實(shí)體的view的計(jì)數(shù)器就加1。我們使用該計(jì)數(shù)值作為ETag,這樣當(dāng)客戶端將ETag送回時(shí)我們就知道頁面背后的一個(gè)或多個(gè)對(duì)象是否被修改了。

          代碼

          我們就從ModifiedObjectTracker開始吧:

          public interface ModifiedObjectTracker {
          void notifyModified(> String entity);
          }

          夠簡(jiǎn)單吧?這個(gè)實(shí)現(xiàn)還有一點(diǎn)更有趣的。任何時(shí)候一個(gè)實(shí)體改變了,我們就更新每個(gè)受其影響的view的計(jì)數(shù)器:

          public void notifyModified(String entity) {
          // entityViewMap is a map of entity -> list of view names
          List views = getEntityViewMap().get(entity);

          if (views == null) {
          return; // no views are configured for this entity
          }

          synchronized (counts) {
          for (String view : views) {
          Integer count = counts.get(view);
          counts.put(view, ++count);
          }
          }
          }

          一個(gè)“改變”就是插入、更新或者刪除。這里給出的是偵聽刪除操作的處理器(配置為Hibernate 3 LocalSessionFactoryBean上的事件偵聽器):

          public class DeleteHandler extends DefaultDeleteEventListener {
          private ModifiedObjectTracker tracker;

          public void onDelete(DeleteEvent event) throws HibernateException {
          getModifiedObjectTracker().notifyModified(event.getEntityName());
          }

          public ModifiedObjectTracker getModifiedObjectTracker() {
          return tracker;
          }
          public void setModifiedObjectTracker(ModifiedObjectTracker tracker) {
          this.tracker = tracker;
          }
          }

          ModifiedObjectTracker通過Spring配置被注入到DeleteHandler中。還有一個(gè)SaveOrUpdateHandler來處理新建或更新POJO。

          如果客戶端發(fā)送回當(dāng)前有效的ETag(意味著自上次請(qǐng)求之后我們的內(nèi)容沒有改變),我們將阻止更多的處理,以實(shí)現(xiàn)我們的性能提升。在Spring MVC里,我們可以使用HandlerInterceptorAdaptor并覆蓋preHandle方法:

          public final boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws
          ServletException, IOException {
          String method = request.getMethod();
          if (!"GET".equals(method))
          return true;

          String previousToken = request.getHeader("If-None-Match");
          String token = getTokenFactory().getToken(request);

          // compare previous token with current one
          if ((token != null) && (previousToken != null && previousToken.equals('"' + token + '"'))) {
          response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
          // re-use original last modified timestamp
          response.setHeader("Last-Modified", request.getHeader("If-Modified-Since"))
          return false; // no further processing required
          }

          // set header for the next time the client calls
          if (token != null) {
          response.setHeader("ETag", '"' + token + '"');

          // first time through - set last modified time to now
          Calendar cal = Calendar.getInstance();
          cal.set(Calendar.MILLISECOND, 0);
          Date lastModified = cal.getTime();
          response.setDateHeader("Last-Modified", lastModified.getTime());
          }

          return true;
          }

          我們首先確信我們正在處理GET請(qǐng)求(與PUT一起的ETag可以用來檢測(cè)不一致的更新,但其超出了本文的范圍。)。如果該記號(hào)與上次我們發(fā)送的記號(hào)相匹配,我們返回一個(gè)“304未修改”響應(yīng)并“短路”請(qǐng)求處理鏈的其余部分。否則,我們?cè)O(shè)置ETag響應(yīng)頭以便為下一次客戶端請(qǐng)求做好準(zhǔn)備。

          你需注意到我們將產(chǎn)生記號(hào)邏輯抽出到一個(gè)接口中,這樣可以插接不同的實(shí)現(xiàn)。該接口有一個(gè)方法:

          public interface ETagTokenFactory {
          String getToken(HttpServletRequest request);
          }

          為了把代碼清單減至最小,SampleTokenFactory的簡(jiǎn)單實(shí)現(xiàn)還擔(dān)當(dāng)了ETagTokenFactory的角色。本例中,我們通過簡(jiǎn)單返回請(qǐng)求URI的更改計(jì)數(shù)值來產(chǎn)生記號(hào):

          public String getToken(HttpServletRequest request) {
          String view = request.getRequestURI();
          Integer count = counts.get(view);
          if (count == null) {
          return null;
          }

          return count.toString();
          }

          大功告成!

          會(huì)話

          這里,如果什么也沒改變,我們的攔截器將阻止任何搜集數(shù)據(jù)或展現(xiàn)view的開銷。現(xiàn)在,讓我們看看HTTP頭(借助于LiveHTTPHeaders),看看到底發(fā)生了什么。下載文件中包含了配置該攔截器的說明,因此owner.htm“能夠使用ETag”:

          我們發(fā)起的第一個(gè)請(qǐng)求說明該用戶已經(jīng)看過了這個(gè)頁面:

          ----------------------------------------------------------  
          http://localhost:8080/petclinic/owner.htm?ownerId=10

          GET /petclinic/owner.htm?ownerId=10 HTTP/1.1
          Host: localhost:8080
          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
          Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
          Accept-Language: en-us,en;q=0.5
          Accept-Encoding: gzip,deflate
          Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
          Keep-Alive: 300
          Connection: keep-alive
          Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8
          X-lori-time-1: 1182364348062
          If-Modified-Since: Wed, 20 Jun 2007 18:29:03 GMT
          If-None-Match: "-1"

          HTTP/1.x 304 Not Modified
          Server: Apache-Coyote/1.1
          Date: Wed, 20 Jun 2007 18:32:30 GMT

          我們現(xiàn)在應(yīng)該做點(diǎn)修改,看看ETag是否改變了。我們給這個(gè)物主增加一個(gè)寵物:

          ----------------------------------------------------------
          http://localhost:8080/petclinic/addPet.htm?ownerId=10

          GET /petclinic/addPet.htm?ownerId=10 HTTP/1.1
          Host: localhost:8080
          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
          Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
          Accept-Language: en-us,en;q=0.5
          Accept-Encoding: gzip,deflate
          Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
          Keep-Alive: 300
          Connection: keep-alive
          Referer: http://localhost:8080/petclinic/owner.htm?ownerId=10
          Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8
          X-lori-time-1: 1182364356265

          HTTP/1.x 200 OK
          Server: Apache-Coyote/1.1
          Pragma: No-cache
          Expires: Thu, 01 Jan 1970 00:00:00 GMT
          Cache-Control: no-cache, no-store
          Content-Type: text/html;charset=ISO-8859-1
          Content-Language: en-US
          Content-Length: 2174
          Date: Wed, 20 Jun 2007 18:32:57 GMT
          ----------------------------------------------------------
          http://localhost:8080/petclinic/addPet.htm?ownerId=10

          POST /petclinic/addPet.htm?ownerId=10 HTTP/1.1
          Host: localhost:8080
          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
          Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
          Accept-Language: en-us,en;q=0.5
          Accept-Encoding: gzip,deflate
          Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
          Keep-Alive: 300
          Connection: keep-alive
          Referer: http://localhost:8080/petclinic/addPet.htm?ownerId=10
          Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8
          X-lori-time-1: 1182364402968
          Content-Type: application/x-www-form-urlencoded
          Content-Length: 40
          name=Noddy&birthDate=1000-11-11&typeId=5
          HTTP/1.x 302 Moved Temporarily
          Server: Apache-Coyote/1.1
          Pragma: No-cache
          Expires: Thu, 01 Jan 1970 00:00:00 GMT
          Cache-Control: no-cache, no-store
          Location: http://localhost:8080/petclinic/owner.htm?ownerId=10
          Content-Language: en-US
          Content-Length: 0
          Date: Wed, 20 Jun 2007 18:33:23 GMT

          因?yàn)閷?duì)addPet.htm我們沒有配置任何已知ETag,也沒有設(shè)置頭信息。現(xiàn)在,我們?cè)僖淮尾榭磇d為10的物主。注意ETag這時(shí)是1:

          ----------------------------------------------------------
          http://localhost:8080/petclinic/owner.htm?ownerId=10

          GET /petclinic/owner.htm?ownerId=10 HTTP/1.1
          Host: localhost:8080
          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
          Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
          Accept-Language: en-us,en;q=0.5
          Accept-Encoding: gzip,deflate
          Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
          Keep-Alive: 300
          Connection: keep-alive
          Referer: http://localhost:8080/petclinic/addPet.htm?ownerId=10
          Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8
          X-lori-time-1: 1182364403109
          If-Modified-Since: Wed, 20 Jun 2007 18:29:03 GMT
          If-None-Match: "-1"

          HTTP/1.x 200 OK
          Server: Apache-Coyote/1.1
          Etag: "1"
          Last-Modified: Wed, 20 Jun 2007 18:33:36 GMT
          Content-Type: text/html;charset=ISO-8859-1
          Content-Language: en-US
          Content-Length: 4317
          Date: Wed, 20 Jun 2007 18:33:45 GMT

          最后,我們?cè)俅尾榭磇d為10的物主。這次我們的ETag命中了,我們得到一個(gè)“304未修改”響應(yīng):

          ----------------------------------------------------------
          http://localhost:8080/petclinic/owner.htm?ownerId=10

          GET /petclinic/owner.htm?ownerId=10 HTTP/1.1
          Host: localhost:8080
          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
          Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
          Accept-Language: en-us,en;q=0.5
          Accept-Encoding: gzip,deflate
          Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
          Keep-Alive: 300
          Connection: keep-alive
          Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8
          X-lori-time-1: 1182364493500
          If-Modified-Since: Wed, 20 Jun 2007 18:33:36 GMT
          If-None-Match: "1"

          HTTP/1.x 304 Not Modified
          Server: Apache-Coyote/1.1
          Date: Wed, 20 Jun 2007 18:34:55 GMT

          我們已經(jīng)利用HTTP緩存節(jié)約了帶寬和計(jì)算時(shí)間!

          細(xì)粒度印記(The Fine Print):實(shí)踐中,我們可以通過以更細(xì)粒度的跟蹤對(duì)象變化來獲得更大的功效,例如使用對(duì)象id。然而,這種使修改對(duì)象關(guān)聯(lián)到view上的想法高度依賴應(yīng)用程序的整體數(shù)據(jù)模型設(shè)計(jì)。這里的實(shí)現(xiàn)(ModifiedObjectTracker)是說明性的,有意為更多的探索提供想法。它并不是旨在生產(chǎn)環(huán)境中使用(比如它在簇中使用還不穩(wěn)定)。一個(gè)可選的更深的考慮是使用數(shù)據(jù)庫觸發(fā)器來跟蹤變化,讓攔截器訪問觸發(fā)器所寫入的表。

          結(jié)論

          我們已經(jīng)看了兩種使用ETag減少帶寬和計(jì)算的方法。我希望本文已為你當(dāng)下或?qū)砘赪eb的項(xiàng)目提供了精神食糧,并正確評(píng)價(jià)在底層利用ETag響應(yīng)頭的做法。

          正如牛頓(Isaac Newton)的名言所說:“如果說我看得更遠(yuǎn),那是因?yàn)槲艺驹诰奕说募绨蛏稀?#8221;REST風(fēng)格應(yīng)用的核心是簡(jiǎn)單、好的軟件設(shè)計(jì)、不要重新發(fā)明輪子。我相信隨著使用量和知名度的增長(zhǎng),針對(duì)基于Web應(yīng)用的REST風(fēng)格架構(gòu)有益于主流應(yīng)用開發(fā)的遷移,我期盼著它在我將來的項(xiàng)目中發(fā)揮更大的作用。

          posted on 2010-01-21 18:57 小卓 閱讀(225) 評(píng)論(0)  編輯  收藏 所屬分類: 服務(wù)器架構(gòu)


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 边坝县| 宜兰县| 邓州市| 天峨县| 涪陵区| 乌兰浩特市| 芮城县| 阆中市| 鄯善县| 舒兰市| 长丰县| 宁海县| 砀山县| 浮梁县| 闽侯县| 平阴县| 依安县| 山西省| 泾源县| 娱乐| 疏勒县| 鲜城| 新化县| 民丰县| 蕲春县| 邯郸县| 永善县| 永嘉县| 江华| 东安县| 千阳县| 七台河市| 东乡族自治县| 普兰店市| 阳曲县| 松溪县| 舞阳县| 贡嘎县| 孟村| 灵石县| 集安市|