技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> 源码分析 --> ERLANG OTP源码分析 – code_server

ERLANG OTP源码分析 – code_server

浏览:1238次  出处信息

    继续上一次的sys模块升级的话题,我们再讨论一下升级基本原理,了解一下code和code_server模块。

简单升级

    上文也说到过,如果升级未改动 State 里的内容,你可以这样升级。

    在集群的每个节点内(当然也可以通过 rpc)

  • 拷贝 beam 到线上的 ebin 目录。
  • code:purge(Module).
  • code:load_file(Module).
  • 让代码触发 Fully qualified function calls,走到新的 beam 代码。
  •     如果你想省略后 2,3 步,可以利用mochiweb的 reloader 模块进行自动加载。

    升级的原理

        结合code_server代码来研究一下升级的原理。

        erlang 可以同时允许两套代码同时加载运行在虚拟机内部运行, 一套被成为老版本代码 , 一套被成为当前版本代码,也成为最新代码。

        升级调度的命令code模块其实是code_server模块的接口模块,上面的 purge, load_file 实际上都是向code_server模块发送相应的话 call 消息。

        code_server是一个gen_serverlike 的进程,但却没有使用behaviour,因为是 kernel 里的模块吧。

    code:purge

        code:purge 底层调度的是 codeserver:dopurge 函数,它的作用就是杀掉在调用老代码的进程,并清空老代码,之后就只有当前代码了。

    do_purge(Mod) -> do_purge(processes(), to_atom(Mod), false). do_purge([P|Ps], Mod, Purged) -> case erlang:check_process_code(P, Mod) of true -> Ref = erlang:monitor(process, P), exit(P, kill), receive {\'DOWN\',Ref,process,_Pid,_} -> ok end, do_purge(Ps, Mod, true); false -> do_purge(Ps, Mod, Purged) end; do_purge([], Mod, Purged) -> catch erlang:purge_module(Mod), Purged. 

        erlang:checkprocesscode 就是用来判断进程是否有在调用老代码的函数。

    code:load_file

        code:loadfile 最后调度的是 erlang:loadmodule, 该函数做了两步事情:

  • 把当前代码覆盖老代码。
  • 载入新的 beam 文件,把其代码作为当前代码。
  •     结合例子更好说明问题:

  • 应用开始vsn=1这就是当前代码, 老代码不存在。
  • 升级后,vsn=2这就是当前代码,老代码为vsn=1。
  • 再升级,分三步:
  • purege 先把还在调用vsn=1代码的进程杀掉。
  • load module 把当前代码vsn=2视为成老代码。
  • 把新的beamvsn=3当做当前代码。
  •     我们用例子来证明一下。是否会杀掉调用老代码的进程。

    -module(e3). -compile(export_all). start() -> register(?MODULE, spawn(?MODULE, loop, [])). switch() -> ?MODULE ! code_switch. compile() -> compile:file(?MODULE), code:purge(?MODULE), code:load_file(?MODULE). msg() -> ?MODULE ! hello. loop() -> receive code_switch -> ?MODULE:loop(); _ -> io:format("~p~n", ["bb"]), loop() end. 

        我们开始实验,启动进程后,两次加载 e3 这个模块。

    1> c(e3). {ok,e3} 2> e3:start(). true 3> whereis(e3). <0.41.0> 4> c(e3). {ok,e3} 5> whereis(e3). <0.41.0> 6> c(e3). {ok,e3} 7> whereis(e3). undefined 

        可以看到第二次的加载 c(e3) 杀掉了 e3 这个进程, 从代码可以知道 e3 一直在 receive 等待外界的消息, 一直调用者老代码, 所以被杀。

    Fully qualified function calls`

        热升级的目的就是让老代码的程序能运行新的代码,要触发这个切换,最关键一步必须使用完全限定的函数调用,即上面的 ?MODULE:loop 这样会触发虚拟机寻找当前代码继续运行。

        上面的场景,代码就是停留在 receive 这块,连续发生了两次升级,老的进程被杀死, 如何避免这种情况呢,尽量不要在 用户模块里挂起,让程序一旦发生挂起时都停留在系统模块里。

        例如上个例子如果使用gen_server则 receive 在gen_server这个模块,程序占用的模块就不是 e3 这个模块, 而 gen 进程调用 callback 函数都使用 Mod:handle_call 这类Fully qualified function calls的调用方法,所以加载过新代码之后, gen 进程一接收到消息,老程序立马就走到当前代码上了。

        网络类的应用,尽量不要使用 {active, false}, 这样 Socket 会阻塞在用户模块的的 gen_tcp:recv 里,如果这个 Socket 在升级之后没有任何数据交互(未离开过用户模块),那么下次升级这个连接将被杀掉,这个也是我们要避免的。

        升级之前,我们可以运行如下的程序可以提前发现即将被杀掉的进程。

    [Pid|| Pid <- processes(), true =:= erlang:check_process_code(Pid, Module)] 

    code_server其它函数都比较简单

        未完待续

    参考

  • http://www.cnblogs.com/me-sa/archive/2011/10/29/erlang0010.html
  • http://erldocs.com/R15B/kernel/code.html
  • 建议继续学习:

    1. Erlang match_spec引擎介绍和应用    (阅读:4528)
    2. whatsapp深度使用Erlang有感    (阅读:4569)
    3. php-erlang    (阅读:4308)
    4. gen_tcp调用进程收到{empty_out_q, Port}消息奇怪行为分析    (阅读:3541)
    5. hibernate使用注意事项    (阅读:3218)
    6. Erlang linkin driver用port_control方式时的一些经验分享    (阅读:2965)
    7. Erlang如何限制节点对集群的访问之net_kernel:allow    (阅读:2971)
    8. ERLANG OTP源码分析 – gen_server    (阅读:2856)
    9. erlang学习手记    (阅读:2697)
    10. gen_tcp容易误用的一点解释    (阅读:2635)
    QQ技术交流群:445447336,欢迎加入!
    扫一扫订阅我的微信号:IT技术博客大学习
    © 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

    京ICP备15002552号-1