馬榮彥
(中央宣傳部電影數字節目管理中心,北京 100866)
近年來,隨著計算機互聯網技術的發展,大數據的存取以及網站的快速響應成為現在web應用發展所面臨的一個巨大的挑戰。農村數字電影公共服務平臺基于影片信息庫中的影片向監管部門、院線、發行方等用戶提供了流動放映業務的信息、制作、分發、管理等技術監管服務,影片基礎信息庫中引入了許多市場上的影片新片,這是整個平臺賴以生存的基礎,各種角色的用戶登陸平臺都需要瀏覽查詢影片的基本信息,這對平臺的檢索展現速度有了更高的要求,同時也對數據庫的性能方面提出了更高的要求。為了保證系統運行性能的冗余,查詢性能是系統整體優化的關鍵,因此引入memcached分布式緩存來減輕數據庫壓力,它通過將從數據庫中得到的查詢結果緩存起來,來減少系統與DB 的訪問連接頻次,這樣系統讀取數據的時候如果命中就會從緩存中獲取數據,來提高系統頁面的展示速度,增強用戶體驗。
大部分應用系統都將數據存儲到數據庫管理系統RDMS中,應用服務從數據庫中讀取相應的數據并進行頁面展示。但有時會由于數據庫管理系統響應變慢、頁面數據或者圖片延遲展示等問題影響用戶的訪問體驗,這是由于系統在一段時間內訪問量的增長或者對頁面中通用展示數據集中訪問而導致的。
memcached經常被用來在動態互聯網服務中緩解數據庫的負載壓力。它的主要原理是通過緩存第一次數據庫查詢結果,通過算法提高數據命中率,來減少和數據庫之間的連接訪問次數。memcached基于存儲鍵/值對的hashmap,memcached 在啟動時增加-d參數將其啟動為后臺運行進程即為守護進程,盡管它的守護進程是C 語言編寫的,但是客戶端程序只要支持memcached協議,不管使用何種語言都可以與之進行通信。
memcached 進程運行之后,會提前申請一塊較大的內存空間由自己進行管理,用完之后再去申請,不是每次使用時都去向操作系統申請。所以如果分配足夠大的內存空間給memcached的時候,基本上memcached的時間消耗就只剩下網絡連接的時間了。

圖1 memcached使用方式
memcached作為一個高效的分布式緩存,它是由自己向服務器申請一塊內存,對存儲的Hash-Table內容進行有效管理。服務器的內存使用一般僅限于自身使用,不能進行共享,而memcached的出現解決了此問題,允許多個用戶同時進行訪問,并且同時使用,而且也不會發生在與數據庫進行連接訪問的時候因磁盤讀寫消耗資源較多導致進程阻塞的情況。它主要有以下幾大特點。
(1)基于文本行的通信協議:memcached的內容管理采用的是簡單的、便于操作的、基于文本行的協議,即使通過遠程登錄也能對緩存內容數據進行讀取。
(2)基于libevent庫的處理:libevent是一個事件通知程序庫,它將Linux的許多事件處理功能封裝起來進行統一調用,而且在多線程方面有很好的性能。memcached基于此庫可以高效地運行在多種類型的操作系統OS上。
(3)預申請內存的方式:memcached會提前申請較大的內存空間供自己使用,常用的數據都會被存放在申請的空間中,并且會基于某些算法(例如:LRU 即最近最少使用算法)自動移除不經常使用的緩存數據,騰出空間給需要緩存的數據,并且它也會隨著memcached或者操作系統的重啟全部消失。
(4)獨立的分布式緩存:memcached的緩存是一種通過客戶端程序來實現的分布式緩存,它們獨立工作,多個memcached 不會互相通信來共享信息,亦不會相互干擾,解決了共享內存只能單機應用的局限。
(5)支持多種語言的客戶端:許多語言都實現了memcached的客戶端,僅memcached網站上列出的語言就有Perl、PHP、Python、Ruby、C#、C/C++、java等。
memcached的內存緩存用于在系統中提升系統的響應速度。它的主要應用場景如下:
(1)由于memcached緩存是基于分布式的,相對來說比較適合分布式服務系統。
(2)獨立于應用:web應用系統響應慢的重要瓶頸是數據庫的高并發,和其他的緩存機制例如java的Hibernate緩存機制比較,Hibernate是和應用程序本身的耦合性比較高,不像memcached是基于分布式的、獨立于應用系統的。
(3)不同服務系統間信息互通:兩個不同的應用服務系統信息需要同步,這時候就可以使用memcached了,其中一個系統將需要共享的信息進行memcached緩存,另一個系統服務就可以通過memcached獲得共享的信息,就像獲取本地信息一樣方便。
如果使用memcached后不僅不會帶來任何便利之處,相反還會拖慢整個系統,因為網絡連接同樣需要消耗資源,那么這時候就不適合使用memcached。不適合使用memcached的主要業務:
(1)數據對象占用較大空間:由于memcached存儲信息在內存中,空間有限因此不適合那些較大數據塊的存儲。
(2)應用服務運行在內存不受控制的服務器上:memcached需要申請和控制大塊的內存供自己調配,如果memcached管理的內存被其它服務占用或者丟失,memcached的緩存命中率將會大大降低,性能也會隨之下降。
(3)沒有安全策略的應用中:如果沒有安全保障,memcached緩存的數據就很容易被不適當的進程獲取,memcached本身并未提供任何安全策略以及安全管理機制,因此需要對服務本身以及運行環境考慮增強安全策略。
(4)需要存儲的是持久化數據:由于memcached本身是為緩存而設計的服務器,因此它的數據是有時效的,在緩存數據量達到一定的值后,就會根據某些特定的算法移除部分緩存數據,并不適合永久性數據的存儲情況。
本地緩存local cache顧名思義就是應用服務器本身的緩存空間,它能夠利用的內存容量受到服務器空閑內存空間的限制,memcached則不會,不過local cache不但可以存儲任意的數據,而且沒有網絡存取的延遲,在這一點上有其獨特的優勢。
(1)本地緩存數據查詢速度更優:可以把頁面中使用非常頻繁的或者訪問次數最多的數據放在本地緩存中,這樣每次加載數據時就可以實現查詢的秒級響應,沒有網絡延遲。
(2)本地緩存不會自動更新失效數據:在memcached集群中,對key-value的修改刪除會通過某種方式同時通知所有的客戶端獲取此數據的變更。這一點本地緩存方式劣勢明顯,它需要刷新服務器數據,效率很慢。
(3)本地緩存受限于本地服務器的空閑內存大小。
由于memcached 之間不進行任何數據復制備份,本身也沒有內置分布式功能,并且服務器與服務器之間沒有任何通信都是相互獨立的,因此memcached本身是沒有任何策略維持失效轉發的,所以當任何服務器節點出現故障時,可能會導致獲取不到有效數據,所以我們可以利用magent 將memcache做成集群方式來避免出現單點故障,實現多臺memcached緩存服務器的高效管理。
magent是一款比較常用的memcached代理服務器,從圖2 可以看到有兩個magent節點,每個magent節點又分別連接memcached節點,magent下memcached有主備的區別,memcache主節點可以有多臺機器,它分散存儲所有緩存的鍵值數據,備節點則存儲了一個完整的鍵值數據。magent有效地解決了memcached的不能節點分布式問題,如果其中一臺緩存服務器宕掉,系統依然可以繼續工作,magent依然可以讀取到數據,這樣數據就不會丟失并且可以保證數據的完整性。特別需要注意的是,memcached重啟后緩存數據會被清空,這時盡管備份的memcached還有數據,magent取得的仍可能會是空值,可采用定時維護服務器,來同步恢復memcached緩存數據。

圖2 memcached與magent的混合模型
magent和每個memcached服務器之間保持著長連接的連接方式,這樣可以減少創建連接、銷毀連接的資源消耗。它和memcached 一樣,基于libevent的事件程序庫來處理IO 讀寫請求,并且支持memcached的許多通信協議指令,來實現系統請求的轉發。
memcached的主要原理是通過在預先申請到的內存中維護一張hashtable來存儲各種格式的數據,比如圖片、數字、文本以及從數據庫中查詢到的結果數據等。memcached緩存是存儲了很多 〈key,value〉鍵值對的表,通過兩段哈希算法可以存儲或查詢任意的數據。客戶端程序實現了memcached的分布式,它把數據使用一定的算法存儲在不同的memcached緩存服務器里,不同服務器存儲的數據不同,當用戶需要使用此數據時,程序會首先使用內置算法計算出(key)的哈希值即階段一哈希,找到一個服務器節點并將數據請求發送給此節點,此節點再通過一次哈希即階段二哈希,這時才查找到最后需要的數據 (value)。各種語言的客戶端實現的哈希算法是不同的,因此在緩存服務器中數據的存儲方式也是不盡相同的。
假設有客戶端client,memcached服務器A、B、C。應用程序要保存〈“key1”,“11”〉,〈“key2”,“22”〉的數據:client首先根據某種算法計算出鍵“key1”的哈希值,假設選中了服務器A,然后client會與服務器A 連接,把數據“11”存儲到鍵“key1”的value中去。同樣 〈“key2”,“22”〉也通過哈希算法選擇相應的緩存服務器進行數據存儲。接下來我們要訪問數據,獲取時也要將鍵“key1”的hash值傳遞給函數庫,然后使用與存儲“key1”時相同的哈希算法 (哈希算法相同,就可以保證兩次選中同一臺服務器),計算出“key1”在服務器A上,然后發送get 指令,就可以獲得value 數據“11”。只要緩存的數據沒有因為故障、超時等某些原因被服務器移除,就能獲得之前存儲的value,如圖3所示。

圖3 memcached存取數據示意圖
如果memcached服務器數量比較多,不同的數據value就會被分配到不同的服務器上進行存儲,這就實現了memcached的緩存數據分散存儲即分布式的功能,而且即使其中一臺服務器因故障無法被連接,也不會影響其他服務器的正常運行,因為它們都是各自獨立處理不會相互干擾,在邏輯層面用戶是感覺不到故障的發生的,因此被認為是正常運行的。
在了解memcached 的原理之后,為了驗證memcached的有效性,本文采用3000 多部影片基礎信息作為測試對象,比較從數據庫中查詢數據和從memcached中查詢符合條件的20條數據的性能。一部影片的基本信息包括影片名稱、許可證號、出品年代、導演、主演、編劇、英文名稱、制片人、時長、題材、影片類型、國家/地區、出品單位、攝制單位、影片簡介等。
在對比測試前做好一些準備工作,首先安裝memcached服務器端,要先安裝libevent庫,如果系統已有此庫,可跳過。本次實驗采用的是centos操作系統,因此使用yum install memcached進行安裝,安裝完成后操作目錄為/usr/bin/memcached,并成功啟動該服務。
本文采用的語言為java,因此采用memcached的java客戶端xmemcached 版本2.0.0 進行測試,對于memcached的所有java客戶端之間的對比可以參考下一節內容。表1為單線程的情況下查詢10次的平均值的對比測試結果。

表1 從數據庫中和memcached中查詢數據的性能對比結果
從表1和表2可以看到memcached存取數據的時間明顯比數據庫中直接查詢有很大的提升,尤其是線程數越多越明顯。當數據庫的連接數目超過一定的值后會造成數據庫的崩潰,因此memcached對于緩解數據庫的壓力有很好的幫助。

表2 不同線程數情況下響應時間結果比較
盡管系統使用memcached后性能有了很大的提升,但畢竟它只是緩存,這時如果數據發生變化,我們可以有兩種方式進行處理:一是對緩存和數據庫的數據同時進行更新,二是對緩存的數據進行直接移除或者刪除,等下次訪問的時候再進行處理,這樣就可以避免讀取到臟數據,造成系統數據展示不正確。因為寫操作總是要重新進行緩存處理,消耗大量的資源占用帶寬,因此緩存是不適合有大量寫和更新操作的數據應用場景的。
memcachedClient:該客戶端基于傳統的I/O阻塞模型,在高并發的時候比較容易報內存溢出的異常。
xmemcached:它的性能和穩定性比較高,可以作為首選。它基于Java NIO,和傳統I/O 阻塞模型對比,它的優勢比較明顯即效率高、資源耗費少,已經被越來越多地應用到大型應用服務中,成為解決高并發與大量連接、I/O 處理問題的有效方式。NIO 是一種同步非阻塞的I/O 模型,也是I/O 多路復用的基礎,只需要創建和維護一個連接 (當然NIO 也可以做池化處理),這樣便省去了線程創建和切換的資源消耗,在并發量比較多的用戶連接下有著非常突出的表現。
當今社會需要提供實時的動態頁面和信息,針對數據庫高并發的讀寫需求,如果使用傳統的直連數據庫模式,并發負載明顯偏高容易造成數據庫死鎖或者宕機。本文對memcached進行了簡要的介紹,在3000多部影片基礎信息數據的基礎上做了不同的對比,從對比結果來看,memcached在性能上的優異很明顯,并且還可以和magent實現memcached集群,來解決服務器節點單點故障的問題。但是它也有本身的弱點,memcached對內部存儲的緩存數據同等對待,并沒有進一步來區分數據,比如訪問頻次多的數據可以更容易被查詢等,因此需要進一步的優化,可以從命中率、空間利用率、安全性能等許多方面對此進行考慮,還需要進一步的探索研究。