memcache是一套分佈式的高速緩存系統,由LiveJournal的Brad Fitzpatrick開發,但目前被許多網站使用php
以提高網站的訪問速度,尤爲對於一些大型的、須要頻繁訪問數據庫的網站訪問速度提高效果十分顯著[1] 。這是一套開放源代碼軟件,以BSD license受權發佈。linux
MemCache的工做流程以下:先檢查客戶端的請求數據是否在memcached中,若有,直接把請求數據返回,再也不對數據庫進行任何操做;若是請求的數據不在memcached中,就去查數據庫,把從數據庫中獲取的數據返回給客戶端,同時把數據緩存一份到memcached中(memcached客戶端不負責,須要程序明確實現);每次更新數據庫的同時更新memcached中的數據,保證一致性;當分配給memcached內存空間用完以後,會使用LRU(Least Recently Used,最近最少使用)策略加上到期失效策略,失效數據首先被替換,而後再替換掉最近未使用的數據。程序員
Memcahce不適合緩存大數據,超過1MB的數據,能夠考慮在客戶端壓縮或拆分到多個key中。大的數據在進行load和uppack到內存的時候須要花很長時間,從而下降服務器的性能。最新的memchche 會自動壓縮,100M的數據能夠壓縮至100k,因此解決了這個問題。web
memcached默認狀況下采用了名爲Slab Allocator的機制分配、管理內存。在該機制出現之前,內存的分配是經過對全部記錄簡單地進行malloc和free來進行的。可是,這種方式會致使內存碎片,加劇操做系統內存管理器的負擔,最壞的狀況下,會致使操做系統比memcached進程自己還慢。Slab Allocator就是爲解決該問題而誕生的。Slab Allocator的基本原理是按照預先規定的大小,將分配的內存分割成特定長度的塊,以徹底解決內存碎片問題.算法
簡單的分析組織了一下memcached的知識點,主要是爲了引入slab Allocator 對象緩存分配機制,在介紹這個機制前,咱們先分析一下malloc 和 free ,以及 new 與 delete這幾個函數的性能shell
2. malloc 與 free 的性能對比:數據庫
malloc函數的實質體如今,它有一個將可用的內存塊鏈接爲一個長長的列表的所謂空閒鏈表。調用malloc函數時,它沿鏈接表尋找一個大到足以知足用戶請求所須要的內存塊。而後,將該內存塊一分爲二(一塊的大小與用戶請求的大小相等,另外一塊的大小就是剩下的字節)。接下來,將分配給用戶的那塊內存傳給用戶,並將剩下的那塊(若是有的話)返回到鏈接表上。調用free函數時,它將用戶釋放的內存塊鏈接到空閒鏈上。到最後,空閒鏈會被切成不少的小內存片斷,若是這時用戶申請一個大的內存片斷,那麼空閒鏈上可能沒有能夠知足用戶要求的片斷了。因而,malloc函數請求延時,並開始在空閒鏈上翻箱倒櫃地檢查各內存片斷,對它們進行整理,將相鄰的小空閒塊合併成較大的內存塊。api
malloc() 的實現有不少,這些實現各有優勢與缺點。在設計一個分配程序時,要面臨許多須要折衷的選擇,其中包括:
分配的速度。
回收的速度。
有線程的環境的行爲。
內存將要被用光時的行爲。
局部緩存。
簿記(Bookkeeping)內存開銷。
虛擬內存環境中的行爲。
小的或者大的對象。
實時保證。
每個實現都有其自身的優缺點集合。在咱們的簡單的分配程序中,分配很是慢,而回收很是快。另外,因爲它在使用虛擬內存系統方面較差,因此它最適於處理大的對象。數組
最關鍵的問題:緩存
屢次調用malloc()後空閒內存被切成不少的小內存片斷,這就使得用戶在申請內存使用時,因爲找不到足夠大的內存空間,malloc ()須要進行內存整理,使得函數的性能愈來愈低。聰明的程序員經過老是分配大小爲2的冪的內存塊,而最大限度地下降潛在的malloc性能喪失。也就是說,所分配的內存塊大小爲4字節、8字節、16字節、18446744073709551616字節,等等。這樣作最大限度地減小了進入空閒鏈的怪異片斷(各類尺寸的小片斷都有)的數量。儘管看起來這好像浪費了空間,但也容易看出浪費的空間永遠不會超過50%。
delete 性能消耗與malloc相比比較小,這裏不討論
3. new 與 delete 性能對比:
new是C++中引入的概念,其中
Memory Pool的設計哲學和無痛運用
http://shaomeng95.iteye.com/blog/1142015
Memcached是一個高效的分佈式內存cache,瞭解memcached的內存管理機制,便於咱們理解memcached,讓咱們能夠針對咱們數據特色進行調優,讓其更好的爲我所用。這裏簡單談一下我對 memcached的內存管理的一些認識,在沒有特別註明的狀況下,這裏談到的memcached是1.2版本,1.1和1.2版本有一些差別。
基本概念:Slab和chunk
在Memcached內存結構中有兩個很是重要的概念:slab 和 chunk,咱們先從下圖中對這兩個概念有一個感性的認識:
圖 1 memcached內存結構
Slab是一個內存塊,它是memcached一次申請內存的最小單位。在啓動memcached的時候通常會使用參數-m指定其可用內存,可是並非在啓動的那一刻全部的內存就所有分配出去了,只有在須要的時候纔會去申請,並且每次申請必定是一個slab。Slab的大小固定爲1M(1048576 Byte),一個slab由若干個大小相等的chunk組成。每一個chunk中都保存了一個item結構體、一對key和value。
雖然在同一個slab中chunk的大小相等的,可是在不一樣的slab中chunk的大小並不必定相等,在memcached中按照chunk的大小不一樣,能夠把slab分爲不少種類(class)。在啓動memcached的時候能夠經過-vv來查看slab的種類:
圖2 slab分組信息
從上圖能夠看到,默認狀況下memcached把slab分爲40類(class1~class40),在class 1中,chunk的大小爲80字節,因爲一個slab的大小是固定的1048576字節(1M),所以在class1中最多能夠有13107個chunk:
13107×80 + 16 = 1048576
在class1中,剩餘的16字節由於不夠一個chunk的大小(80byte),所以會被浪費掉。每類chunk的大小有必定的計算公式的,假定i表明分類,class i的計算公式以下:
chunk size(class i) : (default_size+item_size)*f^(i-1)+ CHUNK_ALIGN_BYTES
default_size: 默認大小爲48字節,也就是memcached默認的key+value的大小爲48字節,能夠經過-n參數來調節其大小;
item_size: item結構體的長度,固定爲32字節。default_size大小爲48字節,item_size爲32,所以class1的chunk大小爲48+32=80字節;
f爲factor,是chunk變化大小的因素,默認值爲1.25,調節f能夠影響chunk的步進大小,在啓動時可使用-f來指定;
CHUNK_ALIGN_BYTES是一個修正值,用來保證chunk的大小是某個值的整數倍(在32位機器上要求chunk的大小是4的整數倍)。
從上面的分析能夠看到,咱們實際能夠調節的參數有-f、-n,在memcached的實際運行中,咱們還須要觀察咱們的數據特徵,合理的調節f,n的值,使咱們的內存獲得充分的利用減小浪費。
內存申請分配
Memcached內存管理採起預分配、分組管理的方式,分組管理就是咱們上面提到的slab class,按照chunk的大小slab被分爲不少種類。下面解釋一下memcached的內存預分配過程。
向memcached添加一個item時候,memcached首先會根據 item的大小,來選擇最合適的slab class:例如item的大小爲190字節,默認狀況下class 4的chunk大小爲160字節顯然不合適,class 5的chunk大小爲200字節,大於190字節,所以該item將放在class 5中(顯然這裏會有10字節的浪費是不可避免的),計算好所要放入的chunk以後,memcached會去檢查該類大小的chunk還有沒有空閒的,若是沒有,將會申請1M(1個slab)的空間並劃分爲該種類chunk。例如咱們第一次向memcached中放入一個190字節的item 時,memcached會產生一個slab class 2(也叫一個page),並會用去一個chunk,剩餘5241個chunk供下次有適合大小item時使用,當咱們用完這全部的5242個chunk以後,下次再有一個在160~200字節之間的item添加進來時,memcached會再次產生一個class 5的slab(這樣就存在了2個pages)。查看slab的使用狀況,咱們能夠telnet ip port,而後輸入命令 stats slabs便可:
例如:telnet 10.0.4.210 11211
stats slabs STAT 5:chunk_size 200 STAT 5:chunks_per_page 5242 STAT 5:total_pages 1 STAT 5:total_chunks 5242 STAT 5:used_chunks 5242 STAT 5:free_chunks 0 STAT 5:free_chunks_end 5241 STAT active_slabs 1 STAT total_malloced 1048400 |
http://ju.outofmemory.cn/entry/8014
Memcached在啓動時經過-m指定最大使用內存,可是這個不會一啓動就佔用,是隨着須要逐步分配給各slab的。
若是一個新的緩存數據要被存放,memcached首先選擇一個合適的slab,而後查看該slab是否還有空閒的chunk,若是有則直接存放進去;如 果沒有則要進行申請。slab申請內存時以page爲單位,因此在放入第一個數據,不管大小爲多少,都會有1M大小的page被分配給該slab。申請到 page後,slab會將這個page的內存按chunk的大小進行切分,這樣就變成了一個chunk的數組,在從這個chunk數組中選擇一個用於存儲 數據。以下圖,slab 1和slab 2都分配了一個page,並按各自的大小切分紅chunk數組。
memcached的內存分配策略就是:按slab需求分配page,各slab按需使用chunk存儲。
這裏有幾個特色要注意,
Memcached分配出去的page不會被回收或者從新分配
Memcached申請的內存不會被釋放
slab空閒的chunk不會借給任何其餘slab使用
新版本中Page能夠調配給其它的Slab,shell> memcached -o slab_reassign,slab_automove
爲了規避內存碎片問題,Memcached採用了名爲SlabAllocator的內存分配機制。內存以Page爲單位來分配,每一個Page分給一個特定長度的Slab來使用,每一個Slab包含若干個特定長度的Chunk。實際保存數據時,會根據數據的大小選擇一個最貼切的Slab,並把數據保存在對應的Chunk中。若是某個Slab沒有剩餘的Chunk了,系統便會給這個Slab分配一個新的Page以供使用,若是沒有Page可用,系統就會觸發LRU機制,經過刪除冷數據來爲新數據騰出空間,這裏有一點須要注意的是:LRU不是全局的,而是針對Slab而言的。
http://www.ibm.com/developerworks/cn/linux/l-linux-slab-allocator/
The fundamental idea behind slab allocation technique is based on the observation that some kernel data objects are frequently created and destroyed after they are not needed anymore. This implies that for each allocation of memory for these data objects, some time is spent to find the best fit for that data object. Moreover, deallocation of the memory after destruction of the object contributes to fragmentation of the memory, which burdens the kernel some more to rearrange the memory.Slab 算法的發現是基於內核中內存使用的一些特色:一些須要頻繁使用的一樣大小數據常常在使用後不久又再次被用到;找到合適大小的內存所消耗的時間遠遠大於釋放內存所須要的時間。因此Slab算法的發明人認爲內存對象在使用以後不是當即釋放給系統而是將它們用鏈表之類的數據結構管理起來以備未來使用,頻繁分配和釋放的內存對象應該用緩存管理起來。Linux的slab分配器就是基於這樣的想法實現的,這個算法在空間和時間上都有很高的效率。
內存管理的目標是提供一種方法,爲實現各類目的而在各個用戶之間實現內存共享。內存管理方法應該實現如下兩個功能:
最小化管理內存所需的時間
最大化用於通常應用的可用內存(最小化管理開銷)
內存管理其實是一種關於權衡的零和遊戲。您能夠開發一種使用少許內存進行管理的算法,可是要花費更多時間來管理可用內存。也能夠開發一個算法來有效地管理內存,但卻要使用更多的內存。最終,特定應用程序的需求將促使對這種權衡做出選擇。
每一個內存管理器都使用了一種基於堆的分配策略。在這種方法中,大塊內存(稱爲 堆)用來爲用戶定義的目的提供內存。當用戶須要一塊內存時,就請求給本身分配必定大小的內存。堆管理器會查看可用內存的狀況(使用特定算法)並返回一塊內存。搜索過程當中使用的一些算法有first-fit(在堆中搜索到的第一個知足請求的內存塊 )和 best-fit(使用堆中知足請求的最合適的內存塊)。當用戶使用完內存後,就將內存返回給堆。
這種基於堆的分配策略的根本問題是碎片(fragmentation)。當內存塊被分配後,它們會以不一樣的順序在不一樣的時間返回。這樣會在堆中留下一些洞,須要花一些時間纔能有效地管理空閒內存。這種算法一般具備較高的內存使用效率(分配須要的內存),可是卻須要花費更多時間來對堆進行管理。
另一種方法稱爲 buddy memory allocation,是一種更快的內存分配技術,它將內存劃分爲 2 的冪次方個分區,並使用 best-fit 方法來分配內存請求。當用戶釋放內存時,就會檢查 buddy 塊,查看其相鄰的內存塊是否也已經被釋放。若是是的話,將合併內存塊以最小化內存碎片。這個算法的時間效率更高,可是因爲使用 best-fit 方法的緣故,會產生內存浪費。
本文將着重介紹 Linux 內核的內存管理,尤爲是 slab 分配提供的機制。
Linux 所使用的 slab 分配器的基礎是 Jeff Bonwick 爲 SunOS 操做系統首次引入的一種算法。Jeff 的分配器是圍繞對象緩存進行的。在內核中,會爲有限的對象集(例如文件描述符和其餘常見結構)分配大量內存。Jeff 發現對內核中普通對象進行初始化所需的時間超過了對其進行分配和釋放所需的時間。所以他的結論是不該該將內存釋放回一個全局的內存池,而是將內存保持爲針對特定目而初始化的狀態。例如,若是內存被分配給了一個互斥鎖,那麼只需在爲互斥鎖首次分配內存時執行一次互斥鎖初始化函數(mutex_init
)便可。後續的內存分配不須要執行這個初始化函數,由於從上次釋放和調用析構以後,它已經處於所需的狀態中了。
Linux slab 分配器使用了這種思想和其餘一些思想來構建一個在空間和時間上都具備高效性的內存分配器。
圖 1 給出了 slab 結構的高層組織結構。在最高層是 cache_chain
,這是一個 slab 緩存的連接列表。這對於 best-fit 算法很是有用,能夠用來查找最適合所須要的分配大小的緩存(遍歷列表)。cache_chain
的每一個元素都是一個 kmem_cache
結構的引用(稱爲一個 cache)。它定義了一個要管理的給定大小的對象池。
圖 1. slab 分配器的主要結構
每一個緩存都包含了一個 slabs 列表,這是一段連續的內存塊(一般都是頁面)。存在 3 種 slab:
slabs_full
徹底分配的 slab
slabs_partial
部分分配的 slab
slabs_empty
空 slab,或者沒有對象被分配
注意 slabs_empty
列表中的 slab 是進行回收(reaping)的主要備選對象。正是經過此過程,slab 所使用的內存被返回給操做系統供其餘用戶使用。
slab 列表中的每一個 slab 都是一個連續的內存塊(一個或多個連續頁),它們被劃分紅一個個對象。這些對象是從特定緩存中進行分配和釋放的基本元素。注意 slab 是 slab 分配器進行操做的最小分配單位,所以若是須要對 slab 進行擴展,這也就是所擴展的最小值。一般來講,每一個 slab 被分配爲多個對象。
因爲對象是從 slab 中進行分配和釋放的,所以單個 slab 能夠在 slab 列表之間進行移動。例如,當一個 slab 中的全部對象都被使用完時,就從slabs_partial
列表中移動到 slabs_full
列表中。當一個 slab 徹底被分配而且有對象被釋放後,就從 slabs_full
列表中移動到slabs_partial
列表中。當全部對象都被釋放以後,就從 slabs_partial
列表移動到 slabs_empty
列表中。
與傳統的內存管理模式相比, slab 緩存分配器提供了不少優勢。首先,內核一般依賴於對小對象的分配,它們會在系統生命週期內進行無數次分配。slab 緩存分配器經過對相似大小的對象進行緩存而提供這種功能,從而避免了常見的碎片問題。slab 分配器還支持通用對象的初始化,從而避免了爲同一目而對一個對象重複進行初始化。最後,slab 分配器還能夠支持硬件緩存對齊和着色,這容許不一樣緩存中的對象佔用相同的緩存行,從而提升緩存的利用率並得到更好的性能。