ERLANG OTP源码分析 – sys
sys 模块主要有两大用途:统计跟踪目标 gen 进程、代码热升级,尤其后者非常重要,下面从 erlang 源码层面来讲述这两大工作原理。
代码热升级
代码热升级能做到 gen 进程不停(自然其中已经打开的 socket,file 都不需要关闭,保证100%的在线率)的情况下自动载入新的代码,这点对于其它程序语言来说是件非常难的事情,而对于 erlang 确只是简单几行代码就能解决问题。
演示代码
这里有 演示代码,你可以自行运行。
热升级的原理和步骤
1. 首先 gen 进程启动并处于正常工作状态处理业务逻辑,现在需要对代码进行升级,修改原来的内部State状态。
2. 修改代码,编译出新的beam文件。
compile:file(Mod).
注意不能使用 c(Mod)。 c命令实际包含一个编译、加载、清理老代码作用,这里还不能加载哦, 否则老的内存状态运行在新的代码肯定报错,因为 State 不一致。
3. 利用 sys:suspend 函数使得 gen 进程陷入一种挂起状态
这种挂起状态的 gen 进程仅仅会处理 system 和 ‘EXIT’ 两类的消息,而不会处理业务逻辑。
sys.erl
====
suspend_loop(SysState, Parent, Mod, Debug, Misc, Hib) ->
case Hib of
true ->
suspend_loop_hib(SysState, Parent, Mod, Debug, Misc, Hib);
_ ->
receive
{system, From, Msg} ->
handle_system_msg(SysState, Msg, From, Parent, Mod, Debug, Misc, Hib);
{'EXIT', Parent, Reason} ->
Mod:system_terminate(Reason, Parent, Debug, Misc)
end
end.
此时业务消息因为不能模式匹配到,都被临时的存在 mailbox 中。
4. 因为业务不会运行了,我们可以放心的清理老代码,载入新代码,
code:purge(Mod). code:load_file(Mod).
好了这个时候已经新的代码了,而 gen 进程里的内存状态还是老状态。
5. 调用 change_code 做一个内存状态的转化
sys:change_code(Name, Mod, OldVsn, Extra).
change_code 属于 system 消息,告诉 gen 进程去调用 Mod 里的 code_change 函数,针对的 vsn是 OldVsn,额外的参数是Extra,调用 code_change 函数之后,gen 进程内存的 State 在此时进行了转化。
6. 回到业务逻辑
sys:resume(Mod).
好了代码,内存里的信息都是正确的了,我们就开启业务。
resume 函数最终会让 gen 进程跑到 system_continue 这个函数
system_continue(Parent, Debug, [Name, StateName, StateData, Mod, Time]) ->
loop(Parent, Name, StateName, StateData, Mod, Time, Debug).
于是gen进程又走回到正常的业务逻辑loop函数去,于是积累的消息和新的消息就开始正常的处理了.
release_handler_1.erl 底层也是调用 sys 模块进行代码热升级的,所以理解 sys 的工作原理很重要。
另外 sys:resume 也可以把挂起的进程立即切换回正常模式。
统计跟踪
开启统计
sys:statistics(Name, true).
实际上是开始记录了一个初始值
init_stat() -> {erlang:localtime(), process_info(self(), reductions), 0, 0}.
接下来再调用 sys:statistics(Name,get) 就可以获得从开启之后进程的运行时间和处理过的消息数目了。
3> sys:statistics(swap_test, get).
{ok,[{start_time,{{2012,7,31},{19,58,25}}},
{current_time,{{2012,7,31},{19,58,32}}},
{reductions,18},
{messages_in,0},
{messages_out,0}]}
关闭统计
sys:statistics(Name, false).
获取进程和进程内字典的信息
5> sys:get_status(swap_test).
{status,,
{module,gen_server},
[[{'$ancestors',[]},
{'$initial_call',{swap_test,init,1}}],
running,,[],
[{header,"Status for generic server swap_test"},
{data,[{"Status",running},
{"Parent",},
{"Logged events",[]}]},
{data,[{"State",{state,1,2}}]}]]}
最终调用的是 sys.get_status 和 gen 进程里的 format_status/2 函数
get_status(SysState, Parent, Mod, Debug, Misc) ->
{status, self(), {module, Mod},
[get(), SysState, Parent, Debug, Misc]}.
跟踪进程
sys:trace(code_lock, true).
实际上使得gen进程在处理每一条消息的时候多调用一次 print_event 函数打印出这条信息和前后状态。
Debug1 = sys:handle_debug(Debug, {?MODULE, print_event},
Name, {in, Msg}),
看看激活trace后的效果
4> swap_test:test_call().
*DBG* swap_test got call counter from
call counter
*DBG* swap_test sent 1 to , new state {state,3,2}
以上就是 sys 模块的重要功能。
