当前位置: 首页 > news >正文

MIX OTP——使用 GenServer 进行客户端-服务器通信

在上一章中,我们使用代理来表示存储容器。在 mix 的介绍中,我们指定要命名每个存储容器,以便我们可以执行以下操作:

在上面的会话中,我们与“购物”存储容器进行了交互。

由于代理是进程,因此每个存储容器都有一个进程标识符 (PID),但存储容器没有名称。在“进程”一章中,我们了解到可以通过为进程赋予原子名称来在 Elixir 中注册进程:

但是,用原子命名动态进程是一个糟糕的想法!如果我们使用原子,我们需要将存储容器名称(通常从外部客户端接收)转换为原子,并且我们永远不应该将用户输入转换为原子。这是因为原子不会被垃圾收集。一旦创建了原子,它就永远不会被回收。从用户输入生成原子意味着用户可以注入足够多的不同名称来耗尽我们的系统内存!

实际上,在内存耗尽之前,您更有可能达到 Erlang VM 的最大原子数限制,这无论如何都会使您的系统崩溃。

我们不会滥用内置名称功能,而是创建自己的进程注册表,将存储容器名称与存储容器进程关联起来。

注册表需要保证它始终是最新的。例如,如果其中一个存储容器进程由于错误而崩溃,注册表必须注意到这一变化并避免提供过时的条目。在 Elixir 中,我们说注册表需要监视每个存储容器。由于我们的注册表需要能够接收和处理来自系统的临时消息,因此 Agent API 是不够的。

我们将使用 GenServer 创建一个可以监控存储桶进程的注册表进程。GenServer 为在 Elixir 和 OTP 中构建服务器提供了工业级功能。

如果您还没有阅读 GenServer 模块文档,请阅读概述。一旦您这样做,我们就可以继续了。

GenServer 回调

GenServer 是在特定条件下调用一组有限函数的过程。当我们使用 Agent 时,我们会将客户端代码和服务器代码并排放置,如下所示:

让我们稍微分解一下这段代码:

在上面的代码中,我们有一个进程,我们称之为“客户端”,它向代理(即“服务器”)发送请求。该请求包含一个匿名函数,该函数必须由服务器执行。

在 GenServer 中,上面的代码将是两个独立的函数,大致如下:

GenServer 代码中还有相当多的繁琐,但正如我们将看到的,它也带来了一些好处。

目前,我们将只为存储容器注册逻辑编写服务器回调,而不提供适当的 API,稍后我们将提供。

在 lib/kv/registry.ex 创建一个新文件,内容如下:

您可以向 GenServer 发送两种类型的请求:调用和强制类型转换。调用是同步的,服务器必须向此类请求发送响应。当服务器计算响应时,客户端正在等待。强制类型转换是异步的:服务器不会发送响应,因此客户端不会等待响应。这两种请求都是发送到服务器的消息,将按顺序处理。在上面的实现中,我们对 :create 消息进行模式匹配,将其作为强制类型转换处理,对 :lookup 消息进行模式匹配,将其作为调用处理。

为了调用上面的回调,我们需要遍历相应的 GenServer 函数。让我们启动一个注册表,创建一个命名的 bucket,然后查找它:

我们的 KV.Registry 进程按此顺序接收到一个带有 {:create, "shopping"} 的转换和一个带有 {:lookup, "shopping"} 的调用。一旦消息发送到注册表,GenServer.cast 将立即返回。另一方面,我们将在 GenServer.call 中等待由上述 KV.Registry.handle_call 回调提供的答案。

您可能还注意到,我们在每个回调之前都添加了 @impl true。@impl true 通知编译器,我们对后续函数定义的意图是定义一个回调。如果我们在函数名称或参数数量上犯了错误,例如我们定义了一个 handle_call/2,编译器会警告我们没有任何 handle_call/2 可定义,并为我们提供 GenServer 模块已知回调的完整列表。

这一切都很好,但我们仍然希望为用户提供一个允许我们隐藏实现细节的 API。

客户端 API

GenServer 由两部分实现:客户端 API 和服务器回调。您可以将两个部分组合成一个模块,也可以将它们分成客户端模块和服务器模块。客户端是调用客户端函数的任何进程。服务器始终是我们将明确作为参数传递给客户端 API 的进程标识符或进程名称。在这里,我们将对服务器回调和客户端 API 使用单个模块。

编辑 lib/kv/registry.ex 文件,填写客户端 API 的空白:

第一个函数是 start_link/1,它通过传递选项列表来启动一个新的 GenServer。start_link/1 调用 GenServer.start_link/3,它接受三个参数:

1. 实现服务器回调的模块,在本例中为 __MODULE__(表示当前模块)
2. 初始化参数,在本例中为原子 :ok
3. 可用于指定服务器名称等内容的选项列表。现在,我们将在 start_link/1 上收到的选项列表转发到 GenServer.start_link/3

接下来的两个函数 lookup/2 和 create/2 负责将这些请求发送到服务器。在本例中,我们分别使用了 {:lookup, name} 和 {:create, name}。请求通常被指定为元组,就像这样,以便在第一个参数槽中提供多个“参数”。通常将请求的操作指定为元组的第一个元素,并在其余元素中指定该操作的参数。请注意,请求必须与 handle_call/3 或 handle_cast/2 的第一个参数匹配。

这就是客户端 API。在服务器端,我们可以实现各种回调来保证服务器初始化、终止和处理请求。这些回调是可选的,目前,我们只实现了我们关心的回调。让我们回顾一下。

第一个是 init/1 回调,它接收给 GenServer.start_link/3 的第二个参数并返回 {:ok, state},其中 state 是一个新的映射。我们已经注意到 GenServer API 如何使客户端/服务器隔离更加明显。start_link/3 发生在客户端,而 init/1 是在服务器上运行的相应回调。

对于 call/2 请求,我们实现了一个 handle_call/3 回调,它接收请求、我们从中接收请求的进程 (_from) 和当前服务器状态 (names)。handle_call/3 回调返回一个格式为 {:reply, reply, new_state} 的元组。元组的第一个元素 :reply 表示服务器应该将回复发送回客户端。第二个元素 reply 是将发送给客户端的内容,而第三个元素 new_state 是新的服务器状态。

对于 cast/2 请求,我们实现了一个 handle_cast/2 回调,它接收请求和当前服务器状态 (names)。handle_cast/2 回调返回一个格式为 {:noreply, new_state} 的元组。请注意,在实际应用中,我们可能会使用同步调用而不是异步转换来实现 :create 的回调。我们这样做是为了说明如何实现转换回调。

handle_call/3 和 handle_cast/2 回调都可能返回其他元组格式。我们还可以实现其他回调,例如,terminate/2 和 code_change/3。欢迎您浏览完整的 GenServer 文档以了解有关这些内容的更多信息。

现在,让我们编写一些测试来保证我们的 GenServer 能够按预期工作。

测试 GenServer

测试 GenServer 与测试代理没有太大区别。我们将在设置回调中生成服务器并在整个测试中使用它。在 test/kv/registry_test.exs 创建一个文件,其中包含以下内容:

我们的测试用例首先断言我们的注册表中没有存储容器,创建一个命名的存储容器,查找它,并断言它表现为存储容器。

我们为 KV.Registry 编写的设置块和为 KV.Bucket 编写的设置块之间有一个重要的区别。我们没有通过调用 KV.Registry.start_link/1 手动启动注册表,而是调用 ExUnit.Callbacks.start_supervised!/2 函数,传递 KV.Registry 模块。

通过使用 ExUnit.Case 将 start_supervised! 函数注入到我们的测试模块中。它通过调用其 start_link/1 函数来完成启动 KV.Registry 进程的工作。使用 start_supervised! 的优势!是 ExUnit 将保证在下一个测试开始之前关闭注册表进程。换句话说,它有助于保证一个测试的状态不会干扰下一个测试,以防它们依赖于共享资源。

在测试期间启动进程时,我们应该始终优先使用 start_supervised!。我们建议您将 bucket_test.exs 中的设置块也更改为使用 start_supervised!。

运行测试,它们都应该通过!

监控的必要性

到目前为止,我们所做的一切都可以通过 Agent 来实现。在本节中,我们将看到 GenServer 可以实现的众多功能之一,而 Agent 无法实现这些功能。

让我们从一个测试开始,该测试描述了当存储容器停止或崩溃时我们希望注册表如何表现:

上面的测试将在最后一个断言上失败,因为即使我们停止存储容器进程,存储容器名称仍保留在注册表中。

为了修复这个错误,我们需要注册表监控它生成的每个存储容器。一旦我们设置了一个监视器,每次存储容器进程退出时,注册表都会收到通知,让我们可以清理注册表。

让我们首先使用 iex -S mix 启动一个新控制台来使用监视器:

注意 Process.monitor(pid) 返回一个唯一引用,允许我们将即将到来的消息与该监视引用匹配。停止代理后,我们可以 flush/0 所有消息并注意到 :DOWN 消息到达,其中包含监视器返回的确切引用,通知存储容器进程因 :normal 原因退出。

让我们重新实现服务器回调以修复错误并使测试通过。首先,我们将 GenServer 状态修改为两个字典:一个包含 name -> pid,另一个包含 ref -> name。然后我们需要在 handle_cast/2 上监视存储桶,并实现 handle_info/2 回调来处理监视消息。完整的服务器回调实现如下所示:

请注意,我们能够在不更改任何客户端 API 的情况下显著更改服务器实现。这是明确隔离服务器和客户端的好处之一。

最后,与其他回调不同,我们为 handle_info/2 定义了一个“catch-all”子句,用于丢弃和记录任何未知消息。要了解原因,让我们继续下一节。

call, cast or 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/2 或 GenServer.cast/2 发送的消息,包括使用 send/2 发送的常规消息。监控 :DOWN 消息就是一个例子。

由于任何消息(包括通过 send/2 发送的消息)都会转到 handle_info/2,因此可能会有意外消息到达服务器。因此,如果我们不定义 catch-all 子句,这些消息可能会导致我们的注册表崩溃,因为没有子句匹配。不过,我们不必担心 handle_call/3 和 handle_cast/2 的此类情况。调用和强制转换仅通过 GenServer API 完成,因此未知消息很可能是开发人员的错误。

为了帮助开发人员记住 call、cast 和 info 之间的区别、支持的返回值等,我们准备了一个小型 GenServer 备忘单。

监视器还是链接?

我们之前在“进程”一章中学习了链接。现在,注册表已完成,您可能想知道:我们何时应该使用监视器,何时应该使用链接?

链接是双向的。如果您链接两个进程,其中一个崩溃,则另一端也会崩溃(除非它捕获退出)。监视器是单向的:只有监视进程会收到有关被监视进程的通知。换句话说:当您想要链接崩溃时使用链接,当您只想收到崩溃、退出等通知时使用监视器。

回到我们的 handle_cast/2 实现,您可以看到注册表既链接又监视存储容器:

这是一个坏主意,因为我们不希望注册表在存储容器崩溃时崩溃。正确的解决方法是实际上不将存储桶链接到注册表。相反,我们将每个存储容器链接到一种称为 Supervisors 的特殊类型的进程,这些进程明确设计用于处理故障和崩溃。我们将在下一章中了解有关它们的更多信息。

相关文章:

MIX OTP——使用 GenServer 进行客户端-服务器通信

在上一章中,我们使用代理来表示存储容器。在 mix 的介绍中,我们指定要命名每个存储容器,以便我们可以执行以下操作: 在上面的会话中,我们与“购物”存储容器进行了交互。 由于代理是进程,因此每个存储容器…...

2024年云安全发展趋势预测

《2024年云安全发展趋势预测》 摘要: 云计算的普及带来了新的安全挑战。本文汇总了多家云安全厂商、专业媒体和研究机构对2024年云安全发展趋势的预测,为企业组织提供了洞察云安全威胁的新视角。 云计算的灵活性和可扩展性使其成为企业关键任务负载的首…...

java.io.eofexception:ssl peer shut down incorrectly

可能是因为 1)https设置 2)超时设置 FeignConfig.java package zwf.service;import java.io.IOException; import java.io.InputStream; import java.security.KeyStore;import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory;import org.apac…...

Unity之HTC VIVE Cosmos环境安装(适合新手小白)(一)

提示:能力有限,错误之处,还望指出,不胜感激! 文章目录 前言一、unity版本电脑配置相关关于unity版本下载建议:0.先下载unity Hub1.不要用过于旧的版本2.不要下载最新版本或者其他非长期支持版本 二、官网下…...

入门JavaWeb之 Response 验证码和重定向

Response 写验证码: package com.demo.response;import javax.imageio.ImageIO; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse…...

2024-06-26 问AI: 在大数据模型中,deep speed 是什么?

文心一言 在大数据模型中,DeepSpeed是一个由微软开发并维护的开源深度学习优化库。其主要目的是提高大规模模型训练的效率和可扩展性,帮助开发者更有效率地管理及优化大模型的训练、部署任务。以下是DeepSpeed的主要特点和功能: 提高效率和…...

mobaxterm x11 转发Ubuntu mac

目录 royal tsx —— 一款Mac平台MobaXterm平替工具 mobaxterm x11 转发Ubuntu 软件 royal tsx —— 一款Mac平台MobaXterm平替工具 Royal Apps Termius Mac mobaxterm x11 转发Ubuntu 软件 所以直接在 ssh 的时候加上 - X 就可以了 ssh -X -p xxx usernameIP 运行 xclock …...

python数据分析实训任务三(‘职业’)

import pandas as pd import matplotlib.pyplot as plt data pd.read_csv(rC:\Users\XXGC\Desktop\职业2.csv,\ encodinggb2312) # 创建 DataFrame df pd.DataFrame(data) # 分析年龄和工资的关系 plt.scatter(df[年龄], df[工资]) plt.xlabel(年龄) pl…...

vscode连接SSH

1、安装Remote-SSH插件 2、点击左下角,选择SSH 3、点击连接到主机后,添加新的SSH主机,示例ssh 用户ip 4、点击服务器,输入密码登录服务器 5、可在远程资源管理器选项卡中查看 6、可以在ssh设置中打开ssh配置文件 config中的文件…...

金融科技行业创新人才培养与引进的重要性及挑战

金融科技行业作为金融与科技的深度融合产物,正以前所未有的速度改变着传统金融业的格局。在这一变革中创新人才的培养与引进成为了行业发展的核心驱动力。然而,尽管其重要性不言而喻,但在实际操作中却面临着诸多挑战。 一、创新人才培养与引进…...

【C++题解】1714. 输出满足条件的整数4

问题:1714. 输出满足条件的整数4 类型&#xff1a;简单循环 题目描述&#xff1a; 输出 1∼n 中含有数字 3 或者含有数字 5 &#xff0c;且因数有 2 &#xff08;即能被 2 整除&#xff09;的所有整数。&#xff08;n<1000&#xff09; 输入&#xff1a; 从键盘输入一个…...

如何安装和配置 Django 与 Postgres、Nginx 和 Gunicorn

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 先决条件 本教程假设您已经在Debian 7或类似的Linux发行版&#xff08;如Ubuntu&#xff09;上设置了您的droplet&#xff08;VPS&#…...

Graphwalker基于模型的自动化测试

Graphwalker 基于模型的自动化测试 基于模型的自动化测试&#xff08;Model-Based Testing&#xff0c;MBT&#xff09;作为一种创新的测试方法&#xff0c;正逐渐受到广泛关注。Graphwalker 作为一款强大的基于模型的自动化测试工具&#xff0c;为我们提供了一种高效、全面的…...

Macbook M1 Fusion安装Debian/Linux

背景 本人主力工作电脑已经迁移到苹果芯片m1的macbook上&#xff0c;曾经尝试使用Fusion安装CentOS、OpenEuler、Ubuntu的一些版本&#xff0c;都没有安装成功。最近开始研究Linux/Unix系统编程&#xff0c;迫切需要通过VMware Fusion安装一台Linux操作系统的虚拟机。 Linux安…...

ERP收费模式是怎样的?SAP ERP是如何收费的?

一、购置SAP ERP系统的费用组成 1、软件费用 传统的ERP系统大多为许可式&#xff0c;即企业在购买ERP服务时付清所有费用&#xff0c;将ERP系统部署于自己的服务器中。根据所购买ERP系统品牌的不同&#xff0c;价格上也有一定的差异。采购ERP系统许可后&#xff0c;后续维护、…...

如何实现免交互

如何实现免交互 一、免交互 交互&#xff1a;我们发出指令控制程序的运行&#xff0c;程序在接收到指令之后按照指令的效果做出对应的反应 免交互&#xff1a;间接的通过第三方的方式把指令传送给程序&#xff0c;不用直接的下达指令 Here Document免交互&#xff1a;这是命…...

浏览器userAgent大全及JS判断当前APP

文章目录 userAgent 检测PC/Mobile 浏览器 userAgent 大全Mobile APP userAgent 大全JS 判断当前 APP userAgent 检测 https://useragent.buyaocha.com/ PC/Mobile 浏览器 userAgent 大全 系统浏览器User-Agent字符串MacChromeMozilla/5.0 (Macintosh; Intel Mac OS X 10_12…...

11.异常(java版)

异常的概念 在日常开发时 代码在程序运行过程中 难免会出现一些其奇奇怪怪的问题 有时通过代码很难去控制 比如&#xff1a;数据格式不对、网络不流畅、内存报警等 在Java中 将程序执行过程中发生的不正常行为称为异常 比如我们之前写代码时经常会遇到的&#xff1a; 1.算术…...

单片机学习记录

一&#xff0c;单片机及开发板介绍 1&#xff0c;基本介绍 单片机&#xff0c;英文Micro Controller Unit&#xff0c;简称MCU内部集成了CPU、RAM、ROM、定时器、中断系统、通讯接口等一系列电脑的常用硬件功能单片机的任务是信息采集(依靠传感器)、处理(依靠CPU)和硬件设备(…...

flask的基本使用1

【 一 】Flask介绍 python 界的web框架 -Django&#xff1a;大而全&#xff0c;使用率较高 &#xff1a;https://github.com/django/django -FastAPI&#xff1a;新项目选择使用它&#xff1a;https://github.com/tiangolo/fastapi -flask&#xff1a;公司一些小项目使用它&a…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来

一、破局&#xff1a;PCB行业的时代之问 在数字经济蓬勃发展的浪潮中&#xff0c;PCB&#xff08;印制电路板&#xff09;作为 “电子产品之母”&#xff0c;其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透&#xff0c;PCB行业面临着前所未有的挑战与机遇。产品迭代…...

【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密

在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

华为OD机试-食堂供餐-二分法

import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...

反射获取方法和属性

Java反射获取方法 在Java中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时访问和操作类的内部属性和方法。通过反射&#xff0c;可以动态地创建对象、调用方法、改变属性值&#xff0c;这在很多Java框架中如Spring和Hiberna…...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建

华为云FlexusDeepSeek征文&#xff5c;DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色&#xff0c;华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型&#xff0c;能助力我们轻松驾驭 DeepSeek-V3/R1&#xff0c;本文中将分享如何…...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容

目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法&#xff0c;当前调用一个医疗行业的AI识别算法后返回…...

论文笔记——相干体技术在裂缝预测中的应用研究

目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术&#xff1a;基于互相关的相干体技术&#xff08;Correlation&#xff09;第二代相干体技术&#xff1a;基于相似的相干体技术&#xff08;Semblance&#xff09;基于多道相似的相干体…...

Mysql中select查询语句的执行过程

目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析&#xff08;Parser&#xff09; 2.4、执行sql 1. 预处理&#xff08;Preprocessor&#xff09; 2. 查询优化器&#xff08;Optimizer&#xff09; 3. 执行器…...