伺服器做了兩個優化 CPU 使用率減低 40%

Summer發表於2018-06-19

1. 問題描述

大家應該注意到了最近社群訪問速度有點慢,一直以為是家裡 wifi 不給力覆蓋範圍不夠,直到 @leo 喊我說伺服器太慢,CPU 爆了:

file

上 UCloud 後臺看資料比較直觀:

file

CPU 使用率居高不下,有時候還伴隨著 MySQL 爆掉的情況,如下圖。當這種情況發生時,你就會發現網頁請求卡住不動:

file

經過一番調查,定位到兩個問題,下面分別講解各自的解決方案。

問題一:話題顯示時實時更新檢視數

file

每次頁面的檢視,都要觸發一次資料庫 UPDATE 操作,當社群帖子 PV 很高時候,瓶頸就會很明顯。

解決方案

核心思路是儘量少碰到資料庫,利用較快的記錄器來做記錄。這裡選擇使用 Redis 的 Hash 資料型別來儲存。Redis 執行在記憶體中,效率將會非常高。

實現邏輯:

  1. 按照日期分開儲存 view_count 到 Redis 裡,按日期命名 Hash,如:laravelchina:counter_cache_2018-06-17;
  2. 欄位名按照命名規則 資料表_欄位名_ID,如話題表裡 ID 為 1 的 view_count 欄位 —— topics_view_count_1
  3. 設定計劃任務每天凌晨將昨天的 Hash 同步到 DB 中,並刪除;

問題二:未讀訊息 Ajax 請求太頻繁

file

登入使用者情況下,每一個瀏覽器開啟的社群頁面,每過 15 秒鐘就會傳送一次未讀訊息的 Ajax 請求,像我查閱資料時,很多時候會同時開啟幾十個頁面:

file

上圖這種情況每 15 秒鐘就會給伺服器傳送幾十個請求… 看著自己寫的 Bug,無奈感嘆:

file

解決方案

核心思路是不論瀏覽器開啟了多少視窗,瀏覽器內的所有視窗在單位時間內(15 秒),只能傳送一個請求。怎麼做到呢?利用現代瀏覽器內建的 localStorage 功能可以很容易實現:

  1. JS 端使用 localStorage 在請求成功後記錄 notification_requested_at 的值為 Date.now()
  2. 在每一次請求傳送前,拿當前時間 Date.now() 減去 notification_requested_at 時間;
  3. 如果大於 15 秒,就傳送請求;
  4. 否則放棄請求,直接讀取 localStorage 裡的 notification_count
  5. 請求成功後將獲取到未讀訊息數存入 localStorage 鍵為 notification_count
  6. 每次重新整理頁面,JS 初始化時未讀訊息數存入 localStorage 鍵為 notification_count

有同學在問為啥不使用長連結,首先這裡要求的實時性不需要那麼高,其次,我有意保持程式架構的簡單,Keep it simple and stupid ,越簡單越方便維護,夠用就行。

優化結果

file

看來又可以延遲升級伺服器配置的時間了。效能優化的路漫漫,準備找個時間好好優化下社群程式,順便看看能不能輸出一些優化日記。聽說 PHP 7.2 效能要好個 10%, 現在也趨於穩定,有在考慮升級下 PHP 版本。

最後問大家一個問題:你寫過最愚蠢的 Bug 是啥,分享下哈?

本作品採用《CC 協議》,轉載必須註明作者和本文連結

擯棄世俗浮躁,追求技術精湛

相關文章