php網站速度性能優化(轉)

一個網站的訪問打開速度相當重要,特別是首頁的打開加載過慢是致命性的,本文介紹關於php網站性能優化方面的實戰案例:淘寶首頁加載速度優化實踐 。想必不少人都已經看到了新版的淘寶首頁,它與以往不太同樣,這一版頁面中四處彌散着個性化的味道,因爲獨特的個性化需求,前端也面臨各方面的技術挑戰:php

 

  • 數據來源多css

  • 串行請求渲染一個模塊前端

  • 運營數據和個性化數據匹配和管理git

  • 數據兜底容災github

本次淘寶首頁改版,雖已再也不支持 IE6 和 IE7 等低版本的古董瀏覽器,但依然存在多個影響首頁性能的因素:web

  • 依賴系統過多,數據的請求分爲三塊,其一是靜態資源(如 js/css/image/iconfont 等);其二是推到 CDN 的靜態數據(如運營填寫的數據、前端配置信息等);其三是後端接口,不一樣的模塊對應不一樣的業務,並且頁面中還有很多的廣告內容,粗略估計頁面剛加載時首屏發出的接口請求就有 8 個,滾到最底下,得發出 20 多個請求。算法

  • 沒法直接輸出首屏數據,首屏不少數據是經過異步請求獲取的,因爲系統限制,這些請求不可避免,並且請求個數較多,十分影響首屏時間。json

  • 模塊過多,爲了可以在後臺隔離運營之間填寫數據的權限,模塊必須作細粒度的拆分,以下圖所示:
    多模塊的拆分
    一個簡單的模塊必須拆分紅多個行業小模塊,頁面中其餘位置也是如此,並且這些被拆分出來的模塊還不必定會展示出來,須要讓算法告訴前端展現哪些模塊。後端

  • 圖片過多,翻頁往下滾動,很明顯看到,頁面整屏整屏的圖片,有些圖片是運營填寫,有些圖片由個性化接口提供,這些圖片都沒有固定的尺寸。瀏覽器

網頁性能衡量指標

網頁性能衡量指標有不少,假若可以把握關鍵的幾個,集中優化,性能天然也就上去了。

FPS

最能反映頁面性能的一個指標是 FPS(frame per second),通常系統設定屏幕的刷新率爲 60fps,當頁面元素動畫、滾動或者漸變時繪製速率小於 60,就會不流暢,小於 24 就會卡頓,小於 12 基本認定卡爆了。

1 幀的時長約 16ms,除去系統上下文切換開銷,每一幀中只留給咱們 10ms 左右的程序處理時間,若是一段腳本的處理時間超過 10ms,那麼這一幀就能夠被認定爲丟失,若是處理時間超過 26ms,能夠認定連續兩幀丟失,依次類推。咱們不能容忍頁面中屢次出現連續丟失五六幀的狀況,也就是說必須想辦法分拆執行時間超過 80ms 的代碼程序,這個工做並不輕鬆。

頁面在剛開始載入的時候,須要初始化不少程序,也可能有大量耗時的 DOM 操做,因此前 1s 的必要操做會致使幀率很低,咱們能夠忽略。固然,這是對 PC 而言,Mobile 內容少,不管是 DOM 仍是 JS 腳本量都遠小於 PC,1s 可能就有點長了。

DOMContentLoaded 和 Load

DOM 加載而且解析完成纔會觸發 DOMContentLoaded 事件,假若源碼輸出的內容過多,客戶端解析 DOM 的時間也會響應加長,不要小看這裏的解析時間,若是 DOM 數量增長 2000 個而且嵌套層級較深,解析時間也會相應增長 50-200ms,這個消耗對大多數頁面來講實際上是不必的,保證首屏輸出便可,後續的內容只保留鉤子,利用 JS 動態渲染。

Load 時間能夠用來衡量首屏加載中,客戶端接受的信息總量,若是在首屏中充滿了大尺寸圖片或者客戶端與後端創建鏈接次數較多,Load 時間也會相應被拖長。

流暢度

流暢度是對 FPS 的視覺反饋,FPS 值越高,視覺呈現越流暢。爲了保障頁面的加載速度,不少內容不會在頁面打開的時候所有加載到客戶端。這裏提到的流暢度是等待過程當中的視覺緩衝,以下方是 Google Plus 頁面的一個效果圖:

Google Plus Item

牆內訪問 google 的速度不是很快,上面元素中的的不少內容都是經過異步方式加載,而從上圖能夠看出 Google 並無讓用戶產生等待的焦慮感。

淘寶首頁的性能優化

因爲平臺限制,淘寶首頁面臨一個先天的性能缺陷,首屏的渲染須要從 7 個不一樣的後端取數據,這些數據請求是難以合併的,若是用戶屏幕比較大,則首屏的面積也比較大,對應的後端平臺數據接口就更多。數據是個性化內容或者爲廣告內容,故請求也不能緩存。

關鍵模塊優先

不論用戶首屏的面積有多大,保證關鍵模塊優先加載。下面代碼片斷是初始化全部模塊的核心部分:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

$('.J_Module').each(function(mod) {  var $mod = $(mod);  var name = $mod.attr('tms');  var data = $mod.attr('tms-data');  if($mod.hasClass('tb-pass')) {

    Reporter.send({

      msg: "跳過模塊 " + name

    });    return;

  // 保證首屏模塊先加載

  if (/promo|tmall|tanx|notice|member/.test(name)) {

    window.requestNextAnimationFrame(function(){      // 最後一個參數爲 Force, 強制渲染, 不懶加載處理

      new Loader($mod, data, /tanx/.test(name));

    });

  } else {    // 剩下的模塊進入懶加載隊列    lazyQueue.push({

      $mod: $mod,

      data: data,

      force: /fixedtool|decorations|bubble/.test(name)

    });

  }

});

TMS 輸出的模塊都會包含一個 .J_Module 鉤子,而且會預先加載 js 和 css 文件。

對於無 JS 內容的模塊,會預先打上 tb-pass 的標記,初始化的時候跳過此模塊;對於首屏模塊關鍵模塊,會直接進入懶加載監控:

1

2

3

// $box 進入瀏覽器視窗後渲染// new Loader($box, data) ->datalazyload.addCallback($box, function() {

  self.loadModule($box, data);

});// $box 當即渲染// new Loader($box, data, true) ->self.loadModule($box, data);

除必須當即加載的模塊外,關鍵模塊被加到懶加載監控,緣由是,部分用戶進入頁面就可能急速往下拖拽頁面,此時,不必渲染這些首屏模塊。

非關鍵模塊統一送到 lazyQueue 隊列,沒有基於將非關鍵模塊加入到懶加載監控,這裏有兩個緣由:

  • 一旦加入監控,程序滾動就須要對每一個模塊作計算判斷,模塊太多,這裏可能存在性能損失

  • 若是關鍵模塊尚未加載好,非關鍵模塊進入視窗就會開始渲染,這勢必會影響關鍵模塊的渲染

那麼,何時開始加載非關鍵模塊呢?

1

2

3

4

5

6

7

8

9

10

11

12

var lazyLoaded = false;function runLazyQueue() {  if(lazyLoaded) {    return;

  }

  lazyLoaded = true;

  $(window).detach("mousemove scroll mousedown touchstart touchmove keydown resize onload", runLazyQueue);  var module;  while (module = lazyQueue.shift()) {    ~function(m){      // 保證在瀏覽器空閒時間處理 JS 程序, 保證不阻塞

      window.requestNextAnimationFrame(function() {        new Loader(m.$mod, m.data, m.force);

      });

    }(module);

  }

}

$(window).on("mousemove scroll mousedown touchstart touchmove keydown resize onload", runLazyQueue);// 擔憂未觸發 onload 事件, 5s 以後執行懶加載隊列window.requestNextAnimationFrame(function() {

  runLazyQueue();

}, 5E3);

上面的代碼應該十分清晰,兩種請求下會開始將非關鍵模塊加入懶加載監控:

  • 當頁面中觸發 mousemove scroll mousedown touchstart touchmove keydown resize onload 這些事件的時候,說明用戶開始與頁面交互了,程序必須開始加載。

  • 若是用戶沒有交互,可是頁面已經 onload 了,程序固然不能浪費這個絕佳的空檔機會,趁機加載內容;經測試,部分狀況下,onload 事件沒有觸發(緣由尚不知),因此還設定了一個超時加載,5s 以後,不論頁面加載狀況如何,都會將剩下的非關鍵模塊加入到懶加載監控。

懶執行,有交互才執行

若是說上面的優化叫作懶加載,那麼這裏的優化能夠稱之爲懶執行。

首頁上有幾個模塊是包含交互的,如頭條區域的 tab ,便民服務的浮層和主題市場的浮層,部分用戶進入頁面可能根本不會使用這些功能,因此程序上並無對這些模塊作完全的初始化,而是等到用戶 hover 到這個模塊上再執行所有邏輯。

更懶的執行,刷新頁面才執行

首屏中有兩個次要請求,一個是主題市場的 hot 標,將用戶最常逛的三個類目打標;第二個是我的中心的背景,不一樣的城市會展現不一樣的背景圖片,這裏須要請求拿到城市信息。

這兩處的渲染策略都是,在程序的 idle(空閒)時期,或者 window.onload 十秒以後去請求,而後將請求的結果緩存到本地,當用戶第二次訪問淘寶首頁時可以看到效果。這是一種更懶的執行,用戶刷新頁面纔看獲得.這種優化是產品可以接受,也是技術上合理的優化手段。

圖片尺寸的控制和懶加載

不論圖片連接的來源是運營填寫仍是接口輸出,都難以保證圖片具有恰當的寬高,加上現在 retina 的屏幕愈來愈多,對於這種用戶也要提供優質的視覺體驗,圖片這塊的處理並不輕鬆。

<img src='//g.alicdn.com/s.gif' src='//g.alicdn.com/real/path/to/img.png' />

阿里 CDN 是支持對圖片尺寸作壓縮處理的,以下圖爲 200×200 尺寸的圖片:

加上 _100x100.jpg 的參數後,會變成小尺寸:

咱們知道 webp 格式的圖片比對應的 jpg 要小三分之一,如上圖加上 _.webp 參數後:

視覺效果並無什麼折扣,可是圖片體積縮小了三分之一,圖片越大,節省的越明顯。顯然,淘寶首頁的全部圖片都作了如上的限制,針對坑位大小對圖片作壓縮處理,只是這裏須要注意的是,運營填寫的圖片可能已是壓縮過的,如:

1

$img = '//g.alicdn.com/real/path/to/img.png_400x400.jpg';<img src='{{$img}}_100x100jpg_.webp' />

上面這種狀況,圖片是不會正確展現的。首頁對全部的圖片的懶加載都作了統一的函數處理:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

src = src.replace(/\s/g, '');var arr;if (/(_\d{2,}x\d{2,}\w*?\.(?:jpg|png)){2,}/.test(src) && src.indexOf('_!!') == -1) {

  arr = src.split('_');  if (arr[arr.length - 1] == '.webp') {

    src = [arr[0], arr[arr.length - 2], arr[arr.length - 1]].join('_');

  } else {

    src = [arr[0], arr[arr.length - 1]].join('_');

  }

}if (src.indexOf('_!!') > -1) {

  src = src.replace(/((_\d{2,}x\d{2,}[\w\d]*?|_co0)\.(jpg|png))+/, '$1');

}

WebP.isSupport(function(isSupportWebp) {  // https 協議訪問存在問題 IE8,去 schema

  if (/^http:/.test(src)) {

    src = src.slice(5);

  // 支持 webp 格式,而且 host 以 taobaocdn 和 alicdn 結尾,而且不是 s.gif 圖片

  if (isSupportWebp && /(taobaocdn|alicdn)\.com/.test(src) && (src.indexOf('.jpg') ||

    src.indexOf('.png')) && !/webp/.test(src) && !ignoreWebP && !/\/s\.gif$/.test(src)) {

    src += '_.webp';

  }

  $img.attr('src', src);

});

模塊去鉤子,走配置

TMS 的模塊在輸出的時候會將數據的 id 放在鉤子上:

1

<p class='J_Module' tms-datakey='2483'></p>

若是模塊是異步展現的,能夠經過 tms-datakey 找到模塊數據,而首頁的個性化是從幾十上百個模塊中經過算法選出幾個,若是把這些模塊鉤子所有輸出來,雖然說取數據方便了不少,卻存在大量的冗餘,對此的優化策略是:將數據格式相同的模塊單獨拿出來,新建頁面做爲數據頁。因此能夠在源碼中看到好幾段這樣的配置信息:

1

<textarea class="tb-hide">[{"backup":"false","baseid":"1","mid":"222726","name":"iFashion","per":"false","tid":"3","uid":"1000"},{"backup":"false","baseid":"3","mid":"222728","name":"美妝秀","per":"false","tid":"3","uid":"1001"},{"backup":"false","baseid":"4","mid":"222729","name":"愛逛街","per":"false","tid":"4","uid":"1002"},{"backup":"false","baseid":"2","mid":"222727","name":"全球購","per":"false","tid":"4","uid":"1003"}]</textarea>

減小了大量的源碼以及對 DOM 的解析。

低頻修改模塊,緩存請求

有一些模塊數據是不多被修改的,好比接口的兜底數據、阿里 APP 模塊數據等,能夠經過調整參數,設置模塊的緩存時間,如:

1

2

3

4

5

6

7

io({

  url: URL,

  dataType: 'jsonp',

  cache: true,

  jsonpCallback: 'jsonp' + Math.floor(new Date / (1000 * 60)),

  success: function() {    //...  }

});

Math.floor(new Date / (1000 * 60)) 這個數值在一分鐘內是不會發生變化的,也就是說將這個請求在本地緩存一分鐘,對於低頻修改模塊,緩存時間能夠設置爲一天,即:

 

1

Math.floor(new Date / (1000 * 60 * 60 * 24))

固然,咱們也能夠採用本地儲存的方式緩存這個模塊數據:

1

offline.setItem('cache-moduleName', JSON.stringify(data), 1000 * 60 * 60 * 24);

緩存過時時間設置爲 1 天,淘寶首頁主要採用本地緩存的方式。

使用緩動效果減小等待的焦急感

這方面的優化不是不少,可是也有一點效果,不少模塊的展現並非乾巴巴的 .show(),而是經過動畫效果,緩動呈現,這方面的優化推薦使用 CSS3 屬性去控制,性能消耗會少不少。

優化的思考角度

頁面優化的切入點不少,咱們不必定可以面面俱到,可是對於一個承載較大流量的頁面來講,下面幾條必須有效執行:

  • 首屏必定要快

  • 滾屏必定要流暢

  • 能不加載的先別加載

  • 能不執行的先別執行

  • 漸進展示、圓滑展示

固然,性能優化的切入角度不只僅是上幾個方面,對照 Chrome 的 Timeline 柱狀圖和折線圖,咱們還能夠找到不少優化的點,如:

淘寶首頁 Chrome Timeline

  • 在 1.0s 左右存在一次 painting 阻塞,可能由於一次性展現的模塊面積過大

  • 從 FPS 的柱狀圖能夠看出,在 1.5s-2.0s 之間,存在幾回 Render 和 JavaScript 丟幀

  • 從多出的紅點能夠看出頁面 jank 次數,也可以定位到代碼堆棧

在優化的過程當中須要更多地思考,如何讓阻塞的腳本分批執行,如何將長時間執行的腳本均勻地分配到時間線上。這些優化都體如今代碼的細節上,宏觀上的處理難以有明顯的效果。固然,在宏觀上,淘寶首頁也有一個明顯的優化:\

1

2

3

4

5

6

7

8

9

10

11

12

13

// https://gist.github.com/miksago/3035015#file-raf-js(function() {  var lastTime = 0;  var vendors = ['ms', 'moz', 'webkit', 'o'];  for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {

    window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];

    window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];

  if (!window.requestAnimationFrame) {

    window.requestAnimationFrame = function(callback, element) {      var currTime = new Date().getTime();      var timeToCall = Math.max(0, 16 - (currTime - lastTime));      var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);

      lastTime = currTime + timeToCall;      return id;

    };

  if (!window.cancelAnimationFrame) {

    window.cancelAnimationFrame = function(id) {

      clearTimeout(id);

    };

  }

})();

這段代碼基本保證每一個模塊的初始化都是在瀏覽器空閒時期,減小了不少沒必要要的丟幀。這個優化也能夠被應用到每一個模塊的細節代碼之中,不過優化難度會更高。

小結

代碼的性能優化是一個精細活,若是你要在一個龐大的未經優化的頁面上作性能優化,可能會面臨一次重構代碼。本文從php網站性能優化引出的問題出發,依實戰淘寶首頁速度優化提高實戰爲案例,從微觀到宏觀講述了頁面的優化實踐,提出了幾條能夠借鑑的優化標準,但願對你有所啓發。優化的細節點描述的不夠完善也不夠全面,可是都是值得去優化的方向。

相關文章
相關標籤/搜索