На моих серверах HP Proliant запущено приложение с высокой степенью одновременности. Приложение - это индексатор файловой системы, который я написал на erlang. Он порождает процесс для каждой папки, найденной в файловой системе, и записывает все пути к файлам во фрагментированной базе данных Mnesia. (База данных состоит из таблиц disc_only_copies
и снимок экрана ее файловой системы можно просмотреть здесь .)
Фрагмент кода, который выполняет интенсивную работу по прохождению файловой системы, показан ниже:
%%% -------- COPYRIGHT NOTICE --------------------------------------------------------------------
%% @author Muzaaya Joshua, <joshmuza@gmail.com> [http://joshanderlang.blogspot.com]
%% @version 1.0 free software, but modification prohibited
%% @copyright Muzaaya Joshua (file_scavenger-1.0) 2011 - 2012 . All rights reserved
%% @reference <a href="http://www.erlang.org">OpenSource Erlang WebSite</a>
%%
%%% ---------------- EDOC INTRODUCTION TO THE MODULE ----------------------------------------------
%% @doc This module provides the low level APIs for reading, writing,
%% searching, joining and moving within directories.The module implementation
%% took place on @date at @time.
%% @end
-module(file_scavenger_utilities).
%%% ------- EXPORTS -------------------------------------------------------------------------------
-compile(export_all).
%%% ------- INCLUDES -----------------------------------------------------------------------------
%%% -------- MACROS ------------------------------------------------------------------------------
-define(IS_FOLDER(X),filelib:is_dir(X)).
-define(IS_FILE(X),filelib:is_file(X)).
-define(FAILED_TO_LIST_DIR(X),error_logger:error_report(["*** File Scavenger Utilities Error ***** ",{error,"Failed to List Directory"},{directory,X}])).
-define(NOT_DIR(X),error_logger:error_report(["*** File Scavenger Utilities Error ***** ",{error,"Not a Directory"},{alleged,X}])).
-define(NOT_FILE(X),error_logger:error_report(["*** File Scavenger Utilities Error ***** ",{error,"Not a File"},{alleged,X}])).
%%%--------- TYPES -------------------------------------------------------------------------------
%% @type dir() = string().
%% Must be containing forward slashes, not back slashes. Must not end with a slash
%% after the exact directory.e.g this is wrong: "C:/Program Files/SomeDirectory/"
%% but this is right: "C:/Program Files/SomeDirectory"
%% @type file_path() = string().
%% Must be containing forward slashes, not back slashes.
%% Should include the file extension as well e.g "C:/Program Files/SomeFile.pdf"
%% -----------------------------------------------------------------------------------------------
%% @doc Enters a directory and executes the fun ForEachFileFound/2 for each file it finds
%% If it finds a directory, it executes the fun %% ForEachDirFound/2.
%% Both funs above take the parent Dir as the first Argument. Then, it will spawn an
%% erlang process that will spread the found Directory too in the same way as the parent directory
%% was spread. The process of spreading goes on and on until every File (wether its in a nested
%% Directory) is registered by its full path.
%% @end
%%
%% @spec spread_directory(dir(),dir(),funtion(),function())-> ok.
spread_directory(Dir,Top_Directory,ForEachFileFound,ForEachDirFound) when is_function(ForEachFileFound),is_function(ForEachDirFound) ->
case ?IS_FOLDER(Dir) of
false -> ?NOT_DIR(Dir);
true ->
F = fun(X)->
FileOrDir = filename:absname_join(Dir,X),
case ?IS_FOLDER(FileOrDir) of
true ->
(catch ForEachDirFound(Top_Directory,FileOrDir)),
spawn(fun() -> ?MODULE:spread_directory(FileOrDir,Top_Directory,ForEachFileFound,ForEachDirFound) end);
false ->
case ?IS_FILE(FileOrDir) of
false -> {error,not_a_file,FileOrDir};
true -> (catch ForEachFileFound(Top_Directory,FileOrDir))
end
end
end,
case file:list_dir(Dir) of
{error,_} -> ?FAILED_TO_LIST_DIR(Dir);
{ok,List} -> lists:foreach(F,List)
end
end.
Функция spread_directory/4
является общей в том смысле, что она занимает два funs
. Одно удовольствие: ForEachFileFound/2
берет вместе с каталогом Top Most, найденным файлом и делает с ним что угодно, а другое удовольствие: ForEachDirFound/2
берет вместе с каталогом Top Most, папкой, которую он находит, и использует его любым способом, каким захочет .
Скрипт запуска, который я использую для этого приложения, гарантирует, что erlang сможет порождать как можно больше процессов. Как только процесс завершает индексирование папки, он выходит.
#!/usr/bin/env sh
echo "Starting File Scavenger System. Layer 1 on the P2P File Sharing System....."
erl \
-name file_scavenger@127.0.0.1 \
+P 13421779 \
-pa ./ebin ./lib/*/ebin ./include \
-mnesia dir '"./database"' \
-mnesia dump_log_write_threshold 10000 \
-eval "application:load(file_scavenger)" \
-eval "application:start(file_scavenger)"
Существует gen_server, который связывает интенсивный модуль с базой данных, в которую я записываю все пути. Ниже приведен фрагмент, где начинается работа spread_directory:
handle_cast(index_dirs,#scavenger{directory_paths = Dirs} = State)->
{File,Folder} = case {State#scavenger.verbose,State#scavenger.verbose_to} of
{true,tty} ->
{
fun(TopDir,Fl)->
io:format(" File: ~p~n",[Fl]),
file_scavenger_database:insert_file(filename:basename(Fl),file,Fl,TopDir,filename:extension(Fl))
end,
fun(TopDir,Fd) ->
io:format(" Folder: ~p~n",[Fd]),
file_scavenger_database:insert_file(Fd,folder,Fd,TopDir,undefined)
end
};
{true,SomeFile}->
{
fun(TopDir,Fl)->
os:cmd("echo File: " ++ Fl ++ " >> " ++ SomeFile),
file_scavenger_database:insert_file(filename:basename(Fl),file,Fl,TopDir,filename:extension(Fl))
end,
fun(TopDir,Fd)->
os:cmd("echo Folder: " ++ Fd ++ " >> " ++ SomeFile),
file_scavenger_database:insert_file(Fd,folder,Fd,TopDir,undefined)
end
}
end,
Main = fun(Dir) ->
error_logger:info_msg("*** File scavenger Server indexing directory: ~p~n",[Dir]),
spawn(fun() -> file_scavenger_utilities:spread_directory(Dir,Dir,File,Folder) end)
end,
lists:foreach(Main,Dirs),
{noreply,State};
handle_cast(stop, State) -> {stop, normal, State}.
Дополнительные сведения об источнике можно найти во всем приложении.
Весь исходный код приложения и сборку можно найти здесь: File_scavenger-1.0.zip .
Теперь я запускаю приложение на сервере (HP Proliant G6, содержащем процессоры Intel (2 процессора, каждые 4 ядра, скорость 2,4 ГГц каждое ядро, размер кэша 8 МБ), объем ОЗУ 20 ГБ, дисковое пространство 1,5 ТБ. Теперь 2 из этих мощных машин находятся в нашем распоряжении. Системная база данных должна быть реплицирована на две. Каждый сервер работает под управлением Solaris 10, 64-битный), чей терминал теперь выглядит следующим образом:
bash-3.00# sh file_scavenger.sh
Starting File Scavenger System. Layer 1 on the P2P File Sharing System.....
Erlang R14B03 (erts-5.8.4) [source] [smp:8:8] [rq:8] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.8.4 (abort with ^G)
(file_scavenger@127.0.0.1)1>
=INFO REPORT==== 18-Aug-2011::09:36:04 ===
Starting File Scavenger Database......
=INFO REPORT==== 18-Aug-2011::09:36:04 ===
Database Successfully Started....
=INFO REPORT==== 18-Aug-2011::09:36:04 ===
Starting File Scavenger Database......
=INFO REPORT==== 18-Aug-2011::09:36:04 ===
Database Successfully Started....
=INFO REPORT==== 18-Aug-2011::09:36:04 ===
File Scavenger Server starting with default verbose settings....
(file_scavenger@127.0.0.1)1> file_scavenger_server:index_dirs().
Сервер начинает работать и выводит на терминал все файлы и папки, которые он находит. Сервер оснащен слишком большим объемом оперативной памяти (20 ГБ) и местом подкачки (объем подкачки составляет 16 ГБ). Однако он работал около 18 часов, и, наконец, виртуальная машина erlang сообщила следующее:
File: "/proc/4324/root/opt/csw/gcc4/share/locale/ja/LC_MESSAGES/gcc.mo"
Folder: "/proc/4324/root/opt/csw/gcc4/share/locale/da"
Folder: "/proc/4324/root/opt/csw/gcc4/share/locale/es/LC_MESSAGES"
File: "/proc/4324/root/proc/4984/root/.thumbnails/normal/dc259e3897e8af4b379c6d956b6c1393.png"
File: "/proc/4324/root/proc/4984/root/.thumbnails/fail/gnome-thumbnail-factory/223c19786421b7101d14075bdec46f61.png"
File: "/proc/4324/root/opt/csw/gcc4/libexec/gcc/i386-pc-solaris2.10/4.5.1/install-tools/mkheaders"
File: "/proc/4324/root/opt/csw/gcc4/libexec/gcc/i386-pc-solaris2.10/4.5.1/cc1plus"
File: "/proc/4324/root/opt/csw/gcc4/lib/libsupc++.la"
Crash dump was written to: erl_crash.dump
eheap_alloc: Cannot allocate 153052320 bytes of memory (of type "heap").
Abort - core dumped
bash-3.00#
Вопрос 1. С таким мощным сервером, почему операционная система не может предоставить такую память приложению (это было единственное запущенное приложение)?
Вопрос 2. Запущенный эмулятор Эрланга дает возможность создавать столько процессов, сколько ему нужно. значение +P 13421779
. Erlang VM не может получить доступ к этой памяти или не может выделить ее для своих процессов?
Вопрос 3. Для Solaris он видит один процесс: epmd
, возможно, содержащий и запускающий тысячи микропотоков. Какие конфигурации я могу сделать в Solaris, чтобы иметь возможность никогда не останавливать мое приложение, каким бы «голодным» оно ни было? Доступное пространство подкачки составляет 16 ГБ, ОЗУ 20 ГБ, честно говоря, должно быть что-то не так.
Вопрос 4. Какие конфигурации я могу настроить для эмулятора Erlang, чтобы избежать этих аварийных дампов динамической памяти, особенно когда вся необходимая память доступна на сервере? Как я буду запускать больше приложений, потребляющих память, на этом сервере, если Erlang все еще не в состоянии выделить такую память простому индексатору файловой системы (ну, в общем, сильно параллельному)?
наконец, приветствуются все другие настройки, которые я мог бы сделать, чтобы избежать проблем с кучей памяти на таком способном оборудовании. Заранее спасибо