首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

代理 | Agent

代理是一种简单的状态抽象。

通常在Elixir中,需要共享或存储必须从不同进程或在不同时间点通过相同进程访问的状态。

Agent模块提供了一个基本的服务器实现,允许通过简单的API检索和更新状态。

实例

例如,在Elixir附带的Mix工具中,我们需要保留一组由给定项目执行的任务。由于这个集合是共享的,我们可以使用代理来实现它:

defmodule Mix.TasksServer do
  use Agent

  def start_link do
    Agent.start_link(fn -> MapSet.new end, name: __MODULE__)
  end

  @doc "Checks if the task has already executed"
  def executed?(task, project) do
    item = {task, project}
    Agent.get(__MODULE__, fn set ->
      item in set
    end)
  end

  @doc "Marks a task as executed"
  def put_task(task, project) do
    item = {task, project}
    Agent.update(__MODULE__, &MapSet.put(&1, item))
  end

  @doc "Resets the executed tasks and returns the previous list of tasks"
  def take_all() do
    Agent.get_and_update(__MODULE__, fn set ->
      {Enum.into(set, []), MapSet.new}
    end)
  end
end

代理提供客户端和服务器API之间的隔离(类似于GenServers)。特别是,给予的匿名函数Agent在代理(服务器)内执行。这种区别很重要,因为您可能想要避免代理内部进行昂贵的操作,因为它们将有效阻止代理,直到请求得到满足。

考虑这两个例子:

# Compute in the agent/server
def get_something(agent) do
  Agent.get(agent, fn state -> do_something_expensive(state) end)
end

# Compute in the agent/client
def get_something(agent) do
  Agent.get(agent, &(&1)) |> do_something_expensive()
end

第一个功能阻止代理。第二个函数将所有状态复制到客户端,然后在客户端执行操作。要考虑的一个方面是数据是否足够大以至于需要在服务器中进行处理,至少在初始阶段或足够小以便廉价地发送给客户端。另一个因素是数据是否需要以原子方式处理:do_something_expensive(state)在代理之外获取状态和呼叫意味着代理的状态可以在此期间更新。这在更新情况下特别重要,因为如果多个客户端试图将相同状态更新为不同的值,则在客户端而不是服务器中计算新状态可能会导致竞争状况。

最后注释use Agent定义了一个child_spec/1函数,允许将定义的模块置于监督树下。生成的child_spec/1可以使用以下选项进行自定义:

  • :id - 子规范ID,默认为当前模块
  • :start- 如何启动子进程(默认为调用__MODULE__.start_link/1
  • :restart - 当孩子应该重新启动时,默认为 :permanent
  • :shutdown-如何关闭孩子

例如:

use Agent, restart: :transient, shutdown: 10_000

有关Supervisor更多信息,请参阅文档。

名称注册

代理被绑定到与GenServers相同的名称注册规则。在GenServer文档中阅读更多关于它的信息。

关于分布式代理的一句话

考虑分布式代理的局限性很重要。代理提供了两个API,一个使用匿名函数,另一个使用显式模块,函数和参数。

在具有多个节点的分布式安装中,仅当调用方(客户端)和代理具有相同版本的调用方模块时,才能使用接受匿名函数的API。

请记住,使用代理执行“滚动升级”时也会出现此问题。通过滚动升级,我们意味着以下情况:您希望通过关闭一些节点并将它们替换为运行新版本软件的节点来部署新版本的软件。在这种设置中,你的环境的一部分将有一个给定模块的版本,而另一部分则是同一模块的另一个版本(较新版本)。

最好的解决方案是在使用分布式代理时简单地使用显式的模块,函数和参数API。

热码交换

只需将模块,函数和参数元组传递给更新指令,代理就可以实现其代码热插拔。例如,假设您有一个名为代理:sample并想要将其内部状态从关键字列表转换为地图。它可以通过以下指令完成:

{:update, :sample, {:advanced, {Enum, :into, [%{}]}}}

代理的状态将[%{}]作为第一个参数添加到arguments()的给定列表中。

类型

agent()

代理参考

name()

代理名称

on_start()

start*函数的返回值

州%28%29

代理状态

函数

cast(agent, fun)

对代理状态执行强制(火灾和遗忘)操作

cast(agent, module, fun, args)

对代理状态执行强制(火灾和遗忘)操作

get(agent, fun, timeout \ 5000)

通过给定的匿名函数获取代理值

get(agent, module, fun, args, timeout \ 5000)

通过给定的函数获取代理值

get_and_update(agent, fun, timeout \ 5000)

通过给定的匿名函数在一次操作中获取并更新代理状态

get_and_update(agent, module, fun, args, timeout \ 5000)

通过给定的函数在一次操作中获取并更新代理状态

start(fun, options \ [])

启动没有链接的代理程序进程(在监督树之外)

start(module, fun, args, options \ [])

启动一个没有与给定模块,函数和参数链接的代理

start_link(fun, options \ [])

用给定的函数启动链接到当前进程的代理

start_link(module, fun, args, options \ [])

启动链接到当前进程的代理

stop(agent, reason \ :normal, timeout \ :infinity)

同步停止给定的代理 reason

update(agent, fun, timeout \ 5000)

通过给定的匿名函数更新代理状态

update(agent, module, fun, args, timeout \ 5000)

通过给定的函数更新代理状态

agent()

agent() :: pid | {atom, node} | name

代理参考

name()

name() :: atom | {:global, term} | {:via, module, term}

代理名称

on_start()

on_start ::
  {:ok, pid} |
  {:error, {:already_started, pid} | term}

start*函数的返回值

state()

state() :: term

代理状态

cast(agent, fun)

cast(agent, (state -> state)) :: :ok

对代理状态执行强制(火灾和遗忘)操作。

该函数fun被发送给agent调用传递代理状态的函数。返回值fun成为代理的新状态。

请注意,无论agent是否存在(或应存活的节点),都会立即cast返回:ok

cast(agent, module, fun, args)

cast(agent, module, atom, [term]) :: :ok

对代理状态执行强制(火灾和遗忘)操作。

cast/2模块,函数和参数相同,而不是匿名函数。该状态作为第一个参数添加到给定的参数列表中。

get(agent, fun, timeout \ 5000)

get(agent, (state -> a), timeout) :: a when a: var

通过给定的匿名函数获取代理值。

该函数fun被发送给agent调用传递代理状态的函数。该函数返回函数调用的结果。

timeout是一个大于零的整数,它指定代理在执行函数之前允许多少毫秒,并返回结果值或:infinity无限期等待的原子。如果在指定的时间内没有收到结果,则函数调用失败,调用方退出。

实例

iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
iex> Agent.get(pid, fn state -> state end)
42

get(agent, module, fun, args, timeout \ 5000)

get(agent, module, atom, [term], timeout) :: any

通过给定函数获取代理值。

get/3但是需要一个模块、函数和参数,而不是匿名函数。状态作为第一个参数添加到给定的参数列表中。

get_and_update(agent, fun, timeout \ 5000)

get_and_update(agent, (state -> {a, state}), timeout) :: a when a: var

通过给定的匿名函数在一个操作中获取和更新代理状态。

该函数fun被发送给agent调用传递代理状态的函数。该函数必须返回一个带有两个元素的元组,第一个元素是要返回的值(即“get”值),第二个元素是代理的新状态。

timeout是一个大于零的整数,它指定代理在执行函数之前允许多少毫秒,并返回结果值或:infinity无限期等待的原子。如果在指定的时间内没有收到结果,则函数调用失败,调用方退出。

实例

iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
iex> Agent.get_and_update(pid, fn state -> {state, state + 1} end)
42
iex> Agent.get(pid, fn state -> state end)
43

get_and_update(agent, module, fun, args, timeout \ 5000)

get_and_update(agent, module, atom, [term], timeout) :: any

通过给定的函数在一个操作中获取和更新代理状态。

get_and_update/3模块,函数和参数相同,而不是匿名函数。该状态作为第一个参数添加到给定的参数列表中。

start(fun, options \ [])

start((() -> term), GenServer.options) :: on_start

启动没有链接的代理程序进程(在监督树之外)。

查看start_link/2更多信息。

实例

iex> {:ok, pid} = Agent.start(fn -> 42 end)
iex> Agent.get(pid, fn(state) -> state end)
42

start(module, fun, args, options \ [])

start(module, atom, [any], GenServer.options) :: on_start

启动一个没有与给定模块,函数和参数链接的代理。

start_link/4想了解更多信息。

start_link(fun, options \ [])

start_link((() -> term), GenServer.options) :: on_start

用给定的函数启动链接到当前进程的代理。

这通常用于启动代理作为监督树的一部分。

代理生成后,将fun调用给定的函数,并将其返回值用作代理状态。请注意,start_link/2直到给定的函数返回才会返回。

备选方案

:name选项用于模块文档中所述的注册。

如果该:timeout选项存在,则允许代理程序在初始化时最多花费给定的毫秒数,否则代理程序将被终止并且启动函数将返回{:error, :timeout}

如果:debug选项存在,:sys模块中的相应功能将被调用。

如果:spawn_opt选项存在,其值将作为选项传递给底层流程Process.spawn/4

返回值

如果服务器已成功创建并初始化,则该函数返回{:ok, pid},其中pid是服务器的PID。如果具有指定名称的代理已经存在,则函数返回{:error, {:already_started, pid}}该进程的PID。

如果给定的函数回调失败,则函数返回{:error, reason}

实例

iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
iex> Agent.get(pid, fn state -> state end)
42

iex> {:error, {exception, _stacktrace}} = Agent.start(fn -> raise "oops" end)
iex> exception
%RuntimeError{message: "oops"}

start_link(module, fun, args, options \ [])

start_link(module, atom, [any], GenServer.options) :: on_start

启动链接到当前进程的代理。

start_link/2模块,函数和参数相同,而不是匿名函数; funmodule将被调用给定的参数args来初始化状态。

stop(agent, reason \ :normal, timeout \ :infinity)

stop(agent, reason :: term, timeout) :: :ok

同步停止给定的代理reason

如果代理以给定的原因终止,它将返回:ok。如果代理因其他原因而终止,则该呼叫将退出。

该函数保持关于错误报告的OTP语义。如果原因是其他原因:normal:shutdown或者{:shutdown, _}将记录错误报告。

实例

iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
iex> Agent.stop(pid)
:ok

update(agent, fun, timeout \ 5000)

update(agent, (state -> state), timeout) :: :ok

通过给定的匿名函数更新代理状态。

该函数fun被发送给agent调用传递代理状态的函数。返回值fun成为代理的新状态。

这个函数总是返回:ok

timeout是一个大于零的整数,它指定代理在执行函数之前允许多少毫秒,并返回结果值或:infinity无限期等待的原子。如果在指定的时间内没有收到结果,则函数调用失败,调用方退出。

实例

iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
iex> Agent.update(pid, fn state -> state + 1 end)
:ok
iex> Agent.get(pid, fn state -> state end)
43

update(agent, module, fun, args, timeout \ 5000)

update(agent, module, atom, [term], timeout) :: :ok

通过给定的函数更新代理状态。

update/3模块,函数和参数相同,而不是匿名函数。该状态作为第一个参数添加到给定的参数列表中。

扫码关注腾讯云开发者

领取腾讯云代金券