elixir 高可用系列(二) 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/

分享给朋友:
您可能感兴趣的文章:
随机阅读: