[Erlang 0055] Erlang Shared Data using mochiglobal


  %% @doc Abuse module constant pools as a "read-only shared heap" (since erts 5.6)

 

    Erlang 進程之間的消息發送都是通過數據拷貝實現的,只有一個例外就是同一個Erlang節點內的 refc binaries.關於Erlang二進制相關的內容可以參看[Erlang 0024]Erlang二進制數據處理 和 [Erlang 0032] Erlang Binary的內部實現 .消息向另外一個Erlang節點發送,首先會編碼成Erlang外部數據格式(Erlang External Format)然后通過TCP/IP Socket 發送.接收到消息的節點進行消息解碼然后派發到具體的進程.Erlang中就沒有全局變量,像這位老兄遇到的問題,我們怎么辦? Erlang中想要共享數據怎么辦?
 
  1. 在當前進程內共享狀態數據,首先應該想到的是使用gen_server, gen_server創建之初初始化Loop State,然后在后續操作行為中被使用,更新;
    [Erlang 0023] 理解Erlang/OTP gen_server  
  2. 在當前進程內共享數據可以使用進程字典Process Dictionary,數據保存在當前進程
    進程字典無鎖,哈希實現,內容參與GC,速度很快但是幾乎所有的教材里面都不建議使用Erlang進程字典,主要是擔心這樣會造成全局變量,帶來了副作用;實際應用中對於一些一次性讀入就不再變化的數據,或者變動頻率非常低的數據,會放在進程字典中.速度那么快,又符合我們的應用場景,用用又何妨?
    看霸爺的測試:進程字典到底有多快 百萬條級別,插入100ns, 查詢40ns. 而ets的速度大概是us,差了一個數量級別。

  3. 跨進程共享數據,可以使用ETS表,從ETS表讀取數據是通過內存拷貝實現的
    在Erlang VM中ETS有自己的內存管理系統,擁有獨立於進程的數據區域;換句話說ETS的操作就是通過內存拷貝完成的讀寫;這樣要拷貝的數據大小就很重要了.
    看這篇論文: A Study of Erlang ETS Table Implementations
   mochiweb項目的mochiglobal模塊提供了另外一種方法:它把需要的常量編譯到新的模塊,Erlang Code Server加載這個模塊,就可以在各個進程之間共享數據了;同一節點內避免了數據的拷貝,先看一下是怎么用的吧:
Eshell V5.9  (abort with ^G)
1> mochiglobal:put(abc,'this_the_app_config_value_never_change').
ok
2> mochiglobal:get(abc).                                         
this_the_app_config_value_never_change
3> mochiglobal:put(abc,'ho_changed_fml').                        
ok
4> mochiglobal:get(abc).                 
ho_changed_fml
5>
 
使用非常簡單,我們可以把它看作Erlang節點內一個全局的Key_Value服務;上面說會把常量值編譯到新的模塊,這個什么意思呢?看下面的模塊:
-module(abc).
-export([term/0]).

term() ->
         this_the_app_config_value_never_change.
 
動態編譯的模塊就是這樣一個簡單的實現,調用abc:term().返回值this_the_app_config_value_never_change.換句話說,我們調用mochiglobal:get(abc).實際上是執行了類似於abc:term()這樣一個方法;我們還是在shell中看一下調用,因為動態編譯的結果還略有不同:
Eshell V5.9  (abort with ^G)
1> abc:term().
this_the_app_config_value_never_change
2> abc:module_info().
[{exports,[{term,0},{module_info,0},{module_info,1}]},
{imports,[]},
{attributes,[{vsn,[242773849471402131574616398046036072850]}]},
{compile,[{options,[{outdir,"/box/mochi-mochiweb-b277802"}]},
           {version,"4.8"},
           {time,{2012,4,19,9,32,18}},
           {source,"/box/mochi-mochiweb-b277802/abc.erl"}]}]
 
下面就要看看mochiglobal是怎么實現的了
-spec compile(atom(), any()) -> binary().
compile(Module, T) ->
    {ok, Module, Bin} = compile:forms(forms(Module, T),
                                      [verbose, report_errors]),
    Bin.

-spec forms(atom(), any()) -> [erl_syntax:syntaxTree()].
forms(Module, T) ->
    [erl_syntax:revert(X) || X <- term_to_abstract(Module, term, T)].

-spec term_to_abstract(atom(), atom(), any()) -> [erl_syntax:syntaxTree()].
term_to_abstract(Module, Getter, T) ->
    [%% -module(Module).
     erl_syntax:attribute(
       erl_syntax:atom(module),
       [erl_syntax:atom(Module)]),
     %% -export([Getter/0]).
     erl_syntax:attribute(
       erl_syntax:atom(export),
       [erl_syntax:list(
         [erl_syntax:arity_qualifier(
            erl_syntax:atom(Getter),
            erl_syntax:integer(0))])]),
     %% Getter() -> T.
     erl_syntax:function(
       erl_syntax:atom(Getter),
       [erl_syntax:clause([], none, [erl_syntax:abstract(T)])])].
      上面的代碼term_to_abstract完成了句法構造,erl_syntax:revert將句法轉成句法樹,然后 compile:forms完成編譯;term_to_abstract方法的三個參數Module是模塊名,它實際上是做了一個key值加前綴的拼接list_to_atom("mochiglobal:" ++ atom_to_list(K)).也就是說實際上mochiglobal:get(abc).調用產生的
動態模塊名是mochiglobal:abc,實際中我們幾乎不會這樣給模塊命名,最大程度上避免了和已有命名模塊沖突;注意在shell中我們調用的時候要加一下單引號'mochiglobal:abc'否則會有語法錯誤.Getter參數是硬編碼了原子term,參數T就是我們對應的Value值了;了解了這些我們在Erlang Shell中操練一下:
     Eshell V5.9  (abort with ^G)
1> mochiglobal:put(abc,'this_the_app_config_value_never_change').
ok
2> abc:term().  %%%是加了前綴的 並沒有abc這個模塊
** exception error: undefined function abc:term/0
3> mochiglobal:abc:term().  %%%這樣有語法錯誤
* 1: syntax error before: ':'
3> 'mochiglobal:abc':term().  %%%這樣OK
this_the_app_config_value_never_change
4> mochiglobal:put(abc,'new_value_now').                         
ok
5> 'mochiglobal:abc':term().            
new_value_now
6> 'mochiglobal:abc':module_info(). %%看一下動態模塊的元數據信息
[{exports,[{term,0},{module_info,0},{module_info,1}]},
{imports,[]},
{attributes,[{vsn,[241554446202275028379059762912985937376]}]},
{compile,[{options,[]}, %%注意這里並沒有源代碼的路徑
           {version,"4.8"},
           {time,{2012,4,19,9,52,33}},
           {source,"/box/mochi-mochiweb-b277802/ebin"}]}]
 
那我們重新復制又是怎么實現的呢?聯系上面的實現,容易想到其實是走了一個代碼熱更新的過程
-spec get(atom()) -> any() | undefined.
%% @equiv get(K, undefined)
get(K) ->
    get(K, undefined).

-spec get(atom(), T) -> any() | T.
%% @doc Get the term for K or return Default.
get(K, Default) ->
    get(K, Default, key_to_module(K)).

get(_K, Default, Mod) ->
    try Mod:term()
    catch error:undef ->
            Default
    end.

-spec put(atom(), any()) -> ok.
%% @doc Store term V at K, replaces an existing term if present.
put(K, V) ->
    put(K, V, key_to_module(K)).

put(_K, V, Mod) ->
    Bin = compile(Mod, V),
    code:purge(Mod),
    {module, Mod} = code:load_binary(Mod, atom_to_list(Mod) ++ ".erl", Bin),
    ok.
類似的,執行delete方法實際上就是Erlang Code Server執行了模塊的移除:
-spec delete(atom()) -> boolean().
%% @doc Delete term stored at K, no-op if non-existent.
delete(K) ->
    delete(K, key_to_module(K)).

delete(_K, Mod) ->
    code:purge(Mod),
    code:delete(Mod).
     通過分析mochiglobal的實現,我們知道了它的實現機制,並且知道這種實現成本還是比較高的,每一次復制都是走了一次熱更新的過程,所以它適用的場景是數據幾乎不動的情況;mochiglobal同一節點內避免了數據的拷貝,一些我們希望避免大數據拷貝的場景可以考慮使用,換一個角度去想,一些配置型靜態數據可以放在ETS中,更好的策略是用工具直接生成Erlang模塊文件.
 
 
相關話題
 



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM