PHP 進階之路 - 億級 pv 網站架構實戰之性能壓榨

本博客並不是所有原創,實際上是一個知識的概括和彙總,裏面我引用了不少網上、書上的內容。也給出了相關的連接。javascript

本文涉及的知識點比較多,你們能夠根據關鍵字去搜索相關的內容和購買相應的書籍進行系統的學習。不對的地方你們予以批評指正。php

有人給我留言說,億級 PV 就別寫文章了,隨便用幾個開源軟件就能搞定了,只要不犯什麼大錯。我不覺得然,若是你利用了相同的思想,使用了更高性能的基礎服務,也許就能支持更多的流量併發,節約更多的服務器,優化的思路纔是重點。css

本內容的視頻分享見個人直播html

性能優化的原則

  1. 性能優化是創建在對業務的理解之上的
  2. 性能優化與架構、業務相輔相成、密不可分的

性能優化的引入

咱們先看一張簡單的 web 架構圖前端

億級 pv 網站架構實戰之性能壓榨.002.jpeg

從上到下從用戶的瀏覽器到最後的數據庫,那麼咱們說先前端的優化。java

前端優化

雅虎軍規:http://www.cnblogs.com/paul-3...
億級 pv 網站架構實戰之性能壓榨.004.jpegnode

減小 http 請求數

  1. 圖片、css、script等等這些都會增長http請求數,減小這些元素的數量就能減小響應時間。

把多個JS、CSS在可能的狀況下寫進一個文件,頁面裏直接寫入圖片也是很差的作法,應該寫進CSS裏,小圖拼合後利用 background 來定位。mysql

  1. 如今不少 icon 都是直接作成字體,矢量高清,也減小網絡請求數
  2. 如今的前端框架都會經過組件的方式開發,最後打包生成一個 js 或者 兩個 js 文件 + 一個 css 或者兩個 css 文件。

利用瀏覽器緩存

expires,cache-control,last-modified,etag
http://blog.csdn.net/eroswang...
防止緩存,好比資源更新了,原來的作法是?v=xxxx 如今前端的打包工做能夠能會生成 /v1.2.0/xxx.jslinux

使用分佈式存儲前端資源

接地氣利用 cdn 存儲前端資源nginx

多域名訪問資源

  • 緣由一:瀏覽器對同一域名的並行請求數有上限,多個域名則支持更多並行請求
  • 緣由二:使用同一域名的時候無用的 cookie 簡直是噩夢

億級 pv 網站架構實戰之性能壓榨.012.jpeg

數據壓縮

  1. 開啓gzip
  2. 前端資源自己的壓縮,js/css 打包編譯(去掉空格,語意簡化)圖片資源的壓縮等。

優化首屏展現速度

  1. 資源的按需加載,延時加載 https://mengkang.net/229.html
  2. 圖片的懶加載,淘寶的商品介紹太多圖,用戶點擊進來又有多少人一直往下看圖的呢?

nginx 優化

圖片描述
分爲下面三個部分來

nginx 自己配置的優化

  1. worker_processes auto 設置多少子進程
  2. worker_cpu_affinity 親緣性綁定
  3. worker_rlimit_nofile 65535 worker 進程打開的文件描述符的最大數
  4. worker_connections 65535 子進程最多處理的鏈接數
  5. epoll 多路複用
  6. sendfile on 是對文件I/O的系統調用的一個優化,系統api
  7. 若是是反向代理web服務器,須要配置fastcgi相關的參數
  8. 數據返回開啓gzip壓縮
  9. 靜態資源使用 http 緩存協議
  10. 開啓長鏈接 keepalive_timeout
fastcgi_connect_timeout 300;
        fastcgi_send_timeout 300;
        fastcgi_read_timeout 300;
        fastcgi_buffer_size 64k;
        fastcgi_buffers 4 64k;
        fastcgi_busy_buffers_size 128k;
        fastcgi_temp_file_write_size 256k;
gzip on;
        gzip_min_length  1k;
        gzip_buffers     4 16k;
        gzip_http_version 1.0;
        gzip_comp_level 2;
        gzip_types       text/plain application/x-javascript text/css application/xml text/javascript application/json;
        gzip_vary on;
        gzip_proxied        expired no-cache no-store private auth;
        gzip_disable        "MSIE [1-6]\.";
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
            {
                expires      30d;
            }

http://mailman.nginx.org/pipe...

tcp/ip 網絡協議配置的優化

  1. /proc/sys/net/ipv4/tcp_tw_recycle 1 開啓TCP鏈接中TIME-WAIT sockets的快速回收,保證tcp_timestamps = 1
  2. /proc/sys/net/ipv4/tcp_tw_reuse 1 容許將TIME-WAIT sockets從新用於新的TCP鏈接 https://mengkang.net/564.html
  3. /proc/sys/net/ipv4/tcp_syncookies 0 是否須要關閉洪水抵禦 看本身業務,好比秒殺,確定須要關閉了
  4. /proc/sys/net/ipv4/tcp_max_tw_buckets 180000 不然常常出現 time wait bucket table overflow
  5. tcp_nodelay on 小文件快速返回,我以前經過網絡掛載磁盤出現找不到的狀況
  6. tcp_nopush on
tcp_tw_recycle 快速回收可能致使丟包的問題 https://mengkang.net/1144.html

linux 系統的優化

除了上面的網絡協議配置也是在系統基礎以外,爲了配合nginx本身裏面的設定須要作以下修改

  1. /proc/sys/net/core/somaxconn 65535
  2. ulimit -a 65535

更多詳細的優化配置說明:http://os.51cto.com/art/20140...

php 優化

升級到 php7

注意有不少函數和擴展被廢棄,好比mysql相關的,有風險,作好測試再切換。

opcode 緩存

圖片描述
php 5.5 以後好像就內置了吧,須要在php.ini裏添加以下配置

opcache.revalidate_freq=60
opcache.validate_timestamps=1
opcache.max_accelerated_files=1000
opcache.memory_consumption=512
opcache.interned_strings_buffer=16
opcache.fast_shutdown=1
  1. opcache.revalidate_freq

這個選項用於設置緩存的過時時間(單位是秒),當這個時間達到後,opcache會檢查你的代碼是否改變,若是改變了PHP會從新編譯它,生成新的opcode,而且更新緩存。

  1. opcache.validate_timestamps

當這個選項被啓用(設置爲1),PHP會在opcache.revalidate_freq設置的時間到達後檢測文件的時間戳(timestamp)。

  1. opcache.max_accelerated_files

這個選項用於控制內存中最多能夠緩存多少個PHP文件。

  1. opcache.memory_consumption

你能夠經過調用opcachegetstatus()來獲取opcache使用的內存的總量

  1. opcache.interned_strings_buffer

字符串opcache的複用,單位爲MB

  1. opcache.fast_shutdown=1

開啓快速中止續發事件,依賴於Zend引擎的內存管理模塊

php7 hugepage 的使用

Hugepage 的做用:間接提升虛擬地址和內存地址轉換過程當中查表的TLB緩存命中率

opcache.huge_code_pages=1

鳥哥博客詳細介紹:http://www.laruence.com/2015/...

代碼僞編譯

以thinkphp爲例,它會把框架基礎組件(必須用到的組件)合併壓縮到一個文件中,不只減小了文件目錄查找,文件打開的系統調用。
圖片描述

clipboard.png

經過stracephp-fpm子進程,能夠清楚系統調用的過程,在我上面例子中有打開一個文件有12次系統調用(只是舉例,我這裏相對路徑設置的緣由致使多了兩次文件查找)。若是有10個文件,那就是120次,優化的效果可能不是那麼明顯,可是這是一種思路。
順便說下 set_include_path能不用就不要用,上面的demo的截圖裏面找不到目錄就是證實。

模板編譯

圖片描述
模板把它們自定義的語法,最後轉換成php語法,這樣方便解析。而不是每次都解析一遍。

xhprof 查找性能瓶頸

個人截圖一直上傳不成功,正好社區有這樣的博客,推薦下 https://segmentfault.com/a/11...

業務優化

非侵入式擴展開發

好比原來有一個model,叫問答,如今須要開發一個有獎問答,須要支持話題打賞,裏面多了不少功能。這個時候應該利用面向對象的繼承的特性。而不是作下面的開發

<?php
class AskModel {
    public function detail($id){
        $info = 從數據庫查詢到該問題的信息;
        // 邏輯1
        
        // 邏輯2
        
    }
}
<?php
class AskModel {
    public function detail($id){
        $info = 從數據庫查詢到該問題的信息;
        // 邏輯1
        if($info['type'] == 2){
            //...
        }else{
            
        }
        
        // 邏輯2
        if($info['type'] == 2){
            //...
        }else{
            
        }
    }
}

這樣邏輯多了,子類型多了,邏輯判斷就很是重複,程序運行起來低效多是一方面,更多的是不可維護性。

業務和架構不分家,架構是創建在對業務的理解之上的。再放下上次直播的PPT (sf故障沒法傳圖,等會補吧)

異步思想

舉例:

  1. 處理郵件發送。
  2. gearman 圖片裁剪。
  3. 頁面上 ajax 加載動態數據。
  4. 圖片的懶加載,雙擊圖片看大圖。
  5. sf 上經過websocket 通知你有新的消息,可是並無告訴你有什消息,點擊消息圖標纔會去異步請求具體的消息。

這些都是異步的思想。能分步走就分步走,能不能請求的就不請求。

靜態化

專題頁面,好比秒殺頁面,爲了應對更大的流量、併發。並且更新起來也比較方便。

業務解耦

好比剛剛上面說的專題頁面,還有必要走整個框架的一套流程嗎?進來引用一大堆的文件,初始化一大堆的東西?是否是特別低效呢?因此須要業務解耦,專題頁面若是真要框架(能夠首次訪問以後生成靜態頁面)也應該是足夠輕量級的。不能與傳統業務混爲一談。

分佈式以及 soa

說業務優化,真的不得不提架構方面的東西,業務解耦以後,就有了分佈式和soa,由於這在上次分享中已經都說過了,就很少說了。
只說下 soa 自定義 socket 傳輸協議。
https://segmentfault.com/img/remote/1460000010158196
最重要的就是在自定義頭裏面強調body_len,注意設置爲緊湊型,才能保證跨平臺性 具體說明:https://mengkang.net/586.html

Mysql 優化

數據索引相關的文章網上不少了,不足的地方你們補充。

表設計 - 擁抱 innodb

如今大多數狀況都會使用innodb類型了。具體緣由是 mysql 專家給的意見。
我本身對 mysql 的優化不瞭解,每個細分領域都是一片汪洋,每一個人的時間精力是有限的,因此你們也不用什麼都非要深刻去研究,每每是一些計算機基礎更爲重要。
參考這份ppt https://static.mengkang.net/u...

表設計 - 主鍵索引

  1. innodb 須要一個主鍵,主鍵不要有業務用途,不要修改主鍵。
  2. 主鍵最好保持順序遞增,隨機主鍵會致使聚簇索引樹頻繁分裂,隨機I/O增多,數據離散,性能降低。

舉例:
以前項目裏有些索引是article_id + tag_id 聯合作的主鍵,那麼這種狀況下,就是業務了屬性了。主鍵也不是順序遞增,每插入新的數據都有可能致使很大的索引變更(瞭解下數據庫b+索引的原理)

表設計 - 字段選擇

  1. 能選短整型,不選長整型。好比一篇文章的狀態值,不可能有超過100種吧,不過怎麼擴展,不必用int了。
  2. 能選 char 就避免 varchar,好比圖片資源都有一個hashcode,固定長度20位,那麼就能夠選char了。
  3. 當使用 varchar 的時候,長度夠用就行,不要濫用。
  4. 大文本單獨分離,好比文章的詳情,單獨出一張表。其餘基本信息放在一張表裏,而後關聯起來。
  5. 冗餘字段的使用,好比文章的詳情字段,增長一個文章markdown解析以後的字段。

索引優化

大多數狀況下,索引掃描要比全表掃描更快,性能更好。但也不是絕對的,好比須要查找的數據佔了整個數據表的很大比例,反而使用索引更慢了。

  1. 沒有索引的更新,可能會致使全表數據都被鎖住。因此更新的時候要根據索引來作。
  2. 聯合索引的使用
  3. explain 的使用

聯合索引「最左前綴」,查詢優化器還會幫你調整條件表達式的順序,以匹配組合索引的要求。

CREATE TABLE `test` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `a` int(10) unsigned NOT NULL,
  `b` int(10) unsigned NOT NULL,
  `c` int(10) unsigned NOT NULL,
  PRIMARY KEY (`id`),
  KEY `index_abc` (`a`,`b`,`c`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

能使用到索引

explain select * from test where a=1;
explain select * from test where a=1 and b=2;
explain select * from test where a=1 and b=2 and c=3;
explain select * from test where a=1 and b in (2,3) and c=3;
explain select * from test where a=1 and b=2 order by c desc;

不能使用索引

explain select * from test where a=1 and b in (2,3) order by c desc;
explain select * from test where b=2;
索引更詳細講解 https://mengkang.net/1302.html

explain 搜到一篇不錯的: http://blog.csdn.net/woshiqjs...
很重要的參數type,key,extra

type 最多見的

system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

說明
const 經過索引直接找到一個匹配行,通常主鍵索引的時候
ref 沒有主鍵索引或者惟一索引的條件索引,查詢結果多行,在聯合查詢中很常見
index 利用到了索引,有可能有其它排序,where 或者 group by 等
all 全表掃描,沒有使用到索引

extra

若是有Using filesort或者Using temporary的話,就必需要優化了

收集慢查詢

my.ini 配置裏增長

long_query_time=2
log-slow-queries=/data/var/mysql_slow.log

使用 nosql

redis 豐富的數據類型,很是適合配合mysql 作一些關係型的查詢。好比一個很是複雜的查詢列表能夠將其插入zset 作排序列表,而後具體的信息,經過zset裏面的紙去mysql 裏面去查詢。

緩存優化

多級緩存

  1. 請求內緩存

static 變量存儲,好比朋友圈信息流,在一次性獲取20條信息的時候,有可能,點讚的人裏面20條裏面有30我的是重複的,他們點贊你的a圖片也點讚了你的b圖片,因此這時,若是能使用static數組來存放這些用戶的基本信息就高效了些。

  1. 本地緩存

請求結束了,下拉更新朋友圈,裏面又出現了上面的一樣的好友,還得從新請求一次。因此本地常駐內存的緩存就更高效了。

  1. 分佈式緩存

在A服務器上已經查詢過了,在下拉更新的時候被分配到B服務器上了,難道一樣的數據再查一次再存到B服務器的本地緩存裏面嗎,弄一個分佈式緩存吧,這樣防止了重複查詢。可是多了網絡請求這一步。

不少時候是三者共存的。

避免緩存的濫用

案例分析

  1. 用戶積分更新

    • 好比用戶的基本信息和積分混在一塊兒,當用戶登陸的時候贈送積分。則須要更新用戶的積分,這個時候更新整個用戶的基本信息緩存麼?
    • 因此這裏也能夠運用下面 hashes 分片的原則去更新
  2. 禮物和主題綁定緩存

爲了取數據方便把多個數據源混合緩存了,這種狀況,相比你們可能都見過,這是災難性的設計。

{
id:x,
title:x,
gift:{
        id:x,
        name:x,
        img:x,
    }
}

若是須要更新禮物的圖片,那麼全部用到過這個禮物的話題的緩存都要更新。

redis 使用場景舉例

因爲比較基礎基礎好的老司機就能夠忽略了,新人同窗能夠看下 https://mengkang.net/356.html

redis 優化

  1. 多實例化,更高效地利用服務器 cpu
  2. 內存優化,官方意見 https://redis.io/topics/memor... 有點老
  3. 儘量的使用 hashes ,時間複雜度低,查詢效率高。同時還節約內存。Instagram 最開始用string來存圖片id=>uid的關係數據,用了21g,後來改成水平分割,圖片id 1000 取模,而後將分片的數據存在一個hashse 裏面,這樣最後的內容減小了5g,四分之一基本上。
每一段使用一個Hash結構存儲,因爲Hash結構會在單個Hash元素在不足必定數量時進行壓縮存儲,因此能夠大量節約內存。這一點在String結構裏是不存在的。而這個必定數量是由配置文件中的hash-zipmap-max-entries參數來控制的。

服務器認知的提高

下面的內容,只能是讓你們有一個大概的認識,瞭解一個優化的方向,具體的內容須要系統學習不少不少的知識。

多進程的優點

多進程有利於 CPU 計算和 I/O 操做的重疊利用。一個進程消耗的絕大部分時間都是在磁盤I/O和網絡I/O中。
若是是單進程時cpu大量的時間都在等待I/O,因此咱們須要使用多進程。

減小上下文切換

爲了讓全部的進程輪流使用系統資源,進程調度器在必要的時候掛起正在運行的進程,同時恢復之前掛起的某個進程。這個就是咱們常說的「上下文切換」。

關於上下文我以前寫一個簡單筆記 https://mengkang.net/729.html

無限制增長進程數,則會增多 cpu 在各個進程間切換的次數。
若是咱們但願服務器支持較大的併發數,那麼久要儘可能減小上下文切換的次數,好比在nginx服務上nginx的子進程數不要超過cpu的核數。
咱們能夠在壓測的時候經過vmstat,nmon來監控系統上下文切換的次數。

IOwait 不必定是 I/O 繁忙

# top

top - 09:40:40 up 565 days,  5:47,  2 users,  load average: 0.03, 0.03, 0.00
Tasks: 121 total,   2 running, 119 sleeping,   0 stopped,   0 zombie
Cpu(s):  8.6%us,  0.3%sy,  0.0%ni, 90.7%id,  0.2%wa,  0.0%hi,  0.2%si,  0.0%st

通常狀況下IOwait表明I/O操做的時間佔(I/O操做的時間 + I/O和CPU時間)的比例。
可是也時候也不許,好比nginx來做爲web服務器,當咱們開啓不少nginx子進程,IOwait會很高,當再減小進程數到cpu核數附近時,IOwait會減小,監控網絡流量會發現也增長。

多路複用 I/O 的使用

只要是提供socket服務,就能夠利用多路複用 I/O 模型。
須要補充的知識 https://mengkang.net/726.html

減小系統調用

strace 很是方便統計系統調用

# strace -c -p 23374
Process 23374 attached - interrupt to quit
^CProcess 23374 detached
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 30.68    0.000166           0       648           poll
 12.01    0.000065           0       228           munmap
 11.65    0.000063           0       228           mmap
 10.54    0.000057           0       660           recvfrom
 10.35    0.000056           0       708           fstat
  7.76    0.000042           0       252           open
  6.10    0.000033           1        36           write
  5.73    0.000031           0        72        24 access
  5.18    0.000028           0        72           read
  0.00    0.000000           0       276           close
  0.00    0.000000           0        13        13 stat
  0.00    0.000000           0       269       240 lstat
  0.00    0.000000           0        12           rt_sigaction
  0.00    0.000000           0        12           rt_sigprocmask
  0.00    0.000000           0        12           pwrite
  0.00    0.000000           0        48           setitimer
  0.00    0.000000           0        12           socket
  0.00    0.000000           0        12           connect
  0.00    0.000000           0        12           accept
  0.00    0.000000           0       168           sendto
  0.00    0.000000           0        12           shutdown
  0.00    0.000000           0        48           fcntl
  0.00    0.000000           0        12           flock
  0.00    0.000000           0       156           getcwd
  0.00    0.000000           0        24           chdir
  0.00    0.000000           0        24           times
  0.00    0.000000           0        12           getuid
------ ----------- ----------- --------- --------- ----------------
100.00    0.000541                  4038       277 total

經過strace查看「系統調用時間」和「調用次數」來定位問題 https://huoding.com/2013/10/0...

本身構建web服務器

要想理解web服務器優化的原理,最好的辦法是瞭解它的前因後果,實踐就是最好的方式,我分爲如下幾個步驟:

  1. 用 PHP 來實現一個動態 Web 服務器 https://mengkang.net/491.html
  2. 簡單靜態 web 服務器(循環服務器)的實現 https://mengkang.net/563.html
  3. 多進程併發的面向鏈接 Web 服務器的實踐 https://mengkang.net/571.html
  4. 簡單靜態 Select Web 服務器的實現 https://mengkang.net/568.html
  5. I/O 多路複用 https://mengkang.net/726.html

上面是個人學習筆記,圖片資源丟失了,你們能夠根據相關關鍵詞去搜搜相關的文章和書籍,更推薦你們去看書。

相關文章
相關標籤/搜索