关于elixir:elixir-0061-高负载高并发问题的万能钥匙-队列queue

45次阅读

共计 2542 个字符,预计需要花费 7 分钟才能阅读完成。

高负载高并发问题,不仅仅呈现在面试中,在日常生活中也很常见,比方周末去冷落的商场吃饭,餐厅们口常常会须要排队取号。能够演绎为“需要”和“资源”的不匹配,多进去的“需要”的不到满足,就须要有适合的机制让这些”需要“进行 期待 或者 撤销。

让咱们用 elixir 模仿这样一个场景:一个服务器里一共有 3 个座位,每进来 1 个客人就要占用一个座位,座位占满之后服务器就无奈提供服务。

defmodule M5 do
  use GenServer

  @seats 3
  @wait_timeout 1000

  def start() do
    GenServer.start(__MODULE__, :ok)
  end

  def enter(pid) do
    GenServer.call(pid, :enter, @wait_timeout)
  end

  def leave(pid) do
    GenServer.cast(pid, :leave)
  end

  def init(_) do
    {:ok, @seats}
  end

  def handle_call(:enter, {_pid, _ref}, seats) do
    IO.puts("got enter request")

    if seats > 0 do
      {:reply, :ok, print(seats - 1)}
    else
      {:noreply, print(seats)}
    end
  end

  def handle_cast(:leave, seats) do
    IO.puts("free seats: #{seats}")
    {:noreply, print(seats + 1)}
  end

  defp print(seats) do
    IO.puts("free seats: #{seats}")
    seats
  end
end

再定义这样一个函数,模仿客人们同时要求进入服务器,如果得不到响应,就会 BOOM!

  def concurrent_enter(pid, n, t) do
    for _ <- 1..n do
      spawn(fn ->
        try do
          enter(pid)
          :timer.sleep(t)
          leave(pid)
        catch
          _, _ ->
            IO.puts("????")
        end
      end)
    end
  end

在同时进来的客人小于 3 人时,所有都很好,然而咱们晓得理论状况必定不会是这样,同时呈现的客人肯定会大于 3 人。
咱们晓得这里一共就 3 个座位,所以无论如何不能够同时解决超过 3 位客人。然而好消息是,每个客人有 1 秒钟的期待急躁,所以只有在客人失去急躁之前有座位空进去,咱们就不至于丢掉这位客人。
所以按理说只有在 1 秒钟之前,有客人来到,新的客人就能够进来,咱们来试试看是不是这样。设置同时进入的客人数量为 4,每位客人用餐工夫为 500 毫秒:

iex(8)> concurrent_enter s, 4, 500
got enter request
free seats: 2
got enter request
free seats: 1
got enter request
free seats: 0
got enter request
free seats: 0
free seats: 0
free seats: 1
free seats: 1
free seats: 2
free seats: 2
free seats: 3
????      

BOOM!为什么会这样?咱们留神到第 4 为客人申请进入时,是没有空座的,然而座位空进去之后,他也没有失去任何告诉,也就是他并不知道有空座了。
一种简略的解决方案就是应用 队列。让期待中的客人进入队列排队,每次服务器里有客人来到,就检查一下期待队列。只须要对咱们的代码做如下批改:

  def init(_) do
    {:ok, %{seats: @seats, queue: :queue.new()}}
  end

  def handle_call(:enter, {_pid, _ref} = from, %{seats: seats} = state) do
    IO.puts("got enter request")

    if seats > 0 do
      {:reply, :ok, do_enter(state)}
    else
      handle_overload(from, state)
    end
  end

  defp do_enter(%{seats: seats} = state) do
    %{state | seats: print(seats - 1)}
  end

  def handle_overload(from, %{queue: queue} = state) do
    {:noreply, %{state | queue: :queue.in(from, queue)}}
  end

  def handle_cast(:leave, %{seats: seats} = state) do
    IO.puts("free seats: #{seats}")

    {:noreply,
     state
     |> do_leave()
     |> check_queue()}
  end

  defp do_leave(state) do
    %{state | seats: print(state.seats + 1)}
  end

  defp check_queue(%{queue: queue} = state) do
    case :queue.out(queue) do
      {:empty, _queue} ->
        state

      {{:value, from}, queue} ->
        GenServer.reply(from, :ok)

        %{state | queue: queue}
        |> do_enter()
    end
  end

当初咱们能够挑战一些刺激的:6 人同时申请进入服务器,这是咱们实践上能够达到的最高负载:

iex(21)> concurrent_enter s, 6, 500
got enter request
free seats: 2
got enter request
free seats: 1
got enter request
free seats: 0
got enter request
got enter request
got enter request
free seats: 0
free seats: 1
free seats: 0
free seats: 0
free seats: 1
free seats: 0
free seats: 0
free seats: 1
free seats: 0
free seats: 0
free seats: 1
free seats: 1
free seats: 2
free seats: 2
free seats: 3

Perfect! 留神到每当有座位空进去,马上就会被期待队列里的客人应用。

正文完
 0