Erlang及其对堆内存的消耗

时间:2011-08-18 07:06:13

标签: mysql linux oracle erlang solaris

我一直在HP Proliant服务器上运行高度并发的应用程序。该应用程序是我在erlang中编码的文件系统索引器。它在文件系统上找到每个文件夹的进程,并在碎片化的Mnesia数据库中记录所有文件路径。 (数据库由disc_only_copies类表组成,其文件系统的屏幕截图可以在 here 中查看。)

执行文件系统高密集工作的代码片段如下所示:


%%% -------- 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与最热门目录,找到的文件一起使用,并对其执行任何操作,另一个有趣: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(" 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,包含英特尔处理器(2个处理器,每个4核,每个核心速度为2.4 GHz,8 MB高速缓存),20 GB RAM大小,1.5 TB磁盘空间。现在,我们可以使用其中两台高功率机器。系统数据库应该在两者之间复制。每台服务器运行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().

服务器开始运行并向终端详细检查它找到的所有文件和文件夹。服务器配备了太多RAM(20 GB)和交换空间(Swap为16 GB)。然而,它运行了大约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。我启动的Erlang Emulator被指示能够生成尽可能多的进程。值+P 13421779。 Erlang VM无法访问此内存或无法将其分配给其进程吗?

问题3。对于Solaris,它会看到一个进程:epmd,可能包含并启动数千个微线程。我可以为Solaris做什么配置,以便能够永远不会停止我的应用程序,但可能是“内存饥饿”?可用的交换空间是16 GB,RAM 20 GB,老实说,必定有问题。

问题4。我可以对Erlang仿真器进行哪些配置,以避免这些堆内存崩溃转储,尤其是当服务器上可能需要的所有内存时?如果Erlang仍然无法将这样的内存分配给简单的文件系统索引器(以及它的大量并发),我将如何在此服务器上运行更多内存消耗应用程序?

最后,我可以做的所有其他调整,以避免在这种功能强大的硬件上的堆内存问题。提前致谢

1 个答案:

答案 0 :(得分:6)

我没有时间查看来源,但这里有一些评论:

  

问题1.有了这么强大的服务器,为什么要运行呢   系统无法为应用程序提供这样的内存(它是唯一的   应用程序运行)?

因为Erlang VM试图消耗的可用内存超过可用内存。

  

问题2.我启动的Erlang Emulator被指示能够   产生尽可能多的进程。值+ P 13421779.是   Erlang VM无法访问此内存或未能将其分配给   它的过程?

没有。如果你已经没有进程了,那么Erlang VM就会这么说(并且VM仍然会启动并运行):

=ERROR REPORT==== 18-Aug-2011::10:04:04 ===
Error in process <0.31775.138> with exit value: {system_limit,[{erlang,spawn_link,    [erlang,apply,[#Fun<shell.3.130303173>,[]]]},{erlang,spawn_link,1},{shell,get_command,5},    {shell,server_loop,7}]}
  

问题3.对于Solaris,它会看到一个进程:epmd,可能包含   并启动数以千计的微线程。我可以配置什么配置   使Solaris能够永远不会停止我的应用程序   “记忆力饥渴”可能是?可用的交换空间为16 GB,RAM为20 GB,   老实说,肯定有问题。

epmd是Erlang端口映射deamon。它负责管理分布式Erlang,并且与您的个人Erlang应用程序无关。您应该查找的进程最有可能是名称beam.smp。这些将显示Erlang VM等的OS内存消耗。

  

问题4.我可以对Erlang仿真器进行哪些配置   避免这些堆内存崩溃转储,尤其是当所有内存都存在时   可能需要在服务器上?我将如何运行更多内存   如果Erlang仍然无法分配此类服务器,则会在此服务器上使用应用   内存到一个简单的文件系统索引器(以及它的大量并发)?

Erlang VM应该能够使用您机器中的所有可用内存。但是,这取决于您的应用程序的编写方式。内存泄漏的原因有很多:

  • 填充原子表(创建太多独特原子)
  • ETS或Mnesia表不是垃圾回收(您不删除旧的未使用的元素)
  • 进程内存不足(产生太多进程)
  • 创建了太多的二进制文件(您可能会保留对旧二进制文件的未使用的引用)