構建比HTTP快100倍的Elixir Redis 伺服器 - statetrace

banq發表於2021-11-15

執行 Redis 協議比依賴 HTTP 快 100 倍以上。在大約 10 分鐘內編寫了一個基於 Redis 協議的高效能伺服器。該伺服器可以輕鬆處理數千個連線,並且開銷最小。一個缺點是,在使用 HTTP 以外的協議進行多節點部署時,負載平衡變得更加困難。
如果您有一個或數千個客戶端需要以最快的方式與伺服器通訊,請考慮使用 Redis 作為您的首選協議,而不是 HTTP。
構建基於 RESP(Redis 協議)的伺服器意味著您正在削減與 HTTP 相關的大量開銷。除了更精簡的協議之外,幾乎每種語言都有一個為 Redis 構建的高效能客戶端,允許流水線操作。流水線在您傳送命令時組合命令,以提高效率。大多數 redis 客戶端甚至支援池化以在高併發下工作。
使用這些內建功能,您無需做太多事情就可以以極高的效能方式與伺服器通訊。
完整示例:

defmodule MyRedisServer.Redis do
  require Logger

  def accept(port) do
    {:ok, socket} = :gen_tcp.listen(port, [:binary, active: false, reuseaddr: true])
    Logger.info("Accepting connections on port #{port}")
    loop_acceptor(socket)
  end

  defp loop_acceptor(socket) do
    {:ok, client} = :gen_tcp.accept(socket)

    {:ok, pid} =
      Task.start(fn ->
        serve(client, %{continuation: nil})
      end)

    :ok = :gen_tcp.controlling_process(client, pid)

    loop_acceptor(socket)
  end

  defp serve(socket, %{continuation: nil}) do
    case :gen_tcp.recv(socket, 0) do
      {:ok, data} ->  handle_parse(socket, Redix.Protocol.parse(data))
      {:error, :closed} -> :ok
    end
  end

  defp serve(socket, %{continuation: fun}) do
    case :gen_tcp.recv(socket, 0) do
      {:ok, data} ->  handle_parse(socket, fun.(data))
      {:error, :closed} -> :ok
    end
  end

  defp handle_parse(socket, {:continuation, fun}) do
    serve(socket, %{continuation: fun})
  end

  defp handle_parse(socket, {:ok, req, left_over}) do
    resp = handle(req)

    :gen_tcp.send(socket, Redix.Protocol.pack(resp))

    case left_over do
      "" -> serve(socket, %{continuation: nil})
      _ -> handle_parse(socket, Redix.Protocol.parse(left_over))
    end
  end

  def handle(data) do
    data
  end
end

有關完整的基準測試,請參閱原始碼
 

相關文章