基於大數據的用戶行爲預測在前端性能優化上的應用

首先,我得說,這篇文章有點標題黨了,其實內容並無標題看起來那麼高大上。其次,本文只是作一個技術方案可能性的探討,並無提供完善的解決方案,至多給了一個Demo供參考。javascript

 

目的php

前端性能優化,我以爲最主要的目的就兩個:一、提高頁面加載速度;二、節約服務器資源。前端

這裏特別提一下節約服務器資源,不少人在作前端性能優化的時候,每每只考慮前端性能的問題,而徹底忽視前端的性能優化對後端服務器性能的影響。其實,對於一個網絡流量比較大的站點來講,節約服務器資源就是省錢啊。好比,js文件、圖片文件的大小越小,服務器所需的磁盤IO貸款和網絡IO貸款也就越小,天然就可相應省下部分開支了。java

 

現有的方法jquery

前端性能優化,咱們目前主流的技術方案主要也就兩個:一、合併;二、壓縮;三、緩存。後端

舉個例子,一個網站有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文件的緩存。緩存

 

現有的問題

上述的這個性能優化方案,我想不少人一眼就能夠看出來,其實還存在不少問題。

一、首次加載頁面時,緩存策略沒法發揮做用,拖慢了頁面加載速度。

雖然咱們配置了緩存策略,使得用戶訪問過B頁面一次以後再訪問B頁面是能夠從瀏覽器緩存中直接加載其依賴的a\x兩個js文件的。可是,若是用戶只訪問過A頁面而沒有訪問過B頁面,此時再訪問B頁面的話,只有a的緩存可以生效,而x是沒有緩存的。

二、b\x\y\d這四個js文件的內容存在冗餘,浪費了服務器資源。

x包含了b\c兩個js文件的內容,但當用戶使用瀏覽器請求了x以後再請求b,任然須要從新下載整個b文件,這裏對x的緩存是沒法使用在b上面的。

 

從這兩個問題來看,彷佛咱們還有進步的空間!

 

新方法

針對上面的問題,咱們一個個來解決。

首先是首次加載頁面時緩存策略沒法發揮做用的問題。其實這個問題也是本文的核心,個人解決方案是預加載。也就是說,當用戶尚未訪問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

 

歡迎關注個人微信公衆號:老虎的小窩
微信公衆號 老虎的小窩

相關文章
相關標籤/搜索