文档章节

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

ljzn
 ljzn
发布于 2016/08/10 15:20
字数 1889
阅读 147
收藏 0
点赞 0
评论 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官方入门教程 学习资料

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

ljzn
2016/08/06
144
0
elixir官方教程Mix与OTP(一) Mix入门

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

ljzn
2016/08/07
1K
2
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官方入门教程 进程

进程 和 链接 任务 状态 在Elixir中,所有代码都运行在进程内。进程相互独立,并发地运行,通过传送信息来交流。进程不是Elixir中唯一的并发基础,但它意味着能够构建分布式的,可容错的程序...

ljzn
2016/08/04
34
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
elixir官方教程Mix与OTP(九) 分布式任务与配置

分布式任务与配置 我们的第一个分布式代码 同步/等待 分布式任务 路由层 测试过滤器与标签 应用环境与配置 总结 本章,我们将回到应用并添加一个路由层,它能让我们根据桶名来在节点之间分布请...

ljzn
2016/08/12
37
0
elixir官方教程Mix与OTP(六) 依赖和伞形项目

依赖和雨伞项目 外部依赖 内部依赖 雨伞项目 伞内依赖 总结 本章,我们将讨论如何在Mix中管理依赖. 我们的应用已经完成,所以是时候实现能够处理我们在第一章中定义的请求的服务器了: 然而,我们...

ljzn
2016/08/11
62
0
elixir官方教程Mix与OTP(四) 主管与应用

主管与应用 我们的第一个主管 理解应用 开启应用 应用回调 项目还是应用? 简单的一对一主管 监督树 观察者 测试中的共用状态 现在,我们的应用有个一个能监控几十个桶,不是几百个,的注册表.尽...

ljzn
2016/08/10
47
0
elixir官方教程Mix与OTP(三) 通用服务器

通用服务器 我们的第一个通用服务器 测试一个通用服务器 监控的需要 ,还是? 监控器还是链接? 之前的章节里我们用代理来代表桶.在第一章,我们曾指出想要给每个桶命名,之后我们可以这样做: 因为...

ljzn
2016/08/10
60
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

防火墙实例

3、一个包过滤防火墙实例 环境:redhat9 加载了string time等模块 eth0 接外网──ppp0 eth1 接内网──192.168.0.0/24 #!/bin/sh modprobe ipt_MASQUERADE modprobe ip_conntrack_ftp modp...

李超小牛子
3分钟前
0
0
TensorFlow 作用域与操作符的受限范围

variable_scope 影响变量和操作符 name_scope 只影响操作符 with tf.name_scope(""),使用空字符串将作用域返回到顶层 tf.variable_scope("") 相当于添加一个空层 import tensorflow as tf...

阿豪boy
13分钟前
0
0
Java面试基础篇——第六篇:常见Map类的区别

常见的map类有: HashMap, ConcurrentHashMap (Jdk1.8) , LinkedHashMap, TreeMap, Hashtable。 其中我们最常用的莫过于HashMap, 和并发情况下使用的ConcurrentHashMap了,它们的主要区别就在...

developlee的潇洒人生
15分钟前
0
0
崛起于Springboot2.X之前端模版freemaker(23)

1、配置文件 spring: freemarker: allow-request-override: false cache: true check-template-location: true charset: UTF-8 content-type: text/html ......

木九天
32分钟前
1
0
spring-boot:run启动时,指定spring.profiles.active

Maven启动指定Profile通过-P,如mvn spring-boot:run -Ptest,但这是Maven的Profile。 如果要指定spring-boot的spring.profiles.active,则必须使用mvn spring-boot:run -Drun.profiles=test......

夜黑人模糊灬
33分钟前
0
0
大数据分析挖掘技术学习:Python文本分类

引言 文本分类作为自然语言处理任务之一,被广泛应用于解决各种商业领域的问题。文本分类的目的是将 文本/文档 自动地归类为一种或多种预定义的类别。常见的文本分类应用如下: • 理解社交媒...

加米谷大数据
38分钟前
0
0
istio-0.8 指标监控,prometheus,grafana

配置: https://istio.io/docs/tasks/telemetry/metrics-logs/ https://istio.io/docs/tasks/telemetry/tcp-metrics/ envoy拦截请求>上报mixer>对接prometheus>grafana 效果截图: promethe......

xiaomin0322
40分钟前
0
0
公众号推荐

阿里技术 书籍:《不止代码》

courtzjl
43分钟前
0
0
关于改进工作效率

1.给不同的业务线建立需求群,所有的数据需求都在群里面提。 2.对于特别难搞定的事情,到对应的技术哪去做,有问题随时沟通。 3.定期给工作总结形成方法论。 4.学习新的技术,尝试用新的方法...

Avner
50分钟前
0
0
关于thinkphp 框架开启路径重写,无法获取Authorization Header

今天遇到在thinkphp框架中获取不到header头里边的 Authorization ,后来在.htaccess里面加多一项解决,记录下: <IfModule mod_rewrite.c> Options +FollowSymlinks -Multiviews Rewrite......

殘留回憶
53分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部