Как сделать параллельные звонки в Erlang и дождаться всех результатов? - PullRequest
0 голосов
/ 21 января 2019

Я работаю над бэкэндом для мобильных игр в Erlang.Для каждого HTTP-запроса может потребоваться запрос различных источников данных, таких как PostgreSQL, MongoDB и Redis.Я хочу сделать независимые вызовы к этим источникам данных параллельно, но не могу найти четкий способ Эрланга сделать это.

Например,

handle_request(?POST, <<"login">>, UserId, Token) ->
    % Verify token from PostgreSQL
    AuthResult = auth_service:login(UserId, Token), 

    % Get user data such as outfits and level from MongoDB
    UserDataResult = userdata_service:get(UserId),

    % Get messages sent to the user from Redis
    MessageResult = message_service:get(UserId),

    % How to run the above 3 calls in parallel?
    % Then wait for all their results here? 

    % Combine the result and send back to client
    build_response(AuthResult, UserDataResult, MessageResult).

Каждая служба в конечном итоге вызовет соответствующий драйвер данных (epgsql, eredis, mongo_erlang), который заканчивается вызовом pooboy :action и gen_server:.Как разработать эти сервисные модули, пока не решено.

Я хочу убедиться, что 3 вышеупомянутых вызова данных могут выполняться параллельно, а затем функция handle_request ожидает завершения всех этих 3 вызовов, а затемвызвать build_response.Как я могу сделать это правильно?

В качестве справки, в NodeJS я мог бы сделать это

var authPromise = AuthService.login(user_id, token);
var userDataPromise = UserdataService.get(user_id);
var messagePromise = MessageService.get(user_id);
Promise.all(authPromise, userDataPromise, messagePromise).then( function(values) { 
    buildResponse(values); 
}

В Scala я мог бы сделать это

val authFuture = AuthService.login(userId, token)
val userDataFuture = UserdataService.get(userId)
val messageFuture = MessageService.get(userId)
for {
    auth <- authFuture
    userData <- userDataFuture
    message <- messageFuture
} yield ( buildResponse(auth, userData, message )

Видимо,Я думаю, что проблема как проблема обещание / будущее / доходность.Но мне сказали, что, если я ищу Обещание в Эрланге, я могу пойти в неправильном направлении.Какова будет лучшая практика в Эрланге для достижения этой цели?

1 Ответ

0 голосов
/ 21 января 2019

Как сделать параллельные звонки в Erlang и дождаться всех результатов?

Вы можете использовать сложенные пункты получения . Erlang будет ждать в предложении приема вечно, пока не придет сообщение от процесса (или вы можете указать тайм-аут с помощью after) - что похоже на в ожидании обещания в nodejs:

-module(my).
-compile(export_all).

all_results() -> 
    Pid1 = spawn(?MODULE, getdata1, [self(), {10, 20}]),
    Pid2 = spawn(?MODULE, getdata2, [self(), 30]),
    Pid3 = spawn(?MODULE, getdata3, [self()]),

    [receive {Pid1, Result1} -> Result1 end, 
     receive {Pid2, Result2} -> Result2 end,
     receive {Pid3, Result3} -> Result3 end].

getdata1(From, {X, Y}) -> 
    %% mimic the time it takes to retrieve the data:
    SleepTime = rand:uniform(100),
    io:format("Sleeping for ~w milliseconds~n", [SleepTime]), 
    timer:sleep(SleepTime),

    From ! {self(), X+Y}.  %% send the data back to the main process

getdata2(From, Z) ->
    SleepTime = rand:uniform(100),
    io:format("Sleeping for ~w milliseconds~n", [SleepTime]),
    timer:sleep(SleepTime),

    From ! {self(), Z+1}.

getdata3(From) ->
    SleepTime = rand:uniform(100),
    io:format("Sleeping for ~w milliseconds~n", [SleepTime]),
    timer:sleep(SleepTime),

    From ! {self(), 16}. 

Обратите внимание, что этот код:

[receive {Pid1, Result1} -> Result1 end, 
 receive {Pid2, Result2} -> Result2 end,
 receive {Pid3, Result3} -> Result3 end].

эквивалентно:

R1 = receive {Pid1, Result1} -> 
         Result1 
     end,
R2 = receive {Pid2, Result2} -> 
         Result2 
     end,
R3 = receive {Pid3, Result3} -> 
         Result3 
     end,

[R1, R2, R3].

В оболочке:

~/erlang_programs$ erl
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V9.3  (abort with ^G)

1> c(my).                        
my.erl:2: Warning: export_all flag enabled - all functions will be exported
{ok,my}

2> timer:tc(my, all_results, []).
Sleeping for 66 milliseconds
Sleeping for 16 milliseconds
Sleeping for 93 milliseconds
{96356,[30,31,16]}

3> timer:tc(my, all_results, []).
Sleeping for 57 milliseconds
Sleeping for 30 milliseconds
Sleeping for 99 milliseconds
{100153,[30,31,16]}

4> timer:tc(my, all_results, []).
Sleeping for 66 milliseconds
Sleeping for 31 milliseconds
Sleeping for 24 milliseconds
{66426,[30,31,16]}

timer: tc () возвращает время, которое требуется функции для выполнения в микросекундах (1000 микросекунд = 1 миллисекунда), вместе с возвращаемым значением функции. Например, в первый раз, когда был вызван all_results(), потребовалось 96,4 миллисекунды для завершения, в то время как отдельным процессам потребовалось бы 66 + 16 + 93 = 175 + миллисекунд для завершения при последовательном выполнении.

...