Elixir in Actionの続き。ようやっと3分の2くらい。写経もほどほどに実施しながらだとこのくらいですかね。
Chatpter6、7ではOpen Telecom Platformと、その上に構成されるGenServerに関する内容をざーっと通してました。
自前のloopで作っていた簡易サーバから、GenServerへと一般化、どのように処理しているのか?をボトルネックを解消していくという話の流れのなかで説明していっています。
サーバの基本的な役割は以下
Spawnでプロセスを分ける- loopでプロセスを回す
- processの状態を管理する
- messageに反応する
- 送信元に応答を返す(送る)
最終的にはGenServerに近づいていくので、大きくここら辺は割愛。
ただ、 非常に考え方は重要 だし、ここら辺がErlang/Elixirのアーキテクチャとして選ぶ価値があると判断される箇所だと思うので、ちゃんと理解する方が良さそうです。
Elixir/Erlangのサーバで重要な役割を持つのは以下。
- gen_server
- init, handle_call, handle_castなどのcallbacksを持つ
- 想定していない処理を無視するために、
def handle_info(_, state), do: somethingといった処理も入れる
- superevisor
- errorハンドリングやリカバリを担う
- application
- gen_event
- gen_fsm
single processで処理される時のボトルネックは、多くのリクエストが溜まるにつれて応答(process)が遅くなること。
そこで、concurrentに処理できるように拡張する。ただ、cncurrentに処理できるように spawn により別プロセスで処理可能にすると、同期的に処理したい時が複雑になる。そこで、Elixir/Erlangでは以下のように GenServer.reply を使い、うまいこと非同期処理を利用している。
def hanldle_call(....) do
spawn(fn ->
data = # 処理
GenServer.reply(caller, data) # 処理が終われば、GenServerの機構でメッセージを送る
end)
{:noreply, do_folder} # まずは非同期的に応答する
end
single processでリクエストを操作し、その実際の処理は子プロセスに実施させる。必ずこの方法が良いというわけではないが、本書ではこの方法を解の1つとして紹介していた。
ボトルネックの話でいうと、databaseを相手にしはじめると、DBとアプリをつなぐ間のpoolを処理するためのプロセスも関係してきますが、そこら辺はEctoでは poolboy が役割を担っているそうな。
Getting Startedを読んでなくていきなりこれでは辛いけれど、Getting Startedの後なら特にconcurrencyやfault toleranceの話を知る上ではこれは読んで価値ありそうです。特に、Erlang/Elixirに限らず、concurrencyやfault toleranceの考え方は参考になると思います。
補足コード
- Process.registerは以下
@doc """
Associates the name with a pid or a port identifier. `name`, which must
be an atom, can be used instead of the pid / port identifier with the
`Kernel.send/2` function.
`Process.register/2` will fail with `ArgumentError` if the pid supplied
is no longer alive, (check with `alive?/1`) or if the name is
already registered (check with `whereis/1`).
"""
@spec register(pid | port, atom) :: true
def register(pid, name) when not name in [nil, false, true] do
:erlang.register(name, pid)
end
- GenServer.reply/2は以下。Erlangのgen_server.replayも検索してみると似た感じ。
@doc """
Replies to a client.
This function can be used by a server to explicitly send a reply to a
client that called `call/3` or `multi_call/4`. When the reply cannot be
defined in the return value of `handle_call/3`.
The `client` must be the `from` argument (the second argument) received
in `handle_call/3` callbacks. Reply is an arbitrary term which will be
given back to the client as the return value of the call.
This function always returns `:ok`.
"""
@spec reply({pid, reference}, term) :: :ok
def reply(client, reply)
def reply({to, tag}, reply) do
try do
send(to, {tag, reply})
:ok
catch
_, _ -> :ok
end
end
1 Comment