1. 程式人生 > >Erlang:RabbitMQ原始碼分析 4. file_handle_cache實現分析

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裡即返回。