首先,我得說,這篇文章有點標題黨了,其實內容並沒有標題看起來那么高大上。其次,本文只是做一個技術方案可能性的探討,並沒有提供完善的解決方案,至多給了一個Demo供參考。
目的
如需轉載,請注明轉自:http://www.cnblogs.com/silenttiger/p/4929841.html
前端性能優化,我覺得最主要的目的就兩個:1、提升頁面加載速度;2、節約服務器資源。
這里特別提一下節約服務器資源,很多人在做前端性能優化的時候,往往只考慮前端性能的問題,而完全忽視前端的性能優化對后端服務器性能的影響。其實,對於一個網絡流量比較大的站點來說,節約服務器資源就是省錢啊。比如,js文件、圖片文件的大小越小,服務器所需的磁盤IO貸款和網絡IO貸款也就越小,自然就可相應省下部分開支了。
現有的方法
如需轉載,請注明轉自:http://www.cnblogs.com/silenttiger/p/4929841.html
前端性能優化,我們目前主流的技術方案主要也就兩個:1、合並;2、壓縮;3、緩存。
舉個例子,一個網站有A,B,C,D四個頁面,分別需要引用a\b, a\b\c, a\b\c\d, a\d這幾個js文件。於是我們考慮到a這個js文件在四個頁面中均有引用,所以不參與合並。然后把b\c兩個js文件合並成x,把b\c\d三個js文件合並為y。現在A,B,C,D四個頁面對js文件的引用規則變成了分別引用a\b, a\x, a\y, a\d這幾個js文件。接下來,我們將a\b\x\y\d這五個js文件分別混淆壓縮。
通過以上一系列的處理,現在用戶通過瀏覽器訪問我們的站點的時候,在A\B\C\D四個頁面都只需要發起兩個對js文件的請求。同時,四個頁面還可以共享對a這個js文件的緩存。
現有的問題
如需轉載,請注明轉自:http://www.cnblogs.com/silenttiger/p/4929841.html
上述的這個性能優化方案,我想很多人一眼就可以看出來,其實還存在很多問題。
1、首次加載頁面時,緩存策略無法發揮作用,拖慢了頁面加載速度。
雖然我們配置了緩存策略,使得用戶訪問過B頁面一次之后再訪問B頁面是可以從瀏覽器緩存中直接加載其依賴的a\x兩個js文件的。但是,如果用戶只訪問過A頁面而沒有訪問過B頁面,此時再訪問B頁面的話,只有a的緩存能夠生效,而x是沒有緩存的。
2、b\x\y\d這四個js文件的內容存在冗余,浪費了服務器資源。
x包含了b\c兩個js文件的內容,但當用戶使用瀏覽器請求了x之后再請求b,任然需要重新下載整個b文件,這里對x的緩存是無法使用在b上面的。
從這兩個問題來看,似乎我們還有進步的空間!
新方法
如需轉載,請注明轉自:http://www.cnblogs.com/silenttiger/p/4929841.html
針對上面的問題,我們一個個來解決。
首先是首次加載頁面時緩存策略無法發揮作用的問題。其實這個問題也是本文的核心,我的解決方案是預加載。也就是說,當用戶還沒有訪問B這個頁面的時候,我們就預先讓用戶的瀏覽器加載B頁面所依賴的x這個js文件。
我設計了一個前端資源預加載系統,包括前端js代碼、后端預加載策略邏輯,還有用於計算加載策略的數據庫。還是用上面的那個例子。假設A頁面是網站的首頁,當用戶訪問A頁面后,前端js將用戶的SessionID和當前頁面的URL發送到后端,並由后端邏輯將這條訪問行為記錄到數據庫的visit_sequence表,然后收集前端資源形成資源列表,包括頁面中引用的link、script等,並計算此資源列表的MD5發往后端進行比對。后端邏輯根據頁面URL在page_resource_signature表中查找相應的MD5值,如果沒有找到,就要求前端js代碼發送整個資源列表以及頁面URL和資源列表的MD5值並記錄到page_resource_signature和page_resource表中;如果根據頁面URL找到記錄但MD5值不匹配,則要求前端js代碼發送整個資源列表以及頁面URL和資源列表的MD5值並並更新page_resource_signature和page_resource兩個表中的數據;如果根據頁面URL找到記錄且MD5值也匹配,則后端程序根據數據庫中visit_sequence表和page_resource表的記錄,計算出用戶在當前頁面下訪問系統中其他頁面資源的可能性,並返回給前端代碼邏輯,接下來,前端代碼預加載后端返回的預加載資源列表中的資源。
前端代碼:
/* desc: performancecollector依賴於jquery及md5.js,用於收集用戶在系統中各個頁面間跳轉的路徑,以及每個頁面所引用的靜態資源列表 */ if(typeof performance !== 'undefined' && typeof performance.timing !== 'undefined'){ $(document).ready(function(){ //統計頁面ready時間,並將用戶的SessionID和當前頁面的URL發送到后端 $.post('http://127.0.0.2/index.php/Home/VisitSequence/Insert/', { SessionID: document.cookie.substr(document.cookie.indexOf('PHPSESSID=') + 10, 26), PageUrl: window.location.href.indexOf('#') < 0 ? window.location.href : window.location.href.substring(0, window.location.href.indexOf('#')), Cost: performance.timing.domContentLoadedEventStart - performance.timing.responseStart }); //收集頁面資源信息 var resources = []; $('link').each(function(){ resources.push($(this).attr('href')); }); $('script[src]').each(function(){ resources.push($(this).attr('src')); }); //計算resource的MD5並發往服務器比對 setTimeout(function(){ var resourceSignature = md5(JSON.stringify(resources)); $.post('http://127.0.0.2/index.php/Home/VisitSequence/CompareSignature/', { Signature: resourceSignature, PageUrl: window.location.href.indexOf('#') < 0 ? window.location.href : window.location.href.substring(0, window.location.href.indexOf('#')) }, function(data){ //如果沒找到此頁面資源的簽名,就安排延時上傳頁面資源簽名及資源列表 if(data.find === 0){ $.post('http://127.0.0.2/index.php/Home/VisitSequence/UpdateResource/', { Signature: resourceSignature, Resources: resources, PageUrl: window.location.href.indexOf('#') < 0 ? window.location.href : window.location.href.substring(0, window.location.href.indexOf('#')) }); }else{ //安排延時預加載資源 loadResource(data.resources); } }); }, Math.random() * 1000 + 2000); //預加載資源 function loadResource(resources){ //在這個方法里面加載resources參數中列出的資源 console.log('loadResource', resources); } }); }
數據庫表結構:
序列圖:
上述這個流程中,最關鍵的步驟就是“計算各個頁面資源被訪問的可能性”這一步了,也就是序列圖中標紅的部分。這個動作可以通過用程序分析用戶以往的瀏覽記錄來實現。比如我們常見的Piwik系統中,就直接提供了每個頁面的上下游關系:
如上圖,我們可以通過Piwik的接口清晰的看到,訪問index.php這個頁面之后,有37%的用戶接下來會訪問xxxx/xx=attendance&menuid=19這個頁面,還有20%的用戶接下來會訪問xxxx/xx=ast&a=index&menuid=30這個頁面。加入這兩個頁面都引用了sharelib.js這個文件,那么用戶訪問index.php這個頁面后,需要訪問sharelib.js這個資源的可能性就高達57%,那么我們是不是就可以讓index.php的前端代碼預先加載sharelib.js這個資源呢?這樣當用戶真的發生頁面跳轉去瀏覽別的頁面的時候,很可能跳轉后的頁面所需的前端資源我們已經預先加載過了,瀏覽器可以直接從緩存中讀取相應的數據,從而實現加快頁面加載速度的效果!
由此,通過詳細記錄用戶瀏覽站點的行為,並分析每個頁面的資源引用情況,我們就可以實在在用戶訪問某個頁面之前就預先判斷出那先資源是值得預先加載的。從而實現資源預加載的效果。
我們再來看第二個問題,也就是資源合並導致的內容冗余問題。
其實,當我們解決第一個問題的時候,第二個問題也就不復存在了。因為我們可以在用戶進入頁面之前就預先加載頁面的資源,所以前端資源的合並也就沒有存在的必要了,也就不存在因資源整合導致的內容冗余問題了。
新問題
如需轉載,請注明轉自:http://www.cnblogs.com/silenttiger/p/4929841.html
這個新的方案雖然解決了我們的一些問題,但也並非完美無缺。
1、團隊協作更復雜
以往的前端性能優化方案中,我們往往只需要一組前端開發人員參與就行了,可是現在的這個方案,由於需要后端提供用戶行為預測的數據,所以很可能需要后端開發的同學也參與進來。如果站點的用戶數據收集是專門的團隊進行的,那么很可能還需要這個專門的團隊參與整個方案的設計和實施。這無疑大大增加了團隊協作的復雜度,對項目管理水平的要求進一步提高。
2、對站點首頁沒有任何效果
基於用戶行為預測的優化方案,只有在用戶進入站點之后才能生效,如果用戶根本就沒有進入站點,我們就什么都做不了了,所以,網站的首頁在這種優化方案中完全得不到任何好處。而網站的首頁往往又是整個站點中受訪量最大的幾個頁面之一,所以這個問題帶來的影響還是比較大的。
3、需要權衡數據實時性和性能
服務端在返回給前端用戶接下來可能需要訪問的資源的時候,是實時地通過數據庫中的數據計算出各個資源被訪問的概率,還是我們通過某種機制事先計算好然后直接讀取返回給前端?如果是實時計算,可能概率的准確性會更高,但是用戶訪問的歷史數據太多的話,這個實時計算是否會消耗過多的系統資源又是個大問題,而如果我們事先計算好這些數據,當站點頁面更新的時候,若這些計算的概率數據沒有更新,則用戶在訪問我們的站點的時候,就無法享受到預加載帶來的好處,而且會因為我們放棄了傳統優化方式而獲得更糟的用戶體驗,那么這些概率數據什么時候才能得到更新又是個問題。
如需轉載,請注明轉自:http://www.cnblogs.com/silenttiger/p/4929841.html
此文完全源於本人的一個腦洞,就是忽然靈光一現,想到了這個性能優化的方案。我在網絡上嘗試搜索相關的關鍵字,但是並沒有找到很好的資料,所以我想,難道這還是我首創的?如果真是,那肯定還有很多沒有考慮到的細節和不足,寫出來供大家參考。如果不是,那還請各位過來人不靈賜教分享你的實踐經驗!
如需轉載,請注明轉自:http://www.cnblogs.com/silenttiger/p/4929841.html
歡迎關注我的微信公眾號:老虎的小窩