當你打算使用獨立的快取伺服器架構,例如memcached,來做為快取系統時,那麼,所有要放置到快取系統中的資料,都必須透過跨越行程的通訊(Inter-Process Communication,IPC)來傳送,而通常這跨越行程的通訊方式就是透過網路來傳輸。

一旦牽扯到跨越行程的傳輸時,便意謂著資料必須轉換成一個個連續的位元組,通常我們會將這個過程稱為序列化(serialization)。當然,當序列化後的資料欲還原成原始的資料時,也必須經過所謂解序列化(deserialization)的過程。

快取機制處理物件資料的不同方式
當你在撰寫連接至快取伺服器的應用程式時,通常你會運用現成的快取程式庫,來協助你處理和快取相關的種種機制,當然,序列化及解序列化的動作,通常也會包裝在這其中。而對於某些物件導向程式語言,例如Java、C#,資料乃是以物件的型式來表示,而將物件資料序列化及解序列化的動作,也都已經內建在語言本身。

因此,程式設計者可以輕易將物件進行序列化及解序列化,這一切都是以十分直覺而且高階的方式去進行。

倘若程式設計者使用的是該語言及標準程式庫中所內建的物件,那麼,便毋需自行定義物件進行序列化及解序列化的行為。也就是說,當程式設計者置放快取伺服器中的資料,只是單純的語言內建物件型別時,可以完成不涉及序列化以及解序列化的概念,因為一切都會在無形中自動完成。這可以說是相當便利。

序列化與解序列化是要付出代價的
但是,這無形中也構成了一個陷阱。那麼,是什麼陷阱呢?

在前文中便提到,序列化的動作基本上,就是將物件的狀態轉換成為一個接一個連續的位元組資料來表示。而相反的,解序列化的動作,則是將物件所對應的一個接一個連續的位元組資料,轉換回原始的物件狀態。這兩種動作都得經過某種程度的計算來完成。當程式設計者可以在完全不理解序列化與解序列化的情況下,完成快取資料的存取時,他很容易會忽略:序列化與解序列化本身其實是需要耗費計算時間的。

而且,更重要的是,同樣等價的資料.用不同的物件來表示,就有可能因為不同物件的序列化及解序列化程式碼不同,而在效能上有高下的分別。而甚至同一種物件,若是搭配不同的序列化、解序列化程式碼,也有可能有不同的效能表現。

但是,因為這動作很容易忽略,但偏偏又和效能息息相關,因此,許多程式設計者不會試著檢視他們將資料置入快取系統、或是從快取系統中取出時的效率。

我們曾經在快取系統上遭遇過效能問題。那時的情況是,即使利用了快取系統來改善存取某種特定資料,但是效能提升的程度不若我們所預期。因此,在更深入的檢查對快取系統的存取效率之後,我們發現造成效能不彰的原因,竟然是物件在置入快取系統前的序列化動作花費了太多的時間。
我們怎麼解決這個問題呢?我們改用了另一個序列化、解序列化動作效率較高的物件來表示相同的資料內容。這麼一來,快取存取的效能也因此達到了我們的要求。

這就是一個很典型的例子。因為將物件置入快取系統中的動作太過於高階而且直覺,使得程式設計者會忽略掉這個環節,而事實上,這個環節有可能影響到最後的效能表現。

如何做到大規模資料快取?
在使用快取系統時,除了考慮存取時的效能因素之外,你可能還會面臨到如何做大規模資料快取的議題。

在前文中有提到,使用獨立快取伺服器的好處就是,它的記憶體,不受限於作業系統對單一行程的記憶體使用限制、甚至不受限於單一主機的總記憶體量,因為,使用快取伺服器時,你可以串連多部運行快取伺服器的主機,構成一個快取伺服器的群集(cluster),這麼一來,可用的快取記憶體總量,便是多部快取伺服器主機可用的記憶體總量。而且,更重要的是,透過增加主機,你便可以輕易的擴增快取的規模,而這意謂著真正的規模擴充性(scalability)。

當你使用多部主機分別運行快取伺服器時,你可以獲得這些主機記憶體總和的快取記憶體量,但是,你要如何將資料分別的置放到每一部主機上呢?一個最簡單的模式,就是將資料平均地分散到每一部快取伺服器上。
通常,我們不會希望資料存放到多部快取伺服器上的負荷,呈現不平均的情況,因為這意謂著有可能發生當某部快取伺服器滿載時,其他的快取伺服器還有不少空餘的空間,但滿載的快取伺服器就會成為整個系統的瓶頸,變相地限制了系統的能力。因此,最理想的情況當然是將資料平均分散到每部快取伺服器上,因為這能使所有的記憶體資源都能夠善用。

要如何將資料平均分散到每部快取伺服器呢?有一個最簡單的方法。我們在存取快取系統時,都是將資料以一鍵值對應。在前文中提過,雖然這個鍵值只是個單純的字串,可以直接用具有可讀性的字串來做為鍵值。但是,為了安全性的因素,可以將這個字串經過一些hashing的演算法,例如MD5,來得到一組digest。當我們想要將資料平均分散到多部快取伺服器時,便可以利用這組digest,來做為平均分散的計算依據。

因為這些hashing的演算法所計算出來的digest,在理論上都是十分均勻地散布在整個digest的值域中。因此,假設整個系統有n部快取伺服器。那麼把hashing演算法算出來的digest值除以n之後,便可以決定究竟要將這筆快取資料置於那一部快取伺服器之上了。

因為digest的值很均勻,所以理論上,系統所用的所有鍵值計算出來的digest也會很平均,那麼除以n之後的數量也會很平均。這麼一來,資料依據其鍵值便會平均分散到所有的快取伺服器上。

當快取伺服器構成一個群集時,除了可以很容易提升規模之外,也可以藉此提高快取容錯的能力。也就是說,你可以把同一筆快取資料,有機會放置到多個快取伺服器上。在這種情況下,你必須可以依鍵值分別計算出首要的快取伺服器、次要的快取伺服器、……依此類推。存取資料時,自然是先到首要的快取伺服器上嘗試存取資料,倘若這部快取伺服器損毀時,便可以接著嘗試次要的快取伺服器。除非可供備份的快取伺服器全部損毀,否則還是可以存取快取中的資料。

你可以想像,即使快取伺服器有部份損毀,整個系統也不會因此而無法運作。倘若系統中只有一部快取伺服器,那麼,只要它損毀了,整個系統就失去了快取的能力。但是,若是能有多部快取伺服器,並且搭配容錯的機制,只要不是全部的快取伺服器陣亡,系統仍舊能夠保有快取的能力,而這是相當重要的。

快取系統,已經是現代許多應用程式不可或缺的一個組成,不論是高階或低階的應用程式,都能夠善用快取來提升存取資料的效率。在本系列的文章中,我已簡單、快速走訪一些和快取相關的議題,希望能為程式設計者提供一個簡單、概括性的說明及介紹。

專欄作者

熱門新聞

Advertisement