我正在从Ruby背景学习Erlang,并且在掌握思维过程时遇到了一些困难。我试图解决的问题如下:
我需要向api发出相同的请求,每次我在响应中收到一个唯一的ID,我需要传递给下一个请求,直到没有返回ID。从每个响应中我需要提取某些数据并将其用于其他事情。
首先获取迭代器:
ShardIteratorResponse = kinetic:get_shard_iterator(GetShardIteratorPayload).
{ok,[{<<"ShardIterator">>,
<<"AAAAAAAAAAGU+v0fDvpmu/02z5Q5OJZhPo/tU7fjftFF/H9M7J9niRJB8MIZiB9E1ntZGL90dIj3TW6MUWMUX67NEj4GO89D"...>>}]}
解析shard_iterator ..
{_, [{_, ShardIterator}]} = ShardIteratorResponse.
向溪流记录发出kinesis请求......
GetRecordsPayload = [{<<"ShardIterator">>, <<ShardIterator/binary>>}].
[{<<"ShardIterator">>,
<<"AAAAAAAAAAGU+v0fDvpmu/02z5Q5OJZhPo/tU7fjftFF/H9M7J9niRJB8MIZiB9E1ntZGL90dIj3TW6MUWMUX67NEj4GO89DETABlwVV"...>>}]
14> RecordsResponse = kinetic:get_records(GetRecordsPayload).
{ok,[{<<"NextShardIterator">>,
<<"AAAAAAAAAAFy3dnTJYkWr3gq0CGo3hkj1t47ccUS10f5nADQXWkBZaJvVgTMcY+nZ9p4AZCdUYVmr3dmygWjcMdugHLQEg6x"...>>},
{<<"Records">>,
[{[{<<"Data">>,<<"Zmlyc3QgcmVjb3JkISEh">>},
{<<"PartitionKey">>,<<"BlanePartitionKey">>},
{<<"SequenceNumber">>,
<<"49545722516689138064543799042897648239478878787235479554">>}]}]}]}
我正在努力的是如何编写一个循环来持续点击该流的kinesis端点,直到没有更多的碎片迭代器,我想要所有记录。因为我不能像在Ruby中那样重新分配变量。
答案 0 :(得分:1)
警告:我的代码可能会被窃听,但是它会关闭&#34;。我从未运行它,也不知道最后的迭代器是什么样的。
我看到你正试图完全用shell做你的工作。它可能但很难。您可以使用命名函数和递归(since release 17.0 it's easier),例如:
F = fun (ShardIteratorPayload) ->
{_, [{_, ShardIterator}]} = kinetic:get_shard_iterator(ShardIteratorPayload),
FunLoop =
fun Loop(<<>>, Accumulator) -> % no clue how last iterator can look like
lists:reverse(Accumulator);
Loop(ShardIterator, Accumulator) ->
{ok, [{_, NextShardIterator}, {<<"Records">>, Records}]} =
kinetic:get_records([{<<"ShardIterator">>, <<ShardIterator/binary>>}]),
Loop(NextShardIterator, [Records | Accumulator])
end,
FunLoop(ShardIterator, [])
end.
AllRecords = F(GetShardIteratorPayload).
但输入shell太复杂了......
在模块中编写代码要容易得多。
erlang中的一个常见模式是生成另一个进程或进程来获取数据。为了简单起见,您可以通过调用spawn or spawn_link
来生成其他流程,但现在不要使用链接,只使用spawn/3
。
让我们编译简单的消费者模块:
-module(kinetic_simple_consumer).
-export([start/1]).
start(GetShardIteratorPayload) ->
Pid = spawn(kinetic_simple_fetcher, start, [self(), GetShardIteratorPayload]),
consumer_loop(Pid).
consumer_loop(FetcherPid) ->
receive
{FetcherPid, finished} ->
ok;
{FetcherPid, {records, Records}} ->
consume(Records),
consumer_loop(FetcherPid);
UnexpectedMsg ->
io:format("DROPPING:~n~p~n", [UnexpectedMsg]),
consumer_loop(FetcherPid)
end.
consume(Records) ->
io:format("RECEIVED:~n~p~n",[Records]).
和抓手:
-module(kinetic_simple_fetcher).
-export([start/2]).
start(ConsumerPid, GetShardIteratorPayload) ->
{ok, [ShardIterator]} = kinetic:get_shard_iterator(GetShardIteratorPayload),
fetcher_loop(ConsumerPid, ShardIterator).
fetcher_loop(ConsumerPid, {_, <<>>}) -> % no clue how last iterator can look like
ConsumerPid ! {self(), finished};
fetcher_loop(ConsumerPid, ShardIterator) ->
{ok, [NextShardIterator, {<<"Records">>, Records}]} =
kinetic:get_records(shard_iterator(ShardIterator)),
ConsumerPid ! {self(), {records, Records}},
fetcher_loop(ConsumerPid, NextShardIterator).
shard_iterator({_, ShardIterator}) ->
[{<<"ShardIterator">>, <<ShardIterator/binary>>}].
正如您所看到的,两个流程可以同时完成工作。 试试你的shell:
kinetic_simple_consumer:start(GetShardIteratorPayload).
现在您看到您的shell进程转向使用者,并且在fetcher发送{ItsPid, finished}
之后您将返回shell。
下次而不是
kinetic_simple_consumer:start(GetShardIteratorPayload).
运行:
spawn(kinetic_simple_consumer, start, [GetShardIteratorPayload]).
你应该玩产卵过程 - 它是erlang的主要力量。
答案 1 :(得分:0)
在Erlang中,您可以使用尾递归函数编写循环。我不知道动态API,所以为了简单起见,我假设kinetic:next_iterator/1
在没有更多分片时返回{ok, NextIterator}
或{error, Reason}
。
loop({error, Reason}) ->
ok;
loop({ok, Iterator}) ->
do_something_with(Iterator),
Result = kinetic:next_iterator(Iterator),
loop(Result).
您正在用迭代替换循环。第一个子句处理的情况是,没有更多的分片(总是以结束条件开始递归)。第二个子句处理case,我们得到了一些迭代器,我们用它做一些事情然后调用它。
递归调用是函数体中的最后一条指令,称为尾递归。 Erlang优化了这样的调用 - 它们不使用调用堆栈,因此它们可以在常量内存中无限运行(你不会得到类似“堆栈级别太深”的内容)