Erlang 熱更新

from:http://www.cnblogs.com/me-sa/archive/2011/10/29/erlang0010.html

Erlang脫胎於電信業,Joe Armstrong在描述Erlang的設計要求時其中就提到了軟件維護應該能在不停止系統的情況下進行。在實踐中,我們也因爲這種不停服務的熱更新獲益良多,終於不再用等到半夜沒有人的時候再做更新了.那麼如何進行熱更新?Erlang又是如何做到熱更新的呢?這就是我們本文要回答的問題.

     如何進行熱更新?

     關於熱更新(hot_swap)可以看一下維基百科的介紹:http://en.wikipedia.org/wiki/Hot_swapping,Erlang如何進行熱更新呢?其實在Erlang文檔在講述rpc模塊時就給出了這樣一個在多個節點上進行熱更新的示例:

%% Find object code for module Mod
{Mod, Bin, File} = code:get_object_code(Mod),
%% and load it on all nodes including this one
{ResL, _} = rpc:multicall(code, load_binary, [Mod, Bin, File,]),
%% and then maybe check the ResL list.

%如果是在指定節點上執行熱更新還可以這樣:

{_Module, Binary, Filename} = code:get_object_code(Module),
rpc:call(Node, code, load_binary, [Module, Filename, Binary])

%我們也可以這樣:

network_load(Module)->
    {_Module, Binary, Filename} = code:get_object_code(Module),
    [rpc:call(Node, code, load_binary, [Module, Filename, Binary]) || Node <- nodes()],
    ok.

network_load(Node, Module)->
    {_Module, Binary, Filename} = code:get_object_code(Module),
    rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
    ok.

      一起看一下上面的代碼,code:get_object_code(Mod)方法會查詢代碼路徑來定位指定模塊所在的位置,返回值是{Module,Binary,FileName}即{模塊名,模塊的二進制數據,模塊的文件信息}.這個方法的執行後我們就獲得了指定模塊的元數據和二進制數據,後面就是通過code:load_library/3來加載新的模塊了.執行完load_library方法就大功告成,完成了熱更新.那麼,這些操作後面發生了什麼呢?

 

     如何做到的?

     Erlang熱更新的祕密其實都集中在code模塊,code模塊是Erlang Code Server暴露出來的對外接口其職責就是把已經編譯好的模塊加載到Erlang的運行時環境.

     代碼是如何被加載的?

    運行時系統可以有embedded,interactive兩種啓動模式,默認是interactive模式啓動,這兩種模式在模塊的加載策略上還是有很大差異的:embedded模式受應用場景的限制,模塊的加載都是需要顯示指定code server加載模塊的順序,比如按照啓動腳本進行加載.而interactive模式在系統啓動的時候只有部分代碼會被加載,通常都是一些運行時自己需要的模塊.其他的代碼模塊都是在第一次被使用的時候動態加載.調用一個方法的時候發現一個模塊沒有加載,code server就會搜索並加載模塊.

    這個搜索是如何進行的呢?在interactive模式,code server維護了一個搜索代碼路徑的列表,通常被稱作代碼路徑code path.code模塊的set_path(Path) get_path() add_path(Dir) add_pathz(Dir)add_patha(Dir)等一系列的方法就是來管理代碼路徑的. Kernel和Stdlib相關的文件夾都會首先加載,後面的模塊如果出現和OTP模塊重名,就會被OTP中同名的模塊覆蓋;換句話說kernel和stdlib中的模塊是不會被覆蓋的.

     Erlang運行時本身是Code Server是正常運行的前提,熱更新也依賴於這個基礎設施的穩定可靠.爲了防止從新加載模塊影響到運行時本身,kernel stdlib compiler 這三個文件夾被標記爲sticky.這意味着如果你嘗試重新加載這些模塊運行時會發出警告並拒絕執行.取消sticky文件夾使用-nostick 選項.code模塊提供了stick_dir(Dir) unstick_dir(Dir) is_sticky(Module) 方法來查看那些文件夾是sticky的,判斷一個文件夾是否爲sticky.

    代碼的版本是如何切換的?

    代碼版本有兩個概念 當前版本代碼'current'和老版本代碼'old',一旦模塊被加載就變成'current',再有一個版本過來被加載,之前的版本就變成'old',新加載的變成'current'.這時候,兩個版本還是同時存在,新的請求執行的時候會使用新的版本,而老版本的代碼還會被使用因爲還有其他模塊的調用'old'版本中(比如方法中間有一個timer:sleep會導致進程在這個方法駐留).如果再進行一次熱更新,這時就有第三個實例被加載,code server就會終止掉還在駐留在'old'版本代碼依賴的進程.然後第三個實例成爲'current',之前版本的'current'被標記成'old'.

     code模塊中有一個purge(Module)的方法,用於清理舊版本的模塊,移除被標記成'old'的版本,如果還有進程在佔用舊版本的代碼,這些進程將首先被幹掉.還有一個soft_purge的方法,這個方法僅僅處理那些沒有被佔用的'old'版本的代碼.code模塊還有一些用來查看版本衝突,檢查模塊加載狀態的方法,等等;

     總結一下:Erlang用兩個版本的共存的方法來保證某一時刻總有一個版本可用,這樣對外服務就不會停止,新的版本在後續調用中就會自動使用;如果同一個模塊出現多個實例,依然按照新舊版本的規則進行更換.

 

    下面我們通過兩個開源項目的代碼片段看看Erlang code模塊在熱更新的一些應用:

    首先是mochiweb(地址:git://github.com/mochi/mochiweb.git)項目reloader模塊這個模塊的職責就是 automatically reloading modified modules.我們直接看它reload相關的代碼:

         reload_modules(Modules) ->
            [begin code:purge(M), code:load_file(M) end || M <- Modules].

reload(Module) ->
    io:format("Reloading ~p ...", [Module]),
    code:purge(Module),
    case code:load_file(Module) of
        {module, Module} ->
            io:format(" ok.~n"),
            case erlang:function_exported(Module, test, 0) of
                true ->
                    io:format(" - Calling ~p:test() ...", [Module]),
                    case catch Module:test() of
                        ok ->
                            io:format(" ok.~n"),
                            reload;
                        Reason ->
                            io:format(" fail: ~p.~n", [Reason]),
                            reload_but_test_failed
                    end;
                false ->
                    reload
            end;
        {error, Reason} ->
            io:format(" fail: ~p.~n", [Reason]),
            error
    end. 

      另外一個項目我們看smerl(地址:http://code.google.com/p/smerl/),這個項目的定位就是Simple Metaprogramming for Erlang, 它做得更爲靈活,它甚至可以這樣:

  test_smerl() ->
      M1 = smerl:new(foo),
      {ok, M2} = smerl:add_func(M1, "bar() -> 1 + 1."),
      smerl:compile(M2),
      foo:bar(),   % returns 2``
      smerl:has_func(M2, bar, 0). % returns true

    在smerl的compile方法中使用的依然是我們熟悉的code:purge(Module)和erlang:load_module(Module, Bin) 方法.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章