我一直在我的惠普Proliant服务器上运行高并发应用程序。这个应用程序是我用Erlang编写的文件系统索引器。它为它在文件系统中找到的每个文件夹生成一个进程,并记录所有文件路径在一个碎片化的Mnesia数据库中。(数据库由
下面显示了执行浏览文件系统的高强度工作的代码片段:
我用的启动脚本确保erlang能够尽可能地生成多个进程。一旦一个进程完成了对文件夹的索引,它就会退出。
服务器开始运行并向终端输出它发现的所有文件和文件夹。服务器配备了过多的RAM(20 GB)和Swap空间(Swap为16 GB)。然而,它运行了大约18个小时,最终,erlang虚拟机报告了这个问题:
最后,欢迎提出所有其他调整以避免在这样一台功能强大的硬件上出现堆内存问题。提前致谢。
disc_only_copies
类型的表组成,可以在这里查看其文件系统的屏幕截图here。)下面显示了执行浏览文件系统的高强度工作的代码片段:
%%% -------- COPYRIGHT NOTICE -------------------------------------------------------------------- %% 作者:Muzaaya Joshua,<joshmuza@gmail.com> [http://joshanderlang.blogspot.com] %% 版本:1.0 免费软件,但禁止修改 %% 版权:Muzaaya Joshua(file_scavenger-1.0)2011 - 2012。版权所有。 %% 参考:<a href="http://www.erlang.org">OpenSource Erlang WebSite</a> %% %%% ---------------- EDOC INTRODUCTION TO THE MODULE ---------------------------------------------- %% @doc 该模块提供了读取、写入、搜索、连接和移动目录的低级API。模块实现于@date @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(). %% 必须包含正斜杠,而不是反斜杠。不能以斜杠结尾 %% 准确的目录,例如: "C:/Program Files/SomeDirectory/" %% 但这是正确的:"C:/Program Files/SomeDirectory" %% @type file_path() = string(). %% 必须包含正斜杠,而不是反斜杠。 %% 应该包括文件扩展名,例如 "C:/Program Files/SomeFile.pdf"
%% ----------------------------------------------------------------------------------------------- %% @doc 进入一个目录并为找到的每个文件执行fun ForEachFileFound/2。 %% 如果它发现一个目录,它会执行fun ForEachDirFound/2。 %% 上述两个函数都将父目录作为第一个参数。然后,它将生成一个erlang进程, %% 该进程也将以与父目录相同的方式传播找到的目录。传播过程一直持续到每个文件(无论它是否在嵌套目录中)都被其完整路径注册。 %% @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
。其中一个fun:ForEachFileFound/2
会将最上层目录和找到的文件一起处理,并对其进行任何操作;另一个fun:ForEachDirFound/2
会将最上层目录和找到的文件夹一起处理,并以任何方式使用它。我用的启动脚本确保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(" 文件: ~p~n",[Fl]), file_scavenger_database:insert_file(filename:basename(Fl),file,Fl,TopDir,filename:extension(Fl)) end, fun(TopDir,Fd) -> io:format(" 文件夹: ~p~n",[Fd]), file_scavenger_database:insert_file(Fd,folder,Fd,TopDir,undefined) end }; {true,SomeFile} -> { fun(TopDir,Fl) -> os:cmd("echo 文件: " ++ Fl ++ " >> " ++ SomeFile), file_scavenger_database:insert_file(filename:basename(Fl),file,Fl,TopDir,filename:extension(Fl)) end, fun(TopDir,Fd) -> os:cmd("echo 文件夹: " ++ Fd ++ " >> " ++ SomeFile), file_scavenger_database:insert_file(Fd,folder,Fd,TopDir,undefined) end } end, Main = fun(Dir) -> error_logger:info_msg("*** 文件清理服务器正在索引目录: ~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 GHz速度,8 MB缓存大小),20 GB RAM大小,1.5 Terabytes磁盘空间)。现在,我们拥有两台这样的高性能机器。系统数据库应在这两台机器之间复制。每个服务器运行Solaris 10,64位),其终端现在看起来像下面这样:
开始文件搜寻系统。在P2P文件共享系统上进行第一层搜索...... Erlang R14B03(erts-5.8.4)[源] [smp:8:8] [rq:8] [async-threads:0] [hipe] [kernel-poll:false] Eshell V5.8.4(使用^ G中止) (file_scavenger@127.0.0.1)1> =INFO报告==== 18-Aug-2011 :: 09:36:04 === 启动文件搜寻数据库...... =INFO报告==== 18-Aug-2011 :: 09:36:04 === 数据库成功启动.... =INFO报告==== 18-Aug-2011 :: 09:36:04 === 启动文件搜寻数据库...... =INFO报告==== 18-Aug-2011 :: 09:36:04 === 数据库成功启动.... =INFO报告==== 18-Aug-2011 :: 09:36:04 === 默认详细设置下启动文件搜寻服务器.... (file_scavenger@127.0.0.1)1> file_scavenger_server:index_dirs()。服务器开始运行并向终端输出它发现的所有文件和文件夹。服务器配备了过多的RAM(20 GB)和Swap空间(Swap为16 GB)。然而,它运行了大约18个小时,最终,erlang虚拟机报告了这个问题:
文件路径: "/proc/4324/root/opt/csw/gcc4/share/locale/ja/LC_MESSAGES/gcc.mo" 文件夹路径: "/proc/4324/root/opt/csw/gcc4/share/locale/da" 文件夹路径: "/proc/4324/root/opt/csw/gcc4/share/locale/es/LC_MESSAGES" 文件路径: "/proc/4324/root/proc/4984/root/.thumbnails/normal/dc259e3897e8af4b379c6d956b6c1393.png" 文件路径: "/proc/4324/root/proc/4984/root/.thumbnails/fail/gnome-thumbnail-factory/223c19786421b7101d14075bdec46f61.png" 文件路径: "/proc/4324/root/opt/csw/gcc4/libexec/gcc/i386-pc-solaris2.10/4.5.1/install-tools/mkheaders" 文件路径: "/proc/4324/root/opt/csw/gcc4/libexec/gcc/i386-pc-solaris2.10/4.5.1/cc1plus" 文件路径: "/proc/4324/root/opt/csw/gcc4/lib/libsupc++.la"问题1: 有这么强大的服务器,为什么操作系统不能将这样的内存提供给应用程序(它是唯一运行的应用程序)? 问题2: 我启动的 Erlang Emulator 被指示能够生成所需的许多进程。值为
崩溃转储写入到:erl_crash.dump eheap_alloc:无法分配153052320字节的内存(类型为“heap”)。 中止 - 核心已转储 bash-3.00#
+P 13421779
。Erlang VM 是无法访问此内存还是无法将其分配给其进程?
问题3: 对于 Solaris,它只看到一个进程:epmd
,可能包含并启动了数千个微线程。我可以对 Solaris 进行哪些配置,以便无论应用程序需要多少内存都不会停止?可用的交换空间为16 GB,RAM为20 GB,老实说,肯定有问题。
问题4: 我可以对 Erlang Emulator 进行哪些配置,以避免这些堆内存崩溃转储,尤其是当服务器上可用所有内存时?如果 Erlang 仍然无法将此类内存分配给简单的文件系统索引器(它是高度并发的),那我该如何在此服务器上运行更消耗内存的应用程序?最后,欢迎提出所有其他调整以避免在这样一台功能强大的硬件上出现堆内存问题。提前致谢。
erl_crash.dump
文件并使用崩溃转储查看器查看。您应该能够看到是否有进程分配了过多内存,或者ETS表比预期更大。 - legoscia