热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

elixir官方教程Mix与OTP(三)通用服务器

为什么80%的码农都做不了架构师?#通用服务器我们的第一个通用服务器测试一个通用服务器监控的需要call,cast还是info?监控器还是链接?之前的章节里我们

为什么80%的码农都做不了架构师?>>>   hot3.png

#通用服务器

  1. 我们的第一个通用服务器
  2. 测试一个通用服务器
  3. 监控的需要
  4. call,cast还是info?
  5. 监控器还是链接?

之前的章节里我们用代理来代表桶.在第一章,我们曾指出想要给每个桶命名,之后我们可以这样做:

CREATE shopping
OKPUT shopping milk 1
OKGET shopping milk
1
OK

因为代理也是进程,所以每个桶有一个进程辨识符(pid)却没有名字.我们已经在进程章节中学习过名称注册,你可以在这里使用它.例如,我们可以这样创造一个桶:

iex> Agent.start_link(fn -> %{} end, name: :shopping)
{:ok, #PID<0.43.0>}
iex> KV.Bucket.put(:shopping, "milk", 1)
:ok
iex> KV.Bucket.get(:shopping, "milk")
1

然而,这是一个糟糕的主意!在Elixir中进程名必须是原子,这意味着我们需要将桶名(通常接收自外部客户端)转换成原子,而我们永远不应该将用户输入转换成原子.这是因为原子不支持垃圾回收.原子一旦创造出来,就永远不会被回收.从用户输入中生成原子意味着用户可以注入足够多的不同名字来耗尽我们的系统内存!

在实际中你会在耗尽内存之前达到Erlang虚拟机的原子上限,然后系统会被强制关闭.

没有滥用名称注册功能,我们创造了自己的_注册过程_.就是将桶名与桶进程的联系放到了一个映射中.

注册表需要保证词典总是最新的.例如,如果一个桶进程因为一个bug而崩溃了,注册表必须将词典清理干净以避免提供陈旧的条目.在Elixir中,我们将此描述为注册表需要_监控_每个桶.

我们将使用通用服务器来创建一个能监控桶进程的注册表进程.GenServers是Elixir和OTP中用于构建通用服务器的go-to抽象.

#我们的第一个通用服务器

一个通用服务器由两部分实现:客户端API和服务器回调,要么是在一个模块中,要么是在两个不同的模块中实现的它们.客户端与服务器运行在不同的进程中,当函数被调用时,客户端将会传递信息给服务器.在此我们使用一个模块来定义服务器回调和客户端API.新建lib/kv/registry.ex文件:

defmodule KV.Registry douse GenServer## Client API&#64;doc """Starts the registry."""def start_link doGenServer.start_link(__MODULE__, :ok, [])end&#64;doc """Looks up the bucket pid for &#96;name&#96; stored in &#96;server&#96;.Returns &#96;{:ok, pid}&#96; if the bucket exists, &#96;:error&#96; otherwise."""def lookup(server, name) doGenServer.call(server, {:lookup, name})end&#64;doc """Ensures there is a bucket associated to the given &#96;name&#96; in &#96;server&#96;."""def create(server, name) doGenServer.cast(server, {:create, name})end## Server Callbacksdef init(:ok) do{:ok, %{}}enddef handle_call({:lookup, name}, _from, names) do{:reply, Map.fetch(names, name), names}enddef handle_cast({:create, name}, names) doif Map.has_key?(names, name) do{:noreply, names}else{:ok, bucket} &#61; KV.Bucket.start_link{:noreply, Map.put(names, name, bucket)}endend
end

第一个函数是start_link/3,它开启了一个新的GenServer并传送了三个参数:

  1. 服务器回调将会在哪一个模块中实现,__MODULE__意为当前模块

  2. 初始参数,此时是原子:ok

  3. 选项列表,它有保存服务器名称等功能.现在我们传送一个空列表

你可以向GenServer发送两种请求:呼叫与投掷.呼叫是同步的,服务器必须回应.投掷是异步的,服务器不会回应.

接下来的两个函数,lookup/2create/2负责发送这些请求到服务器.这些请求会出现在handle_call/3handle_cast/2的第一个参数.本例中,我们分别使用{:lookup, name}{:create, name}.请求通常以元组形式表达,这是为了在第一个参数的位置上提供不止一个"参数".通常元组的第一个元素是请求的动作,余下的元素是该动作的参数.

在服务器端,我们可以实现多种回调来保证服务器的初始化,终止和请求处理.这些回调是可选的,现在我们只实现了我们在乎的那些.

第一个回调是init/1,它得到了提供给GenServer.start_link/3的参数,并返回{:ok, state},这里的状态是一个新映射.我们已经可以注意到GenServerAPI是如何使客户端/服务器隔离得更明显的.start_link/3发生在客户端,而init/1是运行在服务器上的回调.

我们必须实现一个handle_call/3回调来接收call/2request,我们是从哪个进程收到请求的(_from),和当前服务器状态(names).handle_call/3回调返回了一个格式为{:reply, reply, new_state}的元组,reply是将要发送到客户端的内容,而new_state是新的服务器状态.

我们必须实现一个handle_cast/2回调来接收cast/2request,和当前服务器状态(names).handle_cast/2回调返回了一个格式为{:noreply, new_state}的元组.

这里还有一些handle_call/3handle_cast/2回调都可能返回的元组格式.我们还可以实现其它的回调,例如terminate/2code_change/3.欢迎到full GenServer文档中学习更多相关内容.

现在,让我们来写一些测试来保证我们的GenServer运作正常.

#测试通用服务器

测试GenServer与测试代理差不多.我们将生成一个设置回调上的服务器,并在测试中使用它.创建一个名为test/kv/registry_test.exs的文件:

defmodule KV.RegistryTest douse ExUnit.Case, async: truesetup do{:ok, registry} &#61; KV.Registry.start_link{:ok, registry: registry}endtest "spawns buckets", %{registry: registry} doassert KV.Registry.lookup(registry, "shopping") &#61;&#61; :errorKV.Registry.create(registry, "shopping")assert {:ok, bucket} &#61; KV.Registry.lookup(registry, "shopping")KV.Bucket.put(bucket, "milk", 1)assert KV.Bucket.get(bucket, "milk") &#61;&#61; 1end
end

我们的测试已经完成!

我们不需要明确地关闭注册,因为它将在测试结束时接收到一个:shutdown信号.尽管对于测试来说这种方法很正常,但如果需要将停止GenServer作为应用逻辑中的一部分,我们可以使用GenServer.stop/1函数:

## Client API&#64;doc """
Stops the registry.
"""
def stop(server) doGenServer.stop(server)
end

#监控的需要

我们的注册表快要完成了.唯一的问题就是注册表在桶停止或崩溃时有可能过时.让我们添加一个测试到KV.Registry_Test来引发这个bug:

test "removes buckets on exit", %{registry: registry} doKV.Registry.create(registry, "shopping"){:ok, bucket} &#61; KV.Registry.lookup(registry, "shopping")Agent.stop(bucket)assert KV.Registry.lookup(registry, "shopping") &#61;&#61; :error
end

这个测试会在最后一个断言失败,因为桶名仍然存在于注册表中,即使桶进程已经停止了.

为了修复这个bug,我们需要注册表监控每个由它生成的桶.设置好监视器之后,每当有桶退出,注册表就会收到一个通知,让我们清理词典.

让我们与监视器开始初次接触,通过运行iex -S mix来开启一个新的控制台:

iex> {:ok, pid} &#61; KV.Bucket.start_link
{:ok, #PID<0.66.0>}
iex> Process.monitor(pid)
#Reference<0.0.0.551>
iex> Agent.stop(pid)
:ok
iex> flush
{:DOWN, #Reference<0.0.0.551>, :process, #PID<0.66.0>, :normal}

注意Process.monitor(pid)返回了一个独特的参照,我们能够用它来匹配即将到来的信息.在我们停止了代理之后,我们可以flush/0所有消息,发现收到了一个:DOWN信息,带有由监视器返回的确切的参照,提醒我们桶进程因为:normal原因退出了.

让我们重新实现服务器回调来修复bug并使测试通过.首先,我们会将GenServer状态修改成两个词典:一个包含name -> pid,另一个包含ref -> name.然后我们需要在handle_cast/2上监控桶,还需要实现handle_info/2回调来处理监控信息.完整的服务器回调实现如下所示:

## Server callbacksdef init(:ok) donames &#61; %{}refs &#61; %{}{:ok, {names, refs}}
enddef handle_call({:lookup, name}, _from, {names, _} &#61; state) do{:reply, Map.fetch(names, name), state}
enddef handle_cast({:create, name}, {names, refs}) doif Map.has_key?(names, name) do{:noreply, {names, refs}}else{:ok, pid} &#61; KV.Bucket.start_linkref &#61; Process.monitor(pid)refs &#61; Map.put(refs, ref, name)names &#61; Map.put(names, name, pid){:noreply, {names, refs}}end
enddef handle_info({:DOWN, ref, :process, _pid, _reason}, {names, refs}) do{name, refs} &#61; Map.pop(refs, ref)names &#61; Map.delete(names, name){:noreply, {names, refs}}
enddef handle_info(_msg, state) do{:noreply, state}
end

观察到,我们能大大改变服务器的实现,而不改变任何客户端接口.这是将服务器与客户端明确分离的好处之一.

最后,与其他回调不同,我们为handle_info/2定义了一个"全部捕获"从句,它抛弃了所有未知信息.想知道为什么,请看下一部分.

#call,cast还是info?

目前我们已经使用了三种回调:handle_call/3,handle_cast/2以及handle_info/2.它们适用于不同场景:

  1. handle_call/3必须用于同步任务.这应当是默认的选择,因为等待服务器回应是一个很有用的背压机制.

  2. handle_cast/2必须用于异步请求,当你不关心回复时.投掷甚至不保证服务器接收到信息,所以必须谨慎使用.例如,我们在本章中定义的create/2函数就应当使用call/2.出于教学目的我们使用了 cast/2.

  3. handle_info/2必须用于服务器可接收的所有不由GenServer.call/2GenServer.cast/2发送的信息,包括由send/2发送的定期信息.监控:DOWN信息就是一个完美的例子.

因为任何信息,包括由send/2发送的,都传给了handle_info/2,服务器就有可能受到不想要的信息.因此,如果我们不定义捕获全部的从句,这些信息可能会使我们的注册表崩溃,因为没有从句可以匹配.

我们不需要为handle_call/3handle_cast/2担心这个,因为这些请求只由GenServer来完成,所以未知信息很有可能是因为开发者的错误.

#监控器还是链接?

我们已经在进程那一章中学过链接了.现在,注册表完成了,你可能想知道:我们什么时候应该使用监控器,什么时候该使用链接?

链接是双向的.如果你将两个进程链接在一起,那么如果其中一个崩溃了,另一个也会崩溃(除非它捕获退出).监控器是单向的:只有监控进程会收到关于被监控者的消息.简而言之,当你想传递崩溃时就用链接,当你只想得到崩溃,退出等等的提示时就用监控器.

回到我们的handle_cast/2实现,你会看到注册表即链接又监控了桶:

{:ok, pid} &#61; KV.Bucket.start_link
ref &#61; Process.monitor(pid)

这是一个坏主意,因为我们不想桶的崩溃导致注册表崩溃!我们避免直接创建新的进程,而是将此责任交于监督者.如下一章我们将看到的,监督者依赖于链接,这解释了为什么在Elixir和OTP中基于链接的API(spawn_link,start_link等等)如此流行.


转:https://my.oschina.net/ljzn/blog/730665



推荐阅读
  • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
    本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • 本文介绍了在mac环境下使用nginx配置nodejs代理服务器的步骤,包括安装nginx、创建目录和文件、配置代理的域名和日志记录等。 ... [详细]
  • 微软评估和规划(MAP)的工具包介绍及应用实验手册
    本文介绍了微软评估和规划(MAP)的工具包,该工具包是一个无代理工具,旨在简化和精简通过网络范围内的自动发现和评估IT基础设施在多个方案规划进程。工具包支持库存和使用用于SQL Server和Windows Server迁移评估,以及评估服务器的信息最广泛使用微软的技术。此外,工具包还提供了服务器虚拟化方案,以帮助识别未被充分利用的资源和硬件需要成功巩固服务器使用微软的Hyper - V技术规格。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 本文介绍了在使用Python中的aiohttp模块模拟服务器时出现的连接失败问题,并提供了相应的解决方法。文章中详细说明了出错的代码以及相关的软件版本和环境信息,同时也提到了相关的警告信息和函数的替代方案。通过阅读本文,读者可以了解到如何解决Python连接服务器失败的问题,并对aiohttp模块有更深入的了解。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • 本文介绍了在Windows环境下如何配置php+apache环境,包括下载php7和apache2.4、安装vc2015运行时环境、启动php7和apache2.4等步骤。希望对需要搭建php7环境的读者有一定的参考价值。摘要长度为169字。 ... [详细]
  • WebSocket与Socket.io的理解
    WebSocketprotocol是HTML5一种新的协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • Spring框架《一》简介
    Spring框架《一》1.Spring概述1.1简介1.2Spring模板二、IOC容器和Bean1.IOC和DI简介2.三种通过类型获取bean3.给bean的属性赋值3.1依赖 ... [详细]
author-avatar
平凡屋之换
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有