一、什麼是MemCache
官方說明:html
MemCache是一個自由、源碼開放、高性能、分佈式的分佈式內存對象緩存系統,用於動態Web應用以減輕數據庫的負載。它經過在內存中緩存數據和對象來減小讀取數據庫的次數,從而提升了網站訪問的速度。MemCaChe是一個存儲鍵值對的HashMap,在內存中對任意的數據(好比字符串、對象等)所使用的key-value存儲,數據能夠來自數據庫調用、API調用,或者頁面渲染的結果。MemCache設計理念就是小而強大,它簡單的設計促進了快速部署、易於開發並解決面對大規模的數據緩存的許多難題,而所開放的API使得MemCache能用於Java、C/C++/C#、Perl、Python、PHP、Ruby等大部分流行的程序語言。算法
補充:數據庫
MemCache是一個高性能的分佈式的內存對象緩存系統,用於各類動態應用以減輕數據庫負擔。它經過在內存中緩存數據和對象,來減小讀取數據庫的次數,從而提升動態、數據庫驅動應用速度。MemCache會在內存中開闢一塊空間,創建一個統一的巨大的hash表,hash表可以用來存儲各類格式的數據,包括圖像、視頻、文件以及數據庫檢索的結果等。 數組
MemCache 和 MemCached:緩存
MemCache是這個項目的名稱,而MemCached是服務器端的主程序名稱。安全
二、MemCached使用場景
一般,咱們會在訪問量高的Web網站和應用中使用MemCache,用來緩解數據庫的壓力,而且提高網站和應用的響應速度。服務器
在應用程序中,咱們一般在如下節點來使用MemCached:網絡
- 訪問頻繁的數據庫數據(身份token、首頁動態)
- 訪問頻繁的查詢條件和結果
- 做爲Session的存儲方式(提高Session存取性能)
- 頁面緩存
- 更新頻繁的非重要數據(訪客量、點擊次數)
- 大量的hot數據
經常使用工做流程(以下圖):多線程

- 客戶端請求數據
- 檢查MemCached中是否有對應數據
- 有的話直接返回,結束
- 沒有的話,去數據庫裏請求數據
- 將數據寫入MemCached,供下次請求時使用
- 返回數據,結束
(注意:緩存到MemCached中的數據庫數據,在更新數據庫時要注意同時更新MemCached)架構
三、MemCached工做原理
MemCached採用了C/S架構,在Server端啓動後,以守護程序的方式,監聽客戶端的請求。啓動時能夠指定監聽的IP(服務器的內網ip/外網ip)、端口號(因此作分佈式測試時,一臺服務器上能夠啓動多個不一樣端口號的MemCached進程)、使用的內存大小等關鍵參數。一旦啓動,服務就會一直處於可用狀態。
爲了提升性能,MemCached緩存的數據所有存儲在MemCached管理的內存中,因此重啓服務器以後緩存數據會清空,不支持持久化。
(1)Memcached內存管理
內存結構

- slab_class裏,存放的是一組組chunk大小相同的slab
- 每一個slab裏面包含若干個page,page的默認大小是1M,若是slab大小100M,就包含100個page
- 每一個page裏面包含若干個chunk,chunk是數據的實際存放單位,每一個slab裏面的chunk大小相同
內存分配方式
- Memcached利用slab allocation機制來分配和管理內存,它按照預先規定的大小,將分配的內存分割成特定長度的內存塊,再把尺寸相同的內存塊分紅組,數據在存放時,根據鍵值大小去匹配slab大小,找就近的slab存放,因此存在空間浪費現象。而傳統的內存管理方式是,使用完經過malloc分配的內存後經過free來回收內存,這種方式容易產生內存碎片並下降操做系統對內存的管理效率。
- 存放數據時,首先slab要申請內存,申請內存是以page爲單位的。因此在放入第一個數據的時候,不管大小爲多少,都會有1M大小的page被分配給該slab。申請到page後,slab會將這個page的內存按chunk的大小進行切分,這樣就變成了一個chunk數組,最後從這個chunk數組中選擇一個用於存儲數據。
示例:

MemCache中的value存放位置是由value的大小決定,value會被存放到與chunk大小最接近的一個slab中,好比slab[1]的chunk大小爲88字節、slab[2]的chunk大小爲112字節、slab[3]的chunk大小爲144字節(默認相鄰slab內的chunk基本以1.25爲比例進行增加,MemCache啓動時能夠用-f指定這個比例),那麼一個100字節的value,將被放到2號slab中。
內存回收方式
- 當數據容量用完MemCached分配的內存後,就會基於LRU(Least Recently
Used)算法清理失效的緩存數據(放入數據時可設置失效時間),或者清理最近最少使用的緩存數據,而後放入新的數據。
- 在LRU中,MemCached使用的是一種Lazy
Expiration策略,本身不會監控存入的key/vlue對是否過時,而是在獲取key值時查看記錄的時間戳,檢查key/value對空間是否過時,這樣可減輕服務器的負載。
- 須要注意的是,若是若是MemCache啓動沒有追加-M,則表示禁止LRU,這種狀況下內存不夠會報Out Of Memory錯誤。
針對MemCache的內存分配及回收算法,總結三點:
- MemCache的內存分配chunk裏面會有內存浪費,88字節的value分配在128字節(緊接着大的用)的chunk中,就損失了30字節,可是這也避免了管理內存碎片的問題
- MemCache的LRU算法不是針對全局的,是針對slab的
- 應該能夠理解爲何MemCache存放的value大小是限制的,由於一個新數據過來,slab會先以page爲單位申請一塊內存,申請的內存最多就只有1M,因此value大小天然不能大於1M了
(2)MemCached分佈式
爲了提高MemCached的存儲容量和性能,咱們應用的客戶端可能會對應多個MemCached服務器來提供服務,這就是MemCached的分佈式。
分佈式實現原理
MemCached的目前版本是經過C實現,採用了單進程、單線程、異步I/O,基於事件(event_based)的服務方式.使用libevent做爲事件通知實現。多個Server能夠協同工做,但這些 Server 之間保存的數據各不相同,並且並不通訊(與之造成對比的,好比JBoss Cache,某臺服務器有緩存數據更新時,會通知集羣中其餘機器更新緩存或清除緩存數據),每一個Server只是對本身的數據進行管理。
Client端經過IP地址和端口號指定Server端,將須要緩存的數據是以key->value對的形式保存在Server端。key的值經過hash進行轉換,根據hash值把value傳遞到對應的具體的某個Server上。當須要獲取對象數據時,也根據key進行。首先對key進行hash,經過得到的值能夠肯定它被保存在了哪臺Server上,而後再向該Server發出請求。Client端只須要知道保存hash(key)的值在哪臺服務器上就能夠了。

當向MemCached集羣存入/取出key/value時,MemCached客戶端程序根據必定的算法計算存入哪臺服務器,而後再把key/value值存到此服務器中。也就是說,存取數據分二步走,第一步,選擇服務器,第二步存取數據。
分佈式算法解析
- 餘數算法:
先求得鍵的整數散列值(也是就鍵string的HashCODE值 什麼是HashCode),再除以服務器臺數,根據餘數肯定存取服務器,這種方法計算簡單,高效,但在memcached服務器增長或減小時,幾乎全部的緩存都會失效。
- 散列算法:
先算出MemCached服務器的散列值,並將其分佈到0到2的32次方的圓上,而後用一樣的方法算出存儲數據的鍵的散列值並映射至圓上,最後從數據映射到的位置開始順時針查找,將數據保存到查找到的第一個服務器上,若是超過2的32次方,依然找不到服務器,就將數據保存到第一臺MemCached服務器上。若是添加了一臺MemCached服務器,只在圓上增長服務器的逆時針方向的第一臺服務器上的鍵會受到影響。
(3)MemCached線程管理
MemCached網絡模型是典型的單進程多線程模型,採用libevent處理網絡請求,主進程負責將新來的鏈接分配給work線程,work線程負責處理鏈接,有點相似與負載均衡,經過主進程分發到對應的工做線程。
MemCached默認有7個線程,4個主要的工做線程,3個輔助線程,線程可劃分爲如下4種:
- 主線程,負責MemCached服務器初始化,監聽TCP、Unix Domain鏈接請求;
- 工做線程池,MemCached默認4個工做線程,可經過啓動參數修改,負責處理TCP、UDP,Unix域套接口鏈路上的請求;
- assoc維護線程,MemCached內存中維護一張巨大的hash表,該線程負責hash表動態增加;
- slab維護線程,即內存管理模塊維護線程,負責class中slab的平衡,MemCached啓動選項中可關閉該線程。
更多見:MemCache-網絡線程模型-原碼分析
四、MemCached特性與限制
- MemCache中能夠保存的item數據量是沒有限制的,只要內存足夠
- MemCache單進程在32位機中最大使用內存爲2G,這個以前的文章提了屢次了,64位機則沒有限制
- Key最大爲250個字節,超過該長度沒法存儲
- 單個item最大數據是1MB,超過1MB的數據不予存儲
- MemCache服務端是不安全的,好比已知某個MemCache節點,能夠直接telnet過去,並經過flush_all讓已經存在的鍵值對當即失效,因此MemCache服務器最好配置到內網環境,經過防火牆制定可訪問客戶端
- 不可以遍歷MemCache中全部的item,由於這個操做的速度相對緩慢且會阻塞其餘的操做
- MemCached的高性能源自於兩階段哈希結構:第一階段在客戶端,經過Hash算法根據Key值算出一個節點;第二階段在服務端,經過一個內部的Hash算法,查找真正的item並返回給客戶端。從實現的角度看,MemCache是一個非阻塞的、基於事件的服務器程序
- MemCache設置添加某一個Key值的時候,傳入expiry爲0表示這個Key值永久有效,這個Key值也會在30天以後失效,見memcache.c的源代碼:
#define REALTIME_MAXDELTA 60*60*24*30
static rel_time_t realtime(const time_t exptime) {
if (exptime == 0) return 0;
if (exptime > REALTIME_MAXDELTA) {
if (exptime <= process_started)
return (rel_time_t)1;
return (rel_time_t)(exptime - process_started);
} else {
return (rel_time_t)(exptime + current_time);
}
}