High Scaling Websites Structure Learning Notes 大型網站架構學習筆記

大型網站架構概念

當一個網站需要服務很多很多很多人的時候,我們就需要好的存取資料的架構,讓使用者能夠很快的拿到他想要拿的資料,這樣伺服器(Web & Database)才可以有更多「時間」及「空間」去服務更多的人。

你可能會聽到有一些在提高伺服器存取效率的關鍵字,像是「資料快取」、「資料庫索引」、「資料庫讀寫分離」、「資料分散式處理」...等等之類的方式。

提高效能存取的概念

不常異動的資料做快取

後端資料

以部落格文章為例,作者發表一篇文章後,除了文章內有一些小小的錯誤或錯字需要編輯修改,否則大部份的時間文章都是不會去異動的。

在動態去資料庫存取本篇文章時,我們可能會使用這樣的 SQL 語法去存取:

SELECT * FROM posts WHERE post_id = '部落格文章編號' Limit 1;

當每個使用者要來看這篇文章時,都需要透過這樣的 SQL 指令去撈取這篇文章的資訊(標題、內文...blabla),如果文章讀者不多時,偶爾這樣取資料庫撈資料還撐得過去,但像是比較熱門被轉載的文章,每分鐘可能有好幾百幾千人要去看這篇文章的資料,對於資料庫就是執行 1000 次這樣相同的語法去取得相同的資料(要記得,資料庫的存取是很昂貴的)。

為了減少資料庫存取次數,所以在程式方面我們會將這些不長異動的資料在第一次從資料庫撈取之後,把它快取下來,可能用 MemcachedRedisFile 的方式將資料預先存下來,然後再設定資料的過期時間,當超過過期時間後,再重複去資料庫撈取資料。

這樣可以減少資料庫的運算資源,使用者也可以很快地拿到他們想要看的資料,提高了伺服器對於資料的存取量。

前端資料

我們在網站上常常需要用 JavaScript 去完成我們要的 UI 操作效果,但是這些 JavaScript 除非操作方式有做大幅的異動,否則我們很少會修改這些 JavaScript 檔案(在 CSS 檔案部分也相同)。

除非有做特別的設定,再不重新整理畫面的前提下,瀏覽器都會幫我們的 JavaScript 及 CSS 檔案去做快取,載入過一次後就存放在使用者自己的電腦上,等到下次我們需要使用相同的 JavaScript 或 CSS 檔案時,瀏覽器會直接取用使用者電腦本地端的檔案,而不會去遠端伺服器再次拿取 JavaScript 或 CSS 檔案。

我們已可以利用瀏覽器的這個特性,等到 JavaScript 或 CSS 檔案做變更時,再透過版本號不同的檔名去讓瀏覽器讀取最新的 JavaScript 或 CSS 檔案:

版本號

<script src="kejyun.app.js?v=1.0.0"></script>
<script src="kejyun.app.js?v=1.0.1"></script>

<link rel="stylesheet" href="kejyun.app.css?v=1.0.0">
<link rel="stylesheet" href="kejyun.app.css?v=1.0.1">

不同檔名

這個部分通常是用後端程式去控制 JavaScript 及 CSS 現在的版本檔名

<script src="kejyun.app-asdfghjkl.js"></script>
<script src="kejyun.app-qwertyuio.js"></script>

<link rel="stylesheet" href="kejyun.app-asdfghjkl.css">
<link rel="stylesheet" href="kejyun.app-qwertyuio.css">

索引

對於需要常用來查詢的資料做好索引,可以加快查詢的效率

資料庫讀寫分離

以部落格文章為例,在 80/20 法則中,大部份 80% 人都是在看文章比較多(讀取資料:SELECT),只有少部分 20% 的人或意見領袖,才會發表文章表示看法(異動資料:INSERT、UPDATE、DELETE),而在做資料異動的時候很有可能會對資料進行鎖定,進而去影響讀取的速度。

除了有關交易(Transaction)的資料在 SELECT 的時候才有可能對資料進行鎖定,像是購票或購買限量商品時,會把撈取出來加入購物車的資料先行鎖定

所以像是 Facebook、部落格之類的媒體,大多會把資料庫做讀寫分離,使用者做異動的行為會去主資料庫(Master)去做寫入的動作,然後從資料庫(Slave)在定期的去同步資料庫的內容,而其他大部份的讀者在讀取資料時,都去讀取從資料庫(Slave)的資料,這樣就不會有因為資料異動而導致資料被鎖定,造成讀取變慢的問題。

而在主從架構中,可以是有很多台從的資料庫(Slave),透過分散式處理可以將不同的連線分配給不同的從資料庫(Slave),讓每一台從的資料庫平均分配差不多的連線量,因為需要處理的連線減少了,進而讓每個查詢都能夠在短時間都能夠回應查詢結果,提高系統的可用度。


重要觀念

資料庫存取是很昂貴的

每下一段資料庫 SQL 語法去撈取資料,資料庫就要對資料去進行比對運算,當資料很龐大,SQL 語法又太複雜導致運算很繁瑣時,資料庫需要進行運算的時間又會變得更久,所以建立好的索引(Index)及下好的 SQL 語法是很重要的工作

雖然資料都撈得出來,但是撈得漂亮,撈得快也是一種藝術啊~

與資料庫建立的連線是很昂貴的資源

我們要對資料庫進行操作就需要先與資料庫進行連線,但是資料庫的連線建立是很花時間的,時間越久會導致回應的時間也會跟著變長,然而資料庫的連線數又不能無限制的擴張(需要看主機效能的乘載量),所以連線的資源就相對的珍貴。

我們通常會用連線池(Connection Pool)的方式去維持資料庫的連線,建立連線後做完查詢就將連線丟到連線池內,供下一個要做查詢的人使用,下一個要做查詢的人發現有可用的連線,就不用花費時間重新的去建立與資料庫的連線,直接使用就有的連線即可,直到這個連線時間超過資料庫最高的限制失效斷線為止,這樣就能提高資料庫連線使用的效率。

對於需要當作查詢的資料必須建立索引

我們會依照我們想要撈取的資料下 WHEREGROUP BYORDER BY ...等等的條件去撈取我們想要撈取的資料,如果不建立索引資料庫也是有辦法將資料撈出來,只是速度會比較慢,當這些條件資料有預先做索引時,資料庫可以很快地利用索引去完成查詢的動作

索引原理就是將資料透過資料結構(e.g. B-tree), Hash)預先做排序的存放,使用索引時資料庫可以很快的資料放在哪一個地方,以達到快速撈取資料的目的

以書籍為例

索引就像書籍最前面的目錄一樣,可以很快地告訴我們哪一段章節的資料在第幾頁,所以我們就可以很快地翻閱到我們想看的資料,如果書籍沒有做目錄(索引)的話,我們要找到特定章節的資料時,我們就需要從頭到尾翻閱去查看看我們要看的資料到底在哪一頁,這對於書籍的讀者來說是相當浪費時間的一件事。

詳細的索引說明會在稍後章節提到

結論

大部份提高存取效率的作法大多是:

設計好的程式邏輯

在做簡單的資料撈取,能夠用更快的方式(減少迴圈、快速比對資料...blabla)去產生我們要的資料,當然能夠加快請求的速度,但是在現在硬體處理的速度越來越快的情況下,程式的邏輯沒有太複雜下(複雜的可能像是演算法),大部份的資料處理都是很快的,所以大部份的瓶頸都是卡在資料庫資料的撈取邏輯、資料存放方式、資料撈取方式以及索引建置的邏輯。

減少資料庫的存取

資料能夠快取不要重複撈取就不要重複撈取,可以把資料存放在記憶體的快取中,若是整個頁面都很少異動,也可以把整個畫面(View)的 HTML 去做快取,把資料組合成 HTML 的動作都省下來了。

降低資料表資料量

當一個資料表只有 100 筆或是到 1000 筆資料的時候,資料庫大部份都的處理速度都是很快的(只有幾毫秒而已,人感覺不出來差異),但是當一個資料表的資料量有好幾百萬或好幾千萬筆資料,要從這麼多的資料去撈取出想要的資料,需要耗費的運算資源就會更多,所以讓資料能夠分散儲存,降低每個資料表的資料量,這樣也可以大大提高資料庫存取的效率。

以書籍為例

想像一下一本書有 100 頁或是到 1000 頁的量,我們在透過書籍目錄去查詢我們想要看的資料,應該速度不會差太多,但是當一本書有好幾百萬頁(現實上可能會分很多冊,像百科全書),我們需要看的書籍目錄也會比較多(索引表很大),所以也需要花更多時間去查詢我們要的資料。

資料庫分散式處理

當資料異動或讀取的資料量過大時,資料的異動會影響到讀取的效能,所以我們就需要將資料做讀寫分離。

建立良好的資料庫索引

將用來做資料撈取判斷的資料做好索引,而索引的順序及 WHERE 條件順序會影響索引的使用,這個部分會在之後提到。

參考資料