MIX OTP——监督树和应用

news2024/11/26 7:50:55

在上一章关于 GenServer 的内容中,我们实现了 KV.Registry 来管理存储容器。在某个时候,我们开始监控存储容器,这样每当 KV.Bucket 崩溃时,我们就能采取行动。虽然变化相对较小,但它提出了一个 Elixir 开发人员经常问的问题:当出现故障时会发生什么?

在我们添加监控之前,如果存储容器崩溃,注册表将永远指向不再存在的存储容器。如果用户尝试读取或写入崩溃的存储容器,它将失败。任何尝试创建具有相同名称的新存储容器的操作都只会返回崩溃存储容器的 PID。换句话说,该存储容器的注册表条目将永远处于不良状态。一旦我们添加了监控,注册表就会自动删除崩溃存储容器的条目。现在尝试查找崩溃的存储容器(正确)会显示存储容器不存在,系统用户可以根据需要成功创建一个新的存储容器。

实际上,我们并不希望作为存储容器工作的进程失败。但是,如果确实发生了这种情况,无论出于何种原因,我们都可以放心,我们的系统将继续按预期工作。

如果您有编程经验,您可能会想:“我们能保证存储容器不会首先崩溃吗?”。正如我们将看到的,Elixir 开发人员倾向于将这些做法称为“防御性编程”。这是因为实时生产系统有几十种不同的原因导致某些事情可能出错。磁盘可能会发生故障,内存可能会损坏,错误,网络可能会停止工作一秒钟,等等。如果我们要编写试图保护或规避所有这些错误的软件,我们将花费更多时间来处理故障,而不是编写自己的软件!

因此,Elixir 开发人员更喜欢“让它崩溃”或“快速失败”。我们从故障中恢复的最常见方法之一是重新启动系统崩溃的任何部分。

例如,想象一下您的计算机、路由器、打印机或任何设备无法正常工作。您多久通过重新启动来修复它一次?一旦我们重新启动设备,我们就会将设备重置回其初始状态,该状态经过充分测试并保证可以正常工作。在 Elixir 中,我们将同样的方法应用于软件:每当一个进程崩溃时,我们都会启动一个新进程来执行与崩溃进程相同的工作。

在 Elixir 中,这是由 Supervisor 完成的。Supervisor 是一个监督其他进程并在它们崩溃时重新启动它们的进程。为此,Supervisor 管理任何受监督进程的整个生命周期,包括启动和关闭。

在本章中,我们将学习如何通过监督 KV.Registry 进程将这些概念付诸实践。毕竟,如果注册表出现问题,整个注册表都会丢失,并且永远找不到任何存储容器!为了解决这个问题,我们将定义一个 KV.Supervisor 模块,以确保我们的 KV.Registry 在任何给定时刻都处于启动和运行状态。

在本章的最后,我们还将讨论应用程序。正如我们将看到的,Mix 已将我们所有的代码打包到一个应用程序中,我们将学习如何定制我们的应用程序,以确保我们的 Supervisor 和 Registry 在系统启动时正常运行。

我们的第一个监督者

监督者是一个监督其他进程的进程,我们将其称为子进程。监督进程的行为包括三个不同的职责。第一个是启动子进程。一旦子进程运行因为异常终止或达到某个条件,监督者可能会重新启动子进程。例如,如果任何子进程死亡,监督者可能会重新启动所有子进程。最后,监督者还负责在系统关闭时关闭子进程。有关更深入的讨论,请参阅监督者模块。

创建监督者与创建 GenServer 没有太大区别。我们将在 lib/kv/supervisor.ex 文件中定义一个名为 KV.Supervisor 的模块,它将使用 Supervisor 行为:

到目前为止,我们的监督者只有一个子进程:KV.Registry。定义子进程列表后,我们调用 Supervisor.init/2,传递子进程和监督策略。

监督策略规定当其中一个子进程崩溃时会发生什么。:one_for_one 表示如果子进程死亡,它将是唯一重新启动的进程。由于我们现在只有一个子进程,这就是我们所需要的。监督者行为支持多种策略,我们将在本章中讨论。

一旦监督者启动,它将遍历子进程列表,并在每个模块上调用 child_spec/1 函数。

child_spec/1 函数返回子进程规范,该规范描述了如何启动进程,进程是工作者进程还是监督者进程,进程是临时的、瞬态的还是永久的等等。当我们使用 Agent、使用 GenServer、使用 Supervisor 等时,会自动定义 child_spec/1 函数。让我们在终端中使用 iex -S mix 尝试一下:

随着我们继续学习本指南,我们将了解这些细节。如果您想提前了解,请查看 Supervisor 文档。

在 Supervisor 检索所有子规范后,它会按照子规范中 :start 键中的信息,按照定义顺序逐个启动其子规范。对于我们当前的规范,它将调用 KV.Registry.start_link([])。

让我们试用一下 Supervisor:

到目前为止,我们已经启动了监督程序并列出了其子程序。一旦监督程序启动,它也会启动其所有子程序。

如果我们故意使监督程序启动的注册表崩溃,会发生什么?让我们通过在调用时向其发送错误输入来实现这一点:

请注意,一旦我们因错误输入导致注册表崩溃,监管者就会自动启动一个具有新 PID 的新注册表来代替第一个注册表。

在前面的章节中,我们总是直接启动进程。例如,我们将调用 KV.Registry.start_link([]),它将返回 {:ok, pid},这将允许我们通过其 pid 与注册表进行交互。既然进程是由监管者启动的,我们必须直接询问监管者它的子进程是谁,并从返回的子进程列表中获取 PID。实际上,每次这样做都会非常昂贵。为了解决这个问题,我们经常给进程命名,允许它们在我们的代码中的任何位置在单个机器中被唯一地标识。

让我们学习如何做到这一点。

命名进程

虽然我们的应用程序有许多存储容器,但它只有一个注册表。因此,每当我们启动注册表时,我们都希望为其赋予一个唯一的名称,以便我们可以从任何地方访问它。我们通过将 :name 选项传递给 KV.Registry.start_link/1 来实现这一点。

让我们稍微改变一下我们的 children 定义(在 KV.Supervisor.init/1 中),将其改为元组列表,而不是原子列表:

有了这个,监督者现在将通过调用 KV.Registry.start_link(name: KV.Registry) 来启动 KV.Registry。

如果您重新访问 KV.Registry.start_link/1 实现,您会记得它只是将选项传递给 GenServer:

反过来,它将使用给定的名称注册进程。:name 选项需要一个用于本地命名进程的原子(本地命名意味着它可用于此机器 - 还有其他选项,我们不会在这里讨论)。由于模块标识符是原子(在 IEx 中尝试 i(KV.Registry)),我们可以用实现它的模块来命名进程,前提是该名称只有一个进程。这有助于调试和自省系统。

让我们在 iex -S mix 中尝试更新后的监督器:

这次,监管者启动了一个命名注册表,这样我们就可以创建存储容器,而不必从监管者那里显式获取 PID。您还应该知道如何使注册表再次崩溃,而无需查找其 PID:试一试。

此时,您可能想知道:您还应该在本地命名存储容器进程吗?请记住,存储容器是根据用户输入动态启动的。由于本地名称必须是原子,因此我们必须动态创建原子,这是一个坏主意,因为一旦定义了原子,它就永远不会被擦除或垃圾收集。这意味着,如果我们根据用户输入动态创建原子,我们最终将耗尽内存(或者更准确地说,VM 将崩溃,因为它对原子数量施加了硬性限制)。这个限制正是我们创建自己的注册表的原因(或者为什么人们会使用 Elixir 的内置注册表模块)。

我们越来越接近一个完全正常工作的系统。监管者会自动启动注册表。但是,我们如何在系统启动时自动启动监管者?要回答这个问题,让我们谈谈应用程序。

了解应用程序

我们一直在应用程序内部工作。每次我们更改文件并运行 mix compile 时,我们都可以在编译输出中看到一条 Generated kv app 消息。

我们可以在 _build/dev/lib/kv/ebin/kv.app 找到生成的 .app 文件。让我们看看它的内容:

此文件包含 Erlang 术语(使用 Erlang 语法编写)。即使我们不熟悉 Erlang,也很容易猜到这个文件包含我们的应用程序定义。它包含我们的应用程序版本、它定义的所有模块,以及我们依赖的应用程序列表,如 Erlang 的内核、elixir 本身和记录器。

记录器应用程序作为 Elixir 的一部分提供。我们通过在 mix.exs 中的 :extra_applications 列表中指定它来表明我们的应用程序需要它。有关更多信息,请参阅官方文档。

简而言之,应用程序由 .app 文件中定义的所有模块组成,包括 .app 文件本身。应用程序通常只有两个目录:ebin,用于存放 Elixir 工件,例如 .beam 和 .app 文件;priv,用于存放应用程序中可能需要的任何其他工件或资产。

虽然 Mix 会为我们生成并维护 .app 文件,但我们可以通过在 mix.exs 项目文件内的 application/0 函数中添加新条目来自定义其内容。我们很快就会进行第一次自定义。

启动应用程序

我们系统中的每个应用程序都可以启动和停止。启动和停止应用程序的规则也在 .app 文件中定义。当我们调用 iex -S mix 时,Mix 会编译我们的应用程序然后启动它。

让我们在实践中看看这一点。使用 iex -S mix 启动控制台并尝试:

它已经启动了。Mix 会自动启动当前应用程序及其所有依赖项。对于 mix test 和许多其他 Mix 命令也是如此。

但是,我们可以停止 :kv 应用程序以及 :logger 应用程序:

让我们尝试再次启动我们的应用程序:

现在我们收到错误,因为 :kv 所依赖的应用程序(在本例中为 :logger)未启动。我们需要以正确的顺序手动启动每个应用程序,或者调用 Application.ensure_all_started/1,如下所示:

实际上,我们的工具总是会为我们启动应用程序,但如果您需要细粒度的控制,可以使用 API。

应用程序回调

每当我们调用 iex -S mix 时,Mix 都会通过调用 Application.start(:kv) 自动启动我们的应用程序。但是我们可以自定义应用程序启动时发生的情况吗?事实上,我们可以!为此,我们定义一个应用程序回调。

第一步是告诉我们的应用程序定义(例如,我们的 .app 文件)哪个模块将实现应用程序回调。让我们通过打开 mix.exs 并将 def application 更改为以下内容来做到这一点:

:mod 选项指定“应用程序回调模块”,后跟应用程序启动时要传递的参数。应用程序回调模块可以是实现应用程序行为的任何模块。

要实现应用程序行为,我们必须使用 Application 并定义一个 start/2 函数。start/2 的目标是启动一个监督器,然后它将启动任何子服务或执行我们的应用程序可能需要的任何其他代码。让我们利用这个机会启动我们在本章前面实现的 KV.Supervisor。

由于我们已将 KV 指定为模块回调,因此让我们更改 lib/kv.ex 中定义的 KV 模块以实现 start/2 函数:

请注意,这样做会破坏测试 KV 中 hello 函数的样板测试用例。您可以简单地删除该测试用例。

当我们使用 Application 时,我们可能会定义几个函数,类似于使用 Supervisor 或 GenServer 时。这次我们只需要定义一个 start/2 函数。Application 行为也有一个 stop/1 回调,但在实践中很少使用。您可以查看文档以获取更多信息。

现在您已经定义了一个启动我们的监督器的应用程序回调,我们希望 KV.Registry 进程在我们启动 iex -S mix 时立即启动并运行。让我们再试一次:

让我们回顾一下发生了什么。每当我们调用 iex -S mix 时,它都会通过调用 Application.start(:kv) 自动启动我们的应用程序,然后调用应用程序回调。应用程序回调的工作是启动监督树。目前,我们的监督者有一个名为 KV.Registry 的子节点,以名称 KV.Registry 开头。我们的监督者可以有其他子节点,其中一些子节点可以成为自己的监督者,并拥有自己的子节点,从而形成所谓的监督树。

项目还是应用程序?

Mix 区分了项目和应用程序。根据我们的 mix.exs 文件的内容,我们可以说我们有一个定义 :kv 应用程序的 Mix 项目。正如我们将在后面的章节中看到的那样,有些项目没有定义任何应用程序。

当我们说“项目”时,您应该考虑 Mix。Mix 是管理项目的工具。它知道如何编译您的项目、测试您的项目等等。它还知道如何编译和启动与您的项目相关的应用程序。

当我们谈论应用程序时,我们谈论的是 OTP。应用程序是由运行时作为一个整体启动和停止的实体。您可以在应用程序模块的文档中了解有关应用程序的更多信息以及它们与整个系统的启动和关闭的关系。

下一步

虽然本章是我们第一次实现监督器,但这并不是我们第一次使用它!在上一章中,当我们在测试期间使用 start_supervised! 启动注册表时,ExUnit 在由 ExUnit 框架本身管理的监督器下启动了注册表。通过定义我们自己的监督器,我们提供了更多关于如何在应用程序中初始化、关闭和监督进程的结构,使我们的生产代码和测试与最佳实践保持一致。

但我们还没有完成。到目前为止,我们正在监督注册表,但我们的应用程序也在启动存储容器。由于存储容器是动态启动的,我们可以使用一种称为 DynamicSupervisor 的特殊类型的监督器,它经过优化以处理此类场景。接下来让我们探索它。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1873277.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Flat Ads:拥抱 CTV,品牌增长新动力

近年来,CTV(联网电视)在数字营销界正迅速崛起,成为最受青睐和增长迅猛的推广形式之一。 随着更多联网设备的普及,越来越多观众正在从传统电视快速转移到流媒体环境,对传统电视广告取而代之的便是 CTV 广告。据 eMarketer 数据显示,未来四年,CTV 市值将从今年的 250 亿美元增长到…

C++-------多态

一.如何实现多态 1.多态的两个条件: (1) 必须通过基类的指针或者引用调用虚函数 (2) 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写,重写必须返回值,函数名,参数类型相同,同时virtual只…

Win10扩充C盘(把其他盘存储空间分给C盘)

C盘虽然没有安装任何软件,但无奈安装某些软件(例如VS,QuarC等)总会占用C盘容量,且C盘内存很小(只有60G左右),看着D盘的三四十空闲内存,决定把D盘内存分给C盘30G&#xff…

uboot中内存DDR测试之mtest使用

相关代码路径: cmd/mem.c配置: make ARCHarm CROSS_COMPILEaarch64-linux-gnu- menuconfigCC cmd/mem.o cmd/mem.c: In function do_mem_mtest: cmd/mem.c:883:10: error: CONFIG_SYS_MEMTEST_START undeclared (first use in this function); did you mean CONFIG_SYS_…

Pbootcms留言“提交成功”的提示语怎么修改

我们在用到pbootcms建站时候,其中有个留言功能,提交成功后会提示:提交成功(如下图所示),那么我们要修改这个提示语要怎么操作呢? 如果需要修改的话,直接找到文件/apps/home/control…

一投就中,收稿范围大,1个月内录用,国人发文最多,无风险预警

别人费心费力投个一年都不一定有结果,您直接坐上”直升飞机”,1个月录用。下面老毕分享1本超快录用EI期刊,工程电气方向的学者抓紧投稿。 抢占版面,下方【扫一扫】直接安排,1个月内录用🌈 Journal of Elect…

骗2万人13亿的种菜游戏,幕后老板反手去当了榜一大哥…

这一届人民,可以说,对「种菜」式的田园生活,有一种执念。 或是格子间里日复一日的牢笼生活有些厌倦,也或是过快的城市化进程,让藏在基因里的「田园」属性,还能时不时的觉醒一下…… 除了诗与远方及前些年爆…

Pytest--安装与入门

pytest是一个能够简化成测试系统构建、方便测试规模扩展的框架,它让测试变得更具表现力和可读性–模版代码不再是必需的。只需要几分钟的时间,就可以对你的应用开始一个简单的单元测试或者复杂的功能测试。 1. 安装pytest pip install -U pytest检查版…

PD快充诱骗芯片工作原理,USB-C充电器出不来电压是什么原因?

一般使用Type-C接口的充电器基本上都是采用新的快充协议——PD快充协议,它不同于以前的USB-A的QC协议,这种协议,默认是没有快充电压输出的,VBUS和GND是0V。 所以,我们可以使用电阻的方式(电流小&#xff09…

Steam新用户怎么参加夏促 Steam最新注册账号+下载客户端教程

steam夏促来了,这里给新玩家科普一下,steam就是一个游戏平台,里面的海量的各种游戏,而steam经常会有各种打折的活动,夏促就是其中之一,并且是其中规模最大的之一,涵盖游戏数量多,优惠…

【乐吾乐2D可视化组态编辑器】画布

5.1 设置画布属性 默认颜色:预先设置默认颜色,拖拽到画布的节点(基础图形、文字、icon)自动统一默认颜色。 画笔填充颜色:预先设置画笔填充颜色,拖拽到画布的节点(基础图形)自动统…

企业应该如果安全上网,软件防查盗版,企业防盗版

随着信息化的发展,企业日常办公越来越依赖互联网。终端以及普通PC终端在访问互联网过程中,会遇到各种各样不容忽视的风险,例如员工主动故意的数据泄漏,后台应用程序偷偷向外部发信息,木马间谍软件的外联,以…

java服务MultipartFile入参测试

项目中经常会涉及到文件的上传下载以及导入相关的功能,今天针对MultipartFile类型文档导入写一下如何测试。 文档导入接口完成,使用postman测试,使用POST方法,进入Body模块,选择form-data选项,key的框体右侧…

微信小程序根据蓝牙RSSI信号强度测试设备距离

背景 在做小程序连接蓝牙设备的时候,有需求表明在搜索到0.5米之内的设备时自动连接 问题: 蓝牙模组只提供了RSSI信号强度,那又该如何计算蓝牙设备距离小程序的距离呢? 解决方案 通过以下公式做大量测试:求 A、n 的平均…

npm创建一个空的vue3项目的方法或者pnpm创建vue3项目

1、前提我们已经安装了npm,或者pnpm 2、我们用npm来创建vue3项目 快速上手 | Vue.js 官网地址 这里我安装是的 node v18.20.3 以下是安装过程 : npm create vuelatest 根据自己的需要进行创建即可。 3、我们用pnpm来创建vite vue3项目 pnpm create …

SpringBoot项目中获取IP地址

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 OkHttp 是一个由 Square 开发的高效、现代的 HTTP 客户端库,用于 Android 和 Java 应用程序。它支持 HTTP/2 和 SPDY 等现代网络协议,…

Python28 十大机器学习算法之线性回归和逻辑回归

1.三类广义上的机器学习算法 监督学习。工作原理:该算法由一个目标/结果变量(或因变量)组成,该变量将从一组给定的预测变量(自变量)进行预测。使用这组变量,我们生成了一个将输入数据映射到所…

【python】pop()函数

python pop() ,如何在Python的列表或数组中移除元素 使用 pop() 从列表中删除元素 pop() 语法概述 pop() 方法的语法如下: list_name.pop(index)list_name:列表变量名;内置的 pop() 方法仅需要一个可选参数;可选参…

async异步函数

文章目录 异步函数(用 async 声明的函数)异步函数的返回值async/await 的使用异步函数的异常处理总结 感谢铁子阅读,觉得有帮助的话点点关注点点赞,谢谢! 异步函数(用 async 声明的函数) 异步函…

免疫防御和代谢控制十字路口的炎性小体

谷禾健康 人体的肠道粘膜内层形成物理屏障和免疫防御系统,以防止微生物入侵。当身体受到感染或细胞遭受损伤时,免疫系统会启动炎症反应来应对这些情况。炎症是对感染和组织损伤的一种急性反应,以限制对身体的伤害,这种反应是身体自…