我有一个GenStage生产者<-消费者,可以读取我的Amazon SQS队列中的消息并对其做某事,这意味着我的消费者在无事可做时要求需求,生产者只是简单地获取并尝试从亚马逊获取事件。现在,它可以很好地满足我的需求,因为每个使用者处理的事件最大为1。但是考虑到可伸缩性,我希望能够将每个阶段的max_demand设置得更高。
我的第一种方法是将max_demand增加到10。但是,根据文档,提出了警告:
在实现使用者时,我们通常设置:max_demand和 :min_demand订阅。 :max_demand指定最大值 :min_demand指定时必须发生的事件数量 触发更多需求的最低门槛。例如,如果 :max_demand为1000,而:min_demand为750,消费者会要求 最初有1000个事件,并且至少在收到至少1000个事件之后才要求 250。
这意味着如果队列中只有1个事件,并且没有其他事件出现或花费很长时间,则我必须等到达到10个事件才能处理该1个事件。这对我们的业务需求非常不利,在我看来有点奇怪。
因此,我的问题是:
如何绕过此设置,并将最大需求设置为大于1,但使其处理任何传入的请求编号?
(附加问题):GenStage为什么采用这种方式设计?有什么好处?
这是我为从生产者事件中使用而提取的抽象...我认为生产者代码更简单,因为它仅在被询问时提供数据,但如果有人认为有必要,我可以在此处添加:
defmodule MobileApi.GenstageWorkers.AwsSqsConsumer do
use GenStage
require Logger
alias ExAws.SQS
@ex_aws_sqs Application.get_env(
:mobile_api,
:ex_aws_sqs,
ExAws
)
def start_link(init_args, opts \\ []) do
GenStage.start_link(__MODULE__, init_args, opts)
end
def init(%{
producer_id: producer,
queue_name: queue_name,
processor: processor_function,
min_demand: min_demand,
max_demand: max_demand
}) do
state = %{
producer: producer,
subscription: nil,
queue: queue_name,
processor: processor_function
}
GenStage.async_subscribe(
self(),
to: state.producer,
min_demand: min_demand,
max_demand: max_demand
)
{:consumer, state}
end
def handle_subscribe(:producer, _opts, from, state),
do: {:automatic, Map.put(state, :subscription, from)}
def handle_info(:init_ask, %{subscription: subscription} = state) do
GenStage.ask(subscription, state.max_demand)
{:noreply, [], state}
end
def handle_info(_, state), do: {:noreply, [], state}
def handle_events(messages, _from, state) when is_nil(messages), do: {:noreply, [], state}
def handle_events(messages, _from, state) do
handle_messages(messages, state)
{:noreply, [], state}
end
defp handle_messages(messages, state) do
messages
|> Enum.reduce([], &parse_message/2)
|> process_message_batch(state.queue, state.processor)
end
defp parse_message(%{body: body, message_id: message_id, receipt_handle: receipt_handle}, acc) do
case Poison.decode(body) do
{:ok, decoded_body} ->
[{decoded_body, %{id: message_id, receipt_handle: receipt_handle}} | acc]
{:error, error_message} ->
Logger.error(
"An error has ocurred reading from queue, error message: #{inspect(error_message)} message body: #{
inspect(body)
}"
)
acc
end
end
defp process_message_batch([], _, _), do: nil
defp process_message_batch(messages_batch, queue_name, processor) do
{bodies, metadatas} = Enum.unzip(messages_batch)
Enum.map(bodies, fn body -> Task.start(fn -> processor.(body) end) end)
SQS.delete_message_batch(queue_name, metadatas)
|> @ex_aws_sqs.request
end
end