1. 程式人生 > >memcached 原始碼分析總結

memcached 原始碼分析總結

l執行緒模型/事件處理框架:

n採用了半同步/半非同步執行緒模型,主執行緒用於accept連線,將接到的連線分派到子執行緒去處理,分派演算法(輪轉演算法):

last_thread % (settings.num_threads - 1);

n這裡的佇列採用了一種巧妙的方法來設計,主要體現在接收者和傳送者的同步上,採用了管道來作為傳送者通知接受者的渠道。另外,我們也可以通過條件變數來作為同步機制,採用管道程式設計簡單,但是,這種重量級的通訊機制,一般用在程序間。條件變數,一般用線上程間,輕量級的,程式設計難度較大。

n事件處理採用了libevent,每一個執行緒run一個event loop

l

Hash表維護執行緒:用於以2的冪次expanding hash table,這個Hash表用於快速檢索item的。

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.在作業系統使用者態之上,

memcached為了減少系統呼叫,所以事先劃分出了一塊很大的記憶體用於後續的記憶體分配。

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對應著一組itemitem就是我們要存放資料的地方,我們的資料將會被存放在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

DATA1234567890

SUFIX0 60 10

KEYabc

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,如果失敗,重複第二步,嘗試回收expireitem,重複兩遍這樣的嘗試,如果仍然失敗,那麼記憶體分配宣告失敗。

b)do_item_free

i.item返還給slab快取。

c)do_item_unlink

i.item hash表中刪除關聯

ii.itemslab中的雙向連結串列中斷鏈。

iii.引用計數器如果為0,則將item歸還給slab快取。

d)do_item_link

i.建立itemitem 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了。一般會採用下面的部署方式: