やること
Phoenixアプリにmemcachedにアクセスするプロセスを追加する
Phoenixで子プロセスを立ち上げている部分を確認
Elixir(そしてErlang)は複数のプロセスが協働しながら一つのアプリケーションを実行するマルチプロセスモデルを採用しています.
なので当然Elixirで作られているPhoenixFrameworkも同様にマルチプロセスモデルを元に作られています.
実際に, アプリケーション作成時に以下の様なコアモジュールが作成されます.
defmodule PhoenixSample do
use Application
# See http://elixir-lang.org/docs/stable/elixir/Application.html
# for more information on OTP Applications
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [
# Start the endpoint when the application starts
supervisor(PhoenixSample.Endpoint, []),
# Start the Ecto repository
supervisor(PhoenixSample.Repo, []),
# Here you could define other workers and supervisors as children
# worker(PhoenixSample.Worker, [arg1, arg2, arg3]),
]
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: PhoenixSample.Supervisor]
Supervisor.start_link(children, opts)
end
# Tell Phoenix to update the endpoint configuration
# whenever the application is updated.
def config_change(changed, _new, removed) do
PhoenixSample.Endpoint.config_change(changed, removed)
:ok
end
end
これは何をしているかと言うと,
children = [
# Start the endpoint when the application starts
supervisor(PhoenixSample.Endpoint, []),
# Start the Ecto repository
supervisor(PhoenixSample.Repo, []),
# Here you could define other workers and supervisors as children
# worker(PhoenixSample.Worker, [arg1, arg2, arg3]),
]
の部分で, DBへのアクセスを行うプロセスとHTTP経由のアクセスを処理するプロセスをSupervisorとして立ち上げ
Supervisor.start_link(children, opts)
の部分で, 現在のプロセスの子プロセスとして登録しています.
Supervisor(監視者)とは何か?
Elixirでは, 一つの親プロセスの下に複数の子プロセスをぶら下げて,
子プロセスの生き死にに関する処理を親プロセスで一括で行うという方法が,
プロセス管理の一つの良いパターンとしてよく使われています.
この子プロセスの死活を監視している親プロセスのことをSupervisor呼び, 実際に何らかのタスクを処理していくプロセスをWorkerと呼びます.
もっとも, Supervisorの子がすべてWorkerとは限らず, 更にその子プロセスを監視するためのSupervisorだったりもします.
上のコードでも, DBへアクセスするRepositoryプロセスはsupervisorとして生成されており, おそらくこのRepositoryプロセスの子プロセスが,
実際のDBアクセス処理を行っていくことになっているのでしょう.
ここから更に詳しく, SupervisorモジュールについてやGenServerについての説明をしなければいけないと思うのですが,
参考にしたこちらの記事が非常に分かりやすかったので, リンクだけ貼って省略します.
memcachedにアクセスするプロセスを追加する
以上をふまえて, memcachedにアクセスするための子プロセスをサンプルアプリケーションに追加します.
なお, memcachedへのクライアントとしてmcdを, ワーカープロセスのプーリングにpoolboyを使用します.
依存パッケージの修正
mix.exsを開き, depsにライブラリを追加します.
defp deps do
[{:phoenix, "~> 1.1.4"},
{:mariaex, ">= 0.0.0"},
{:phoenix_ecto, "~> 2.0"},
{:phoenix_html, "~> 2.4"},
{:phoenix_live_reload, "~> 1.0", only: :dev},
{:gettext, "~> 0.9"},
{:cowboy, "~> 1.0"},
{:ecto_fixtures, github: "DockYard/ecto_fixtures"},
{:poolboy, "~> 1.5.1"}, # ADD
{:mcd, github: "EchoTeam/mcd"} #ADD
]
end
またapplicationsにも:poolboyを追加します.
def application do
[mod: {PhoenixSample, []},
applications: [:phoenix, :phoenix_html, :cowboy, :logger, :gettext,
:phoenix_ecto, :mariaex, :poolboy]]
end
memcached用Supervisorモジュールの作成
以下のようなモジュールを作成します
defmodule PhoenixSample.Memcached do
use Supervisor
def start_link do
Supervisor.start_link(__MODULE__, [])
end
def init([]) do
pool_options = [
name: {:local, :memcached_pool},
worker_module: :mcd,
size: 5,
max_overflow: 0,
]
arg = ['localhost', 11211]
children = [
:poolboy.child_spec(:memcached_pool, pool_options, arg)
]
supervise(children, strategy: :one_for_one)
end
def set(key, value) do
:poolboy.transaction :memcached_pool, &(:mcd.set(&1, key, value))
end
def get(key) do
:poolboy.transaction(:memcached_pool, fn worker ->
:mcd.get(worker, key)
|> case do
{:ok, resp} -> resp
_ -> "Boom!"
end
end)
end
def delete(key) do
:poolboy.transaction :memcached_pool, &(:mcd.delete(&1, key))
end
end
このモジュールは何をやっているかというと, プロセス起動時に複数のmcd用のワーカープロセスからなるグループを:memcached_poolという名前で作成し
def init([]) do
pool_options = [
name: {:local, :memcached_pool},
worker_module: :mcd,
size: 5,
max_overflow: 0,
]
arg = ['localhost', 11211]
children = [
:poolboy.child_spec(:memcached_pool, pool_options, arg)
]
supervise(children, strategy: :one_for_one)
end
memcachedにアクセスするときは, :memcached_poolという名前を元にプーリングされているプロセスを取得し
それを使ってアクセス実行
def set(key, value) do
:poolboy.transaction :memcached_pool, &(:mcd.set(&1, key, value))
end
:mcd.set/3
の第一引数は, memcachedにアクセスするプロセスのpidです.
サンプルアプリケーションに上記モジュールを子プロセスとして追加する
defmodule PhoenixSample do
use Application
# See http://elixir-lang.org/docs/stable/elixir/Application.html
# for more information on OTP Applications
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [
# Start the endpoint when the application starts
supervisor(PhoenixSample.Endpoint, []),
# Start the Ecto repository
supervisor(PhoenixSample.Repo, []),
# Here you could define other workers and supervisors as children
# worker(PhoenixSample.Worker, [arg1, arg2, arg3]),
supervisor(PhoenixSample.Memcached, []), # ADD
]
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: PhoenixSample.Supervisor]
Supervisor.start_link(children, opts)
end
# Tell Phoenix to update the endpoint configuration
# whenever the application is updated.
def config_change(changed, _new, removed) do
PhoenixSample.Endpoint.config_change(changed, removed)
:ok
end
end
動作確認
iexを使って動作を確認してみます.
[vagrant@vagrant-centos7 phoenix_sample]$ iex -S mix phoenix.server
Erlang/OTP 18 [erts-7.2] [source-e6dd627] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]
...
[info] Running PhoenixSample.Endpoint with Cowboy using http on port 4000
Interactive Elixir (1.3.0-dev) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> [info] Creating interface #PID<0.451.0> to memcached on 'localhost':11211
[info] Creating interface #PID<0.454.0> to memcached on 'localhost':11211
[info] Creating interface #PID<0.457.0> to memcached on 'localhost':11211
[info] Creating interface #PID<0.460.0> to memcached on 'localhost':11211
[info] Creating interface #PID<0.463.0> to memcached on 'localhost':11211
iex(2)> PhoenixSample.Memcached.set("name", "YMST")
{:ok, "YMST"}
iex(3)> PhoenixSample.Memcached.get("name")
"YMST"
$ memcached-tool localhost dump
Dumping memcache contents
Number of buckets: 1
Number of items : 1
Dumping bucket 2 - 1 total items
add ucO902IZy6w7DAHvEHd/Qw== 0 1458551639 10
?mYMST
$
見ての通り, サーバ立ち上げ時にmemcached用のプロセスが5つ作成され, それらのプロセスを使って
memcachedにアクセスできている様子がわかると思います.
おわりに
今回使用したサンプルプロジェクトはこちらになります.
https://github.com/ak-ymst/phoenix_sample
当初目的としていた, memcachedとの通信はできるようになりました.
が, 色々Wrapされたモジュールを使った形での実装になってしまいプリミティブな部分はまだまだ理解できたとは言えないので, もっと詳しく理解できたらまた記事を書いていきたいと思います.
参考
Elixir の OTP (GenServer 編) : http://qiita.com/naoya@github/items/ae17a8166e52fc463012
Phoenix で Poolboy を使ってコネクション(プロセス)プーリング: http://ymmtmsys.hatenablog.com/entry/2015/09/02/214254
Phoenixにオレオレプロセスを追加する: http://qiita.com/yokaigundan/items/d4a049676d1e58d0948e