memcached 原始碼分析總結
l執行緒模型/事件處理框架:
n採用了半同步/半非同步執行緒模型,主執行緒用於accept連線,將接到的連線分派到子執行緒去處理,分派演算法(輪轉演算法):
last_thread % (settings.num_threads - 1);
n這裡的佇列採用了一種巧妙的方法來設計,主要體現在接收者和傳送者的同步上,採用了管道來作為傳送者通知接受者的渠道。另外,我們也可以通過條件變數來作為同步機制,採用管道程式設計簡單,但是,這種重量級的通訊機制,一般用在程序間。條件變數,一般用線上程間,輕量級的,程式設計難度較大。
n事件處理採用了libevent,每一個執行緒run一個event loop。
l
l設定一個timer,每秒鐘更新一下相對時間(系統當前時間-memcached啟動時間),這個時間用於作為slab中的item是否expire的標準。
lMemcached內部使用了狀態機,從連線建立到一個命令處理完成,狀態機轉換如下
nListening (監聽網路連線請求)
nconn_new_cmd(準備處理請求)
nconn_waiting(等待從socket讀取資料)
nconn_read/conn_nread(從socket讀取資料)
nconn_parse_cmd(解析命令並處理)
nconn_mwrite/conn_write(向客戶端回response)
nconn_new_cmd(準備處理下一個命令)
l命令:
nadd/set/replace/prepend/append
nget/bget
ncas
nincr
ngets
ndelete
ndecr
nstats
nflush_all
nversion
nquit
nslabs/reassign
l記憶體管理
這一部分是memcached的核心所在,我將做一個詳細的介紹,首先來看看memcached記憶體管理的層次:
1.整個memcached的記憶體管理是基於使用者態的,不涉及核心態。
2.在作業系統使用者態之上,
3.memcached會建立自己的記憶體管理機制slab機制,之後將會詳細講到。
4.在slab之上,會有一個快取記憶體,memcached在進行記憶體分配的時候,首先會從快取記憶體中進行分配,如果沒有,才會訪問slab,從slab中進行分配,我們稱這個告訴快取為slot。
對於上面提到的幾大塊,slab management這一塊是相對來說最複雜的,下面會做一個詳細介紹,首先看看下面這幅示意圖:
這幅圖示意了memcached記憶體管理中的slab快取。
1.在memcached中將會維護一個slab class的集合(陣列),每一個slab class對應著一類slab。當對應的這一類slab資源不足時,memcached將會為這類slab進行擴充。
2.每一個slab對應著一組item,item就是我們要存放資料的地方,我們的資料將會被存放在item中。
3.如何快速定位使用者資料呢?很簡單,memcached採用了一個hash表,該hash表的key的含義是,對應的item大小((size=sizeof(item) + chunck size)*factor,遞增因子為factor,factor和chunck size都是可配置的)。
4.一個Item的結構,包括頭,關鍵字,字尾和使用者資料。字尾用於存放一些附加資料,舉一個例子:
比如,我執行一條set命令
set abc 0 60 10
1234567890
DATA:1234567890
SUFIX:0 60 10
KEY:abc
5.介紹一個重要的資料結構,這個資料結構在slab的分配中起著很重要的作用。
typedef struct {
unsigned int size; 一個item的大小
unsigned int perslab; 一個slab中可容納幾個item
void **slots; item快取記憶體,每次分配,首先考慮這裡的
unsigned int sl_total; slot的個數
unsigned int sl_curr; 第一個可用於分配的slot的位置
void *end_page_ptr; 在一個slab中,第一個未被分配的item的地址
unsigned int end_page_free; 在一個slab中,未被分配的item的個數
unsigned int slabs; 一個slab class有多少個slab
void **slab_list; 指向一組slab
unsigned int list_size; slab陣列大小
} slabclass_t;
6.介紹幾個重要的記憶體分配函式的實現:
a)do_item_alloc
i.根據item的大小,獲取hash key。
ii.遍歷hash key所對應的item連結串列,有expire的將其回收(unlink)
iii.從slab中,嘗試分配一個item,如果失敗,重複第二步,嘗試回收expire的item,重複兩遍這樣的嘗試,如果仍然失敗,那麼記憶體分配宣告失敗。
b)do_item_free
i.將item返還給slab快取。
c)do_item_unlink
i.從item hash表中刪除關聯
ii.將item從slab中的雙向連結串列中斷鏈。
iii.引用計數器如果為0,則將item歸還給slab快取。
d)do_item_link
i.建立item同item hash表的關聯。
ii.將item鏈入slab對應的雙向連結串列中。
e)do_slab_alloc
i.根據hash key找到對應的slab class。
ii.檢查是否需要在slab class中增加一個slab。如果要,則增加一個slab。
iii.先從slab class對應的slot陣列中分配item,如果分配失敗,在考慮從slab中分配item。
f)do_slab_free
i.根據hash key找到對應的slab class。
ii.將item歸還到slot陣列中,這是item的快取記憶體,為了減少對slab的訪問,提高分配效率而設定的.
7. CAS(check and set):採用了樂觀鎖機制,主要是為了解決多個客戶端併發修改同一條記錄的問題。
8. Item的淘汰演算法使用了LRU演算法。
lHA (主備)
一般來說cache是不用做主備的,因為就算cache死掉了,還可以從資料來源中找到。Cache只是存放資料來源中的拷貝。這些cached data被分佈地快取到整個cluster中,應用程式可以用key計算出它所對應的資料快取在哪臺server上的cache。比如,key=99,現在有三臺server,資料被快取到這三臺server中,key%3就可以找到key所對應的記錄被快取到哪臺server了。一般會採用下面的部署方式: