Elixir:配置GenStages以消耗动态需求

时间:2018-12-12 13:46:50

标签: erlang elixir genstage

我有一个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

0 个答案:

没有答案