2018 年 11 月

第 33 卷,第 11 期

Azure 服务总线-网站后台处理与 Service Bus 队列

通过Will Stott

我正在处理在英国协作试用版的卵巢癌症屏蔽 (UKCTOCS),当我遇到了问题。执行任务的演示我的试用版的项目开发的逻辑回归分类器,我开始构建简单、 价格较低的 Web 站点无法支持它。但我需要同时支持长时间运行后台处理和启动分类器按需的服务器的站点。

这是两种不同的挑战,和一个我就能够使用主要的 Microsoft 技术来解决。在本文中,我将探讨如何利用 Azure Functions 和服务总线队列,若要启用后台处理,而今后我将深入了解 Azure 容器服务和它如何支持按需预配的服务器资源。该第二篇文章中我将介绍如何以编程方式启动和终止 Azure 容器使用 Azure.Management.Fluent API。

至于基本的 Web 站点和其数据库,它们是作为 Web 应用程序基于 ASP.Net Core 2.1 MVC、 Entity Framework 和 SQL Server 的标准 Azure 上托管的。只是以防您是初次接触这一技术,我已为构建 Web 应用和数据库,以及预配关联的 Azure 资源提供分步说明在 Web 上提供的姐妹篇。你可以找到它在msdn.com/magazine/mt830372。但是,大多数读者应能够构造功能所需的系统概述中所示图 1和 OvaryVis 模型中所示图 2。实际上,项目是只是 Web 窗体捕获三个整数表示的 ovary 进行的度量、 将它们保存在一个数据库记录,并显示结果页,其中显示了分类器是否认为要保持与一致的维度ovary。

系统概述
图 1 系统概述

模型文件夹中的 OvaryVis 类
图 2 在 Models 文件夹中的 OvaryVis 类

重新创建这篇文章中的项目不需要远远超出了基本的 Web 开发技能。我假定已创建了 ASP.NET Core 2.1 MVC Web 应用程序和其 SQL Server 数据库,以及相关联的 Azure 资源 (这中的姊妹篇文章所述)。根据工具,你将需要使用.NET Core 2.1 SDK 和 Web 开发工作负载的 Visual Studio 2017 版本 15.7。您还需要一个 Azure 订阅,但你可以获取所需内容免费如果你是新客户。所有的源代码和创建的解决方案中所示的 Azure 资源的说明图 1都可以在 GitHub 存储库 (bit.ly/2NSiIuh)。

概述

Web 应用,如本文中,为开发想并为每个单独的浏览器返回相应的 HTML 页 (视图),通过确定每种支持多个用户。Web 应用程序,以返回视图所需的时间确定您的网站和它可以支持的并发用户数的响应能力。出于此原因没有按需获取你的网站视图所需的数据时避免长时间运行的进程。服务总线队列是解决此类问题,一个好办法,因为它允许 Web 应用来存储包含处理的详细信息,消息,然后提供即时响应用户 — 即使它是只是说得视图返回的结果更高版本。另一个软件组件会在读取队列并为每个消息执行所需的处理在后台运行。

服务总线的优点是,当用户太多要提交的分类数据,队列只需获取更长。是的这意味着人们可能需要等待更长时间为其结果,但它会确保网站保持响应。它还取消将您的系统通过分隔前台中运行后台处理的组件的 Web 应用程序处理。Azure 服务,如存储队列、 EventHubs 和 EventGrid 执行类似的功能,但面向其他类型的使用情况。您可以找到 Azure 存储队列和服务总线队列上的有用比较bit.ly/2QJVwNp,并比较 Azure 消息传送在服务bit.ly/2DoY1T5

对于我的应用程序,服务总线队列是理想之选。也很低成本且非常易于使用,尤其是现在没有使用 Azure Functions,非常适合执行我后台处理的完美集成。

预配 Azure 资源

Azure Cloud Shell 构建到 Azure 门户网站,并允许预配 Azure 资源使用的一系列简单的命令从 PowerShell 控制台中,在线姊妹篇文章中所述。若要创建的 Azure 服务总线队列和 Function App,必须从 Cloud Shell 中,你的订阅、 资源组和位置的相应值发出命令。这些值将为相同的用于预配你的 Web 应用和数据库。此外需要为唯一服务总线命名空间而不是 MSDNOvaryVisSBQ 就给自己。下面是要使用的命令:

az account set --subscription MsdnOvaryVis
az servicebus namespace create  --name MSDNOvaryVisSBQ --location "WestEurope"
  --resource-group resMSDNOvaryVis --sku Basic
az servicebus queue create --name dimsubmission --namespace-name MSDNOvaryVisSBQ
  --resource-group resMSDNOvaryVis --max-size 1024

您会注意到,我选择了最低服务层 (-sku Basic),它让我能接收 1 百万个消息最大为 256 KB 每个大小为 0.05 美元每月免费使用。除了创建服务总线队列还需要创建函数应用和新的存储帐户,如下所示:

az storage account create -name msdnovaryvisstorageac
  --resource-group resMSDNOvaryVis
  --location "WestEurope" --sku Standard_LRS
az functionapp create --name MSDNOvaryVisFnApp --resource-group resMSDNOvaryVis
  --storage-account msdnovaryvisstorageac --consumption-plan-location westeurope

函数应用服务的名称是可公开访问,因此你将需要输入自己的名称代替 MSDNOvaryVisFnApp。您还应注意,在消耗计划创建过程中创建第一个 Azure 函数结果,即如何将对你计费使用情况,但对于这种性质的项目成本应为几乎任何内容。

Microsoft 的意图是无需显式预配运行它们的服务器创建 Azure Functions — 称为无服务器执行。Microsoft 使用消耗计划向客户仅根据函数便开始执行何种频率收费,它使用执行所需的时间和内存量。是的可以通过选择与 Web 应用,在同一个虚拟机 (VM) 上运行 Azure Functions 应用服务中获取的价格固定的帐单,但不提供使用共享的基础结构的 Web 应用的此选项。因此对于此项目,已更便宜,若要使用消耗计划比若要升级我的 Web 应用服务计划。

现在让我们将应用程序设置应用于新的 Function app,若要完成预配步骤。请注意,您必须连接值 MMM 将替换为一个用于你的服务总线,通过从 Azure 门户中的共享访问策略边栏选项卡复制 RootManageSharedAccessKey 主连接字符串。代码如下:

az functionapp config appsettings set --name MSDNOvaryVisFnApp
  --resource-group resMSDNOvaryVis --settings 'AzureServiceBusQueueName=dimsubmission'
az functionapp config appsettings set --name MSDNOvaryVisFnApp
  --resource-group resMSDNOvaryVis --settings 'AzureWebJobsServiceBus=MMM’
az functionapp config appsettings set --name MSDNOvaryVisFnApp
  --resource-group resMSDNOvaryVis
  --settings 'FUNCTIONS_EXTENSION_VERSION=2.0.12050.0'

当然,在生产环境中将在共享访问策略边栏选项卡与创建其他键的代码只是将发送的权限,以及如何将其连接字符串而不是从根项。您还应注意 Azure 函数执行上公共运行时,如果有更新,除非定义使用 FUNCTIONS_EXTENSION_VERSION 设置的特定版本,否则可能会更改。

随着 Microsoft 更新推出后完成本文中,开发工作导致我突然停止工作的函数,已使用的预发行版运行时,我发现需要指定我运行时得到的。幸运的是,我就能够强制使用特定运行时版本,若要确保我的函数将仅执行的服务器上使用相同的运行时在开发过程中使用的 Azure Functions。我也不会发生这种情况现在,运行时已进入其 beta 版本中,但它是觉得有必要了解此类重大更改。您可以通过访问 Azure 门户中的 Azure 函数应用设置边栏选项卡来查看您自己的函数的运行时版本。

实现 Azure 函数应用服务

执行网站的所需的 Azure 函数将从 Visual Studio Azure 函数应用项目开发后台处理。作为.NET Core 2.1 SDK 和 Web 开发工作负载的一部分在电脑上安装所需的模板。

创建 Azure 函数项目需要一分钟内。第一次打开新建项目对话框 (文件 |新的项目),从视觉对象中的云模板文件夹中选择 Azure Functions C# ,并提供合适的名称,为选定项目 (OvaryVisFnApp),从而确保添加到解决方案。在下一个对话框中提供的类型 (在本例中为空) 的项目并选择您刚的存储帐户创建。新的 OvaryVisFnApp 项目然后会在一组初始的文件以及在解决方案资源管理器中。

现在是确保具有最新版本的项目所需的包的好时机。这些是显示在 NuGet 解决方案窗口中时单击工具 |NuGet 包管理器 |管理解决方案的 NuGet 包。我在本文中,使用以下,但你可能想要尝试为您自己的工作的更高版本:

  • NETStandard.Library 技术 2.0.3 版
  • Microsoft.NET.Sdk.Functions v1.0.19

若要实现 Azure 函数本身,需要将新类添加到 OvaryVisFnApp 项目。再次,Visual Studio 提供了良好的支持。选择的项目中,右键单击添加 |新的 Azure 函数和 OvaryVisSubmitProc.cs 将文件命名。接下来,选择服务总线队列触发器作为要创建使用 AzureWebJobsServiceBus 作为连接字符串设置和 dimsubmission 作为队列名称的 Azure 函数的类型 (请参阅图 3)。这将创建所需的类,该类提供与日志 myQueueItem 应更新。信息参数,按如下所示:

public static class OvaryVisSubmitProc
{
  [FunctionName("OvaryVisSubmitProc")]
  public static async Task Run([ServiceBusTrigger("dimsubmission", 
    Connection = "AzureWebJobsServiceBus")]string myQueueItem,
    TraceWriter log)
  {
    log.Info(myQueueItem));
  }
}

为服务总线队列触发器创建 Azure 函数
图 3: 创建 Azure 函数,为服务总线队列触发器

请注意,AzureWebJobsServiceBus 是您应用到的 Azure 函数应用更早版本,而 dimsubmission 是服务总线队列的实际名称的应用程序设置的名称。

现在你可以发布 Azure 函数项目。这是在很大程度的 Web 应用,我在配套文章中介绍的相同方式: 选择该项目,右键单击发布。您需要选择现有 (而不是新) 的 Azure 函数应用服务,以便可以将你的项目发布到前面创建的 MSDNOvaryVisFnApp 资源。还为您,MSDNOvaryVisFnApp 将显示在下一步对话框中的资源组文件夹中。唯一要担心是 Azure 应用服务将暂停之前从 Visual Studio 中,发布,因为其 Dll 不能被覆盖在使用时通过正在运行的进程。可以通过从 Cloud Shell 中发出以下命令来实现此目的:

az functionapp stop --name MSDNOvaryVisFnApp --resource-group resMSDNOvaryVis

已成功发布后你的项目,你可以启动该服务通过 Cloud Shell 中,从任一发出启动命令,或单击开始按钮中的概述边栏选项卡,如中所示图 4

Azure 函数应用服务概述边栏选项卡
图 4 Azure 函数应用服务概述边栏选项卡

在 Azure 门户中测试你的函数是一个好主意,尤其是如果你已经打开你的 Azure 函数应用服务的概述边栏选项卡。OvaryVisSubmitProc 函数列左侧的边栏选项卡,并选择它会显示一个页面,可用于测试,但可能需要单击右侧竖直菜单以完全显示此页上的测试项目。

测试页不是特别完美,如中所示图 5,但非常有用。需要执行的第一件事是在请求正文框中键入适当的消息 — 例如,"你好 world。 稍后我们会将此字符串为按 OvaryVis 记录 ID 创建的 Web 应用 HomeController.Index (HttpPost) 方法。但是,请注意此消息可以具有更大、 复杂内容的数量,以及元数据来描述有效负载和处理指令 (有关详细的信息,请参阅 Azure 服务消息传送文档,"消息、 有效负载和序列化,"在bit.ly/2OCnIjX)。实际上,具体取决于所选的服务层,可以创建服务总线队列能够容纳最多 80 GB 的最多为 1 MB 大小的每个消息的消息。

Azure Function 应用服务测试边栏选项卡
图 5 Azure 函数应用服务测试边栏选项卡

一旦您在测试页面中创建的请求正文文本,您需要单击运行模拟要发送到你运行的函数的事件。此事件的处理中,会显示日志窗口中,在底部所示图 5。请注意在日志中的数据字段的值在请求正文中是相同的。当函数返回时,它将导致状态 202 已接受的消息,才会出现在测试页。这表示您的函数和 Azure Function App 服务正常运行。

从 Web 应用程序将消息发送到服务总线队列

假定您已经已经构建了类似随附的在线文章中所述的 Web 应用。它只需提供处理的简单窗体和其后续回发到 Web 应用与视图创建的两个控制器方法。在本例中即 HomeController 索引为 HttpGet 和 HttpPost 方法。

在 Web 应用程序项目中的安装 Azure.ServiceBus 包将允许它用于将事件消息发送到服务总线队列所需的组件。在 Visual Studio NuGet 包管理器控制台,可添加此类包,如 Microsoft 文档中所述bit.ly/2QIiOmZ。应从工具菜单打开控制台 (工具 |NuGet 包管理器 |包管理器控制台),然后发出以下命令:

Install-Package Microsoft.Azure.ServiceBus -Version 3.1.0 -Project OvaryVisWebApp

这还会安装 WebJobs.Extensions.ServiceBus (3.0.0-beta8) 等依赖项,因此应现可通过将消息发送到服务总线队列所需的一切。

现在它是时间来设置服务总线队列客户端。这需要将您的队列和其连接的名称传递给它。我发现更方便地将这些值保留为应用程序配置设置,因此我初始化服务总线队列客户端在 Web 应用的启动类构造函数,如中所示图 6。然后我使用 Cloud Shell 提供的以下命令,再次使用相同的服务总线连接字符串以使用以前在设置 Azure 函数应用时替换 MMM:

az webapp config appsettings set --name MSDNOvaryVisWebApp
  --resource-group resMSDNOvaryVis --settings 'OvaryVisServiceBus:QueueName=dimsubmission'
az webapp config appsettings set --name MSDNOvaryVisWebApp
  --resource-group resMSDNOvaryVis --settings 'OvaryVisServiceBus:Connection=MMM'

在中看到图 6队列客户端会保留在最初是一个静态变量设置为 null,然后由 SetupServiceBus 方法初始化。锁可以使用此方法线程安全的当测试该 _queueClient 为 null 时可避免正在构造的多个客户端。这是因为 Startup 类仅初始化一次,但是它的确允许 SetupServiceBus 方法复制到 Azure 函数将在下一篇文章中,将需要此类保护的位置中创建 Web 应用的操作有点多余。

图 6 设置 Web 应用的 Startup 类中的服务总线队列客户端

public class Startup
{
  private static IQueueClient _queueClient = null;
  private static readonly object _accesslock = new object();
  private static void SetupServiceBus(string connection, string queueName)
  {
    lock (_accesslock)
    {
      if (_queueClient == null)
        _queueClient = new QueueClient(connection, queueName);
    }
  }
  public static IQueueClient GetQueueClient() { return _queueClient; }
  public Startup(IConfiguration configuration)
  {
    Configuration = configuration;
    SetupServiceBus(Configuration["OvaryVisServiceBus:Connection"],
      Configuration["OvaryVisServiceBus:QueueName"]);
  }
}

接下来,我需要更新 HomeController 类的 HttpPost 索引方法,使其代码类似的中所示图 7。这可以与您未使用的 Azure 函数应用服务测试页相同的方式将事件消息发送到服务总线队列。但是,还需要添加以下 using 语句的文件的顶部:

using Microsoft.Azure.ServiceBus;
using System.Net.Http;

你将看到从图 7 ,最初在保存后的窗体数据作为 OvaryVis 表中的记录,您创建消息对象有其正文设置为记录的 ID 字符串。生成并运行你的 Web 应用本地 (Ctrl + F5) 后,结果页将显示创建作业为索引页的窗体提交后其 StatusMsg。此外,您将看到在门户的 Azure Function 日志中显示的记录 ID 值。这表示不具有仅成功中将记录保存到数据库,但也已传输到 Azure 函数及其 ID。您需要做现在是更新你的 Azure 函数,以便它使用此 ID 来从 Azure 函数中的数据库中读取相应的记录。

图 7 HomeController 索引方法

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Index([Bind("Id,D1mm,D2mm,D3mm")] OvaryVis form)
{
  OvaryVis record = new OvaryVis
  {
    D1mm = form.D1mm,  // First ovary dimension
    D2mm = form.D2mm,  // Second ovary dimension
    D3mm = form.D3mm,  // Third ovary dimension
    JobSubmitted = DateTime.UtcNow,
    ResultVis = -1,    // Result code: -1 = not yet processed 
    StatusMsg = String.Format("Job Created at {0}",
      DateTime.UtcNow.ToString("HH:mm:ss"))
  };
                       // Add record to database
  _context.Add(record);
  await _context.SaveChangesAsync();
                       // Send message with ID value to Service Bus Queue
  var message = new Message(Encoding.UTF8.GetBytes(record.Id));
  await Startup.GetQueueClient().SendAsync(message);
                       // Call Result method to return Result page
  return RedirectToAction("Result", new { id = record.Id });
}

将实体框架添加到 Azure 函数

实体框架提供一种使用服务总线事件消息中传递的 ID 值从数据库检索 OvaryVis 记录的简单方法。实现涉及将实体框架包添加到你的 Azure 函数项目,使用同一个包管理器控制台用来安装服务总线包,如下所示:

Install-Package Microsoft.EntityFrameworkCore.SqlServer
  -Project OvaryVisFnApp –version 2.1.2

此外需要提供连接字符串,以便你的 Azure 函数可以访问 Azure SQL 数据库。这是 Web 应用使用相同的连接字符串,它是从 Azure 函数的应用程序设置,可从其概述边栏选项卡中显示在门户中访问组最佳图 4。您需要向下滚动到连接字符串部分,然后单击添加新的连接字符串,而使用 DefaultConnection 的名称,键入 SQLAzure 和下面的值,但具有自己的数据库服务器和其用户名和密码的名称:

Server=tcp:msdnovaryvisdbsvr.database.windows.net,1433;Database=Msdn.OvaryVisDb;
  User  ID=XXX;Password=YYY;Encrypt=true;Connection Timeout=30;

添加后的字符串,别忘了向下滚动到顶部,单击保存。

您需要执行的操作从 Azure 函数访问该数据库的最后一步是将 DataContext 类和 OvaryVis model 类从 Web 应用项目复制到函数应用项目。将需要将这两个文件的命名空间更改为 OvaryVisFnApp,但否则您应能够重新生成你的解决方案没有任何问题 (按 F7)。最后,您需要实现以下方法,然后从 OvaryVisSubmitProc 函数的 Run 方法中调用它。代码如下:

private static async Task<string> FormSubmittedProc(IConfigurationRoot config,
  ApplicationDbContext dbContext, string queueItem)
{
  string rc = "FormSubmittedProc: ";
  var record = await dbContext.OvaryVis.SingleOrDefaultAsync(a => a.Id == queueItem);
  if (record == null)
    rc += string.Format("record not found: Id={0}", msg.Id);
  else
    rc += string.Format("record Id={0} found, ", record.Id);
  return rc;
}

通过 Azure 函数运行方法传递给 FormSubmittedProc 的配置参数使用 ConfigurationBuilder 获得并提供对其应用程序设置,包括数据库的连接字符串的访问。这再用于创建窗体的第二个参数的 ApplicationDbContext 对象。可以在示例代码下载中找到的确切实现详细信息。 

您已完成这篇文章的开发工作,因此现在是时候重新生成 Visual Studio 解决方案,发布其 Azure 函数应用项目,并为其提供快速执行冒烟测试。如果打开 Azure 函数应用服务测试边栏选项卡中,我前面所述 (图 5),你应看到 ovary 记录在日志中时显示从您的网站,将提交的输入窗体中所示的 Id图 8.这表示 Azure 函数已成功找到对应于从 Web 应用,系统中所示的所有部件发送的服务总线消息中包含的 Id 值的记录图 1按预期方式工作。

输入窗体和结果页的网站
图 8 网站输入窗体和结果页

总结

值得索多么 Azure 使它来开发的可靠的后台处理的任何网站的机制。与生产站点,你想要提供某种方式来自动更新结果页中,而不是依赖于用户按 F4 — 但这不是太难实现使用 SignalR 或类似机制。但是,Azure 函数应用服务和服务总线队列的预配使用从 Azure 门户中,只需几个命令已实现并与 Web 应用的主页控制器的集成所需仅几行代码。

非常少具有关联一个应用服务计划和数据库服务器的小型每月费用之外实现这种的成本。如果运行的应用服务计划在免费层上,您应能够以较少的几个杯咖啡一个月的成本比托管测试 Web 站点。这使得它的真正有吸引力解决方案。

在下一篇文章中,我将探讨如何通过提交中的记录的 Docker 映像中预配的服务器上运行的分类器的维度值来扩展 Azure 函数。将还将了解如何为自动启动此服务器,当流量到达您的网站,并停止流量,实现仅对使用的实际时间计费的按需服务器后停止它。


Dr.Will Stott拥有超过 25 年的工作范围广泛的英国和欧洲,包括 IBM 公司的合同工/顾问经验 Cap Gemini、 Logica CMG 和 Accenture。但是,在过去 10 年里博士的大部分时间花费了进行研究在 University 大学伦敦 (UCL) 上卵巢癌症屏蔽。ScriptoStott 曾在很多会议上发表在英国和国际上。他也是在各种日志,以及本书中发布的文章的作者"Visual Studio Team System:更好的软件开发的敏捷团队"(Addison-wesley Professional,2007年)。

衷心感谢以下 Microsoft 技术专家对本文的审阅:David Barkol


在 MSDN 杂志论坛讨论这篇文章