高负载高并发问题,不仅仅呈现在面试中,在日常生活中也很常见,比方周末去冷落的商场吃饭,餐厅们口常常会须要排队取号。能够演绎为“需要”和“资源”的不匹配,多进去的“需要”的不到满足,就须要有适合的机制让这些”需要“进行 期待 或者 撤销。
让咱们用 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! 留神到每当有座位空进去,马上就会被期待队列里的客人应用。