Web優化躬行記(5)——網站優化

  最近閱讀了很多優秀的網站性能優化的文章,所以自己也想總結一些最近優化的手段和方法。

  個人感覺性能優化的核心是:減少延遲,加速展現。

  本文主要從產品設計、前端、後端和網路四個方面來訴說優化過程。

一、產品設計

  在網上看到一句話:好的性能是設計出來的,而不是優化出來的。

  感覺好有道理,如果將性能瓶頸扼殺在搖籃里,那麼後期的維護成本將變得非常低,並且在避免改造後,都不需要浪費資源了。

1)聊天改版

  最近公司有個聊天活動做改版,改版上線後驚喜的發現,監控數據居然減少了將近20W。

  

  更深一步的探究,才知道在頁面初始化時,會發一個請求,去獲取一些文案,但其實這些文案都是寫死的,完全可以寫到頁面本地。

  如果在產品設計時,就不是通過介面讀取的方式,那麼就可以大大減少瀏覽器和伺服器之間的通訊。

2)數據遷移

  公司有一個審核系統,最近審核人員反應系統使用起來越來越卡。排查後發現,定位到性能瓶頸,是因為數據表巨大導致的,目前已有6千萬條。

  其實在一開始設計時,就該能預估到,未來的數據量,感覺還是偷懶了,想快速完成。感嘆前人挖坑,後人埋坑。

  起初是想分表,但思慮後還是決定不那麼做,最後採用的是MySQL歸檔

  前前後後花了兩周的時間,才將方案落地實現,涉及到運維、QA和我們前端組,人力成本無疑是巨大的。

  其實在每次完成優化後,可以再想想能不能將問題泛化,擴大維度,想想其他地方是否有類似問題,總結經驗供他人學習,以及對未發生的問題進行預判。

3)大批量的504

  這是在上線監控平台後不久發現的,504 的請求量太多,這類通訊錯誤居然佔了25%~30% 左右。

  經過排查定位到一個常規的翻卡活動中,產品設計出現了嚴重的問題。

  在進行一個翻卡操作時會請求兩個介面,並且每個介面各自發送 3 次通訊,這樣會很容易發生 504 錯誤,每天大約有1500個這樣的請求。

  首先是給其中一張表加了個索引,然後是將兩個介面合併成一個,並且每次返回 20 條以上的數據,這樣就不用頻繁的發起請求了。

  經過改造後,每日的 504 請求從 1500 個左右降低到 200 個左右,減少了整整 7.5 倍,效果立竿見影,大部分的 504 集中在 22 點到 0 點之間,這段時間的活躍度比較高。

  還有一個令人意外的發現,那就是監控日誌的量每天也減少了 50W 條。 

二、前端

  前端的優化手段非常多,常規的其實就是延遲請求和減少頁面渲染次數。

1)預請求

  數據預請求是將數據預請求的時機由業務發起請求的時機,提前到用戶點擊時,並行發送數據請求,縮短數據等待時間。

  這個需要客戶端配合,在Webview啟動的時候,並行的去請求首屏需要的數據。

  經過觀察發現每次請求後端數據的API大概要花100~200ms,如果把這段時間省下來,那麼也能減少白屏時間。

  在與客戶端溝通時,他們還是表現的比較抗拒的,在他們看來這點時間微不足道,還會增加他們的工作量。

  有個人還一直強調瀏覽器內置的快取規則是一劑靈藥,絕對可以提升頁面的性能,經過討價還價後敲定最終的預請求方案

2)預載入和懶載入

  預載入和懶載入特指的是圖片的載入,其實就是延遲請求時機。

  兩者的核心演算法就是計算圖片的相對位置,具體參考於此

  這兩招非常有效,可以說是立竿見影,尤其是頁面中圖片量較多以及尺寸較大時,能瞬間提升頁面載入。

  之前做過一次偏動畫的宣傳頁,在進入頁面時,立刻請求所有圖片,直接白屏好幾十秒。

3)串列變並行

  有一張活動頁面,交互並不多,但是要展示的數據需要經過後台比較複雜的計算後才能得到。

  一開始是將所有的數據都包含在一個介面中,這就導致頁面發起請求後,要白屏好幾秒後,才能慢慢載入。

  用戶的等待時間很長,經過排查後發現,其實可以將一些配置參數提取到單獨的一個介面中,核心的數據再抽取到另一個介面中。

  這樣就是將一個串列的介面分離成兩個並行的介面,先快速的讓用戶看到整個頁面的大結構,例如頭圖、規則等資訊。

  然後在將數據渲染到合適的位置,形成了一個時間差,對用戶更加友好。

三、後端

  後端主要涉及是Node.js和資料庫的優化,我們組涉及到的此類性能瓶頸並不多,但也會有一些。

1)記憶體泄露

  自研了監控平台,在剛上線時,就遇到了性能問題,首先是採集的參數在大批量的發送給存儲的伺服器時,伺服器宕機了。

  因為每秒就有幾百甚至幾千的請求發送過來,伺服器需要整理數據後加入到資料庫中,當數據量暴增時,伺服器就會來不及處理。

  這個性能瓶頸發生後,馬上加入任務隊列,伺服器就能平穩的運行了。

  之後又出現了另一個問題,CPU會在不特定時間段(例如21~22、23~02等)飆到70%,記憶體也是一路飆升不會下降,明顯是出現了記憶體泄漏,具體的排查過程可以參考於此

  開通了阿里雲的 Node.js 性能平台,在前前後後調試了三周,有點像探案的過程,才最終定位到一段最為可疑的程式碼。

  原來是為外部的一個對象反覆註冊了一個事件,應該是形成了一個閉包,讓閉包內的變數都無法釋放。

2)資料庫

  仍然是在研發監控平時時遇到的性能瓶頸,這次主要體現在資料庫。

  監控的日誌我都存儲在MySQL中,每日 70W 條記錄,數據量很快達到了千萬級別,在做查詢時,變慢了很多。

  那麼就得讓索引上了,亞洲舞王尼庫拉斯趙四曾說過:一個索引解決不了的,那就來兩個索引。

  我也是這麼做的,加了好多個索引,但維度模糊查詢,這個不是加索引能解決的,嘗試了很久,最終還是決定遷移到ElasticSearch中。

  效果也是立竿見影的,幾千萬條數據,幾秒鐘就出了結果,令我驚嘆不已。

  這裡順便說下,我會在什麼場景中選擇 MongoDB 作為資料庫的。

  MySQL是一種關係型資料庫,會事先聲明表結構,那麼對於數據結構不定的記錄,存儲起來會比較費勁。

  例如記錄中有許多JSON格式的數據,就比較適合存在 MongoDB,當然了,這類數據也可以轉換成字元串存在 MySQL 的一個欄位中。

  只是在讀取和寫入時,要經過序列化和反序列化的操作,並且如果要查詢JSON數據的某一個欄位時,MySQL中就只能模糊查詢了。

  但是平時的話,我還是會盡量將數據存在 MySQL 中,有個很直接的原因是因為線上的 MySQL 有一個功能完善的可視化介面,而 MongoDB 沒有,只有一個我自製的簡陋工具。

3)管理後台掛起

  之前也會偶爾有管理後台服務掛起的情況發生,但最近會在每天的10點左右發生。

  在下圖中,一分鐘內server服務瞬間飆到了 100%,不得不讓我引起注意。  

  

  查找那段時間的日誌,發現有很多送禮的記錄,反向查詢後,定位到一個導出鏈接。

  初步以為是這個功能導致的服務掛起,詢問相關操作人員,得到當時的操作過程。

  在預發環境模擬,得到的結果是可以正常導出,所以應該可以排除它的嫌疑。

  回想到前天也發生了掛起(不過前天的CPU都比較正常),再次翻查日誌,新發現直指審核模組。

  與審核人員溝通後,她們向我一頓吐槽,大倒苦水,說這系統11點後,經常卡頓轉圈。

  這個系統後續有很大的優化空間,那麼目前首要任務是解決服務掛起的問題。

  可以想到的是將審核模組單獨配置一台伺服器,做成獨立服務,這樣也便於觀察。

  服務切換其實就是將特定規則的API指向另一個服務,程式碼和邏輯都不需要改動,非常方面。

  隔天發現仍然會掛起,但是掛起的服務可以定位到審核模組,運維也找到了一條發生504的介面。

  前一天發生的掛起,也是這個介面引起的,順著這條介面查看程式碼,並沒有發現什麼異常。

  打開日誌,發現這個介面的響應的確非常慢,要幾分鐘了,再去查其中涉及到的SQL查詢語句,發現也很耗時。

  將語句拿到資料庫中執行 EXPLAIN 命令,影響的行數要 311W 多條,命中的索引是 idx_create_time。

  而去掉其中的一個日期限制後馬上降到 69 條,命中的是一個聯合索引,回到程式碼中,查看邏輯後,發現這個時間限制是多餘的,馬上就將其去除。

  還有一條SQL語句運行也很慢,影響的行數要 16W 多條,命中的索引是 idx_status,而這個 status 其實只有幾個可選的關鍵字,會影響查詢效率。

  於是再創建一個聯合索引,影響的行數降到 1785 條,優化後,再回饋給審核人員。