19、OTP应用开发:从基础到发布

OTP应用开发:从基础到发布

1. 监督者:可靠性的核心

在开发可靠的应用程序时,监督者起着至关重要的作用。即使序列工作进程崩溃,它也能重新启动并保留状态。这表明,若要编写可靠的应用,仔细的监督是关键。

监督者的作用体现在构建代码的信任环上。外层代码与外界交互,应尽可能可靠;而内层嵌套的代码可以不那么完美,但每层代码都需知道如何处理下一层代码的失败。监督者有多种策略来处理子进程的终止、重启等情况。使用监督者管理工作进程,能促使开发者在设计应用时考虑可靠性和状态,从而打造出高可用性的应用。例如,OTP曾被用于构建可靠性达99.9999999%的系统。

以下是一个练习:
- 练习OTP - 监督者 - 2 :重新设计栈服务器,使用监督树和单独的存储进程来保存状态。验证其功能,并确保服务器崩溃重启后状态得以保留。

2. OTP应用:与传统应用不同

OTP中的应用与我们通常理解的应用有所不同。在OTP世界里,应用是带有描述符的代码包,描述符会告知运行时代码的依赖关系、注册的全局名称等信息。它更像动态链接库或共享对象,而非传统意义上的独立应用。可以将“应用”理解为“组件”或“服务”。

例如,使用HTTPoison库获取GitHub问题时,实际上安装的是包含HTTPoison的独立应用。Mix会自动加载该应用,HTTPoison又会启动其依赖的其他应用,而这一切对开发者来说是透明的。

3. 应用规范文件

Mix会提及名为 name.app 的文件,这就是应用规范文件,用于向运行时环境定义应用。Mix会根据 mix.exs 中的信息以及编译应用时收集的信息自动创建该文件。运行应用时会参考此文件来加载相关内容。即使应用不使用所有OTP功能,该文件也会被创建和引用。当开始使用OTP监督树时, mix.exs 中添加的内容会被复制到 .app 文件中。

4. 将序列程序转换为OTP应用

之前的序列应用已经是一个完整的OTP应用。Mix创建初始项目树时,添加了监督者并在 mix.exs 文件中提供了启动应用所需的信息。具体来说,填充了 application 函数:

def application do
  [mod: { Sequence, [] }]
end

这表明应用的顶层模块是 Sequence ,OTP会假设该模块实现了 start 函数,并将空列表作为参数传递给它。

为了从 mix.exs 中获取初始值,我们进行如下修改:
- 修改 mix.exs 传递初始值(这里使用456):

def application do
  [mod: { Sequence, 456 }]
end
  • 修改 sequence.ex 代码以使用传递的值:
defmodule Sequence do
  use Application
  def start(_type, initial_number) do
    Sequence.Supervisor.start_link(initial_number)
  end
end

可以通过以下命令验证其功能:

$ iex -S mix
Compiling 5 files (.ex)
Generated sequence app
iex> Sequence.Server.next_number
456

mod 选项指定了应用的主入口模块,OTP启动应用时会调用该模块的 start 函数。还可以添加 registered 选项,列出应用将注册的名称,以确保在节点或集群中所有加载的应用中名称唯一。更新后的 mix.exs 配置如下:

# Configuration for the OTP application
def application do
  [
    mod: { Sequence, 456 },
    registered: [ Sequence.Server ]
  ]
end

运行 mix compile 会编译应用并更新 sequence.app 应用规范文件。生成的 sequence.app 文件位于 _build/dev/lib/sequence/ebin 目录下,其内容如下:

{application,sequence,
  [{description,"sequence"},
  {vsn,"0.0.1"},
  {modules,['Elixir.Sequence','Elixir.Sequence.Server',
  'Elixir.Sequence.Stash',
  'Elixir.Sequence.SubSupervisor',
  'Elixir.Sequence.Supervisor']},
  {applications,[kernel,stdlib,elixir]},
  {mod,{'Elixir.Sequence',456}},
  {registered,['Elixir.Sequence.Server']}]}.
5. 关于应用参数

之前的示例中传递了整数456作为初始参数,更推荐传递关键字列表,因为Elixir提供了 Application.get_env 函数来从代码的任何位置检索这些值。可以这样设置 mix.exs

def application do
  [
    mod: { Sequence, [] },
    env: [initial_number: 456],
    registered: [ Sequence.Server ]
  ]
end

然后在 Sequence 模块中使用 get_env 获取值:

defmodule Sequence do
  use Application
  def start(_type, _args) do
    Sequence.Supervisor.start_link(Application.get_env(:sequence, :initial_number))
  end
end
6. 监督是可靠性的基础

通过运行OTP序列应用可以看到,启动了两个监督进程和两个工作进程,它们相互协作,即使与我们交互的工作进程崩溃,系统也能继续运行且不丢失状态。其他Erlang进程也能与序列应用交互。

start 函数有两个参数,第二个参数对应 mix.exs 文件中 mod 选项指定的值,第一个参数指定重启状态,这里暂不深入探讨。

以下是两个练习:
- 练习OTP - 应用 - 1 :将栈服务器转换为OTP应用。
- 练习OTP - 应用 - 2 :目前尚未为应用编写测试,思考可以测试的内容并进行尝试。

7. 代码发布

Erlang通过强大的发布管理系统实现了高可用性的应用。Elixir简化了这个系统的使用。

7.1 术语解释
  • 发布 :包含应用特定版本、依赖项、配置以及运行和持续运行所需元数据的包。
  • 部署 :将发布包放入可使用的环境中的方式。
  • 热升级 :一种部署方式,允许在应用继续运行的情况下更改其发布版本,升级过程对用户无感知。
7.2 EXRM - Elixir发布管理器

EXRM是一个Elixir包,基于Erlang的relx包,利用了Erlang虚拟机的特殊功能,使大多数发布任务变得简单。

7.3 准备工作

在Elixir中,应用代码和其操作的数据都需要进行版本控制,二者相互独立。代码版本存储在 mix.exs 的项目字典中,而数据版本可以在每个服务器模块中进行管理。例如,服务器的状态最初以二元组形式存储,可定义为版本0;后来改为三元组存储,可定义为版本1。可以使用 @vsn 指令设置服务器状态数据的版本:

defmodule Sequence.Server do
  use GenServer
  @vsn "0"
7.4 首次发布

首先,需要将 exrm 添加为项目依赖。打开 sequence 项目的 mix.exs 文件,更新 deps 函数:

defp deps do
  [
    {:exrm, "~> 1.0.6"}
  ]
end

然后安装依赖:

$ mix do deps.get, deps.compile

现在可以创建第一个发布版本:

$ mix release

EXRM会从 mix.exs 文件中获取应用名称和版本号,并将应用打包到 rel/ 目录下。最重要的文件是 rel/sequence/releases/0.0.1/sequence.tar.gz ,它包含了运行该版本所需的所有内容,我们将使用这个文件进行服务器部署。

7.5 模拟部署环境

为了简化部署过程,我们将应用部署到本地机器,但模拟远程部署的方式,使用SSH进行操作。将发布包存储在 deploy 目录中:

$ ssh localhost mkdir ~/deploy
7.6 部署和运行应用

sequence.tar.gz 文件复制到 deploy 目录并解压:

$ scp rel/sequence/releases/0.0.1/sequence.tar.gz localhost:deploy
$ ssh localhost tar -x -f ~/deploy/sequence.tar.gz -C ~/deploy

启动 iex 控制台:

$ ssh -t localhost ~/deploy/bin/sequence console

在控制台中可以与序列应用交互:

iex(sequence@127.0.0.1)2> Sequence.Server.next_number
456
iex(sequence@127.0.0.1)3> Sequence.Server.next_number
457

保持该会话运行,用于后续的热代码加载演示。

7.7 第二个发布版本

根据市场调研,客户希望 next_number 函数返回类似“下一个数字是458”的消息。我们进行如下修改:
- 修改 server.ex 文件:

def next_number do
  with number = GenServer.call(__MODULE__, :next_number),
  do: "The next number is #{number}"
end
  • 提升 mix.exs 文件中应用的版本号:
def project do
  [
    app: :sequence,
    version: "0.0.2",
    deps: deps()
  ]
end

创建新的发布版本:

$ mix release

在服务器上创建新的发布目录并复制发布包:

$ ssh localhost mkdir deploy/releases/0.0.2
$ scp rel/sequence/releases/0.0.2/sequence.tar.gz localhost:deploy/releases/0.0.2

升级正在运行的代码:

$ ssh localhost ~/deploy/bin/sequence upgrade 0.0.2

回到之前的控制台会话,再次调用 next_number 函数:

iex(sequence@127.0.0.1)4> Sequence.Server.next_number
"The next number is 458"
iex(sequence@127.0.0.1)5> Sequence.Server.next_number
"The next number is 459"

如果新发布版本出现问题,可以降级到之前的版本:

$ ssh localhost ~/deploy/bin/sequence downgrade 0.0.1

之后可以再升级回当前版本:

$ ssh localhost ~/deploy/bin/sequence upgrade 0.0.2
7.8 迁移服务器状态

在实际使用中发现 increment_number 函数的实现有问题,它应该设置连续数字之间的差值,而不是一次性增加一个值。

我们对代码进行修改,在状态中添加一个 delta 值。更新后的服务器代码如下:

defmodule Sequence.Server do
  use GenServer
  require Logger
  @vsn "1"

  defmodule State do
    defstruct current_number: 0, stash_pid: nil, delta: 1
  end

  # 外部API
  def start_link(stash_pid) do
    GenServer.start_link(__MODULE__, stash_pid, name: __MODULE__)
  end

  def next_number do
    with number = GenServer.call(__MODULE__, :next_number),
    do: "The next number is #{number}"
  end

  def increment_number(delta) do
    GenServer.cast __MODULE__, {:increment_number, delta}
  end

  # GenServer实现
  def init(stash_pid) do
    current_number = Sequence.Stash.get_value stash_pid
    { :ok, %State{current_number: current_number, stash_pid: stash_pid} }
  end

  def handle_call(:next_number, _from, state) do
    { :reply, state.current_number, %{ state | current_number: state.current_number + state.delta} }
  end

  def handle_cast({:increment_number, delta}, state) do
    {:noreply, %{ state | current_number: state.current_number + delta, delta: delta} }
  end

  def terminate(_reason, state) do
    Sequence.Stash.save_value state.stash_pid, state.current_number
  end
end

由于状态格式发生了变化,我们将版本号更新为1,并实现 code_change 函数来处理状态迁移:

def code_change("0", old_state = { current_number, stash_pid }, _extra) do
  new_state = %State{current_number: current_number,
  stash_pid: stash_pid,
  delta: 1
  }
  Logger.info "Changing code from 0 to 1"
  Logger.info inspect(old_state)
  Logger.info inspect(new_state)
  { :ok, new_state }
end

提升 mix.exs 文件中应用的版本号:

def project do
  [
    app: :sequence,
    version: "0.0.3",
    deps: deps()
  ]
end

创建新的发布版本:

$ mix release

在服务器上创建新的发布目录并复制发布包:

$ ssh localhost mkdir ~/deploy/releases/0.0.3/
$ scp rel/sequence/releases/0.0.3/sequence.tar.gz localhost:deploy/releases/0.0.3/

升级应用:

$ ssh localhost ~/deploy/bin/sequence upgrade 0.0.3

通过以上步骤,我们完成了从序列程序到OTP应用的转换,以及应用的发布和升级过程,充分展示了OTP在构建可靠、可维护应用方面的强大能力。

OTP应用开发:从基础到发布

8. 热升级原理与优势

在OTP应用开发中,热升级是一项非常重要的特性。Erlang允许同时运行一个模块的两个版本,当前正在执行的代码会继续使用旧版本,直到代码显式引用了发生更改的模块名称,此时该特定进程的执行会切换到新版本。

以之前的序列应用为例,当我们调用 Sequence.Server.next_number 时,对 Sequence.Server 的引用会触发代码重新加载,从而让0.0.2版本的代码处理后续请求。这种方式确保了正在运行的代码不会被中断,同时新发布的版本也能在合适的时候生效。热升级的优势在于可以在不影响用户使用的情况下更新应用,大大提高了应用的可用性和用户体验。

9. 代码版本与状态管理总结

在Elixir的OTP应用开发中,代码版本和状态管理是确保应用可靠性和可维护性的关键。

管理对象 管理方式 示例
代码版本 存储在 mix.exs 的项目字典中,每次有重大代码变更时更新版本号 def project do [ app: :sequence, version: "0.0.3", deps: deps() ] end
状态版本 在每个服务器模块中使用 @vsn 指令进行管理,状态格式变化时更新版本号 @vsn "1"

当状态格式发生变化时,需要实现 code_change 函数来处理状态迁移,确保旧状态能正确转换为新状态。例如:

def code_change("0", old_state = { current_number, stash_pid }, _extra) do
  new_state = %State{current_number: current_number,
  stash_pid: stash_pid,
  delta: 1
  }
  Logger.info "Changing code from 0 to 1"
  Logger.info inspect(old_state)
  Logger.info inspect(new_state)
  { :ok, new_state }
end
10. 开发流程总结

整个OTP应用的开发和发布流程可以用以下mermaid流程图表示:

graph LR
    A[设计应用架构] --> B[编写代码]
    B --> C[配置mix.exs]
    C --> D[编译应用]
    D --> E[创建应用规范文件]
    E --> F[测试应用]
    F --> G[添加EXRM依赖]
    G --> H[创建发布版本]
    H --> I[部署应用]
    I --> J[热升级或降级]

具体步骤如下:
1. 设计应用架构 :确定应用的功能需求,设计监督树和工作进程的结构。
2. 编写代码 :实现应用的业务逻辑,包括服务器模块、监督者模块等。
3. 配置mix.exs :设置应用的初始参数、依赖项、版本号等信息。
4. 编译应用 :运行 mix compile 命令编译应用代码。
5. 创建应用规范文件 :Mix会根据 mix.exs 和编译信息自动创建 .app 文件。
6. 测试应用 :编写测试用例,确保应用的功能正常。
7. 添加EXRM依赖 :在 mix.exs 中添加 exrm 依赖,并安装。
8. 创建发布版本 :运行 mix release 命令创建发布包。
9. 部署应用 :将发布包复制到目标服务器并解压,启动应用。
10. 热升级或降级 :根据需要对应用进行热升级或降级操作。

11. 开发技巧与注意事项
  • 参数传递 :使用关键字列表传递应用参数,方便在代码中使用 Application.get_env 函数获取参数值。
  • 状态管理 :当状态格式发生变化时,及时更新状态版本号,并实现 code_change 函数处理状态迁移。
  • 版本控制 :每次代码有重大变更时,及时更新 mix.exs 中的版本号,确保发布版本的准确性。
  • 测试 :虽然OTP应用具有较高的可靠性,但仍需要编写测试用例来验证应用的功能和稳定性。
12. 未来展望

随着技术的不断发展,OTP应用开发也将不断演进。未来可能会有更强大的工具和框架来简化开发流程,提高应用的性能和可靠性。例如,可能会出现更智能的热升级机制,能够自动处理更多复杂的状态迁移和代码变更。同时,与云计算、容器化等技术的结合也将为OTP应用的部署和管理带来更多便利。开发者可以持续关注这些技术趋势,不断提升自己的开发能力,打造出更优秀的OTP应用。

通过对OTP应用开发的深入学习,我们掌握了从基础概念到实际开发、发布的全过程。希望这些知识能帮助开发者在实际项目中构建出可靠、高效的应用。

内容概要:本文为《科技类企业品牌传播白皮书》,系统阐述了新闻媒体发稿、自媒体博主种草与短视频矩阵覆盖三大核心传播策略,并结合“传声港”平台的AI工具与资源整合能力,提出适配科技企业的品牌传播解决方案。文章深入分析科技企业传播的特殊性,包括受众圈层化、技术复杂性与传播通俗性的矛盾、产品生命周期影响及2024-2025年传播新趋势,强调从“技术输出”向“价值引领”的战略升级。针对三种传播方式,分别从适用场景、操作流程、效果评估、成本效益、风险防控等方面提供详尽指南,并通过平台AI能力实现资源智能匹配、内容精准投放与全链路效果追踪,最终构建“信任—种草—曝光”三位一体的传播闭环。; 适合人群:科技类企业品牌与市场负责人、公关传播从业者、数字营销管理者及初创科技公司创始人;具备一定品牌传播基础,关注效果可量化与AI工具赋能的专业人士。; 使用场景及目标:①制定科技产品全生命周期的品牌传播策略;②优化媒体发稿、KOL合作与短视频运营的资源配置与ROI;③借助AI平台实现传播内容的精准触达、效果监测与风险控制;④提升品牌在技术可信度、用户信任与市场影响力方面的综合竞争力。; 阅读建议:建议结合传声港平台的实际工具模块(如AI选媒、达人匹配、数据驾驶舱)进行对照阅读,重点关注各阶段的标准化流程与数据指标基准,将理论策略与平台实操深度融合,推动品牌传播从经验驱动转向数据与工具双驱动。
【3D应力敏感度分析拓扑优化】【基于p-范数全局应力衡量的3D敏感度分析】基于伴随方法的有限元分析和p-范数应力敏感度分析(Matlab代码实现)内容概要:本文档围绕“基于p-范数全局应力衡量的3D应力敏感度分析”展开,介绍了一种结合伴随方法与有限元分析的拓扑优化技术,重点实现了3D结构在应力约束下的敏感度分析。文中详细阐述了p-范数应力聚合方法的理论基础及其在避免局部应力过高的优势,并通过Matlab代码实现完整的数值仿真流程,涵盖有限元建模、灵敏度计算、优化迭代等关键环节,适用于复杂三维结构的轻量化与高强度设计。; 适合人群:具备有限元分析基础、拓扑优化背景及Matlab编程能力的研究生、科研人员或从事结构设计的工程技术人员,尤其适合致力于力学仿真与优化算法开发的专业人士; 使用场景及目标:①应用于航空航天、机械制造、土木工程等领域中对结构强度和重量有高要求的设计优化;②帮助读者深入理解伴随法在应力约束优化中的应用,掌握p-范数法处理全局应力约束的技术细节;③为科研复现、论文写作及工程项目提供可运行的Matlab代码参考与算法验证平台; 阅读建议:建议读者结合文中提到的优化算法原理与Matlab代码同步调试,重点关注敏感度推导与有限元实现的衔接部分,同时推荐使用提供的网盘资源获取完整代码与测试案例,以提升学习效率与实践效果。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值