13
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PhoeixでElixirのマルチプロセスモデルを学びながらMemcachedにアクセスできるようにしてみる

Last updated at Posted at 2016-03-21

やること

Phoenixアプリにmemcachedにアクセスするプロセスを追加する

Phoenixで子プロセスを立ち上げている部分を確認

Elixir(そしてErlang)は複数のプロセスが協働しながら一つのアプリケーションを実行するマルチプロセスモデルを採用しています.
なので当然Elixirで作られているPhoenixFrameworkも同様にマルチプロセスモデルを元に作られています.

実際に, アプリケーション作成時に以下の様なコアモジュールが作成されます.

lib/sample_phoenix.ex
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にライブラリを追加します.

mix.exs
  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を追加します.

mix.exs
  def application do
    [mod: {PhoenixSample, []},
     applications: [:phoenix, :phoenix_html, :cowboy, :logger, :gettext,
                    :phoenix_ecto, :mariaex, :poolboy]]
  end

memcached用Supervisorモジュールの作成

以下のようなモジュールを作成します

lib/memcached.ex
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という名前で作成し

lib/memcached.ex
  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という名前を元にプーリングされているプロセスを取得し
それを使ってアクセス実行

lib/memcached.ex
  def set(key, value) do
    :poolboy.transaction :memcached_pool, &(:mcd.set(&1, key, value))
  end

:mcd.set/3の第一引数は, memcachedにアクセスするプロセスのpidです.

サンプルアプリケーションに上記モジュールを子プロセスとして追加する

phoenix_sample.ex
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

13
10
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
13
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?