我在学习Elixir的多进程模型的同时,尝试让Phoeix能够访问Memcached

做的事情 (zuò de

在Phoenix应用中添加访问memcached的进程

确认在Phoenix中启动子进程的部分。

Elixir(以及Erlang)採用了多進程模型,多個進程協同執行一個應用程序。因此,显然以Elixir构建的Phoenix Framework也是基於多進程模型。

实际上,在创建应用程序时会创建以下类型的核心模块。

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]),
    ]

在这一部分中,我们将启动两个进程作为监管者,一个用于访问数据库,另一个用于处理通过HTTP的访问。

    Supervisor.start_link(children, opts)

在这个部分,它将当前进程注册为子进程。
什么是监督者?
在Elixir中,我们经常使用一种方法来管理进程,即在一个父进程下挂载多个子进程,并且由父进程统一处理子进程的生命周期。这种方法被广泛应用作为进程管理的一种良好模式。监督者是指监视子进程的状态的父进程,而实际上执行任务处理的进程被称为工作者。
最重要的是,并非所有监督者的子进程都是工作者,有时还会有其他监督者来监视这些子进程。
在上面的代码中,执行对数据库的访问的Repository进程被生成为一个监督者,很可能这个Repository进程的子进程将执行实际的数据库访问任务。

我觉得需要进一步详细讲解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

我还会在应用程序中添加poolboy。

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

使用Supervisor模块来创建memcached。

我将创建如下所示的模块。(Wǒ de .)

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
$ 

你可以看到,在服务器启动时会创建5个用于memcached的进程,并且可以通过这些进程访问memcached。

最后

本次使用的示例项目在这里。
https://github.com/ak-ymst/phoenix_sample

当初设定的目标,即与Memcached进行通信,已经实现了。
但是,实际的实现方式使用了各种包装过的模块,所以对于基本部分还不能说已经完全理解,希望能更深入地理解后再写一篇文章。

请参考

《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

bannerAds