Loading...

「為什麼網頁這麼慢!」

相信每個人都有類似經驗:遇到熱門新聞、限量特價商品、演唱會搶票,當短時間內大量流量湧入,網頁的存取常常因此失敗或異常緩慢,使用者抱怨連連。這不是因為程式功能寫錯,而是因大量運算或大流量導致系統表現不佳所產生的問題,屬於非功能性需求,也就是「效能優化」的範疇。隨著接觸的專案規模越大,除了功能實作,效能也是重要的課題。

效能優化牽涉的範疇非常廣泛,除了基本的程式面演算法效率,前後端在系統面也有很多優化議題。前端常見議題如頁面的渲染效率,後端如大量資料運算、資料庫存取、伺服器分流、分散式運算等。

其中,資料庫頻繁存取是後端很常見的效能瓶頸。對於有些不常變動、但查詢頻率很高的資料,例如熱門商品的名稱,如果每一次都要從資料庫重新 query,對資料庫來說不啻是一大負擔。

在後端效能優化中,快取(Cache)是常用的作法。透過快取來優化效能有多種實作技術,本文將介紹其中一種作法——Memory Cache,並透過一個實際專案來示範。

8週後端開發實務,用Node.js開發兼具前後端的Web App

用記憶體幫你快取:Memory Cache

要將資料進行永續化儲存(Persistence),利用資料庫系統將資料存到硬碟裡,是一般系統不可或缺的一環。但當有大量查詢流量湧入時,容易對資料庫造成負擔,甚至導致整體系統的效能拖累。

我們都知道電腦對記憶體的操作要比對硬碟操作快得多,因此就有一種快取方式,將某些會被頻繁查詢的資料暫存在記憶體裡,當有查詢需求時,會優先去記憶體裡找,而不用每次都重新 query 資料庫,提升查詢效能。

Memory Cache 的工具有很多,常見的如 memcached、Redis。其中 memcached 是一套開放原始碼的分散式快取記憶體系統,目前被許多網站服務廣泛使用,包含 Facebook、YouTube、Twitter 等等。memcached 使用 Key-Value 的形式來儲存。

接下來會實際進行一個小專案,具體示範如何利用 memcached 提升系統的查詢效率。


示範專案:短網址系統

短網址是十分普遍的應用,功能也非常單純,很適合作為示範。

系統功能分析

一個基本的短網址系統,主要提供兩個功能:

  1. 讓使用者輸入想縮短的長網址,產生短網址。
  2. 使用者在瀏覽器輸入短網址,能導到原始網址。

實作要點

  • 一個簡單的 hash 演算法,將長網址加上時間戳記,hash 成一個唯一的簡短代碼(如 Qwbrolrv)。
  • 一支 API 負責產生新的短網址,並將 hashCode 和對應的原始網址存入資料庫 (POST /tinyUrl/)。
  • 一支 API 負責根據 hashCode 去資料庫查詢並回傳原始網址資訊(GET /tinyUrl/{hashCode})。
  • 一支 API,根據 hashCode 去資料庫查詢原始網址,並 redirect。
  • 一個主頁面,提供使用者輸入長網址,並顯示短網址結果。

實作成果示意

以下是使用 Node.js + Express + MongoDB 所實作的成果示意——GMTU(Give Me Tiny URL):


使用者輸入原始網址,按下「Let’s Go!」,得到短網址:


在瀏覽器輸入短網址,會自動導到原始網址:



如何提升短網址搜尋的效能

當我們將短網址放在一篇熱門文章內,吸引到大量訪客頻繁存取這個短網址。如果每一位訪客來存取,系統都要從資料庫重新 query,無論在效率和系統負擔上都不盡理想,尤其每個短網址對應的原始網址是不變的,為了同一個結果而在短時間內對資料庫做頻繁檢索,非常浪費資源。

這時候我們就可以利用快取。

當短網址 A 第一次被訪客 1 存取,從資料庫 query 出原始網址 A 的資訊,這時候我們可以將短網址 A 和原始網址 A 當成一對 Key-Value 存入 memcached 裡。

下一次訪客 2 來存取短網址 A 時,就可以直接從 memcached 取出原始網址 A,而不用重新 query 資料庫。

概念示意圖如下:

(圖片來源:Memcached 教程 | 菜鸟教程 )


使用 Node.js 實作 memcached 快取

要在 Node.js 上使用 memcached,有個幾個動作:

  1. 安裝 memcached server
  2. Node.js 安裝 memcached 套件
  3. 改寫既有程式,在 query 資料庫前先查詢 memcached

(還沒學過 Node.js? 來認識八週打通 Node.js 全端開發)

安裝 memcached

我們簡單用 localhost 開發機作為 memcached server,在上面安裝 memcached。在安裝 memcached 前,可能需要安裝幾個 dependencies。

以下是 Mac 上用 homebrew 安裝 memcached 的指令:

$ brew install autoconf automake doxygen libtool pkg-config libevent openssl libevent

$ brew install memcached


安裝完畢後需要啟動 memcached 的 service:

$ brew services start memcached


可以用以下指令檢查 memcached service 是否成功啟動:

$ ps -few | grep memcached


確定啟動後,可以用 telnet 連上 memcached server 進行操作,測試是否正常。預設的 port 是 11211:

$ telnet 127.0.0.1 11211


Node.js 引用 memcached 套件

Node.js 提供操作 memcached 的套件,使用上很容易。

安裝 memcached 的套件:

$ npm install memcached


在程式中引用套件的:

const memcached = require('memcached');


在進行任何讀取或寫入前,需要先連線到 memcached server:

let cache = new memcached("localhost:11211");


memcached 最主要的操作就是 getset


set 時,需要指定 Key 和 Value,以及一個 lifetime,代表這個快取的存活時間。語法範例如下:

<p>CODE: https://gist.github.com/weichih-wen/e82a2a5fa2de89ff0250c73fc71190f2.js</p>


get 時,指定想查詢的 Key,就能回傳對應的 Value。語法範例如下:

<p>CODE: https://gist.github.com/weichih-wen/36a41aa595733d652703f4cb8f27a23c.js</p>

在既有程式增加快取機制

知道怎麼在 Node.js 裡操作 memcached 後,就來將快取機制加入程式裡。

原本查詢短網址的函數,每次都從資料庫查詢:

<p>CODE: https://gist.github.com/weichih-wen/a27e97de59d2f7f620845dd3e8b58fa1.js</p>


加入快取機制後,會先從快取查詢,如果沒有才會從資料庫查詢。從資料庫查詢成功後,也要記得回存快取:

<p>CODE: https://gist.github.com/weichih-wen/f36134662c5f37a5f8aa247a7293c364.js</p>


真的有提升效能嗎?——簡單實驗

前面提到實作要點時,有一支 API 負責根據 hashCode 回傳原始網址資訊(GET /tinyUrl/{hashCode})。實驗方式是另外寫一支小程式,利用 axios 套件去呼叫這支 API。

這邊可以利用 JavaScript 異步的特性輕易模擬多個 client 同時 request。

增加快取機制前

每次從資料庫重新 query,每一個 request 處理時間平均需要花 112 ms。



增加快取機制後

從 memcached 取資料,每一個 request 處理時間平均只需要花 58 ms,提升了大約 48% 的效能。



結語

本文簡單介紹了何謂 Memory Cache、memcached、如何安裝、如何在 Node.js 裡引用,並用一個短網址系統的小專案做具體的快取設計示範,在最後的實驗也成功驗證 Memory Cache 對系統效能有明顯的提升。

本文中使用 memcached 作為示範,在使用上非常容易入門。然而 memcached 有個缺點,一旦 memcached service 發生中斷,重啟後 cache 會消失。這方面可以參考另一套 Memory Cache 工具——Redis,也是一套 Key-Value 的快取工具,具有永續儲存的特性。

在進行快取時,快取存活時間的設計是一個重點,也就是本文範例中的 `lifetime`。由於 Memory Cache 是佔用記憶體,如果把一大堆存取頻率低的資料放在快取裡霸佔記憶體資源,是另一種資源的浪費。在設計時應該根據系統實際需求,評估資料特性來設定適當的存活時間。例如以短網址系統來說,很多短網址經過一段時間後可能沒人存取,這種資料佔用 Memory Cache 太久的意義就不大。

事實上效能優化的議題五花八門,快取只是其中一種方式,在規劃適合的優化方式時需要考量實際的需求場景。隨著開發經驗累積,除了思考如何實作出能動的系統功能,也需要關注如何改善效能,才能開發出撐得住實際市場流量的服務。(也來看看:AC 如何帶你 建立與業界接軌的能力

註:本文中的完整範例程式碼可參考 GitHub

References

成為企業渴求的程式人才!

在家學會 JavaScript 網路開發

全新「全端 Web App 開發」課程,給你看得見的學習成效!
超過 90% 轉職成功,400 位來自亞洲各國的 ALPHA Camp 校友,畢業後達成轉職、創業、出國工作的夢想!

探索「全端 Web App 開發」課程

給期待創新改變的你

前端x後端x全端 完整工程師技能樹

90% 學生轉職成功,職涯競爭力更上層樓
最專業的「全端 Web App 開發」課程,上班族邊工作也能同時培養第二專長!

加入 ALPHA Camp 學程式開發

學期一|程式設計入門

零基礎也學得會的程式入門課!

開始學帶得走的技能,為自己未來的成長鋪路

學期二|JavaScript 完整前端基礎

系統化學習 JavaScript

實作打好前端基礎,成為扎實的網頁開發者

11/19前報名學期一,搶先旁聽「電商專案線上Demo Day」
報名參加