MIX OTP——监督动态子进程

现在,我们已经成功定义了我们的监督器,它将作为应用程序生命周期的一部分自动启动(和停止)。

但请记住,我们的 KV.Registry 在 handle_cast/2 回调中同时链接(通过 start_link)和监控(通过 monitor)存储容器进程:

链接是双向的,这意味着存储容器崩溃会导致注册表崩溃。虽然我们现在有了监督进程,可以保证注册表恢复正常运行,但注册表崩溃仍然意味着我们会丢失将存储容器名称与其各自进程相关联的所有数据。

换句话说,我们希望即使存储容器崩溃,注册表也能继续运行。让我们编写一个新的注册表测试:

该测试类似于“退出时删除存储容器”,只是我们发送 :shutdown 作为退出原因而不是 :normal,这要更严格一些。如果进程因 :normal 以外的原因终止,则所有链接进程都会收到 EXIT 信号,导致链接进程也终止,除非它正在捕获退出。

由于存储容器终止,注册表也停止了,并且当我们尝试 GenServer.call/3 时我们的测试失败:

  1) test removes bucket on crash (KV.RegistryTest)
     test/kv/registry_test.exs:26
     ** (exit) exited in: GenServer.call(#PID<0.148.0>, {:lookup, "shopping"}, 5000)
         ** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
     code: assert KV.Registry.lookup(registry, "shopping") == :error
     stacktrace:
       (elixir) lib/gen_server.ex:770: GenServer.call/3
       test/kv/registry_test.exs:33: (test)

我们将通过定义一个新的监管者来解决这个问题,该监管者将生成并监督所有存储容器。与我们之前定义的监管者相反,子节点并不是预先知道的,而是动态启动的。对于这些情况,我们使用一个针对此类用例优化的监管者,称为 DynamicSupervisor。DynamicSupervisor 在初始化期间不需要子节点列表;而是通过 DynamicSupervisor.start_child/2 手动启动每个子节点。

存储容器监管者

由于 DynamicSupervisor 在初始化期间不定义任何子项,因此 DynamicSupervisor 还允许我们跳过使用通常的 start_link 函数和 init 回调定义整个单独模块的工作。相反,我们可以直接在监控树中定义 DynamicSupervisor,为其指定名称和策略。

打开 lib/kv/supervisor.ex 并将动态监控器添加为子项,如下所示:

请记住,进程的名称可以是任何原子。到目前为止,我们已经将进程命名为与定义其实现的模块相同的名称。例如,由 KV.Registry 定义的进程被赋予了 KV.Registry 的进程名称。这只是一个惯例:如果稍后您的系统中出现错误,提示“名为 KV.Registry 的进程因原因而崩溃”,我们确切地知道要调查的位置。

在这种情况下,没有模块,所以我们选择了 KV.BucketSupervisor 这个名字。它可能是任何其他名字。我们还选择了 :one_for_one 策略,这是目前动态监管者唯一可用的策略。

运行 iex -S mix,这样我们就可以尝试我们的动态监管者:

DynamicSupervisor.start_child/2 需要监管者的名称和要启动的子项的子规范。

最后一步是更改注册表以使用动态监管者:

这足以让我们的测试通过,但是我们的应用程序中存在资源泄漏。当一个 bucket 终止时,supervisor 将在其位置启动一个新的 bucket。毕竟,这是 supervisor 的角色!

但是,当 supervisor 重新启动新的 bucket 时,注册表并不知道这一点。因此,supervisor 中将有一个空的 bucket,没有人可以访问!为了解决这个问题,我们想说 bucket 实际上是临时的。如果它们崩溃,无论出于何种原因,都不应重新启动它们。

我们可以通过在 KV.Bucket 中传递 restart: :temporary 选项来使用 Agent 来实现这一点:

我们还将一个测试添加到 test/kv/bucket_test.exs,以确保 bucket 是临时的:

我们的测试使用 Supervisor.child_spec/2 函数从模块中检索子规范,然后断言其重新启动值为 :temporary。此时,您可能想知道如果 supervisor 永远不会重新启动其子节点,为什么要使用它。有时,监督器提供的不仅仅是重启,它们还负责保证正确启动和关闭,特别是在监督树崩溃的情况下。

监督树

当我们将 KV.BucketSupervisor 添加为 KV.Supervisor 的子进程时,我们开始拥有监管其他监管者的监管者,形成所谓的“监管树”。

每次向监管者添加新子进程时,评估监管者策略是否正确以及子进程的顺序非常重要。在本例中,我们使用 :one_for_one,并且 KV.Registry 在 KV.BucketSupervisor 之前启动。

立即出现的一个缺陷是排序问题。由于 KV.Registry 调用 KV.BucketSupervisor,因此 KV.BucketSupervisor 必须在 KV.Registry 之前启动。否则,注册表可能会在启动之前尝试访问 bucket 监管者。

第二个缺陷与监管策略有关。如果 KV.Registry 死亡,则将 KV.Bucket 名称与 bucket 进程联系起来的所有信息都会丢失。因此 KV.BucketSupervisor 和所有子进程也必须终止 - 否则我们将拥有孤儿进程。

鉴于这一观察,我们应该考虑转向另一种监管策略。另外两个候选者是 :one_for_all 和 :rest_for_one。使用 :rest_for_one 策略的监管者将杀死并重新启动在崩溃的子进程之后启动的子进程。在这种情况下,我们希望 KV.BucketSupervisor 在 KV.Registry 终止时终止。这将要求将 bucket 监管者置于注册表之后,这违反了我们在上面两段中建立的排序约束。

因此,我们的最后一个选择是全力以赴并选择 :one_for_all 策略:只要其中任何一个子进程死亡,监管者就会杀死并重新启动其所有子进程。对于我们的应用程序来说,这是一种完全合理的方法,因为注册表在没有 bucket 监管者的情况下无法工作,而 bucket 监管者应该在没有注册表的情况下终止。让我们在 KV.Supervisor 中重新实现 init/1 来编码这些属性:

在进入下一章之前,还有两个主题。

测试中的共享状态

到目前为止,我们已为每个测试启动一个注册表,以确保它们是隔离的:

由于我们已将注册表更改为使用 KV.BucketSupervisor,因此我们的测试现在依赖于此共享监管者,即使每个测试都有自己的注册表。问题是:我们应该吗?

视情况而定。只要我们仅依赖此状态的非共享分区,就可以依赖共享状态。尽管多个注册表可能会在共享存储容器监管者上启动存储容器,但这些存储容器和注册表彼此隔离。如果我们使用 DynamicSupervisor.count_children(KV.BucketSupervisor) 之类的函数来计算所有注册表中的所有存储容器,我们只会遇到并发问题,当测试同时运行时可能会产生不同的结果。

由于到目前为止我们只依赖存储容器监管者的非共享分区,因此我们不必担心测试套件中的并发问题。如果出现问题,我们可以为每个测试启动一个监管者,并将其作为参数传递给注册表 start_link 函数。

观察者

现在我们已经定义了监督树,这是介绍 Erlang 附带的 Observer 工具的绝佳机会。使用 iex -S mix 启动您的应用程序并输入以下内容:

缺少依赖项

当使用 iex -S mix 在项目中运行 iex 时,观察者将无法作为依赖项使用。为此,您需要先调用以下函数:

如果上述任何调用失败,则可能发生以下情况:某些包管理器默认安装最小化的 Erlang,不带 WX 绑定以提供 GUI 支持。在某些包管理器中,您可能能够用更完整的包替换无头 Erlang(在 Debian/Ubuntu/Arch 上查找名为 erlang vs erlang-nox 的包)。在其他管理器中,您可能需要安装单独的 erlang-wx(或类似名称)包。

有对话可以在未来的版本中改善这种体验。

应会弹出一个 GUI,其中包含有关我们系统的各种信息,从一般统计数据到负载图表,以及所有正在运行的进程和应用程序的列表。

在“应用程序”选项卡中,您将看到系统中当前正在运行的所有应用程序及其监督树。您可以选择 kv 应用程序以进一步探索它:

不仅如此,当您在终端上创建新存储容器时,您应该会看到 Observer 中显示的监督树中生成的新进程:

我们将让您进一步探索 Observer 提供的功能。请注意,您可以双击监督树中的任何进程以检索有关它的更多信息,也可以右键单击进程以发送“终止信号”,这是一种模拟故障并查看您的监督者是否按预期做出反应的完美方式。

归根结底,像 Observer 这样的工具是您希望始终在监督树内启动进程的原因之一,即使它们是临时的,也可以确保它们始终可访问和可自省。

现在我们的存储容器已正确链接和监督,让我们看看如何加快速度。

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

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

相关文章

Spring4.3.x xml配置文件搜索和解析过程

###概述 这篇文章的研究不只是涉及到spring如何创建一个BeanDefinition对象&#xff0c;还涉及到spring如何加载文件、如何读取XML文件、以及我们在使用spring的时候如何扩展spring的配置。 spring在创建BeanFactory时会把xml配置文件和注解信息转换为一个个BeanDefinition对…

分享一个导出数据到 Excel 的解决方案

前言 许多业务场景下需要处理和分析大量的数据&#xff0c;而 Excel 是广泛使用的文件格式&#xff0c;几乎所有人都能打开和查看 Excel 文件&#xff0c;因此将数据库中的原始数据处理后&#xff0c;导出到 Excel 是一个很常见的功能&#xff0c;对于数据管理、分析、备份、展…

侯捷C++面向对象高级编程(上)-2-构造函数

1.inline函数 2.访问级别 3.构造函数 4.重载

《UDS协议从入门到精通》系列——图解0x38:请求上传

《UDS协议从入门到精通》系列——图解0x38&#xff1a;请求上传 一、简介二、数据包格式2.1 服务请求格式2.2 服务响应格式2.2.1 肯定响应2.2.2 否定响应 三、通信示例 Tip&#x1f4cc;&#xff1a;本文描述中但凡涉及到其他UDS服务的&#xff0c;将陆续提供链接跳转方式以便快…

新学期必备,录取情况统计如何制作?

暑假即将开始&#xff0c;新学期离我们又近了一步&#xff0c;老师们是不是在为如何高效统计录取情况而头疼呢&#xff1f;别担心&#xff0c;分享一个超实用的小技巧——使用易查分小程序的新建填表功能&#xff0c;让你的录取统计工作变得简单又高效&#xff01; 打开易查分小…

gin框架 HTML 模板加载,渲染 使用详解和总结

gin框架中默认的HTML模板渲染使用 LoadHTMLGlob() 或者 LoadHTMLFiles() &#xff0c; 这个地方如果是使用的LoadHTMLGlob() 这个方法的话是有坑的&#xff0c;即当你的模板文件放在不同的文件夹中时&#xff0c;使用这个方式加载会将文件夹也作为文件加载进去&#xff0c;从而…

G882磁力仪拖鱼位置是如何计算的?

根据参考文献&#xff0c;磁力仪拖鱼位置计算有两种方法&#xff1a; 1、直线法 直线计算法是假设不考虑海流、船摆等动态因素的影响&#xff0c;拖鱼与拖点始终和航向相同&#xff0c;即整个拖拽系统与船舶是刚性连接。 2、曲线法 实际海洋磁力测量中&#xff0c;在海风、海…

TP8 JS(html2canvas) 生成二维码并与背景图、文字组合生成分享海报

方法一&#xff1a;前端JS生成(推荐) 注意&#xff1a; 这个网页只能截图图片效果代码&#xff0c;其它任何html效果都不能有&#xff0c;不然截图就不准确 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><t…

Windows宝塔面板部署ThinkPHP8.0创建Vue项目案例

安装ThinkPHP8.0 登录宝塔面板&#xff0c;创建一个站点。 输入composer代码&#xff0c;执行完成后自动创建TP目录 composer create-project topthink/think tp 网站目录设置为tp&#xff0c;运行目录设置为public 设置PHP版本为8.0以上&#xff0c;不然会出现下面的报错代…

软考 有向图 数据库之关系模式范式

假设有一个关系 R(A, B, C, D)&#xff0c;并且已知以下函数依赖&#xff1a; A → B B → C BC → D 求候选键? 求候选码? 候选键/候选码 是同一个概念. 数据库范式也分为1NF,2NF,3NF,BCNF,4NF,5NF。 https://cloud.tencent.com/developer/article/2055118 2NF在1NF的基础…

低代码平台技术及其在CRM系统中的应用

随着信息技术的飞速发展&#xff0c;企业对于高效、灵活、可定制的软件开发需求日益增长。在这一背景下&#xff0c;低代码平台&#xff08;Low-Code Platform, LCP&#xff09;技术应运而生&#xff0c;并以其快速开发、易于维护和高度定制化的特性&#xff0c;逐渐成为企业信…

visual studio 2022配置和使用protobuf

上图证明&#xff0c;我真的测了好多遍&#xff0c;测了好多版本的protobuf&#xff0c;花了很多时间。不过好在最后在vs2022上测通了。 下载protobuf 这里是protobuf下载的地址。 Releases protocolbuffers/protobuf GitHub 个人使用的3.21.9这个版本才跑通的。 1、首先…

FastAPI-Body、Field

参考&#xff1a;模式的额外信息 - 例子 - FastAPI 在FastAPI中&#xff0c;Body和Field是两个常用的注解&#xff0c;它们用于定义请求体中的数据或路径参数、查询参数等的处理方式。这两个注解都来自于Pydantic库&#xff0c;用于数据验证和解析&#xff0c;但它们的应用场景…

第6章_libmodbus使用

文章目录 第6章 libmodbus使用6.1 libmodbus开发库6.1.1 功能概要6.1.2 源码获取6.1.3 源码阅读1. 新建工程2. 同步文件3.打开工程4. 操作示例5. 快捷键 6.1.4 libmodbus与应用程序的关系 6.2 libmodbus源代码解析6.2.1 核心函数6.2.2 框架分析与数据结构6.2.3 情景分析1. 初始…

OOXML入门学习

进入-飞入 <par> <!-- 这是一个并行动画序列的开始。"par"代表并行&#xff0c;意味着在这个标签内的所有动画将同时开始。 --><cTn id"5" presetID"2" presetClass"entr" presetSubtype"4" fill"hold&…

vue3 封装获取文件后缀和name的工具方法以及本地下载方法

获取文件后缀 /*** description&#xff1a;获取附件后缀* param {*} file 文件名称* param {*} isDot 是否显示点 0: 显示 1: 不显示*/ export const getFileType: (file?: string, isDot?: 0 | 1) > string (file, isDot 0) > {if (!file) {throw "file is n…

深度解析:Transformer自注意力机制的计算奥秘

深度解析&#xff1a;Transformer自注意力机制的计算奥秘 在自然语言处理&#xff08;NLP&#xff09;的广阔天地中&#xff0c;Transformer模型犹如一颗璀璨的星辰&#xff0c;以其卓越的性能照亮了序列建模的道路。而这一切&#xff0c;都离不开自注意力机制&#xff08;Sel…

C语言指针速成下篇

c语言的指针下篇终于迎来了收尾&#xff0c;那么废话不多说&#xff0c;我们直接进入正题 指针访问数组 # include <stdio.h> int main () { int arr[ 10 ] { 0 }; // 输⼊ int i 0 ; int sz sizeof (arr)/ sizeof (arr[ 0 ]); // 输⼊ int * p arr //这…

在Tomcat中部署war包

1、准备war包 确保已经有一个有效的war包&#xff0c;该war包包含了web应用程序的所有内容&#xff1b; 2、停止tomcat服务器 在部署之前&#xff0c;确保tomcat服务器已经停止&#xff0c;进入tomcat的配置目录执行命令&#xff1a;[路径]/tomcat/conf&#xff1b; 在Linux…

Socket——向FTP服务器发送消息并获得响应

1、简介 Socket&#xff08;套接字&#xff09;是网络编程中用于描述IP地址和端口的一个抽象概念&#xff0c;通过它可以实现不同主机间的通信。套接字可以分为几种不同的类型&#xff0c;每种类型对应不同的协议和传输模式。 1.1、基本概念 IP地址&#xff1a;用于标识网络…