欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

elixir 高可用系列(二) GenServer

程序员文章站 2022-06-16 23:49:11
概述 如果我们需要管理多个进程,那么,就需要一个专门的 server 来集中监控和控制这些进程的状态,启停等。 OTP 平台中的 GenServer 就是对这个 server 通用部分的抽象。 利用 GenServer 中已经提供的通用操作, 可以很方便的开发出可靠,健壮的程序。 下面首先通过一个示 ......
概述

如果我们需要管理多个进程,那么,就需要一个专门的 server 来集中监控和控制这些进程的状态,启停等。
OTP 平台中的 GenServer 就是对这个 server 通用部分的抽象。

利用 GenServer 中已经提供的通用操作, 可以很方便的开发出可靠,健壮的程序。
下面首先通过一个示例演示 GenServer 的方便和强大之处,然后再对其进行介绍。

GenServer 示例

这是一个 GenServer 管理多个进程的示例,模拟控制各个进程的启动,停止,以及状态查询。

defmodule ProcessMonitor do
  use GenServer

  #====================================================
  # api for clients
  #====================================================
  # start GenServer
  def start(data, opt \\ []) do
    GenServer.start_link(__MODULE__, data, opt)
  end

  # add process which is controled by this GenServer
  def process_add(server, name) do
    GenServer.call(server, {:add, name})
  end

  # get process status
  def process_status(server, name) do
    GenServer.call(server, {:status, name})
  end

  # start a process by name
  def process_start(server, name) do
    GenServer.cast(server, {:start, name})
  end

  # stop a process by name
  def process_stop(server, name) do
    GenServer.cast(server, {:stop, name})
  end

  #====================================================
  # callbacks for server
  #====================================================
  def init(data) do
    {:ok, data}
  end

  # handle status message synchronization
  def handle_call({:status, name}, _from, data) do
    val = Map.get(data, name, nil)
    {:reply, val, data}
  end

  # handle add message synchronization
  def handle_call({:add, name}, _from, data) do
    data = Map.put(data, name, "stopped")
    {:reply, name, data}
  end

  # handle start message asynchronization
  def handle_cast({:start, name}, data) do
    data = Map.put(data, name, "running")
    {:noreply, data}
  end

  # handle stop message asynchronization
  def handle_cast({:stop, name}, data) do
    data = Map.put(data, name, "stopped")
    {:noreply, data}
  end

end

上面代码测试方法如下:

$ iex -S mix
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.2.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> {:ok, server} = ProcessMonitor.start(Map.new)               # 创建 GenServer,并初始化一个 map 用于存储此server管理的 process 信息
{:ok, #PID<0.87.0>}
iex(2)> ProcessMonitor.process_status(server, "process01")          # 创建 GenServer 后,默认没有管理任何进程,所以没有 process01 的信息
nil
iex(3)> ProcessMonitor.process_add(server, "process01")             # 给 GenServer 增加一个被管理进程 process01
"process01"
iex(4)> ProcessMonitor.process_status(server, "process01")          # 新加入的进程默认状态是 stopped,示例代码默认这么实现
"stopped"
iex(5)> ProcessMonitor.process_start(server, "process01")           # 启动 process01
:ok
iex(6)> ProcessMonitor.process_status(server, "process01")          # process01 状态变为 running
"running"
iex(7)> ProcessMonitor.process_stop(server, "process01")            # 停止 process01
:ok
iex(8)> ProcessMonitor.process_status(server, "process01")          # process01 状态变为 stopped
"stopped"
iex(9)> ProcessMonitor.process_add(server, "process02")             # 再增加一个被管理进程 process02
"process02"
iex(10)> ProcessMonitor.process_start(server, "process02")          # 启动 process02
:ok
iex(11)> ProcessMonitor.process_status(server, "process02")         # process02 状态变为 running
"running"
iex(12)> ProcessMonitor.process_status(server, "process01")         # process01 状态仍然是 stopped,不受 process02 的影响
"stopped"
iex(13)> ProcessMonitor.stop(server)                                # 停止 GenServer

注 上面的代码是用 mix 创建工程来运行的,mix 的使用方法可以参见 blog:mix 构建工具

GenServer 通用抽象简介

示例代码使用了 GenServer 中的几个关键函数: init handle_call handle_case

init: 这个函数在 GenServer.start_link 时执行,对 start_link 中的参数进行处理 handle_call: 这个函数接受同步消息并处理 handle_cast: 这个函数接受异步消息并处理

处理这3个常用的函数之外,GenServer 中的函数也不是很多,其他的函数,属性以及每个函数返回的值说明请参见:http://elixir-lang.org/docs/stable/elixir/GenServer.html

在上面的示例中,其实 client 也可以直接调用 GenServer 的 handle_call/handle_cast 来发送同步/异步消息,
我之所以封装了一些 api 给 client 调用,一方面,是为了简化客户端的调用(client 的 api 中参数更加简洁直观),
另一方面,将处理消息的代码和 发送消息的代码分开,便于以后扩展(因为,可能存在多个发送消息的处理都对应同一个消息处理)。

来源:http://blog.iotalabs.io/