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 类型:简单循环 题目描述: 输出 1∼n 中含有数字 3 或者含有数字 5 ,且因数有 2 (即能被 2 整除)的所有整数。(n<1000) 输入: 从键盘输入一个…...
如何安装和配置 Django 与 Postgres、Nginx 和 Gunicorn
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 先决条件 本教程假设您已经在Debian 7或类似的Linux发行版(如Ubuntu)上设置了您的droplet(VPS&#…...
Graphwalker基于模型的自动化测试
Graphwalker 基于模型的自动化测试 基于模型的自动化测试(Model-Based Testing,MBT)作为一种创新的测试方法,正逐渐受到广泛关注。Graphwalker 作为一款强大的基于模型的自动化测试工具,为我们提供了一种高效、全面的…...
Macbook M1 Fusion安装Debian/Linux
背景 本人主力工作电脑已经迁移到苹果芯片m1的macbook上,曾经尝试使用Fusion安装CentOS、OpenEuler、Ubuntu的一些版本,都没有安装成功。最近开始研究Linux/Unix系统编程,迫切需要通过VMware Fusion安装一台Linux操作系统的虚拟机。 Linux安…...
ERP收费模式是怎样的?SAP ERP是如何收费的?
一、购置SAP ERP系统的费用组成 1、软件费用 传统的ERP系统大多为许可式,即企业在购买ERP服务时付清所有费用,将ERP系统部署于自己的服务器中。根据所购买ERP系统品牌的不同,价格上也有一定的差异。采购ERP系统许可后,后续维护、…...
如何实现免交互
如何实现免交互 一、免交互 交互:我们发出指令控制程序的运行,程序在接收到指令之后按照指令的效果做出对应的反应 免交互:间接的通过第三方的方式把指令传送给程序,不用直接的下达指令 Here Document免交互:这是命…...
浏览器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版)
异常的概念 在日常开发时 代码在程序运行过程中 难免会出现一些其奇奇怪怪的问题 有时通过代码很难去控制 比如:数据格式不对、网络不流畅、内存报警等 在Java中 将程序执行过程中发生的不正常行为称为异常 比如我们之前写代码时经常会遇到的: 1.算术…...
单片机学习记录
一,单片机及开发板介绍 1,基本介绍 单片机,英文Micro Controller Unit,简称MCU内部集成了CPU、RAM、ROM、定时器、中断系统、通讯接口等一系列电脑的常用硬件功能单片机的任务是信息采集(依靠传感器)、处理(依靠CPU)和硬件设备(…...
flask的基本使用1
【 一 】Flask介绍 python 界的web框架 -Django:大而全,使用率较高 :https://github.com/django/django -FastAPI:新项目选择使用它:https://github.com/tiangolo/fastapi -flask:公司一些小项目使用它&a…...
VoxCPM-1.5-WEBUI场景应用:智能客服、有声读物、教育视频配音
VoxCPM-1.5-WEBUI场景应用:智能客服、有声读物、教育视频配音 1. 开篇:语音合成技术的平民化革命 还记得那些机械感十足的AI语音吗?生硬的语调、奇怪的停顿、模糊的发音,让听众不得不竖起耳朵才能勉强听懂。如今,随着…...
Kubernetes 存储性能优化:从持久卷到存储类
Kubernetes 存储性能优化:从持久卷到存储类 前言 哥们,别整那些花里胡哨的理论。今天直接上硬菜——我在大厂一线优化 Kubernetes 存储性能的真实经验总结。作为一个白天写前端、晚上打鼓的硬核工程师,我对性能的追求就像对鼓点节奏的把控一样…...
Windows右键菜单终极管理指南:ContextMenuManager完全掌控你的系统交互体验
Windows右键菜单终极管理指南:ContextMenuManager完全掌控你的系统交互体验 【免费下载链接】ContextMenuManager 🖱️ 纯粹的Windows右键菜单管理程序 项目地址: https://gitcode.com/gh_mirrors/co/ContextMenuManager Windows右键菜单管理一直…...
Windows 11下用VSCode+CMake+MinGW编译OpenCV 4.8.0,保姆级避坑指南
Windows 11下用VSCodeCMakeMinGW编译OpenCV 4.8.0全流程实战 最近在Windows 11上配置OpenCV开发环境时,发现很多教程都存在版本过时或Win11特有兼容性问题。本文将分享一套经过验证的最新工具链组合:VSCode 1.85CMake 3.28MinGW-w64 12.2OpenCV 4.8.0。不…...
ChatGLM3-6B新手必看:断网可用的本地智能对话解决方案
ChatGLM3-6B新手必看:断网可用的本地智能对话解决方案 1. 引言:为什么你需要一个本地AI助手? 想象一下,你正在处理一份敏感的客户合同,需要AI帮你分析条款;或者你在一个没有稳定网络的环境里,…...
音乐解密技术探秘:从加密困境到跨平台解决方案
音乐解密技术探秘:从加密困境到跨平台解决方案 【免费下载链接】unlock-music 在浏览器中解锁加密的音乐文件。原仓库: 1. https://github.com/unlock-music/unlock-music ;2. https://git.unlock-music.dev/um/web 项目地址: https://gitc…...
探索电池2RC等效电路模型:从参数辨识到SOC估计
电池2RC等效电路模型,最小二乘法参数辩识,电池端电压误差小,扩展卡尔曼估计SOC精度高。 有文档,数据,视频,仿真图。在电池研究领域,准确建模和参数估计对于理解电池行为至关重要。今天咱就唠唠电…...
java rabbitmq实现消息协作
场景:数据下载采用rpa实现,数据服务采用java springboot实现,需要进行一键数据补录操作1、设置消息承载的通信队列,java 发送任务到rabbitmq和rpa端收到消息(neimeng_data_download)后,将下载结…...
Lingbot-Depth-Pretrain-VitL-14处理复杂光照与反射场景效果展示
Lingbot-Depth-Pretrain-VitL-14处理复杂光照与反射场景效果展示 深度估计技术,简单来说就是让计算机像人眼一样,判断出画面中每个物体离我们有多远。这项技术在自动驾驶、机器人导航、增强现实等领域都扮演着关键角色。然而,当场景中出现一…...
Realistic Vision V5.1 虚拟摄影棚实战:利用GitHub管理自定义模型与脚本
Realistic Vision V5.1 虚拟摄影棚实战:利用GitHub管理自定义模型与脚本 你是不是也遇到过这样的烦恼?好不容易在本地电脑上,用Realistic Vision V5.1模型调出了一套完美的参数组合,生成的人像照片质感堪比专业影棚。结果换台电脑…...
