共计 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! 留神到每当有座位空进去,马上就会被期待队列里的客人应用。
正文完