MemLink詳細設計文檔

 DesignDocument  
MemLink詳細設計文檔

 

 

1 工作方式

MemLink是一個獨立的服務程序,類似於memcached,不同的是它並不是提供key-value數據的存儲,而是在內存中存儲的是列表數據。

2 具體結構

這裏我們借用了memcached的網絡框架,在他的基礎上來增加我們想要的列表功能。見下圖:

在網絡處理部分,我們使用memcached的代碼。他是專門用一個線程來接受連接,然後把連接給各個處理線程。我們自己的列表功能就體現在處理線程裏面。這裏每個列表項都有一個名字,也就是一個唯一的列表名字對應一個列表數據,我們使用hash來存儲。列表數據部分(圖中紅色框部分)是我們真實的數據存儲結構。任何的對列表數據的寫操作都必須要獲得寫鎖,寫操作和dump的操作不能同時進行。

線程工作方式

目前,一共有4種線程。分別爲讀線程,寫線程,同步線程,dump線程。讀線程是可以有多個的。其他的線程都只有一個。寫線程用來完成數據的修改,同步線程專門用來主從同步。Dump線程用來dump數據到硬盤。寫線程和同步線程分別對應有一個網絡事件處理機制來接受網絡連接,需要監聽獨立的端口,他們的處理操作由網絡請求來觸發。而dump線程由時間來觸發,動態產生一個線程來處理。

Hash

圖中的藍色框部分爲hash表的數據結構。Hash採用的是鏈表法。Hash的數據節點包含4個數據,在圖中是 key, used, all count, data。其中key爲列表名,used爲該列表實際消耗了多少存儲(這裏是計算數據塊的使用情況,比如總共可以存儲數據1000個,使用了342個,那麼就是342),all count爲實際分配了多少存儲,data爲實際數據的指針。

數據存儲

紅色框爲我們列表實際的數據結構。我們並不是每次爲1個數據分配一塊內存,然後鏈接起來。而是每次分配一定數據量的連續塊(假設我們每次分配100塊的連續數據,實際上我們可以配置的)。同時,在這一個連續塊中還有兩個額外的數據,分別爲“已經使用非標記刪除的數量(無符號2字節短整型)”和“指向下一個數據塊的指針(指針)”。在每個內部的小數據區中,包括一個mask佔用的空間和實際的數據佔用的空間。Mask用來標示數據的屬性。 Mask中有一個保留位,用來表示標記刪除或者標記恢復。

數據操作

對數據塊的操作,我們的原則是寫操作儘量不影響讀操作,這樣就要避免數據塊內的數據移動,取而代之的是拷貝數據到新的數據塊。目前有以下幾種涉及寫的操作:

插入一個數據

如果是在列表的頭部插入,那就是操作數據鏈中第一塊,第一塊內當前位置有空就可以插入,否則需要創建出一個新數據塊,把數據寫入其中最後一個位置,然後在作爲頭部鏈入數據鏈中。寫入最後一個位置的目的是讓下次插入頭部就不需要再創建新數據塊了。如下圖:

如果是在數據鏈的中間插入,如果當前位置爲空,那麼直接插入數據。如果不爲空,那麼我們要新分配一個數據塊,將當前要插入的數據塊中的數據拷貝到新塊,如果新數據塊不能完全存儲(因爲插入了一個),那麼就要再分配一個新數據塊,把溢出的數據放到這個數據塊中,然後鏈接入數據鏈。見下圖:

由於有新的數據塊替代了原來的數據塊。原來的數據塊要被釋放掉,但不是馬上釋放,而是加入一個鏈表。此鏈表保存所有要示範班的數據塊,當以後我們要新分配數據塊的時候,我們就從裏面取。

刪除一個數據

刪除數據只把數據所在的位置0,並且修改該數據塊的“已使用數量”以及hash節點中的“總使用數量”

修改一個數據的mask

Mask是數據的屬性,直接修改即可。

Mask是數據的屬性,直接修改即可

修改位置實際上是刪除數據,以及新插入數據到某個位置。

讀操作

讀操作,這裏主要就是取列表中的某個範圍的數據。還有一個mask條件。讀操作分兩種情況,一是mask爲空,二是mask爲其他值的讀。如果爲空,那麼從每個數據塊中的記錄該塊使用數量,可以很容易的計算出這個範圍涉及到那幾個數據塊,直接到這幾個數據塊中讀取就可以了。如果不是空就只能挨個判斷,全部循環一遍,直到得到這個範圍的數據。

數據存儲空間的回收

由於我們在刪除數據的時候只是把那個位置給置0,所以會出現一些空間浪費。運行時間越長,空間的浪費就越嚴重。因此,我們需要在一定的時候對存儲空間重排,讓數據更加緊湊,以去除空間中的空洞。 觸發這個動作的條件是,空間的利用率小於等於50%(這個值可以配置)。這個檢測在每次寫操作的時候進行。計算方法是hash節點中的 已使用存儲數/分配的存儲數 這個結果和50%來比較。一旦符合條件這個寫操作完成後就馬上進行空間的重排。但並不是一次性就把整個數據鏈全部重排,而是分批進行,一次進行100個數據塊(這個值可以配置)。剩下的塊由event調度,在下次事件循環的時候接着進行。

重排的方法是這樣,我們分配出新的數據塊,然後把老的100個全部拷貝到新的數據塊中,完成後把數據鏈重新鏈起來。老的數據塊重新鏈接到一個空閒鏈表中,等待下次分配後再使用。下圖用兩個數據塊做了個簡單說明:

數據dump

所有列表數據會定時dump一份到硬盤上。這個過程由一個線程來獨立進行。在dump之前,需要在持有寫鎖,這樣侯不允許同時還有寫操作。Dump完成後要生成新的日誌文件。流程如下圖:

Dump文件名dump.dat。在dump過程中,並不直接dump到這個文件,而是dump到名爲dump.dat.tmp的文件,當dump過程成功完成後,才把文件名改爲dump.dat。 Dump文件的格式爲二進制的格式:

 

Dump格式版本號(2字節) Dump文件版本號(4字節) 對應的log版本(4字節) 是否爲master生成(1字節) dump文件大小(8字節) 數據區

 

前2字節是dump格式版本號,無符號短整型,現在默認爲1,每次格式變更才修改此值。Dump文件版本號在每次dump文件生成都要自增1後面4個字節爲log日誌版本,是個無符號整型。數據區,是由所有的列表項緊挨着組成,其中一個列表項格式如下,整個數據區就是由N個列表項組成:

 

Key長度(1字節) Key Value長度(1字節) Mask長度(1字節) Mask項數(1字節) Mask格式(長度由mask項數決定) Mask Value數量(4字節) Value Mask

 

3 日誌記錄

日誌記錄方式爲記錄所有的寫操作的日誌。在寫操作完成的時候記錄,記錄成功後纔給客戶端返回成功。

日誌文件名爲bin.log.xxx。其中的xxx爲自增的版本號。日誌文件在dump操作完成後會新生成一個日誌文件,文件名中版本號自動加一。如果日誌文件中的索引號用完,也會新生成一個日誌文件。

日誌文件格式如下:

 

日誌格式版本(2字節) 日誌文件版本號(4字節) 是否爲master生成(1字節) 日誌塊數(4字節) 索引區 數據區

 

日誌格式版本爲2字節無符號整型,目前默認爲1,只有格式變更才修改此值。日誌文件版本,在每次新生成log文件的時候都自增1。日誌塊數表示裏面可以記錄的日誌的命令數,其實就是索引區的大小。索引區爲日誌索引號,這裏面是一個很大的數組,剛開始就全部創建出來,現在我們認爲默認有10萬個數組項,每個項是四字節,裏面存儲的是日誌命令在此文件中的偏移。數據區就是實際的命令。

數據區記錄的是一條條命令。開頭爲整個命令的長度,然後是命令編號,最後是命令參數。命令參數中的字符串表示爲: 1字節字符串長度+字符串數據。數字型直接表示。參數中的value比較特別,它有可能是字符串或者整型,所以在值的前面還有1個字節表示類型。(詳細命令協議看下面的“客戶端協議描述”)數據區格式如下:

 

數據長度(2字節) 命令編號(1字節) 命令參數

 

涉及寫操作的命令有以下幾個,他們會記錄到日誌文件中:

 

命令 命令參數 編號 說明 例子
Create Key valuelen mask 4 Valuelen爲1字節,mask用1個字節項數+N字節數據。 Create haha 1 4:3:2 表示爲 \x0c\x00\x04\x04haha\x01\x03\x04\x03\x04
Del Key value 5 Value前面要有一個1字節表示類型 Del haha vvv 表示爲 \x0b\x00\x05\x04haha\x01\x03vvv
Insert Key value pos 6 Pos爲4字節 Insert haha gogo 1 表示爲 \x10\x00\x06\x04haha\x01\x04gogo\x02\x00\x00\x00
Update Key value pos 7 Pos爲4字節 Update haha aaa 2 表示爲 \x0f\x00\x07\x04haha\x01\x03aaa\x02\x00\x00\x00
Mask Key value mask 8 Mask爲1字節 Mask haha bbb 3 表示爲 \x0c\x00\x08\x04haha\x01\x03bbb\x03
Tag Key value del/reverse 9 Del用1表示,reverse用0表示。1字節 Tag haha xxx1 del 表示爲 \x0d\x00\x09\x04haha\x01\x04xxx1\x01

 

以上表格中的命令都會被記錄在日誌文件中。

日誌需要在server啓動時進行格式效驗。確認日誌的完整性。效驗方法爲:

  • 讀取日誌塊數
  • 根據日誌塊數,可以確定索引區有多少索引。找到索引區的最後一個索引。
  • 根據索引區最後一個索引,找到對應的數據區。
  • 跳過最後這個索引指向的數據區,判斷後面是否還有數據。如果有數據,通過數據的前面兩個字節,我們可以知道這個數據塊有多大,這樣就可以確定數據是否完整。如果是完整的,就添加索引到索引區。如果不完整就清除掉。

4 主從同步

MemLink支持一個主,多個從的同步模式。主和從的區別:

 

功能
讀請求 yes yes
寫請求 yes no
dump請求 yes yes
更新log yes yes
同步 同步線程監聽從的同步請求 同步線程和主建立連接,接收同步數據

 

從的同步線程流程:

主的同步線程流程:

命令協議爲二進制交互協議,和其他命令的方法一樣。

具體的交互命令如下:

1. sync {log version} {log line}

發送數據的格式爲:

 

長度(2字節) 命令編號(1字節) log version(4字節) log line(4字節)

 

描述:表示同步數據。長度爲後面所有數據的長度(字節數),比如這裏長度應該是9。Sync的命令編號爲100,log version爲主上log版本號,4字節整型,log line爲行號,也是4字節整型。

返回數據的格式如下:

 

長度(4字節)  返回值(2字節)

 

長度爲後面所有數據的長度(字節數),這裏應該是2。如果返回值爲0,主會開始向從發送log記錄。從把接收的log記錄應用到hash table上,然後把log記錄保存到本地的log文件。從的log文件的每條記錄前都保存主的log version和log line。

如果返回值爲1,表示從需要發送dump請求。這時候需要使用getdump的命令還獲取dump文件。

2. getdump {dumpversion} {size}

發送命令數據格式:

 

長度(2字節) 命令編號(1字節) dump version(4字節) size (4字節)

 

描述:getdump命令編號爲101。Dumpversion爲dump版本號,是4字節,size爲已同步文件大小,爲8字節。

返回數據格式:

 

長度(4字節) 返回值(2字節) dump version (4字節) dump size (8字節)

 

返回:是否是原dump文件,1表示是,0表示不是,需要1字節。如果爲1,主接着發送原dump文件。如果爲0,主開始發送新的dump文件。

如果需要發送dump文件,主緊接着開始發送dump文件。

在dump文件期前,主工作的流程:

  1. 打開dump文件。
  2. 讀取併發從固定長度的數據(例如1024字節)直到dump文件結束。

 

在dump文件期前,從工作的流程:

  1. 把主從dump文件同步標誌置爲1。
  2. 創建一個dump文件。
  3. 不斷把從主接收到的數據寫入dump文件。

 

從在接收完dump數據後,更新hash table, 生成新的log文件。

在發送完dump文件之後,主開始向從發送log記錄。

我們可以通過tcp keep-alive來保持主從同步用的TCP連接。

5 客戶端協議描述

客戶端協議是指使用者用php,c等語言寫的程序連接上MemLink進行操作使用的協議。我們這裏支持的是文本方式的協議,基本結構類似memcached的客戶端協議。協議的基本結構如下:

命令 參數1 參數2 參數3\r\n value數據

開頭要以命令開頭,命令都爲小寫英文字母,然後有一個空格,後面跟的是此命令的參數,根據命令的不同,可能參數的數目也是不同的,他們都以一個空格分隔開。最後是\r\n。最後的value數據緊跟\r\n,該項只有在命令中有value的時候才存在。在命令中value只提供一個長度,對應就是後面value數據的長度。

服務器端的應答也是純文本,結構如下:

狀態 [描述字符串]\r\n

狀態表示命令執行結果,有這些返回可能:

 

狀態 說明
200 表示命令成功執行
300 表示客戶端命令格式有問題
400 表示服務器端臨時錯誤,可以重試
500 表示服務器端處理有問題
600 len 表示後面還有數據,數據的長度爲len。

 

狀態後面的描述字符串爲人可讀的一個描述字符串,是可選的(600沒有描述字符串)。狀態和描述字符串以一個空格分隔。

具體我們支持的命令如下: 1. Dump

描述:讓MemLink立即把內存中的數據存儲一份到硬盤

2. Clean key

描述:立即開始回收該key對應的鏈表中的內存

3. Stat key

描述:顯示統計信息。後面的key這個參數是可選的,如果有,表示顯示該key 下對應的鏈表的使用情況。

如果不帶參數返回:

 

返回項 類型 說明
count 整型 存儲的所有項
All_block 整型 所有分配額塊數
All_data 整型 所有分配的數據塊可容納多少數據
all_mem 整型 所有分配的內存
used_mem 整型 已使用的內存

 

帶參數key返回:

 

返回項 類型 說明
count 整型 該key下的data數
All_block 整型 所有分配的塊數
All_data 整型 所有分配的數據塊可容納多少數據
All_mem 整型 所有分配的內存
Used_mem 整型 所有使用的內存

 

4. Create key valuelen maskformat

描述:創建一個列表項,key爲列表項名字。valuelen爲數據塊中存儲的每個value的大小,字符串就是它的長度,整型爲0。Maskformat爲mask的格式。表示方式爲xx:xx:xx。是用:分隔的多個數字,每個數字表示佔用幾bit。比如1:4:3表示mask分爲三部分,第一部分1bit,第二部分4bit,第三部分3bit。

5. Del key value

描述:真實刪除一個key下的某一項。注意這裏的value只是value的長度,數據跟在命令\r\n後面的。以下的命令都一樣。

6. Insert key value mask pos

描述:插入一條新的條目。Pos爲新插入條路在列表中的位置。0表示第一位,以此類推。

7. Update key value pos

描述:更新一個條目的位置。key爲列表名,value爲該條目的名字,pos爲新的位置。

8. Mask key value mask

描述:修改一個條目對應的掩碼。Mask表示掩碼修改值,格式爲xx:xx:xx。是用:分隔的多

9. Tag key value del/reverse

描述:對數據做標記。Del表示標記刪除。Reverse表示標記恢復。

10. Range key mask frompos len

描述:取對應的某個key下,某個範圍的條目。Key爲列表名,mask爲條件,frompos爲要取的開始位置,len爲取的長度。Mask的表示方式爲xx:xx:xx,這個和create的定義裏面的mask是對應的。Xx表示條件值,必須和這個值相等,才符合條件。如果對應的位置沒有條件,那就爲空。比如第二個位置無條件,那麼寫爲xx::xx。 返回數據格式:

6 命令編號

所有客戶端命令以及同步命令,對應都有一個命令編號,如下:

 

命令 編號
Dump 1
Clean 2
Stat 3
Create 4
Del 5
Insert 6
Update 7
Mask 8
Tag 9
range 10
Sync 100
Getdump 101

 

7 啓動流程

下面是server啓動要執行的一些流程:

8 配置信息

程序應該可以配置項有如下幾個

 

名稱 類型 描述 默認值
block_data_count 整型 數據塊的容量 100
block_clean_cond 浮點數 數據重排條件 0.5
dump_interval 整型 Dump數據間隔時間,單位爲分鐘 60
read_port 整型 讀線程佔用端口 11001
write_port 整型 寫線程佔用端口 11002
sync_port 整型 同步線程佔用端口 11003
data_dir 字符串 Dump和log數據文件目錄  
log_level 整型 日誌輸出等級:0-3。 0:無日誌 1:只輸出錯誤 2:只輸出錯誤和警告 3:只輸出錯誤,警告和普通信息 3
timeout 整型    
role 整型 節點角色:1表示主;0表示從  
master_addr 字符串 主IP地址  
sync_interval 整型 主發送給從更新log的時間間隔,單位是毫秒  

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章