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的实现:
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里即返回。