文档章节

elixir官方教程Mix与OTP(二) 代理

ljzn
 ljzn
发布于 2016/08/10 15:20
字数 1889
阅读 153
收藏 0

#代理

  1. 状态问题
  2. 代理
  3. ExUnit回调
  4. 其它代理动作
  5. 代理中的客户端/服务器

本章我们将创建一个名为KV.Bucket的模块.这个模块的作用是以一种可以被其它进程读取和修改的方式存储我们的键值对.

如果你跳过了入门指导,或者你在很久以前读过,请再读一遍关于进程的那一章.我们将以其为起点.

#状态问题

Elixir是一门不可变语言,默认不分享任何东西.如果我们想要提供一个状态,在其中能够从多个地方创建桶的存取值,在Elixir中我们有两个主要选项:

  • 进程
  • ETS(Erlang长期存储)

我们已经讨论过了进程,而ETS是我们将在之后的教程中探索的新东西.我们很少直接操作进程,而是使用Elixir和OTP中的抽象概念:

  • 代理--状态的简单外壳
  • GenServer--"通用服务器"封装了状态(的进程),支持同步和异步调用,支持代码重载,等等.
  • GenEvent--"通用事件"管理将事件发布到多个处理程序.
  • 任务--允许生成进程并在之后悄悄获取结果的异步计算单元.

我们将在本教程中探索这些抽象概念.记住它们都是在进程之上使用VM提供的基础特性来实现的,例如send,receive,spawnlink.

#代理

代理是状态的简单包装.如果你只想用进程来保持状态,代理是最好的选择.让我们在项目内开始一个iex会话:

$ iex -S mix

使用代理:

iex> {:ok, agent} = Agent.start_link fn -> [] end
{:ok, #PID<0.57.0>}
iex> Agent.update(agent, fn list -> ["eggs" | list] end)
:ok
iex> Agent.get(agent, fn list -> list end)
["eggs"]
iex> Agent.stop(agent)
:ok

我们以一个空列表的初始状态最为代理的开始.我们更新了代理的状态,将我们的新元素添加到列表头.Agent.update/3的第二参数是一个函数,它的输入是代理的当前状态,而返回值是期望的新状态.最后,我们试图得到整个列表.Agent.get/3的第二个参数是一个函数,它将状态作为输入,而返回值是Agent.get/3本身将返回的值.完成了代理操作之后,我们可以调用Agent.stop/3来终止代理进程.

让我们使用代理来实现KV.Bucket.在开始实现之前,让我们先写一些测试.创建一个名为test/kv/bucket_test.exs的文件,内容是:

defmodule KV.BucketTest do
  use ExUnit.Case, async: true

  test "stores values by key" do
    {:ok, bucket} = KV.Bucket.start_link
    assert KV.Bucket.get(bucket, "milk") == nil

    KV.Bucket.put(bucket, "milk", 3)
    assert KV.Bucket.get(bucket, "milk") == 3
  end
end

我们的第一个测试开启了一个新的KV.Bucket,并对其进行了一些get/2put/3操作,并断言了结果.我们不需要明确地停止代理,因为它与测试进程是相链接的,一旦测试结束代理会自动关闭.如果进程被命名,这就不管用了.

还要注意的是,我们传送了设置async: trueExUnit.Case.这个设置使得该测试和其它开启了:async选项的测试可以并行运行.这对于使用我们机器上的多核心来提升测试速度非常有用.注意仅当测试不依赖或改变任何全局值时,才可以设置:async选项.例如,如果测试要求写入文件系统,注册进程,访问数据库,你就不能将其设为异步以避免出现测试间的竞争状态.

不论是否是异步的,我们的新测试显然会失败,因为没有函数被实现.

为了修正失败的测试,让我们创建一个内容如下的lib/kv/bucket.ex文件.在偷看下面的实现之前,大胆尝试自己使用代理来实现KV.Bucket模块.

defmodule KV.Bucket do
  @doc """
  Starts a new bucket.
  """
  def start_link do
    Agent.start_link(fn -> %{} end)
  end

  @doc """
  Gets a value from the `bucket` by `key`.
  """
  def get(bucket, key) do
    Agent.get(bucket, &Map.get(&1, key))
  end

  @doc """
  Puts the `value` for the given `key` in the `bucket`.
  """
  def put(bucket, key, value) do
    Agent.update(bucket, &Map.put(&1, key, value))
  end
end

我们使用映射来存储我们的键值.捕获操作符&在入门教程中介绍过了.

现在KV.Bucket模块已经定义好了,我们的测试应该会通过!你可以通过运行mix test来自己试一试.

#ExUnit回调

再继续前进,继续添加更多的特性到KV.Bucket中之前,让我们来讨论一下ExUnit回调.如你期望的那样,KV.Bucket的所有测试都要求在启动阶段开启一个桶,并在测试完成后停止它.幸运的是,ExUnit支持回调,使得我们能够跳过这些重复任务.

让我们重写测试案例,来使用回调:

defmodule KV.BucketTest do
  use ExUnit.Case, async: true

  setup do
    {:ok, bucket} = KV.Bucket.start_link
    {:ok, bucket: bucket}
  end

  test "stores values by key", %{bucket: bucket} do
    assert KV.Bucket.get(bucket, "milk") == nil

    KV.Bucket.put(bucket, "milk", 3)
    assert KV.Bucket.get(bucket, "milk") == 3
  end
end

我们已经先用setup/1宏来定义了一个设置回调.setup/1回调会在每个测试之前运行,在与该测试相同的进程中.

注意我们需要一个机制来讲bucket的pid由回调传递给测试.我们使用_测试内容_来做到它.当我们从回调中返回{:ok, bucket: bucket},ExUnit会将元组的第二个元素(一个词典)融合进测试内容.测试内容是一个我们能够在测试定义中匹配的映射,它提供了获取块中的值得途径:

test "stores values by key", %{bucket: bucket} do
  # `bucket` 现在是设置块中的 bucket
end

你可以从ExUnit.Case模块和ExUnit.Callback文档中得到更多关于ExUnit案例和回调的内容.

#其它代理动作

除了获取值和更细代理状态,代理还允许我们通过调用Agent.get_and_update/2这个函数来同时实现取值与更新.让我们实现一个用于从桶中删除一个键并返回它当前值的函数KV.Bucket.delete/2:

@doc """
Deletes `key` from `bucket`.

Returns the current value of `key`, if `key` exists.
"""
def delete(bucket, key) do
  Agent.get_and_update(bucket, &Map.pop(&1, key))
end

现在轮到你来为上述功能编写测试!别忘了到Agent模块的文档中学习更多相关内容.

#代理中的客户端/服务器

在进入下一章之前,让我们来讨论一下代理中客户端与服务器的区别.让我们扩展一下刚才实现的delete/2函数:

def delete(bucket, key) do
  Agent.get_and_update(bucket, fn dict->
    Map.pop(dict, key)
  end)
end

我们传递给代理的函数中所发生的一切都是在代理进程中.这样,由于代理进程能接收并回应我们的信息,我们称代理进程为服务器.所有函数之外发生的都称为客户端.

这个区分很重要.如果有一个昂贵的动作要被执行,你必须考虑是在客户端还是服务器执行会更好.例如:

def delete(bucket, key) do
  Process.sleep(1000) # puts client to sleep
  Agent.get_and_update(bucket, fn dict ->
    Process.sleep(1000) # puts server to sleep
    Map.pop(dict, key)
  end)
end

当服务器执行一个漫长的操作时,所有对此服务器的其它请求都会被搁置,这有可能导致一些客户端超时.

在下一章我们将探索通用服务器,其中的客户端是相互隔离的,而服务器会被制作得更加明显.

© 著作权归作者所有

共有 人打赏支持
ljzn
粉丝 29
博文 69
码字总数 96245
作品 0
南平
程序员
私信 提问
elixir官方教程Mix与OTP(一) Mix入门

Mix入门 我们的第一个项目 编辑项目 执行测试 环境 探索 在本教程中,我们将学习如何构建一个完整的Elixir应用,包括监督树,配置,测试等等. 这个应用的功能是分布式键值仓库.我们将把键值对安排...

ljzn
2016/08/07
1K
2
elixir官方入门教程 学习资料

下一步该去哪 构建你的第一个Elixir项目 元编程 社区与其它资源 Erlang基础 想要学习更多?继续阅读! 构建你的第一个Elixir项目 为了开始你的第一个项目,Elixir装载了一个叫做Mix的构建工具....

ljzn
2016/08/06
144
0
elixir官方入门教程 介绍

介绍 安装 交互模式 运行脚本 提出疑问 欢迎! 在本教程中我们将教给你Elixir的基础,语法,如何定义模块,如何操作常用数据结构的特性等等.本章将确保Elixir安装好了,并且你能够成功运行Elixir的...

ljzn
2016/08/06
70
0
(译)循序渐进学习Elixir

——Chris Bell Learning Elixir at Made by Many 在今年奥兰多的Elixir大会上演讲之后,就经常有人问我:你们团队是如何学习Elixir和OTP的? 和学习其他语言一样,学习Elixir也需要付出时间...

ljzn
2016/09/29
115
0
Elixir v1.2.0-rc.1 发布,函数式编程语言

Elixir v1.2.0-rc.1 发布,Elixir v1.2 依赖于 Erlang 18 的大量特性,至少要求 Erlang 18+ 版本。 此版本更新内容如下: 改进 [Mix] Raise readable error message when parsertools is not...

oschina
2015/12/31
1K
2

没有更多内容

加载失败,请刷新页面

加载更多

Vue.js 3.0 新特性预览

Evan You(尤雨溪)(2018年11月16日)前几日的早上在 Vue Toronto 的主题演讲中预演了 Vue 3 。 利用现代浏览器支持的新功能,Vue 3 将成为我们已经了解和喜爱的 Vue.js 强大的的改进版本。...

我的卡
12分钟前
1
0
Mybatis自带连接池阅读

1、数据源初始化,初始化入口由SqlSessionFactoryBuilder.build(InputStream inputStream, String environment, Properties properties)方法提供 public SqlSessionFactory build(InputStre......

jcc_codingBoy
18分钟前
1
0
Oracle 数据库勒索病毒 RushQL 处理办法

Oracle 数据库勒索病毒 RushQL 处理办法 办法来自Oracle 官方: https://blogs.oracle.com/cnsupport_news/%E5%AF%B9%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9A%84%E2%80%9C%E6%AF%94%E7%89%B9%E5%......

rootliu
19分钟前
2
0
聊聊flink LocalEnvironment的execute方法

序 本文主要研究一下flink LocalEnvironment的execute方法 实例 final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); DataSet<RecordDto>......

go4it
21分钟前
1
0
Spring Boot中对自然语言处理工具包hanlp的调用详解

概 述 HanLP 是基于 Java开发的 NLP工具包,由一系列模型与算法组成,目标是普及自然语言处理在生产环境中的应用。而且 HanLP具备功能完善、性能高效、架构清晰、语料时新、可自定义的特点,...

左手的倒影
27分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部