Skip to content

Latest commit

 

History

History
361 lines (298 loc) · 9.81 KB

File metadata and controls

361 lines (298 loc) · 9.81 KB

16.2 OTPサーバ

14章のフィボナッチサーバーでは…

  • メッセージハンドリング
  • スケジューラの状態管理
    • プロセスに送る数字のキュー
    • 生成された結果
    • アクティブなプロセスのPIDのリスト

OTPは上記を行うライブラリを提供している

OTPサーバー 標準の名前を持つコールバック関数を1つ以上持つモジュールを書く

状態と単一サーバ

アキュームレーター: 再帰して自身を呼び出している間、パラメーターとして渡し続けられる値

サーバー ループのために再帰を使う 処理関数は状態を受け取り、処理し、状態を次の処理に渡す => OTPがやってくれる? 処理関数だけ書けば良い?

最初のOTPサーバ

  • サーバー開始時に数値を渡す
    数値: 現在の状態
  • :nexe_number というリクエストを受けると、現在の状態を返し、現在の状態を +1 する

mixを使って新しいプロジェクトを作る

goh@goh% mix new sequence
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/sequence.ex
* creating test
* creating test/test_helper.exs
* creating test/sequence_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd sequence
    mix test

Run "mix help" for more commands.

Sequenceサーバの基礎を作る

use GenServer: OTPの GenServer ビヘイビアをモジュールに追加する ビヘイビアにはデフォルトのコールバックが定義されている

handle_call/3

  • パラーメーター
    • クライアントが渡した最初に引数の情報
    • クライアントのPID
    • サーバーの状態
  • 戻り値: Tuple
    • :reply
      OTPに2つ目の要素を返すように指示している
    • 2つ目の要素
      クライアントに返す値
    • 3つ目の要素
      次の状態
      次回 handle_call が呼び出されたときの3つ目のパラメーターになる

手動でサーバを起動する

goh@goh% iex -S mix
Erlang/OTP 21 [erts-10.0.8] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

Compiling 2 files (.ex)
warning: function init/1 required by behaviour GenServer is not implemented (in module Sequence.Server).

We will inject a default implementation for now:

    def init(args) do
      {:ok, args}
    end

You can copy the implementation above or define your own that converts the arguments given to GenServer.start_link/3 to the server state.

  lib/sequence/server.ex:1

Generated sequence app
Interactive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help)
> {:ok, pid} = GenServer.start_link(Sequence.Server, 100)
{:ok, #PID<0.162.0>}
> GenServer.call(pid, :next_number)
100
> GenServer.call(pid, :next_number)
101
> GenServer.call(pid, :next_number)
102

GenServer.start_link(module, args, options \\ [] 新しいプロセスを開始し、リンクするようGenServerに依頼する Kernel.spawn_link/3 のような関数

GenServer.call(server, request, timeout \\ 5000) 第2引数は handle_call の第1引数になる 2つ以上のパラメーターを渡す場合、Tupleを使う

goh@goh% iex -S mix
Erlang/OTP 21 [erts-10.0.8] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

Interactive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help)
> {:ok, pid} = GenServer.start_link(Sequence.Server, 100)
{:ok, #PID<0.130.0>}
> GenServer.call(pid, :next_number)
100
> GenServer.call(pid, :next_number)
101
> GenServer.call(pid, :next_number)
102
> GenServer.call(pid, {:set_number, 999})
999
> GenServer.call(pid, :next_number)
999
> GenServer.call(pid, :next_number)
1000

ハンドラーはTupleやリストを使って複数の値を返せる

goh@goh% iex -S mix
Erlang/OTP 21 [erts-10.0.8] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

Compiling 1 file (.ex)
warning: function init/1 required by behaviour GenServer is not implemented (in module Sequence.Server).

We will inject a default implementation for now:

    def init(args) do
      {:ok, args}
    end

You can copy the implementation above or define your own that converts the arguments given to GenServer.start_link/3 to the server state.

  lib/sequence/server.ex:1

Interactive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help)
> {:ok, pid} = GenServer.start_link(Sequence.Server, 100)
{:ok, #PID<0.139.0>}
> GenServer.call(pid, :next_number)
100
> GenServer.call(pid, :next_number)
101
> GenServer.call(pid, {:set_number, 999})
999
> GenServer.call(pid, :next_number)
999
> GenServer.call(pid, :next_number)
1000
> GenServer.call(pid, {:factors, 0})
{:factors_of, 0, 1}
> GenServer.call(pid, {:factors, 10})
{:factors_of, 10, 11}

一方通行の呼び出し

handle_cast/2

  • パラーメーター
    • クライアントが渡した最初に引数の情報
    • サーバーの状態
  • 戻り値: Tuple
    • :noreply
    • 2つ目の要素
      次の状態

iex.r(module iex実行中に、指定されたモジュールのソースコードを再コンパイルする 再コンパイルしても稼働中のプロセスは更新されない => 新たにサーバーを起動する必要がある

goh@goh% iex -S mix
Erlang/OTP 21 [erts-10.0.8] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

Compiling 1 file (.ex)
warning: function init/1 required by behaviour GenServer is not implemented (in module Sequence.Server).

We will inject a default implementation for now:

    def init(args) do
      {:ok, args}
    end

You can copy the implementation above or define your own that converts the arguments given to GenServer.start_link/3 to the server state.

  lib/sequence/server.ex:1

Interactive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help)
> {:ok, pid} = GenServer.start_link(Sequence.Server, 100)
{:ok, #PID<0.139.0>}
> GenServer.call(pid, :next_number)
100
> GenServer.call(pid, :next_number)
101
> GenServer.cast(pid, {:increment_number, 200})
:ok
> GenServer.call(pid, :next_number)
302

サーバ実行のトラッキング

サーバー実行時のオプション [debug: [:trace] 以下の情報を表示する

  • 呼び出し元
  • 返り値
  • 次の状態
> {:ok, pid} = GenServer.start_link(Sequence.Server, 100, [debug: [:trace]])
{:ok, #PID<0.130.0>}
> GenServer.call(pid, :next_number)
*DBG* <0.130.0> got call next_number from <0.128.0>
*DBG* <0.130.0> sent 100 to <0.128.0>, new state 101
100
> GenServer.call(pid, :next_number)
*DBG* <0.130.0> got call next_number from <0.128.0>
*DBG* <0.130.0> sent 101 to <0.128.0>, new state 102
101

[debug: [:statistics]] 以下の情報を表示する

  • start_time: 開始日時
  • current_time: 現在日時
  • reductions:
    仕事量
    すべてのプロセスが公平にCPUを利用するために使われる値
  • messeges_in: サーバーが受け取ったメッセージ数
  • messeges_out: サーバーが送ったメッセージ数
> {:ok, pid} = GenServer.start_link(Sequence.Server, 100, [debug: [:statistics]])
{:ok, #PID<0.144.0>}
> GenServer.call(pid, :next_number)
100
> GenServer.call(pid, :next_number)
101
> :sys.statistics pid, :get
{:ok,
 [
   start_time: {{2018, 9, 23}, {15, 8, 52}},
   current_time: {{2018, 9, 23}, {15, 9, 5}},
   reductions: 100,
   messages_in: 2,
   messages_out: 2
 ]}

:sys プロセス同士がバックグラウンドでやり取りしているシステムメッセージのインターフェース [debug: [:trace, :statistics]] が指定された場合、 :sys.trace :sys.statistics が呼び出される

http://erlang.org/doc/man/sys.html

サーバーを開始した後で、ON/OFFできる

> GenServer.call(pid, :next_number)
304
> :sys.trace pid, true
:ok
> GenServer.call(pid, :next_number)
*DBG* <0.144.0> got call next_number from <0.128.0>
*DBG* <0.144.0> sent 305 to <0.128.0>, new state 306
305
> :sys.trace pid, false
:ok
> GenServer.call(pid, :next_number)
306

:sys.get_status(pid) プロセスの状態をプロセスのタイプに応じて返す gen_server の場合、コールバックモジュールの状態を返す

> :sys.get_status pid
{:status, #PID<0.144.0>, {:module, :gen_server},
 [
   [
     "$ancestors": [#PID<0.128.0>, #PID<0.69.0>],
     "$initial_call": {Sequence.Server, :init, 1}
   ],
   :running,
   #PID<0.128.0>,
   [statistics: {{{2018, 9, 23}, {15, 8, 52}}, {:reductions, 21}, 2, 2}],
   [
     header: 'Status for generic server <0.144.0>',
     data: [
       {'Status', :running},
       {'Parent', #PID<0.128.0>},
       {'Logged events', []}
     ],
     data: [{'State', 102}]
   ]
 ]}

format_status 関数を定義することで、メッセージを変更することができる

> {:ok, pid} = GenServer.start_link(Sequence.Server, 100)
{:ok, #PID<0.139.0>}
> GenServer.call(pid, :next_number)
100
> GenServer.call(pid, :next_number)
101
> :sys.get_status pid
{:status, #PID<0.139.0>, {:module, :gen_server},
 [
   [
     "$ancestors": [#PID<0.137.0>, #PID<0.73.0>],
     "$initial_call": {Sequence.Server, :init, 1}
   ],
   :running,
   #PID<0.137.0>,
   [],
   [
     header: 'Status for generic server <0.139.0>',
     data: [
       {'Status', :running},
       {'Parent', #PID<0.137.0>},
       {'Logged events', []}
     ],
     data: [{'State', "My current state is '102', and I'm happy"}]
   ]
 ]}

さらに深掘り

:observer.start() サーバーモニタリングツールを起動する

ref. PinterestElixometer: https://github.com/pinterest/elixometer

  • A light Elixir wrapper around exometer.
  • Elixometer allows you to define metrics and subscribe them automatically to the default reporter for your environment.