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

让咱们用 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  endend

再定义这样一个函数,模仿客人们同时要求进入服务器,如果得不到响应,就会 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, 500got enter requestfree seats: 2got enter requestfree seats: 1got enter requestfree seats: 0got enter requestfree seats: 0free seats: 0free seats: 1free seats: 1free seats: 2free seats: 2free 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, 500got enter requestfree seats: 2got enter requestfree seats: 1got enter requestfree seats: 0got enter requestgot enter requestgot enter requestfree seats: 0free seats: 1free seats: 0free seats: 0free seats: 1free seats: 0free seats: 0free seats: 1free seats: 0free seats: 0free seats: 1free seats: 1free seats: 2free seats: 2free seats: 3

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