1. 問題描述
大家應該注意到了最近社群訪問速度有點慢,一直以為是家裡 wifi 不給力覆蓋範圍不夠,直到 @leo 喊我說伺服器太慢,CPU 爆了:
上 UCloud 後臺看資料比較直觀:
CPU 使用率居高不下,有時候還伴隨著 MySQL 爆掉的情況,如下圖。當這種情況發生時,你就會發現網頁請求卡住不動:
經過一番調查,定位到兩個問題,下面分別講解各自的解決方案。
問題一:話題顯示時實時更新檢視數
每次頁面的檢視,都要觸發一次資料庫 UPDATE 操作,當社群帖子 PV 很高時候,瓶頸就會很明顯。
解決方案
核心思路是儘量少碰到資料庫,利用較快的記錄器來做記錄。這裡選擇使用 Redis 的 Hash 資料型別來儲存。Redis 執行在記憶體中,效率將會非常高。
實現邏輯:
- 按照日期分開儲存
view_count
到 Redis 裡,按日期命名 Hash,如:laravelchina:counter_cache_2018-06-17
; - 欄位名按照命名規則
資料表_欄位名_ID
,如話題表裡 ID 為 1 的view_count
欄位 ——topics_view_count_1
; - 設定計劃任務每天凌晨將昨天的 Hash 同步到 DB 中,並刪除;
問題二:未讀訊息 Ajax 請求太頻繁
登入使用者情況下,每一個瀏覽器開啟的社群頁面,每過 15 秒鐘就會傳送一次未讀訊息的 Ajax 請求,像我查閱資料時,很多時候會同時開啟幾十個頁面:
上圖這種情況每 15 秒鐘就會給伺服器傳送幾十個請求… 看著自己寫的 Bug,無奈感嘆:
解決方案
核心思路是不論瀏覽器開啟了多少視窗,瀏覽器內的所有視窗在單位時間內(15 秒),只能傳送一個請求。怎麼做到呢?利用現代瀏覽器內建的 localStorage 功能可以很容易實現:
- JS 端使用 localStorage 在請求成功後記錄
notification_requested_at
的值為Date.now()
; - 在每一次請求傳送前,拿當前時間
Date.now()
減去notification_requested_at
時間; - 如果大於 15 秒,就傳送請求;
- 否則放棄請求,直接讀取 localStorage 裡的
notification_count
; - 請求成功後將獲取到未讀訊息數存入 localStorage 鍵為
notification_count
; - 每次重新整理頁面,JS 初始化時未讀訊息數存入 localStorage 鍵為
notification_count
。
有同學在問為啥不使用長連結,首先這裡要求的實時性不需要那麼高,其次,我有意保持程式架構的簡單,Keep it simple and stupid ,越簡單越方便維護,夠用就行。
優化結果
看來又可以延遲升級伺服器配置的時間了。效能優化的路漫漫,準備找個時間好好優化下社群程式,順便看看能不能輸出一些優化日記。聽說 PHP 7.2 效能要好個 10%, 現在也趨於穩定,有在考慮升級下 PHP 版本。
最後問大家一個問題:你寫過最愚蠢的 Bug 是啥,分享下哈?
本作品採用《CC 協議》,轉載必須註明作者和本文連結