或許是這5件事導(dǎo)致你的web性能低下

我們都知道服務(wù)器的負(fù)載能力的重要性,本文從5個可能影響負(fù)載能力的點上進(jìn)行討論。

首先,有必要了解提高服務(wù)端PHP代碼效率所需的關(guān)鍵操作侠讯。

最重要的是對性能數(shù)據(jù)的收集,如果你想對某個地方進(jìn)行優(yōu)化暑刃,那么你需要測量優(yōu)化前后的數(shù)據(jù)以進(jìn)行對比厢漩。一般來說,程序的響應(yīng)時間以及對內(nèi)存的使用是比較重要的岩臣。對于PHP來說溜嗜,大多數(shù)情況下,頁面的加載時間是影響用戶體驗最大的一個環(huán)節(jié)架谎。當(dāng)然炸宵,還有其他的各種問題同樣對性能有很大的影響,如:網(wǎng)絡(luò)延遲谷扣、I/O等土全。

提示: 對于日志輸出,需要極為謹(jǐn)慎会涎,因為日志系統(tǒng)本身就會對性能有所影響裹匙,如果濫用日志,很可能會成為你的系統(tǒng)中的一塊短板末秃。當(dāng)然概页,也不能完全沒有日志,往往日志是你發(fā)現(xiàn)問題最關(guān)鍵的信息蛔溃。至于如果合理的使用日志绰沥,就得根據(jù)你的業(yè)務(wù)場景來定了。

下面是一個簡單的獲取內(nèi)存使用情況的代碼:

$time = microtime(true);
$mem = memory_get_usage();

// 需要測試的代碼
for ($i = 0; $i < 10000000000; $i ++) {
  $b = $i + $i;
  $c = $b * $i;

  for ($k = 0; $k < 999; $k ++) {

    $d = $k * $i;
    $e = $k * $b * $c;
  }
}


print_r([
  'momory' => (memory_get_usage() - $mem) / (1024 * 1024)
  'seconds' => microtime(true) - $time;
]);

1. 緩存

這個建議可能會出現(xiàn)在所有的性能清單上贺待,這表明了它的重要性徽曲。有很多不錯的工具可以幫你完成緩存的工作,比如:Memcache或者強(qiáng)大的Varnish麸塞。從本質(zhì)上來說秃臣,你必須知道你的程序是否真的需要一遍遍的執(zhí)行。如果你的信息是不變的或者不需要實時的變化,使用緩存可以節(jié)省CPU的執(zhí)行周期奥此,提高程序的速度弧哎。

下面是使用Memcache來緩存數(shù)據(jù)的示例:

function showAndHeavyOperation() {
  sleep(1);
  return date('Y-m-d H:i:s');
}

$item1 = showAndHeavyOperation();
echo $item1;

上面的代碼利用sleep(1)讓程序睡眠1秒鐘來模擬一個慢操作。結(jié)下來就讓我們用緩存來重構(gòu)上面的代碼:

$memcache = new Memcache;
$memcache->connect('localhost', 11211);

function showAndHeavyOperation() {
  sleep(1);
  return data('Y-m-d H:i:s');
}

$item1 = $memcache->get('item');

if ($item1 === false) {
  $item1 = showAndHeavyOperation();
  $memcache->set('item', $item1);
}

echo $item1;

現(xiàn)在腳本在第一次的時候稚虎,showAndHeavyOperation執(zhí)行一次撤嫩。當(dāng)你再次執(zhí)行的時候,就不會再去執(zhí)行showAndHeavyOperation蠢终,而是從緩存中獲取數(shù)據(jù)序攘。但是,你肯定發(fā)現(xiàn)一個問題寻拂,從Memcache中獲取到的數(shù)據(jù)總是老的數(shù)據(jù)程奠,但是Memcache允許你設(shè)置存儲的數(shù)據(jù)的TTL(存活時間)。有了這個功能祭钉,你可以設(shè)置一個刷新策略來緩存數(shù)據(jù)瞄沙,雖然還是無法做到真正的實時數(shù)據(jù),但是為服務(wù)器節(jié)省了大量的資源慌核,特別是在高負(fù)載和高并發(fā)的業(yè)務(wù)下距境,作用尤其明顯。對于變化少或者實時性要求低的數(shù)據(jù)就可將其放入到緩存中來提高程序效率遂铡。更多關(guān)于Memcache的信息肮疗,請參見這里晶姊。

提示:Memcache中的數(shù)據(jù)不是持久化的扒接,當(dāng)你重啟Memcache后,存儲在Memcache中的數(shù)據(jù)將不再存在们衙。所有你的應(yīng)用程序必須能夠在緩存數(shù)據(jù)為空的時候钾怔,重建緩存。換句話說蒙挑,你的應(yīng)用程序不應(yīng)該依賴于數(shù)據(jù)的存在宗侦,特別是在云環(huán)境中。當(dāng)然你可以使用 Redis 來替代Memcache忆蚀。

Memcache為你提供了一個簡單而強(qiáng)大的機(jī)制來創(chuàng)建緩存矾利。如果你還想想創(chuàng)建更加高級的高速緩存,使網(wǎng)站的不同部分擁有不同的TTL馋袜,例如:你可能希望網(wǎng)頁標(biāo)題緩存兩個小時男旗,側(cè)邊欄緩存十分鐘,這種情況下欣鳖,你可以使用Varnish察皇。

Varnish 是緩存和HTTP反向代理的混合。有些人把它稱為 HTTP 加速器。Varnish 非常的靈活什荣,且具有高可定制性矾缓。目前主流的PHP框架,如:Symfony2稻爬,已經(jīng)集成了Varnish嗜闻。

回顧一下,緩存可以幫助我們解決三個問題:

  • 降低對CPU和內(nèi)存的使用
  • 提高頁面的加載時間
  • 利于搜索引擎優(yōu)化(谷歌Analytics認(rèn)為任何網(wǎng)頁加載時間超過1.5秒都屬于慢網(wǎng)頁桅锄,慢網(wǎng)頁對于SEO有著不少的弊端)泞辐。

2. 請密切關(guān)注循環(huán)

我們總是習(xí)慣性的使用循環(huán),它們是個強(qiáng)大的編程工具竞滓,但是往往循環(huán)會造成性能瓶頸咐吼。執(zhí)行一個慢操作本身就是一個問題,但是如果這個慢操作在循環(huán)中執(zhí)行商佑,就會將問題放大锯茄。那么,循環(huán)到底好不好呢茶没?循環(huán)當(dāng)然是個好東西肌幽。就好比菜刀,用于切菜是個很好的東西抓半,但是用來傷人喂急,就不對了。所以需要將循環(huán)利用好笛求,且需要仔細(xì)評估你的循環(huán)廊移,特別是在嵌套循環(huán)中,防止出現(xiàn)問題探入。

以下面的代碼為例:

// 錯誤使用循環(huán)的例子
function expexiveOperation() {
  sleep(1);
  return "Hello";
}

for ($i = 0; $i < 100; $i ++) {
  $value = expexiveOperation();
  echo $value;
}

上面代碼的問題很明顯狡孔,每循環(huán)一次都設(shè)置相同的變量,做了很多的無用功蜂嗽,下面我們優(yōu)化下上面的代碼:

// 正確的使用案例
function expexiveOperation() {
  sleep(1);
  return "Hello";
}

$value = expexiveOperation();

for ($i = 0; $i < 100; $i ++) {
  echo $value;
}

這段代碼輸出的內(nèi)容和上一段代碼完全一致苗膝,但是這里就不需要每次循環(huán)都去調(diào)用慢操作方法,很大程度上的提高了代碼的執(zhí)行效率植旧。

但是辱揭,上面給的案例很簡單,所以你能給很容易的定位到問題的所在病附。在現(xiàn)實的開發(fā)中问窃,往往沒有這么簡單。為了檢測性能問題胖喳,你需要考慮如下幾點:

  • 檢測大循環(huán)(for, foreach, ...)
  • 它們是否會大量的遍歷數(shù)據(jù)
  • 對他們的執(zhí)行速度進(jìn)行測量
  • 是否能夠利用緩存
    • 如果是的話泡躯,那你還在等啥?
    • 如果不能,將它們標(biāo)記為可能存在危險较剃,并專注于它們的檢查咕别。因為它們可能會無限放大你的小問題。

基本上写穴,你必須清楚的知道惰拱,你寫這個循環(huán)是為什么。你很難記住程序的所有代碼啊送,但是你必須意識到偿短,循環(huán)往往需要昂貴的性能。有時候我需要對以前的代碼進(jìn)行重構(gòu)和優(yōu)化馋没,我往往是先用剖析器查找循環(huán)并重構(gòu)可優(yōu)化的昔逗。

我們可以使用性能分析工具幫助我們完成這個工作。Xdebug 和 Zend Debugger 允許我們創(chuàng)建概要分析報告篷朵。如果我們選擇Xdebug勾怒,我們可以使用Webgrind,它可以幫助我們檢查瓶頸声旺。請記住笔链,一個瓶頸是一個問題,而一個瓶頸迭代10000次是將問題放大10000倍腮猖。

3. 使用隊列

我們真的需要執(zhí)行用戶請求中的所有任務(wù)嗎鉴扫?有時候是必要的,但并非總是如此澈缺。想象一下坪创,例如,你需要在用戶提交一個操作時發(fā)送一個電子郵件給用戶谍椅,你可以使用簡單的php腳本發(fā)送此郵件误堡,但這個操作可能需要一秒鐘古话。如果你等到腳本執(zhí)行完最后一句雏吭,你可以確保郵件已經(jīng)發(fā)送成功。但是我們真的有必要等待這一秒鐘呢陪踩?你可以使用一個隊列杖们,將操作放到隊列中,而不需要在此等待一秒肩狂。郵件稍后將被發(fā)送摘完,用戶不需要等到發(fā)送成功。

Gearman是一個框架傻谁,允許你創(chuàng)建隊列和并行處理任務(wù)孝治,你可以閱讀Gearman文檔來獲得更多關(guān)于Gearman的信息。Gearman的主要思想很簡單,你可以定義主角本調(diào)用Worker谈飒,而不是在主腳本中執(zhí)行操作岂座。

下面是一個Gearman的簡單案例:

$filename = '/path/to/img.jpg';
if (realpath(__FILE__) == realpath($filename)) {
  exit();
}

$stringSize = 3;
$footerSize = ($stringSize == 1) ? 12 : 15;
$footer = date('d/m/Y H:i:s');

list($width, $heigth, $image_type) = getimagesize($filename);
$im = imagecreatefromjpeg($filename);
imagefilledrectangle(
  $im,
  0,
  $height,
  $width,
  $height - $footerSize,
  imagecolorallocate($im, 49, 49, 156)
);

imagestring(
  $im,
  $stringSize,
  $width - (imagefontwidth($stringSize) * strlen($footer)) -2,
  $height - $footerSize,
  $footer,
  imagecolorallocate($im, 255, 255, 255);
);

header('Content-Type: image/jpeg');

下面代碼將上面的操作重寫為為Gearman的Worker

$gmw = new GreamanWorker();
$gmw->addServer();
$gmw->addFunction('watermark', function ($job) {
  $workload = $job->workload();
  $workload_size = $job->workloadSize();
  
  list($filename, $footer) = json_encode($workload);
  
  $footerSize = 15;
  list($width, $height, $image_type) = getimagesize($filename);
  
  $im = imagecreateformjpeg($filename);
  
  imagefilledrectangle(
    $im,
    0,
    $height,
    $width,
    $height - $footerSize, 
    imagecolorallocate($im, 49, 49, 156)
  );
  
  imagestring(
    $stringSize,
    $width - (imagefontwidth($stringSize) * strlen($footer)) - 2,
    $height->$footerSize,
    $footer,
    imagecolorallocate($im, 255, 255, 255)
  );
  
  ob_start();
  ob_implicit_flush(0);
  imagepng($im);
  $image = ob_get_content();
  ob_end_clean();
  
  return $img;
});

while(1) {
  $gmw->work();
}

在客戶端腳本上調(diào)用:

$filename = '/path/to/img.jpg';
$footer = date('d/m/Y H:i:s');

$gmclient = new GearmanClient();
$gmclient->addServer();

$handle = $gmclient->do('watermark',json_encode([$filename, $footer]));

if ($gmclient->requestOpc() != GEARMAN_SUCCESS){
  echo "Ups someting wrong happen";
} else {
  headr('Content-Type: image/jpeg');
  echo $handle;
}

關(guān)于Gearman最酷的事情,就是可以平行的增加Worker杭措,而不需要對客戶端代碼進(jìn)行修改费什。這樣當(dāng)用戶量上升后,你只需要多布置幾個Gearman節(jié)點就好了手素。很簡單吧

可能使用Gearman的一些場景:

  • 海量郵件系統(tǒng)
  • 生成PDF
  • 圖像處理
  • Logs
  • ...

Gearman在web應(yīng)用程序中廣泛被使用鸳址,例如Grooveshark和Instagram就使用了Gearman。Instagram大概有200多個使用Python編寫的Worker泉懦。也就是說稿黍,它是語言無關(guān)的。你可以用任何語言來編寫崩哩。

其他隊列系統(tǒng)還有ZeroMQ闻察、RabbitMQ等。

4. 謹(jǐn)慎的訪問數(shù)據(jù)庫

一般在海量數(shù)據(jù)的時候琢锋,數(shù)據(jù)庫往往都是一個大的性能問題來源辕漂。數(shù)據(jù)庫的連接是昂貴的操作,特別是對于PHP這種缺少連接池的語言來說吴超。

此外钉嘹,一個簡單的查詢是否使用索引的差異也是令人難以置信的。強(qiáng)烈建議你檢查數(shù)據(jù)庫索引鲸阻,因為使用錯誤的索引的SQL查詢會大幅的降低程序的性能跋涣。

對索引的檢查不能只檢查一次,因為隨著數(shù)據(jù)的增長鸟悴,索引可能會有所改變陈辱。

另外一個建議是,使用預(yù)處理語句细诸,為什么沛贪?讓我們從例子中看看吧:

$dbh = new PDO('pgsql:dbname=pg1;host=localhost', 'user', 'password');
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$field1 = uniqid();
$dbh->beginTransaction();
foreach (range(1, 5000, 1) as $i) {
  $stmt = $dbh->prepare("UPDATE test.tbl1 set field1 = '{$field1}' where id = 1");
  $field1 = $i;
  $stmt->execute();
}
$dbh->commit();

另外一個:

$dbh = new PDO('pgsql:dbname=pg1;host=localhost', 'user', 'password');
$dbh->setAttribute(PDO_ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$field1 = uniqid();
$dbh->beginTransaction();
$stmt = $dbh->prepare('UPDATE test.tbl1 set field1 = :F1 where id = 1');
foreach (range(1, 5000, 1) as $i) {
  $field1 = $i;
  $stmt->execute(array('F1' => $field1));
}
$dbh->commit();

第一個例子在循環(huán)中,使用了一個新的SQL語句震贵,并執(zhí)行5000次利赋, 數(shù)據(jù)庫需要解析每條SQL并執(zhí)行它。第二個例子中猩系,使用預(yù)處理語句媚送,只是在循環(huán)中,綁定了5000次不同的參數(shù)而已寇甸,而不需要把SQL解析5000次塘偎。而且疗涉,使用預(yù)處理語句,可以有效的防范SQL注入吟秩。

5. 大流量

如果你的應(yīng)用瞬間增加了數(shù)以萬計的并發(fā)甘改,會發(fā)生什么侨颈?你的服務(wù)器能夠處理好這些并發(fā)嗎?這個問題并不容易回答。所以在開發(fā)過程中芽腾,就應(yīng)該模擬大并發(fā)對程序進(jìn)行壓力測試究孕。

類似的測試用具有不少逊抡,我平時用的是Apache AB來對應(yīng)用進(jìn)行性能測試火窒。

Apache AB的使用非常簡單熏矿,我們看看它的基本操作:

ab -n 100 -c http://www.baidu.com/

執(zhí)行上面的命令會直接在終端中打印出結(jié)果,當(dāng)然褪储,你也可以結(jié)果輸出到文件中:

ab -n 100 -c 10 -e test.csv http://www.baidu.com

總結(jié)

如果你想要提高你的WEB性能鲤竹,你需要回答下面這些問題:

  • 我的應(yīng)用程序有多少個數(shù)據(jù)庫連接辛藻?
  • 每個select語句花費(fèi)多少時間互订?
  • 應(yīng)用程序有多少個select語句仰禽?
  • 它們是在循環(huán)內(nèi)嗎坟瓢?
  • 是否真的需要每次都執(zhí)行它們,是否可以將它們放入緩存中?
  • 是否真的有必要在主線程中執(zhí)行用戶的所有請求识颊?
  • 可以將它們放入隊列中嗎奕坟?
  • 我的服務(wù)器是否支持大負(fù)載和大并發(fā)月杉?
  • 每個請求使用多少CPU抠艾?
  • 每個請求使用多少內(nèi)存?

正如你所看到的腌歉,有很多你必須回答的問題齐苛,獲取你開始閱讀這篇文章尋找完美的解決方案凹蜂。但是很抱歉玛痊,沒有什么靈丹妙藥。你必須根據(jù)你的情況來回答上面的問題吟吝,并作出相應(yīng)的調(diào)整剑逃。

還有最后一點蛹磺,對前端的優(yōu)化也不可忽視萤捆,畢竟每個請求俗批,不是只有后端消耗了時間。響應(yīng)時間 = 后端 + 前端岁忘。


Collin
http://ghost.icosplay.cc/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辛慰,一起剝皮案震驚了整個濱河市干像,隨后出現(xiàn)的幾起案子帅腌,更是在濱河造成了極大的恐慌,老刑警劉巖速客,帶你破解...
    沈念sama閱讀 223,002評論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件戚篙,死亡現(xiàn)場離奇詭異溺职,居然都是意外死亡岔擂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評論 3 400
  • 文/潘曉璐 我一進(jìn)店門浪耘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人点待,你說我怎么就攤上這事阔蛉∽丛” “怎么了?”我有些...
    開封第一講書人閱讀 169,787評論 0 365
  • 文/不壞的土叔 我叫張陵毕莱,是天一觀的道長颅夺。 經(jīng)常有香客問我吧黄,道長部服,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,237評論 1 300
  • 正文 為了忘掉前任拗慨,我火速辦了婚禮廓八,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘赵抢。我一直安慰自己剧蹂,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,237評論 6 398
  • 文/花漫 我一把揭開白布烦却。 她就那樣靜靜地躺著宠叼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪短绸。 梳的紋絲不亂的頭發(fā)上车吹,一...
    開封第一講書人閱讀 52,821評論 1 314
  • 那天筹裕,我揣著相機(jī)與錄音醋闭,去河邊找鬼窄驹。 笑死,一個胖子當(dāng)著我的面吹牛证逻,可吹牛的內(nèi)容都是我干的乐埠。 我是一名探鬼主播,決...
    沈念sama閱讀 41,236評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼囚企,長吁一口氣:“原來是場噩夢啊……” “哼丈咐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起龙宏,我...
    開封第一講書人閱讀 40,196評論 0 277
  • 序言:老撾萬榮一對情侶失蹤棵逊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后银酗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辆影,經(jīng)...
    沈念sama閱讀 46,716評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,794評論 3 343
  • 正文 我和宋清朗相戀三年黍特,在試婚紗的時候發(fā)現(xiàn)自己被綠了蛙讥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,928評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡灭衷,死狀恐怖次慢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情翔曲,我是刑警寧澤迫像,帶...
    沈念sama閱讀 36,583評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站瞳遍,受9級特大地震影響闻妓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜傅蹂,卻給世界環(huán)境...
    茶點故事閱讀 42,264評論 3 336
  • 文/蒙蒙 一纷闺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧份蝴,春花似錦犁功、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,755評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至案糙,卻和暖如春限嫌,著一層夾襖步出監(jiān)牢的瞬間靴庆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,869評論 1 274
  • 我被黑心中介騙來泰國打工怒医, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留炉抒,地道東北人。 一個月前我還...
    沈念sama閱讀 49,378評論 3 379
  • 正文 我出身青樓稚叹,卻偏偏與公主長得像焰薄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子扒袖,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,937評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 1塞茅、memcache的概念? Memcache是一個高性能的分布式的內(nèi)存對象緩存系統(tǒng)季率,通過在內(nèi)存里維護(hù)一個統(tǒng)一的巨...
    桖辶殤閱讀 2,243評論 2 12
  • 一野瘦、MemCache簡介 session MemCache是一個自由、源碼開放飒泻、高性能鞭光、分布式的分布式內(nèi)存對象緩存...
    李偉銘MIng閱讀 3,827評論 2 13
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,348評論 25 707
  • 這里要變動了 違章拆建 地鐵通車 周圍的花花草草 都將被鏟除 幾近失業(yè)的我 站在十字街頭 何去何從 每個路口都有一...
    yhbmoren閱讀 193評論 0 0
  • 誤導(dǎo)孩子一生的20個壞習(xí)慣, 真后悔沒早看到蠢络! 榜樣的力量是無窮的衰猛,當(dāng)孩子還不理解真正意義上的對和錯的時候,他只能...
    碧海清天閱讀 154評論 0 0