什么是持久函数?

Avatar of Sarah Drasner
Sarah Drasner

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费信用额度!

哦,不!又是一个行话!“持久函数”究竟是什么意思? 持久函数 与无服务器架构有关。它是 Azure 函数的扩展,允许您在无服务器环境中编写有状态的执行。

这样想吧。当人们谈论无服务器函数时,他们往往会关注几个主要优势。

  • 它们很便宜
  • 它们可以根据您的需求进行扩展(不一定是,但这是许多服务的默认设置)
  • 它们允许您编写事件驱动的代码

让我们花点时间谈谈最后一个。当您可以编写事件驱动的代码时,您可以将您的操作需求分解成更小的函数,这些函数本质上说:当收到此请求时,运行此代码。您不必费心处理基础设施,这些都将为您处理。这是一个相当有说服力的概念。

在这种模式下,您可以将工作流分解成更小、可重用的部分,从而使它们更容易维护。这也让您能够专注于业务逻辑,因为您将问题简化为服务器上运行所需的最简单的代码。

所以,持久函数就是在这里发挥作用的。您可能已经猜到,随着应用程序规模的增长和需要维护更多状态,您将需要不止一个函数来运行。并且,在许多情况下,您需要协调它们并指定它们的运行顺序,才能使它们有效。值得在此指出的是,持久函数是一种仅在 Azure 中可用的模式。其他服务对此主题有不同的变体。例如,AWS 版本称为 Step Functions。因此,虽然我们讨论的是 Azure 的一项特定功能,但它也具有更广泛的适用性。

持久函数的实际应用,一些示例

假设您正在销售机票。您可以想象,当一个人购买机票时,我们需要

  1. 检查机票的可用性
  2. 请求获取座位图
  3. 如果他们是忠诚会员,则获取他们的里程积分
  4. 如果付款成功并且他们安装了应用程序/已请求通知,则向他们发送移动通知

(通常还有更多内容,但我们将其用作基本示例)

有时这些操作将全部并发运行,有时则不会。例如,假设他们想用里程奖励购买机票。那么您必须先检查奖励,然后再检查机票的可用性。然后做一些魔法,确保即使是数据科学家也无法理解您的奖励计划背后的算法。

编排函数

无论您是在同一时刻运行这些函数,按顺序运行它们,还是根据条件是否满足来运行它们,您可能都希望使用称为 编排函数 的东西。这是一种特殊的函数,它定义了您的工作流,正如您所料,它对其他函数进行编排。每当一个函数等待时,它们会自动检查其进度,这对管理复杂异步代码非常有用。

如果没有持久函数,您会遇到混乱的问题。假设一个函数依赖于另一个函数来触发。您可以直接从第一个函数调用另一个函数,但维护代码的人员必须进入每个单独的函数,并在维护它们时记住它们是如何被调用的,如果需要更改,则单独维护它们。很容易陷入类似于回调地狱的东西,调试会变得非常棘手。

另一方面,编排函数管理所有其他函数的状态和计时。编排函数将由 编排触发器 触发,并支持 **输入** 和 **输出**。您可以看到这将非常方便!您以一种全面的方式在一个地方管理状态。此外,无服务器函数本身可以将自己的工作限制在需要执行的任务,使其更具可重用性和更少脆弱性。

让我们回顾一些可能的模式。我们将超越简单的链接,并讨论一些其他的可能性。

模式 1:函数链接

这是所有模式中最简单的实现。它实际上是一个编排器控制几个不同的步骤。编排器触发一个函数,函数完成,编排器注册它,然后触发下一个函数,依此类推。以下是实际操作的可视化效果。

查看 CodePen 上 Sarah Drasner (@sdras) 编写的 持久函数:模式 #1 - 链接

以下是用生成器实现此模式的一个简单示例。

const df = require("durable-functions")

module.exports = df(function*(ctx) {
  const x = yield ctx.df.callActivityAsync('fn1')
  const y = yield ctx.df.callActivityAsync('fn2', x)
  const z = yield ctx.df.callActivityAsync('fn3', y)
  return yield ctx.df.callActivityAsync('fn3', z)
})

我喜欢生成器!如果您不熟悉它们,请查看 Bodil 关于此主题的 精彩演讲

模式 2:扇出/扇入

如果您需要并行执行多个函数,并且需要根据结果触发另一个函数,那么扇出/扇入模式可能是您的选择。我们将累积从第一组函数返回的结果,以便在最后一个函数中使用它们。

查看 CodePen 上 Sarah Drasner (@sdras) 编写的 持久函数:模式 #2,扇出,扇入

const df = require('durable-functions')

module.exports = df(function*(ctx) {
  const tasks = []

  // items to process concurrently, added to an array
  const taskItems = yield ctx.df.callActivityAsync('fn1')
  taskItems.forEach(item => tasks.push(ctx.df.callActivityAsync('fn2', item))
  yield ctx.df.task.all(tasks)

  // send results to last function for processing
  yield ctx.df.callActivityAsync('fn3', tasks)
})

模式 3:异步 HTTP API

您还需要向 API 发出请求,而请求的时间是未知的,这也是很常见的。距离和已处理请求的数量等因素可能会使时间不可预测。有些情况需要先异步执行其中的一些工作,但要同时进行,然后在最初的几个 API 调用完成后触发另一个函数。异步/等待非常适合此任务。

查看 CodePen 上 Sarah Drasner (@sdras) 编写的 持久函数:模式 #3,异步 HTTP API

const df = require('durable-functions')

module.exports = df(async ctx => {
  const fn1 = ctx.df.callActivityAsync('fn1')
  const fn2 = ctx.df.callActivityAsync('fn2')

  // the responses come in and wait for both to be resolved
  await fn1
  await fn2

  // then this one this one is called
  await ctx.df.callActivityAsync('fn3')
})

您可以在 此处查看更多模式!(除了动画。😉)

入门

如果您想 试用持久函数 并了解更多信息,这里有一个 很棒的教程,以及相应的存储库供您分叉和使用。我还在和同事一起编写另一篇文章,将深入研究其中一种模式,很快就会发布!

其他模式

Azure 提供了 逻辑应用中的一个独特功能,它允许您以可视化的方式设计工作流。我通常是一个只写代码、不使用所见即所得的人,但逻辑应用的一个引人注目的功能是它们与 Twilio 和 SendGrid 等服务有现成的连接器,因此您不必编写那些略微令人讨厌、主要是样板的代码。它还可以与您现有的函数集成,以便您可以将连接到中间层系统的部分抽象出来,并手动编写其余部分,这可以真正提高生产力。