Home > Erlang探索, 源码分析 > application配置文件和热升级

application配置文件和热升级

August 29th, 2013

原创文章,转载请注明: 转载自系统技术非业余研究

本文链接地址: application配置文件和热升级

前面我们一直说过erlang是以app为单位来组织程序,数据,配置等信息,让这些信息聚合在一起成为一个整体,设计上和unix系统一模一样。 那app的配置信息存在哪里呢?

配置信息有三种方式体现(其实是4种):
1. .app文件里面的env字段, 通常是MyApplication.app, 具体参见这里
2. .config文件,通常是sys.config,具体参见这里
3. 命令行 erl -ApplName Par1 Val1 … ParN ValN 具体参见这里

我们摘抄重要的信息如下:
方式1:

7.8 Configuring an Application

An application can be configured using configuration parameters. These are a list of {Par, Val} tuples specified by a key env in the .app file.

{application, ch_app,
[{description, “Channel allocator”},
{vsn, “1”},
{modules, [ch_app, ch_sup, ch3]},
{registered, [ch3]},
{applications, [kernel, stdlib, sasl]},
{mod, {ch_app,[]}},
{env, [{file, “/usr/local/log”}]}
]}.
Par should be an atom, Val is any term. The application can retrieve the value of a configuration parameter by calling application:get_env(App, Par) or a number of similar functions, see application(3)

方式2:

A configuration file contains values for configuration parameters for the applications in the system. The erl command line argument -config Name tells the system to use data in the system configuration file Name.config.

Configuration parameter values in the configuration file will override the values in the application resource files (see app(4)). The values in the configuration file can be overridden by command line flags (see erl(1)).

The value of a configuration parameter is retrieved by calling application:get_env/1,2.

方式3:

The values in the .app file, as well as the values in a system configuration file, can be overridden directly from the command line:

% erl -ApplName Par1 Val1 … ParN ValN

这三种方式都可以很方便的来设置应用的配置信息,由于一个应用会依赖于其他很多应用,所以会有很多的配置信息,这里我比较推荐sys.config方式,这也是rebar组织配置文件的标准形式。

摘抄线上的系统的部分设置给大家参考:

[
%%…
{ump_zk, [
{zk_enable, false},
{zk_hosts, “{{zk_hosts}}”},
{zk_dir, “{{controller_system_root}}/{{zk_files}}”},
{zk_root, “/try”},
{zk_lock_path, “/try/lock2”},
{zk_config_path, “/try/ump_config”},
{zk_mysql_cluster_path, “/try/mysql”}
]
},

{ump_proxy, [
{control_nodes, {{controller_nodes}} },
{id, “default”},
{port, {{proxy_port}} },
{enable_ssl, true},
%% util seconds
{idle_timeout, 28800},
%% no define to allow all sql
{allow_sql_type, [create, alter, select, delete, drop, show, use, insert, update, set, truncate, desc, describe]},
%% master and slave sync time, unit ms
{ms_sync_interval, 500}
]
}
%%…
].

当然有了这些配置以后,我们就可以很方便的透过诸如:application:get_env(App, Par) 方式来获取到配置信息,这些配置信息在application启动的时候,application_controller会把这些信息从文件加载到ets表中保存,所以获取信息的性能非常高。

上相关的代码来证明下我们之前的判断:

%%application_controller.erl
do_change_appl({ok, {ApplData, Env, IncApps, Descr, Id, Vsn, Apps}},
               OldAppl, Config) ->
    AppName = OldAppl#appl.name,

    %% Merge application env with env from sys.config, if any
    ConfEnv = get_opt(AppName, Config, []),
    NewEnv1 = merge_app_env(Env, ConfEnv),

    %% Merge application env with command line arguments, if any
    CmdLineEnv = get_cmd_env(AppName),
    NewEnv2 = merge_app_env(NewEnv1, CmdLineEnv),

    %% included_apps is made into an env parameter as well
    NewEnv3 = keyreplaceadd(included_applications, 1, NewEnv2,
                            {included_applications, IncApps}),

    %% Update ets table with new application env
    del_env(AppName),
    add_env(AppName, NewEnv3),

    OldAppl#appl{appl_data=ApplData,
                 descr=Descr,
                 id=Id,
                 vsn=Vsn,
                 inc_apps=IncApps,
                 apps=Apps};
add_env(Name, Env) ->
    foreach(fun({Key, Value}) ->
                          ets:insert(ac_tab, {{env, Name, Key}, Value})
                  end,
                  Env).

del_env(Name) ->
    ets:match_delete(ac_tab, {{env, Name, '_'}, '_'}).

从源码我们可以看到每个app的配置信息有3个来源,凑齐配置信息后,就把原来的从ets表中删除掉,然后上新的信息。

这里面有个蛋疼的问题, 配置信息其实还有一个设置通道:

set_env(Application, Par, Val) -> ok
Sets the value of the configuration parameter Par for Application.

set_env/3 uses the standard gen_server timeout value (5000 ms). A Timeout argument can be provided if another timeout value is useful, for example, in situations where the application controller is heavily loaded.

Warning
Use this function only if you know what you are doing, that is, on your own applications. It is very application and configuration parameter dependent when and how often the value is read by the application, and careless use of this function may put the application in a weird, inconsistent, and malfunctioning state.

通过这个设置的信息是保存在ets表中的,在这种场合下会被洗掉。

handle_call({set_env, AppName, Key, Val}, _From, S) ->
    ets:insert(ac_tab, {{env, AppName, Key}, Val}),
    {reply, ok, S};

所以官方在文档特别警告这个事情了。

在热升级的情况下,这个事情会变得更复杂:

%%release_handler.erl
eval_appup_script(App, ToVsn, ToDir, Script) ->
    EnvBefore = application_controller:prep_config_change(),
    AppSpecL = read_appspec(App, ToDir),
    Res = release_handler_1:eval_script(Script,
                                        [], % [AppSpec]
                                        [{App, ToVsn, ToDir}],
                                        [{App, ToVsn, ToDir}],
                                        []), % [Opt]
    case Res of
        {ok, _Unpurged} ->
            application_controller:change_application_data(AppSpecL,[]),
            application_controller:config_change(EnvBefore);
        _Res ->
            ignore
    end,
    Res.

%%application_controller.erl
handle_call({change_application_data, Applications, Config}, _From, S) ->
    OldAppls = ets:filter(ac_tab,
                          fun([{{loaded, _AppName}, Appl}]) ->
                                  {true, Appl};
                             (_) ->
                                  false
                          end,
                          []),
    case catch do_change_apps(Applications, Config, OldAppls) of
        {error, _} = Error ->
            {reply, Error, S};
        {'EXIT', R} ->
            {reply, {error, R}, S};
        NewAppls ->
            lists:foreach(fun(Appl) ->
                                  ets:insert(ac_tab, {{loaded, Appl#appl.name},
                                                      Appl})
                          end, NewAppls),
            {reply, ok, S#state{conf_data = Config}}
    end;

在热升级的时候,当用户改变了配置文件的时候,系统会重新来加载新的配置,同时会把用户set_env的信息给废掉,这个行为会带来不少不可预期的事情,
所以我们不必要的时候,尽量不用set_env,避免各种不愉快。

当然系统在热升级提供app的配置的时候,会给用户一个机会来做各种变更, 参见这里, 摘抄如下:

Module:config_change(Changed, New, Removed) -> ok
This function is called by an application after a code replacement, if there are any changes to the configuration parameters.

Changed is a list of parameter-value tuples with all configuration parameters with changed values, New is a list of parameter-value tuples with all configuration parameters that have been added, and Removed is a list of all parameters that have been removed.

具体的逻辑代码见这里:

%%application_controller.erl
do_config_change([{App, _Id} | Apps], EnvBefore, Errors) ->
    AppEnvNow = lists:sort(application:get_all_env(App)),
    AppEnvBefore = case lists:keyfind(App, 1, EnvBefore) of
                       false ->
                           [];
                       {App, AppEnvBeforeT} ->
                           lists:sort(AppEnvBeforeT)
                   end,
    Res =
        case AppEnvNow of
            AppEnvBefore ->
                ok;                        
            _ ->
                case do_config_diff(AppEnvNow, AppEnvBefore) of
                    {[], [], []} ->
                        ok;
                    {Changed, New, Removed} ->
                        case application:get_key(App, mod) of
                            {ok, {Mod, _Para}} ->
                                case catch Mod:config_change(Changed, New,
                                                             Removed) of
                                    ok ->
                                        ok;
                                    %% It is not considered as an error
                                    %% if the cb-function is not defined
                                    {'EXIT', {undef, _}} ->
                                        ok;
                                    {error, _} = Error ->
                                        Error;
                                    Else ->
                                        {error, Else}
                                end;
                            {ok, []} ->
                                {error, {module_not_defined, App}};
                            undefined ->
                                {error, {application_not_found, App}}
                        end
                end
        end,
...

有了这个回调,我们在做升级的时候就很方便,可以给我们更多的选择来把事情做好,举个例子,kernel模块就利用了这个回调:

%%——————————————————————-
%% Some configuration parameters for kernel are changed
%%——————————————————————-
config_change(Changed, New, Removed) ->
do_distribution_change(Changed, New, Removed),
do_global_groups_change(Changed, New, Removed),
ok.

总结下来:erlang的application 配置很灵活,有四种不同的形式,但是要注意在热升级情况下set_env配置过的信息可能会丢失。

祝玩得开心!

Post Footer automatically generated by wp-posturl plugin for wordpress.

Comments are closed.