http://www.blogjava.net/yongboy/archive/2011/02/22/346196.html
Servlet 3.0筆記之異步請求Facebook BigPipe簡單模型實現
BigPipe是一個重新設計的基礎動態網頁服務體系。大體思路是,分解網頁成叫做Pagelets的小塊,然後通過Web服務器和瀏覽器建立管道並管理他們在不同階段的運行。這是類似於大多數現代微處理器的流水線執行過程:多重指令管線通過不同的處理器執行單元,以達到性能的最佳。雖然BigPipe是對現有的服務網絡基礎過程的重新設計,但它卻不需要改變現有的網絡瀏覽器或服務器,它完全使用PHP和JavaScript來實現。
1. 請求解析:Web服務器解析和完整性檢查的HTTP請求。
2. 數據獲取:Web服務器從存儲層獲取數據。
3. 標記生成:Web服務器生成的響應的HTML標記。
4. 網絡傳輸:響應從Web服務器傳送到瀏覽器。
5. CSS的下載:瀏覽器下載網頁的CSS的要求。
6. DOM樹結構和CSS樣式:瀏覽器構造的DOM文檔樹,然後應用它的CSS規則。
7. JavaScript中下載:瀏覽器下載網頁中JavaScript引用的資源。
8. JavaScript執行:瀏覽器的網頁執行JavaScript代碼。
這種高度並行系統的最終結果是,多個Pagelets的不同執行階段同時進行。例如,瀏覽器可以正在下載三個Pagelets CSS的資源,同時已經顯示另一Pagelet內容,與此同時,服務器也在生成新的Pagelet。從用戶的角度來看,頁面是逐步呈現的。最開始的網頁內容會更快的顯示,這大大減少了用戶的對頁面延時的感知。如果您要自己親眼看到區別,你可以嘗試以下連接: 傳統模式和BigPipe。第一個鏈接是傳統模式單一模式顯示頁面。第二個鏈接是BigPipe管道模式的頁面。如果您的瀏覽器版本比較老,網速也很慢,瀏覽器緩存不佳,哪麼兩頁之間的加截時間差別將更加明顯。
值得一提的是BigPipe是從微處理器的流水線中得到啓發。然而,他們的流水線過程之間存在一些差異。例如,雖然大多數階段BigPipe只能操作一次Pagelet,但有時多個Pagelets的CSS和JavaScript下載卻可以同時運作,這類似於超標量微處理器。BigPipe另一個重要區別是,我們實現了從並行編程引入的“障礙”概念,所有的Pagelets要完成一個特定階段,如多個Pagelet顯示區,它們都可以進行進一步JavaScript下載和執行。
腳本阻滯:
我們知道直接在html中引入外部腳本會造成頁面阻滯,即使使用無阻腳本下載的一系列方法引入外部js,但因爲JS單線程,當這些腳本load進來之後運行時也會發生阻滯.因爲Facebook頁面多樣,依賴腳本大小不一,這些阻滯會對頁面的快速展現造成影響.
Facebook做法是在ondomready之前只在頭部輸出一個很小的外部腳本,作爲bigpipe的支撐.其餘所有模塊如果依賴外部腳本(比如某一模塊需要日曆控件支持),都會在domready事件之後加載.這樣做即保證了所有頁面所有模塊都能在domready前快速形成展現,又可以保證無腳本阻滯的快速的domready時間.
最快可交互時間:
domready再快也至少是在頁面第一個使用bigpipe輸出的chunked的http請求完成之後,對於Facebook來說,這很可能是2秒之後了.那在這2s期間,如果只是頁面展現了而不能交互(點擊日曆無反應),那方案依然不夠完美.
Facebook的做法是,在domready後所依賴腳本已被加載之前,點擊行爲將會生成一個額外的腳本請求,只將這個點擊所依賴的腳步預先load進來.這樣保證了最快的可交互時間.Facebook在另一篇文章中對這個技術進行了詳細的描述.
Bigpipe原理簡單,實現不復雜,但Facebook卻用了1年的時間才形成完備的解決方案.生成方案需要時間,而解決隨之而來的腳本阻滯,保障最快交互時間等等問題也會消耗大量時間.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
/** * 模擬Facebook BigPipe異步展現頁面 * @author yongboy * @date 2011-2-21 * @version 1.0 */ @WebServlet(urlPatterns = "/bigPipeDemoServlet", asyncSupported = true) public class BigPipeDemoServlet extends HttpServlet { private static final long serialVersionUID = 14526556595656565L; private static final Log log = LogFactory.getLog(BigPipeDemoServlet.class); private static final Executor executor; static { executor = Executors.newFixedThreadPool(500, new ThreadFactory() { public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName("BigPipe Thread " + thread.getId()); thread.setPriority(Thread.MAX_PRIORITY); return thread; } }); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setHeader("Connection", "Keep-Alive"); response.setContentType("text/html;charset=UTF-8"); response.setCharacterEncoding("UTF-8"); PrintWriter out = response.getWriter(); out.println(docType); out.println(headPart); out.println(bodyPart); out.flush(); // 這裏模擬把頁面組件部分計算工作交由異步線程完成 executor.execute(new AsyncRunnable(request.startAsync(request, response))); } /** * 定義工作線程用於處理異步的內容輸出 * @author yongboy * @date 2011-2-21 * @version 1.0 */ private static class AsyncRunnable implements Runnable { private final AsyncContext asyncContext; private int times = 1; public AsyncRunnable(final AsyncContext asyncContext) { this.asyncContext = asyncContext; } public void run() { try { // 設置超時爲20s,系統默認超時時間爲10s asyncContext.setTimeout(20L * 1000L); PrintWriter out = asyncContext.getResponse().getWriter(); // 模擬按照頁面組件重要程度按照順序計算 bussizeMethod(out, "header", "這裏是主頁LOGO區域"); bussizeMethod(out, "left", genStrings("這裏是內容文字",160, false )); bussizeMethod(out, "right", genStrings("肯德基在百事可樂杯子添加其他品牌可樂",41, true)); bussizeMethod(out, "leftLeft", genStrings("肯德基在百事可樂杯子添加其他品牌可樂", 10, true)); bussizeMethod(out, "leftRight", genStrings("肯德基在百事可樂杯子添加其他品牌可樂", 10, true)); // 補齊頁面標籤 outFinish(out); } catch (IOException e) { e.printStackTrace(); } // 結束當前異步請求 try{ asyncContext.complete(); }catch (Exception e) { // 可能因爲出現超時(大於默認的超時時間10s)異常 e.printStackTrace(); } } private void bussizeMethod(PrintWriter writer, String id, String content){ //模擬耗時 try { Thread.sleep(1000 * (times++)); } catch (InterruptedException e) { e.printStackTrace(); } pagelet(writer, id, content); } private void pagelet(PrintWriter writer, String id, String content) { if (writer.checkError()) return; writer.write("<script>" + "show(\"" + id + "\", \"" + content + "\");" + "</script>\n"); writer.flush(); } private void outFinish(PrintWriter writer){ if (writer.checkError()) return; writer.println("</body>"); writer.println("</html>"); writer.flush(); writer.close(); } private String genStrings(String ori, int num, boolean line){ if(num < 1)return ori; StringBuilder sb = new StringBuilder(); for(int i=0; i < num; i++){ sb.append(ori); if(line){ sb.append("<br/>"); } } return sb.toString(); } } private static final String docType = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"; private static final String headPart = "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n" + "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n" + "<title>Bigpipe Demo</title>\n" + "<link href=\"css/grid.css\" type=\"text/css\" rel=\"stylesheet\" media=\"screen\"/>\n" + "<script type=\"text/javascript\">function show(id,text) { var b=document.getElementById(id); b.innerHTML = text; }</script>\n" + "</head>"; private static final String bodyPart = "<body>\n" + "<div class='row'>\n" + "<div class='column grid_12'><div class='container' id='header'>loading ...</div></div>\n" + "</div>\n" + "<div class='row'>\n" + " <div class='column grid_8'><div class='container' id='left'>loading ...</div>\n" + " <div class='row'>\n" + " <div class='column grid_4'><div class='container' id='leftLeft'>loading ...</div></div>\n" + " <div class='column grid_4'><div class='container' id='leftRight'>loading ...</div></div>\n" + " </div>\n" + " </div>\n" + " <div class='column grid_4'><div class='container' id='right'>loading ...</div></div>\n" + "</div>"; }
|