%% @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中想要共享數據怎么辦?
- 在當前進程內共享狀態數據,首先應該想到的是使用gen_server, gen_server創建之初初始化Loop State,然后在后續操作行為中被使用,更新;
[Erlang 0023] 理解Erlang/OTP gen_server - 在當前進程內共享數據可以使用進程字典Process Dictionary,數據保存在當前進程
進程字典無鎖,哈希實現,內容參與GC,速度很快但是幾乎所有的教材里面都不建議使用Erlang進程字典,主要是擔心這樣會造成全局變量,帶來了副作用;實際應用中對於一些一次性讀入就不再變化的數據,或者變動頻率非常低的數據,會放在進程字典中.速度那么快,又符合我們的應用場景,用用又何妨?
看霸爺的測試:進程字典到底有多快 百萬條級別,插入100ns, 查詢40ns. 而ets的速度大概是us,差了一個數量級別。 - 跨進程共享數據,可以使用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模塊文件.
相關話題