开始使用无服务器 API 时应该了解的 7 件事

Avatar of Simona Cotin
Simona Cotin

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

我想让你花点时间想想 Twitter,并从规模的角度思考它。Twitter 有 3.26 亿用户。总的来说,我们每秒创建 ~6,000 条推文。每分钟,就会创建 360,000 条推文。这总计每年近 2000 亿条推文。现在,如果 Twitter 的创建者被如何扩展的问题所困扰,并且他们甚至没有开始呢?

这就像我曾经有过的每一个创业想法,这就是为什么我如此热爱无服务器的原因:它处理了扩展问题,让我去构建下一个 Twitter!

使用 Application Insights 实时指标

如上所示,随着更多用户请求的到来,我们在几秒钟内将规模从一台服务器扩展到七台服务器。您也可以轻松地扩展它。

因此,让我们构建一个 API,它将随着越来越多的用户涌入和工作负载的增加而即时扩展。我们将通过回答以下问题来做到这一点


如何创建一个新的无服务器项目?

对于每项新技术,我们都需要弄清楚有哪些工具可供我们使用,以及如何将它们集成到我们现有的工具集中。在开始使用无服务器时,我们有一些选项需要考虑。

首先,我们可以使用我们常用的浏览器来创建、编写和测试函数。它功能强大,并且使我们能够在任何地方进行编码;我们只需要一台电脑和一个正在运行的浏览器。浏览器是编写我们第一个无服务器函数的良好起点。

浏览器中的无服务器

接下来,随着您对新概念越来越熟悉并变得更有成效,您可能希望使用本地环境来继续开发。通常,您需要一些功能的支持

  • 在您选择的编辑器中编写代码
  • 可以为您完成繁重工作并生成样板代码的工具
  • 在本地运行和调试代码
  • 支持快速部署您的代码

微软是我的雇主,我主要使用 Azure Functions 构建无服务器应用程序,因此在本文的其余部分,我将继续使用它们作为示例。使用 Azure Functions,在使用 Azure Functions Core Tools 时,您将获得所有这些功能的支持,您可以 从 npm 安装

npm install -g azure-functions-core-tools

接下来,我们可以使用交互式 CLI 初始化一个新项目并创建新的函数

func CLI

如果您的首选编辑器恰好是 VS Code,那么您也可以使用它来编写无服务器代码。实际上有一个 很棒的扩展 用于它。

安装后,左侧边栏中会添加一个新图标,在这里我们可以访问所有与 Azure 相关的扩展!所有相关的函数都可以分组到同一个项目中(也称为函数应用)。这就像一个文件夹,用于对应该一起扩展的函数进行分组,以及我们希望同时管理和监控的函数。要使用 VS Code 初始化一个新项目,请点击 Azure 图标,然后点击**文件夹**图标。

创建新的 Azure Functions 项目

这将生成一些帮助我们进行全局设置的文件。让我们现在回顾一下。

host.json

我们可以直接在 host.json 文件中为项目中的所有函数配置全局选项。

在其中,我们的函数应用配置为使用最新版本的无服务器运行时(目前为 2.0)。我们还通过将functionTimeout属性设置为00:10:00来配置函数在十分钟后超时,该属性的默认值为五分钟(00:05:00)。

在某些情况下,我们可能希望控制 URL 的路由前缀,甚至调整设置,例如并发请求的数量。Azure Functions 甚至允许我们自定义其他功能,例如日志记录、healthMonitor和不同类型的扩展。

以下是我如何配置该文件的示例

// host.json
{
  "version": "2.0",
  "functionTimeout": "00:10:00",
  "extensions": {
  "http": {
    "routePrefix": "tacos",
    "maxOutstandingRequests": 200,
    "maxConcurrentRequests": 100,
    "dynamicThrottlesEnabled": true
  }
}
}

应用程序设置

应用程序设置 是用于管理运行时、语言和版本、连接字符串、读写访问权限和 ZIP 部署等的全局设置。有些是平台所需的设置,例如FUNCTIONS_WORKER_RUNTIME,但我们也可以定义将在应用程序代码中使用的自定义设置,例如DB_CONN,我们可以使用它来连接到数据库实例。

在本地开发时,我们在名为local.settings.json的文件中定义这些设置,并像访问任何其他环境变量一样访问它们。

同样,以下是一个连接这些点的示例代码片段

// local.settings.json
{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "your_key_here",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "WEBSITE_NODE_DEFAULT_VERSION": "8.11.1",
    "FUNCTIONS_EXTENSION_VERSION": "~2",
    "APPINSIGHTS_INSTRUMENTATIONKEY": "your_key_here",
    "DB_CONN": "your_key_here",
  }
}

Azure Functions 代理

Azure Functions 代理proxies.json文件中实现,它们使我们能够在同一个 API 下公开多个函数应用,以及修改请求和响应。在下面的代码中,我们在同一个 URL 下发布了两个不同的端点。

// proxies.json
{
  "$schema": "http://json.schemastore.org/proxies",
  "proxies": {
    "read-recipes": {  
      "matchCondition": {
        "methods": ["POST"],
        "route": "/api/recipes"
      },
      "backendUri": "https://tacofancy.azurewebsites.net/api/recipes"
    },
    "subscribe": {
      "matchCondition": {
        "methods": ["POST"],
        "route": "/api/subscribe"
      },
      "backendUri": "https://tacofancy-users.azurewebsites.net/api/subscriptions"
    }
  }
}

通过点击扩展中的**闪电**图标来创建一个新的函数。

创建一个新的 Azure 函数

扩展将使用预定义的模板根据我们所做的选择(语言、函数类型和授权级别)生成代码。

我们使用function.json来配置我们的函数监听哪种类型的事件,以及可选地绑定到特定的数据源。我们的代码响应特定的触发器运行,当我们响应 HTTP 请求时,这些触发器可以是**HTTP 类型**,当我们响应文件上传到存储帐户时运行代码。其他常用的触发器可以是**队列类型**,用于处理上传到队列的消息,或者时间触发器,用于在指定的时间间隔运行代码。函数绑定用于读取和写入数据到数据源或服务(如数据库)或发送电子邮件。

在这里,我们可以看到我们的函数正在监听 HTTP 请求,并且我们通过名为req的对象访问实际请求。

// function.json
{
  "disabled": false,
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["get"],
      "route": "recipes"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ]
}

index.js是我们实现函数代码的地方。我们可以访问上下文对象,使用它与无服务器运行时通信。我们可以执行诸如记录信息、设置函数的响应以及从bindings对象读取和写入数据等操作。有时,我们的函数应用将有多个依赖于相同代码(例如数据库连接)的函数,并且将该代码提取到单独的文件中以减少代码重复是一个好习惯。

//Index.js
module.exports = async function (context, req) {
  context.log('JavaScript HTTP trigger function processed a request.');

  if (req.query.name || (req.body && req.body.name)) {
      context.res = {
          // status: 200, /* Defaults to 200 */
          body: "Hello " + (req.query.name || req.body.name)
      };
  }
  else {
      context.res = {
          status: 400,
          body: "Please pass a name on the query string or in the request body"
      };
  }
};

谁对运行它感到兴奋?

如何本地运行和调试无服务器函数?

当使用 VS Code 时,Azure Functions 扩展为我们提供了许多在本地运行和调试无服务器函数所需的设置。当我们使用它创建一个新项目时,一个.vscode文件夹会自动为我们创建,这就是所有调试配置包含的地方。要调试我们的新函数,我们可以使用命令面板(Ctrl+Shift+P),通过过滤**调试:选择并启动调试**或键入debug来进行过滤。

调试无服务器函数

这成为可能的原因之一是 Azure Functions 运行时是开源的,并且在安装azure-core-tools包时会安装在我们的机器上。

如何安装依赖项?

如果您使用过 Node.js,那么您可能已经知道答案了。与任何其他 Node.js 项目一样,我们首先需要在项目的根文件夹中创建一个package.json文件。这可以通过运行npm init -y来完成,-y将使用默认配置初始化文件。

然后,我们像在任何其他项目中一样使用 npm 安装依赖项。对于此项目,让我们继续从 npm 安装 MongoDB 包,方法是运行

npm i mongodb

该软件包现在可以在函数应用中的所有函数中导入。

如何连接到第三方服务?

无服务器函数非常强大,使我们能够编写对事件做出反应的自定义代码。但是,仅靠代码在构建复杂应用程序时帮助不大。真正的强大之处在于与第三方服务和工具的轻松集成。

那么,我们如何连接并从数据库中读取数据呢?使用 MongoDB 客户端,我们将从我在 Azure 中创建的 Azure Cosmos DB 实例读取数据,但您可以使用任何其他 MongoDB 数据库来执行此操作。

//Index.js
const MongoClient = require('mongodb').MongoClient;

// Initialize authentication details required for database connection
const auth = {
  user: process.env.user,
  password: process.env.password
};

// Initialize global variable to store database connection for reuse in future calls
let db = null;
const loadDB = async () => {
  // If database client exists, reuse it  
  if (db) {
    return db;
  }
  // Otherwise, create new connection  
  const client = await MongoClient.connect(
    process.env.url,
    {
      auth: auth
    }
  );
  // Select tacos database
  db = client.db('tacos');
  return db;
};

module.exports = async function(context, req) {
  try {
    // Get database connection
    const database = await loadDB();
    // Retrieve all items in the Recipes collection 
    let recipes = await database
      .collection('Recipes')
      .find()
      .toArray();
    // Return a JSON object with the array of recipes 
    context.res = {
      body: { items: recipes }
    };
  } catch (error) {
    context.log(`Error code: ${error.code} message: ${error.message}`);
    // Return an error message and Internal Server Error status code
    context.res = {
      status: 500,
      body: { message: 'An error has occurred, please try again later.' }
    };
  }
};

这里需要注意的一点是,我们正在重用数据库连接,而不是为每次后续对我们函数的调用创建一个新的连接。这减少了每次后续函数调用的约 300 毫秒。我认为这是一个胜利!

在哪里可以保存连接字符串?

在本地开发时,我们可以将环境变量、连接字符串以及任何秘密内容存储到 local.settings.json 文件中,然后使用 process.env.yourVariableName 以通常的方式访问它们。

local.settings.json
{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "user": "your-db-user",
    "password": "your-db-password",
    "url": "mongodb://your-db-user.documents.azure.com:10255/?ssl=true"
  }
}

在生产环境中,我们可以在 Azure 门户中函数页面上配置应用程序设置。

但是,另一种巧妙的方法是通过 VS Code 扩展。无需离开 IDE,我们就可以添加新的设置、删除现有的设置或将它们上传/下载到云端。

调试无服务器函数

如何自定义 URL 路径?

对于 REST API,围绕 URL 本身的格式有一些最佳实践。我为我们的食谱 API 选择的格式是

  • GET /recipes:检索食谱列表
  • GET /recipes/1:检索特定食谱
  • POST /recipes:创建新的食谱
  • PUT /recipes/1:更新 ID 为 1 的食谱
  • DELETE /recipes/1:删除 ID 为 1 的食谱

创建新函数时默认提供的 URL 格式为 http://host:port/api/function-name。要自定义 URL 路径和我们侦听的方法,我们需要在 function.json 文件中配置它们。

// function.json
{
  "disabled": false,
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["get"],
      "route": "recipes"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ]
}

此外,我们可以通过使用花括号将参数添加到函数的路由中:route: recipes/{id}。然后,我们可以从 req 对象中的代码中读取 ID 参数。

const recipeId = req.params.id;

如何部署到云端?

恭喜,您已经完成了最后一步!🎉是时候将这些好东西推送到云端了。与往常一样,VS Code 扩展可以为您提供帮助。实际上,只需要单击鼠标右键,我们就完成了。

使用 VS Code 部署

扩展程序将使用 Node 模块压缩代码,并将它们全部推送到云端。

虽然此选项在测试我们自己的代码或在处理小型项目时非常有用,但很容易意外覆盖其他人的更改——或者更糟糕的是,覆盖您自己的更改。

不要让朋友右键单击部署!
——每个 DevOps 工程师

一个更健康的选择是设置 GitHub 部署,这可以通过 Azure 门户中的 **部署中心** 选项卡在几个步骤中完成。

GitHub 部署

您准备好创建无服务器 API 了吗?

本文全面介绍了无服务器 API 的世界。但是,这里介绍的内容远不止这些。无服务器使我们能够以创造性的方式解决问题,并且只需支付使用传统平台通常所需成本的一小部分。

Chris 在 CSS-Tricks 上的其他帖子中提到过,但他创建了 这个优秀的网站,您可以在其中了解有关无服务器的更多信息,并找到您可以使用它构建的事物的想法和资源。请务必查看,并告诉我您是否有其他关于无服务器扩展的技巧或建议。