erlang rpc 遠端呼叫

2021-07-24 23:11:14 字數 4801 閱讀 9558

跨節點進行遠端呼叫的時候,會經常用到rpc模組提供的方法,例如rpc:call、rpc:cast。那麼每個節點上的rpc模組是怎麼工作的呢?

rpc模組的啟動過程很簡單,並沒有初始化做太多事情,以的名稱啟動了乙個gen_server程序,這個gen_server程序的state是乙個gb_trees的資料結構。

-spec start() ->  | 'ignore' | .

start

() ->

gen_server:start(, ?module, , ).

-spec start_link() -> | 'ignore' | .

start_link

() ->

gen_server:start_link(, ?module, , ).

rpc模組中比較常用的就是rpc:call和rpc:cast

call/4 和 call/5 這個兩個介面的區別,只是在timeout時間是自定義還是infinity而已。

call(node,m,f,a,timeout) 內部根據node是否為本地節點,又分為local_call/3和do_call/3。

這裡我們可以通過rpc.erl來檢視。

local_call

(m, f, a)

when

is_atom

(m), is_atom

(f), is_list

(a) ->

case

catch

=v -> ;

other -> other

end.

do_call(node, ,timeout)

do_call

(node, request, infinity) ->

rpc_check(catch gen_server:call(, request, infinity));

do_call

(node, request, timeout) ->

tag = make_ref(),

=erlang:spawn_monitor(

fun() ->

%% middleman process. should be unsensitive to regular

%% exit signals.

process_flag(trap_exit, true),

result = gen_server:call(, request, timeout),

exit()

end),

receive

} ->

rpc_check(result);

->

%% the middleman code failed. or someone did

%% exit(_, kill) on the middleman process => reason==killed

rpc_check_t()

end.

通過上面的**,可以發現業務程序呼叫rpc:call實際上跟本節點的rex程序並沒有什麼關係,因為對業務程序來說rpc:call本質上是呼叫了gen_server:call(, request, timeout), 業務程序主要阻塞在等待gen_server:call的返回中。

這裡但timeout不為infinity的時候的寫法,比較麻煩,monitor自己spawn出來的程序,然後receive阻塞等待該spawn出來的程序結束後的返回值。

不知道這個寫法是為什麼,其實直接都寫成下面這樣,好像也可以。因為如果這個臨時生成的中間**程序(middleman process)卡在gen_server:call中,貌似業務程序也卡住了。

do_call(node, request, timeout) ->

rpc_check(catch

gen_server:call(, request, timeout));

gen_server:call最終呼叫gen:do_call(process, 『$gen_call』, request, timeout), 業務程序使用erlang:send(, , request})將訊息發給node節點上的rex程序,並阻塞在receive中等待結果返回。

其實gen_server這一步我們可以略過,gen_server已是按它承諾的那樣把request訊息發給程序,不管程序是在本地還是在遠端。

注意:-define(name, rex).也就是node節點上的rex程序。

回到rex程序,rex程序是怎麼處理這個訊息的呢?

rpc裡面的gen_server handle_call方法:

handle_call

(, to, s) ->

handle_call_call(mod, fun, args, gleader, to, s);

handle_call_call方法:

handle_call_call

(mod, fun, args, gleader, to, s) ->

rpcserver = self(),

%% spawn not to block the rpc server.

= erlang:spawn_monitor(

fun () ->

set_group_leader(gleader),

reply =

%% in case some sucker rex'es

%% something that throws

case

catch

= exit ->

;result ->

result

end,

rpcserver ! }

end),

.

handle_info

(, s) ->

case

gb_trees:lookup(caller, s) of

->

receive

} ->

gen_server:reply(to, reply)

after

0 ->

gen_server:reply(to, })

end,

%% 按道理rex程序應該先收到執行結束的reply訊息,再收到程序down掉的訊息

%% 這裡這種寫法可能是擔心出現,先收到down再收到reply訊息

; none ->

end;

handle_info

(}, s) ->

case

gb_trees:lookup(caller, s) of

->

receive

->

gen_server:reply(to, reply),

end;

none ->

end;

最終rex呼叫gen_server:reply(to, reply)將結果返回給遠端節點上那個呼叫gen_server:call的業務程序。

整個過程,我們可以發現,基本上rex程序是不會因為call訊息太多而被阻塞住,但是隨著call訊息的增加,rex程序會不停地執行gb_trees:lookup、insert、delete和gen_server:reply操作,這也會導致rex程序的繁忙。

如果我們在node啟動乙個global程序globalserver,

globalserver有乙個call介面,get_value() -> gen_server:call(, get_value).

那麼rpc:call(node, globalserver, get_value, ) 和 gen_server:call(, get_value),這兩種方式那個效率高呢,有什麼區別?

就這種情況而言,gen_server:call比global server的效率更高。

因為rpc:call(node, globalserver, get_value, ) 最終是執行了 gen_server:call()。但是使用rpc的話,還要讓執行結果經過rex程序這層中介,徒增rex的工作。直接gen_server:call global server ,可以一步到位把訊息發給指定的globalserver就行了。

rpc這個看起來很高階的模組其實使用了太多gen_server提供的功能,自己的rex程序做的事情是比較簡單的,它只要在收到rpc 訊息的時候spawn乙個程序來執行,記錄下spawn的程序pid和對應的rpc訊息傳送pid,然後等待執行結束後,收到reply訊息,再將之前記錄的pid找出來,然後呼叫gen_server:repy返回結果。

通過上面的分析可以知道,rpc的實現呼叫了gen_server的介面。

如,rpc:call呼叫gen_server:call,gen_server:call又呼叫了erlang:send。

對於跨節點的遠端呼叫,要想進一步**下去,最終還是得去看erlang:send。

這裡還有很多有意思的地方:

不同節點之間如何通過pid找到對方成功傳遞訊息?

我們知道節點間的通訊使用的tcp協議,那麼這些gen_server:call、gen_server:reply使用的是同乙個tcp連線嗎?還是多個tcp連線?tcp連線一開始時誰建立的?

誰來解析tcp包?tcp包更上一層的協議格式定義是什麼?

Erlang rpc函式初學

剛開始學erlang的函式呼叫,昨天晚上一直不知道rpc這個功能到底是什麼的,今天敲了一下,原來是這麼一回事 module area server1 export loop 0,rpc 2 rpc pid,request pid 這是向指定pid傳送請求的 receive 這個是接收響應結果的 re...

XML RPC(遠端呼叫)

size large the xmlrpcclient size url 客戶端的配置要設定下面幾個物件。img 例子如下 利用預設的transportfactory的客戶端 片段 public static void main string args throws exception intege...

openFeign遠端呼叫

1.pom.xml引入 org.springframework.cloud spring cloud starter openfeign 2.編寫乙個介面 最好建乙個feign資料夾 告訴springcloud這個介面需要呼叫遠端服務 宣告介面的每個方法都是呼叫哪個遠端服務的哪個請求 例如 這是乙個...