LoginSignup
30
25

More than 5 years have passed since last update.

Phoenixにオレオレプロセスを追加する

Last updated at Posted at 2015-08-13

はじめに

 elixirでシステムを組む醍醐味と言えば、プロセス指向プログラミングです。

 その根幹を支えるのがerlang由来のOTPです。オブジェクト指向におけるメッセージ(いわゆるドット)に相当する便利ツールが、プロセス指向ではerlangが提供するプロセス間メッセージング及び、これをOTPとしてラップしたgen_serverやgen_eventなどになります。

 オブジェクト指向ではクラスをひな形として作成したインスタンスがシステムを構成しておりました。プロセス指向においては、モジュールをひな形として作成されたプロセスがシステムを構成します。

 例えばデータを一時的に納めておく場所が欲しい時、オブジェクト指向な人はデータストアクラスが欲しいなと考えます。一方でプロセス指向な人はこれを、データストアプロセスが欲しいな、と考えます。

 都合、プロセスに対するアクセス手段としてコールバックやインタフェース関数が求められるところ、elixirはerlangのOTPを用いて、その実装の手間を解消しています。elixirの元となる言語がerlangではなくerlang/OTPと表記される由来の一つです。

 erlangにとって言語として無くてはならないのがOTPです。

 ブサメンにとっての二次元のようなものですね。

 ということで、phoenixアプリケーションもまた、elixirで書かれたアプリケーションでありますから、同様にOTPを用いてプロセス指向することができます。それではphoenixアプリケーションにオレオレなプロセスを追加してみましょう。

 尚、以降の作業に関しては、以下の記事でevmとkiex、erlang/OTPとexlir、Phoenixをインストールしていることが前提となります。動作の確認はcentos6で行っておりますが、centos7でも動くと思います。デビアンマンは適宜読み替えて下さい。

 phoenixとvernemqを連携させる

実装

 erlang/OTPのお作法として、仕事をするプロセス(worker)は監視用のプロセス(supervisor)に監視されていることが前提です。そこで最初はsupervisorを追加します。その上でsupervisorの管理下にworkerを追加することで、アプリケーションを構成します。

 phoenixのエントリポイントはlib/xxx.exです。xxxは最初にプロジェクトを作成した際のプロジェクト名となります。ここに記載されているsupervisorが最上位に位置するsupervisorとなり、その子としてsupervisorやworkerを追加することになります。

 PhoenixのSupervisorに子(Workers.Supervisor)を登録


$ vi lib/hello_world.ex
hello_world.ex
    children = [
      # Start the endpoint when the application starts
      supervisor(HelloWorld.Endpoint, []),
      # Start the Ecto repository
      worker(HelloWorld.Repo, []),
      # Here you could define other workers and supervisors as children
      # worker(HelloWorld.Worker, [arg1, arg2, arg3]),
      supervisor(HelloWorld.WorkersSupervisor, [])  # ADD
    ]

 ちなみにworker(HelloWorld.Repo)というのが、Phoenixのcontrollerでモデルの参照に利用されているRepoプロセスの起動項です。これを引っこ抜くとPhoenixはモデルに対するアクセス方法を失ってしまいます。

PhoenixのSupervisorに登録した子(HelloWorld.WorkersSupervisor)の実装


$ vi lib/hello_world/worker_supervisor.ex
worker_supervisor.ex
defmodule HelloWorld.WorkersSupervisor do
  use Supervisor

  require Logger

  alias HelloWorld.WorkersSupervisor
  alias HelloWorld.Worker

  def start_link do
    Supervisor.start_link(__MODULE__, [], name: __MODULE__)
  end

  def init(args) do
    children = [
      worker(Worker, [], restart: :transient)
    ]
    options = [
      strategy: :simple_one_for_one
    ]
    supervise(children, options)
  end

  def start_worker(args \\ []) do
    Supervisor.start_child(WorkersSupervisor, [args])
  end

  def count_workers do
    Supervisor.count_children(WorkersSupervisor)
  end

end

HelloWorld.WorkersSupervisorの子としてHelloWorld.Workerの実装


$ vi lib/hello_world/worker.ex
worker.ex
defmodule HelloWorld.Worker do

  use GenServer
  require Logger

  def start_link(args) do
    GenServer.start_link(__MODULE__, [])
  end

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

  def handle_call(msg, from, state) do
    Logger.info(msg)
    {:reply, msg, state}
  end

  def hello(worker_pid) do
    GenServer.call(worker_pid, "hello")
  end

end

 これでサーバ起動時にHelloWorld.WorkersSupervisorのプロセスがphoenixアプリケーションを構成するプロセスの一つとして立ち上がり、その監視下でHelloWorld.Workerを立ち上げることができるようになります。

確認

 では実際にHelloWorld.WorkerをHelloWorld.WorkersSupervisorの下でHelloWorld.Workerを立ち上げてみましょう。


$ iex -S mix phoenix.server
Erlang/OTP 17 [erts-6.4] [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

[info] MQTT connection established
[info] Running HelloWorld.Endpoint with Cowboy on http://localhost:4000
Interactive Elixir (1.0.5) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> HelloWorld.WorkersSupervisor.count_workers
%{active: 0, specs: 1, supervisors: 0, workers: 0}
iex(2)> HelloWorld.WorkersSupervisor.start_worker
{:ok, #PID<0.304.0>}
iex(3)> HelloWorld.WorkersSupervisor.count_workers
%{active: 1, specs: 1, supervisors: 0, workers: 1}
iex(4)> HelloWorld.WorkersSupervisor.start_worker
{:ok, #PID<0.307.0>}
iex(5)> HelloWorld.WorkersSupervisor.count_workers
%{active: 2, specs: 1, supervisors: 0, workers: 2}

 ポコポコとHelloWorld.Workerプロセスが立ち上がることが確認できます。

 ちなみにerlangはプロセスに対してマシンリソースを均等に割り当てるよう動作します。そして、同時に起動できるプロセス数の上限は、デフォルトで32768、最大で268435456です(実際に起動可能なプロセス数はメモリ依存)。つまり相当数のプロセスを捌くことが前提となっております。これがerlangにプロセス指向プログラミングを可能とさせています。

 では、こうして立ち上げたHelloWorld.Workerプロセスに対して、ちゃんと仕事(ログ出力)をしているか確認する為、メッセージを送ってみましょう。


iex(6)> {:ok, pid} = HelloWorld.WorkersSupervisor.start_worker
{:ok, #PID<0.365.0>}
iex(7)> HelloWorld.Worker.hello(pid)
[info] hello
"hello"

 コンソールにHelloWorld.Workerプロセスの出力したログが確認できます。

 このようにelixirは一つのアプリケーション中で幾つものプロセスを立ち上げて、その連携によりアプリケーション全体を構成します。並行だの並列だの言われるが所以は、これらプロセス指向プログラミングを行う為の手立てとも言えますね。

 

 

30
25
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
25