rpc:call函數解析


起因

   由於業務需求,有時候臨時統計數據和查看服務器狀態需要向所有運行的ERLANG結點獲取數據。昨天遇到一個奇葩的問題,在通過一個ERLANG中心結點向其相連的結點進行rpc:call/4來遠程執行一個Func的時出現了大量的超時錯誤,嘗試着給rpc:call加上TimeOut,一直加到100000發現超時問題還存在。為了解決這個問題,因此對rpc:call的實現方式進行了一定的探究。

過程: 
 先看調用rpc:call的代碼:
  rp([begin rpc:call(Node,Mod,Func,[Arg]) end || Node <- nodes([hidden]),string:str(common_tool:to_list(Node),TargtNode) =:= 1]).這里的ARG實際上傳入的是一個需要執行的Func,mod和func是一個運行函數的call方法的封裝。
  將rpc:call(Node,?MOD,?FUNC,[?ARG]) 換成rpc:call(Node,?MOD,?FUNC,[?ARG],Timeout)之后超時問題依舊存在。
  開始糾結rpc:call的實現方式,通過讀文檔和源碼可以得知下面的概念:
    1、rpc:call/4其實相當於rpc:call(Node,?MOD,?FUNC,[?ARG],infinity),就是說call/4是不帶有超時控制的,可見我上面的問題不是出現在rpc:call這一邊。
    2、使用Timeout字段其實會代碼非常大的性能損耗,原因是在rpc模塊,帶有Timeout的調用會會在rpc模塊就額外的創建一個中間進程來實現超時控制和數據返回,這樣的好處就是超市以后收到的消息不會污染調用者進程的信箱。
    3、rpc:call/4和rpc:call/5其實本質上都是調用到了gen_server:call/3,這里的特殊點是:這里的目標進程是{?NAME,Node},進入gen_server模塊之后,加上'$gen_call'參數后繼續向下調用,傳遞到gen模塊
    4、到了gen模塊后,首先調用erlang:monitor(process, Process)函數來確定目標進程或者是結點的存活狀態,而實際上數據發送是通過erlang:send(Process, {Label, {self(), Mref}, Request},[noconnect])來實現的。在發送完消息后,就進入了receive的阻塞狀態,實際上在gen模塊的阻塞狀態中依舊有超時控制。這樣一個帶有Timeout的rpc:call/5其實會因為receive而阻塞兩次,同時還會創建一個新的中間進程,其目的是為了防止call長時間超時帶來的進程阻塞危害。
 
  這樣下來問題依舊還未解決,超時問題不是出現在本地結點的,那么就應該在遠程結點。對傳入的mod和func進行追蹤發現,其本身帶有一個4秒的超時,問題發現!!!
 
解決方案:拋棄掉采用在某個模塊調用call方法來執行func的辦法,采用erlang:apply/2來執行,摒棄掉超時控制。
     於此同時在結點數量過多的時候上面的順序執行的辦法會使得整體的調用速度很慢,既然對每個結點的call都可以算作是並行的,於是改進上面的函數為
[begin erlang:spawn(fun() ->io:format("~w~n",[rpc:call(Node,erlang,apply,[?Func,[]])]) end),[]end || Node <- nodes([hidden]),string:str(common_tool:to_list(Node),TargtNode) =:= 1].
 
結果:采用上述方法后,對多節點的訪問速度有了非常大的提升,但是在shell輸出端多了進程Pid的List輸出,此時的本法可以在后面加上,[].來讓輸出更為清晰。
  
 


免責聲明!

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



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