Erlang:RabbitMQ原始碼分析 4. file_handle_cache實現分析
RabbitMQ的檔案操作使用file_handle_cache,將Erlang的prim_file Module包了一層。增加了writeBuffer和對檔案開啟數量的控制邏輯
file_handle_cache也是一個gen_server2,但一般來說只有Open操作會send message to gen_server2, 讀寫操作,包括writeBuffer都在Client Process裡執行和維護。
file_handle_cache裡儲存了幾張表:
1. Elders: {Pid, EldestUnusedSince}, 儲存每個client的pid 和其最老的Handle開啟時間, 沒有key
2. Clients: #cstate,儲存每個client的資訊,key是pid
在file_handle_cache中可以看出,記憶體資料在Erlang中的儲存無非於幾種:
1. ETS表,例如Elders, Clients; 適合儲存一些集中的資訊,不同的Process都能訪問
2. Process Dictionary,本Process的資訊,例如file_handle_cache中client Process的file和handle
3. gen_server的state,放在loop state裡不斷迴圈反覆
下面我們從四個最基本的檔案操作函式看一下file_handle_cache的實現:
open:
1. file_handle_cache對於同一個檔案是共享讀但不共享寫的,所以在client的Process Dictionary裡儲存了<key = {Path, fhc_file}, value = #file{ reader_count, has_writer}>,當open時如果發現has_writer = TRUE, open的model又是write,就直接返回{error, writer_exists};
2. 新建一個closed的Handle,Handle相關的資訊儲存在client的Process Dictionary裡 <key = {Ref, fhc_handle}, value = #handle>
3. file_handle_cache在client的Process Dictionary裡儲存裡一個gb_tree,用來儲存這個client開啟的所有Handle的開啟時間。這個時間是用來調整Client Open的Handle個數。
1-3都是在client的Process裡做的
4. gen_server:call open, 進入到file_handle_cache的Process loop裡 , 當發現file_handle_cache開啟的檔案數不超過使用者預設的檔案數時,就update Clients table 和State,返回 OK。
5. 回到client Process裡,呼叫prim_file:open開啟檔案,所以開啟檔案的操作實際上市在Client Process裡做的。
6. 如果4中發現file_handle_cache開啟的檔案數超過使用者預設的檔案數時,
6.1 如果這個pid已經打開了很多檔案,就返回close, client接到close後就soft_close所有之前開啟的Handle,所謂軟關閉,就是將Handle對應檔案buffer寫完,sync後再關閉。軟關閉所有之前開啟的Handle後再重新試圖open 這個Handle。
6.2 如果這個pid沒有開啟任何檔案,就對其他的pid下手,計算每個pid的最老時間(最早的開啟的檔案時間),累加算平均值。
一般來說client在開啟檔案前會call register_callback 註冊一個清理函式
6.2.1 如果平均值大於兩秒,說明有很多老檔案還未關閉, 就對每個client call 清理函式。引數是最老時間的平均值。如果沒有註冊清理函式就不管。
6.2.1 如果平均值小於兩秒,說明沒有很多未關閉的老檔案。找到所有註冊清理函式的pid, 假設有N個Open被Block,就用其中N個清理函式去清理N個已開啟的Handle
close:
1. 刪掉ProcessDictionary 裡的{Ref, fhc_handle}
2. Handle的writebuffer 要prim_file:write
3. 如果之前有write沒有sync,call prim_file:sync
4. call prim_file:close
5. 將這個Handle的時間從gb_tree裡刪掉
6. 更新Process Dictionary裡的{Path, fhc_file}, read_count, write_count之類
read: read本身沒有什麼特殊的,也沒有用buffer
1. 雖然已經Open過,但有可能因為開啟檔案過多被close,所以還是看下,如果被close了要reopen一下
2. Handle的writebuffer要prim_file:write
3. 呼叫prim_file:read得到結果
append:
1. 和read一樣,也是有可能會reopen。
2. 如果client在Open時沒有設定WriteBuffer,就直接call prim_file:write(Hdl, Data)了事
3. 所謂的Writebuff是針對於單個Handle的,即Open時搞一個空的List來做Buffer,append時如果BufferSize > Client Open時設定的limit,就call prim_file:write把Buffer都寫到file裡,如果BufferSize < limit,就只是將Data放到Buffer裡即返回。