我试图围绕Flow
包围以构建具有以下特征的并行处理管道:
{offset, %{"id" => id}}
offset
是一个单调递增的整数id
是一个任意数字id
的顺序,因此可以并行地按顺序计算不同的ID。因此分区。以下是生成无限量这些元组的示例流:
stream = Stream.unfold(1, fn i ->
offset = i+1
element = {offset, %{"id" => Enum.random(1..10_000)}}
{element, offset}
end)
我想按键id
对流进行分区。我知道如何做到这一点,例如开始8个平行阶段:
Flow.from_enumerable(stream)
|> Flow.partition(
key: fn {_, m} -> Map.get(m, "id") end,
stages: 8
)
我在此流程中遵循的每个操作现在都是并行发生的,订单仅由分区键id
保留。
offset
排序?要清楚,这是一个无限的流,所以我们需要记住,我们需要通过窗口加入(我已经尝试了多种方式。毕竟10秒的超时可以开始丢弃没有的事件从处理中及时到达。
以下是我应该如何对其进行图像处理的说明:
INCOMING
|
V
* PARTIONING in N-stages by `id`
|\
|-\
|--\
|||| PARALLEL PROCESSING in order by `id`
|--/
|-/
|/
| JOIN in order by `offset`
| timing out after 10 seconds, moving on with the smallest known offset
|
| SEQUENTIAL PROCESSING of each offset of the JOIN
答案 0 :(得分:3)
加入部分可以通过再次调用partition/2
,但将阶段数设置为1来完成。
下面是一个示例脚本,它通过发出带有偏移和随机分区的元组来重现您的用例:
1..10000
|> Stream.map(fn i -> {i, Enum.random([:a, :b, :c, :d])} end)
|> Flow.from_enumerable()
|> Flow.partition(key: {:elem, 1})
|> Flow.reduce(fn -> [] end, fn x, acc -> [process_x(x) | acc] end)
|> Flow.emit(:state)
|> Flow.partition(stages: 1)
|> Flow.reduce(fn -> [] end, &Kernel.++/2)
|> Flow.map_state(&Enum.sort/1)
|> Flow.emit(:state)
|> Enum.to_list()
|> IO.inspect
棘手的部分是分区。 分区后,您必须累积状态。
因此,在第一个分区之后,我们调用Flow.reduce/3
,处理元素,然后将它们放在列表的顶部。通过调用您必须实现的process_x
来完成处理。处理完所有条目后,我们要求将整个分区状态(即事件列表)发送到下一步。
然后我们再次进行分区,但这一次进入一个阶段,简单地连接先前分区的结果,然后在最后对它们进行排序。
上面示例中未考虑的另一点是您的流量是无限的,因此您需要添加一些窗口。您需要选择从每个分区发出项目的频率。对于第一个分区,您可以发出批量为1000个元素。对于已连接的分区,您提到您希望它每10秒发生一次。所以让我们添加它们。
最后,请注意上面的代码不是最有效的,因为在最后一个分区中运行的所有内容都是串行的(单个阶段)。理想情况下,您希望在第一个分区中进行排序,并在您定义的merge_sorted
函数的帮助下简单地合并最后一个分区中的排序结果。
以下是最终结果:
partition_window = Flow.Window.global |> Flow.Window.trigger_every(1000, :reset)
join_window = Flow.Window.global |> Flow.Window.trigger_periodically(10, :second, :reset)
1..10000
|> Stream.map(fn i -> {i, Enum.random([:a, :b, :c, :d])} end)
|> Flow.from_enumerable()
|> Flow.partition(key: {:elem, 1}, window: partition_window)
|> Flow.reduce(fn -> [] end, fn x, acc -> [process_x(x) | acc] end)
|> Flow.map_state(&Enum.sort/1)
|> Flow.emit(:state)
|> Flow.partition(stages: 1, window: join_window)
|> Flow.reduce(fn -> [] end, &merge_sorted/2)
|> Flow.emit(:state)
|> Enum.to_list()
|> IO.inspect