Azure-开发者实用指南-全-
Azure 开发者实用指南(全)
原文:
annas-archive.org/md5/a9409a391ef3d7d0acb0e0c2da2a76f3译者:飞龙
前言
云技术目前是 IT 行业内最受欢迎的趋势之一。每天都有新公司开始其云计算之旅,逐渐远离传统的本地部署方式,而后者往往妨碍快速开发和扩展运营。由于现代世界要求我们迅速适应不断变化的期望和动态工作负载,了解如何在云中开发应用变得越来越重要。
你手中拿着的这本书将引导你了解微软 Azure 这一流行云服务平台的不同功能和服务。我们将主要关注平台即服务(PaaS)组件,这些组件让你跳过繁琐的基础设施配置过程,直接专注于配置各种功能和部署代码,从而让你的应用具备可扩展性、高可用性和弹性。本书每一章的目标是帮助你更好地理解多种云模式、连接和集成,使你能迅速开始自己的项目,并了解在特定架构中应该使用哪个 Azure 服务。
本书适合谁阅读
本书旨在作为一次穿越不同 Azure PaaS 服务的旅程。它涵盖了多种基本和中级概念,这些概念对于基于微软云平台开发可靠且强大的解决方案至关重要。主要读者是刚开始接触 Azure 或有意开始使用 Azure 的开发人员和 IT 专业人士,书中的详细指南将帮助他们拓展云计算技能。
如何充分利用本书
激活你的 Azure 订阅(无论是试用版、MSDN 订阅功能,还是商业版)。
由于大多数示例基于 .NET 技术栈,了解一些相关技术将让你更加轻松,尽管在可能的情况下,其他技术栈也有所涉及,同时,基本的 HTTP 概念(例如协议或通信模型)也将是一个优势。对容器相关主题的基础理解同样有帮助。更高级的主题和详细的操作说明通常会出现在 进一步阅读 部分。确保在完成本书的练习后覆盖所有内容。
下载示例代码文件
你可以从你的 www.packt.com 账户下载本书的示例代码文件。如果你是在其他地方购买本书,可以访问 www.packt.com/support 注册,并直接通过电子邮件获取文件。
你可以按照以下步骤下载代码文件:
-
在 www.packt.com 登录或注册。
-
选择“SUPPORT”标签。
-
点击“Code Downloads & Errata”。
-
在搜索框中输入书名,并按照屏幕上的指示操作。
下载文件后,请确保使用最新版本解压或提取文件夹:
-
适用于 Windows 的 WinRAR/7-Zip
-
适用于 Mac 的 Zipeg/iZip/UnRarX
-
适用于 Linux 的 7-Zip/PeaZip
本书的代码包也托管在 GitHub 上,链接为github.com/PacktPublishing/Hands-On-Azure-for-Developers。如果代码有更新,它将会在现有的 GitHub 仓库中更新。
我们还有其他来自丰富书籍和视频目录的代码包,详情请访问github.com/PacktPublishing/。快来看看吧!
下载彩色图像
我们还提供了一个 PDF 文件,包含本书中截图/图表的彩色图像。你可以在此下载:www.packtpub.com/sites/default/files/downloads/9781789340624_ColorImages.pdf.
使用的规范
本书中使用了多种文本规范。
CodeInText:表示文本中的代码词汇、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 用户名。举个例子:“在应用程序的Main()方法中,添加以下代码。”
代码块的格式如下:
using System;
namespace MyFirstWebJob
{
class Program
{
当我们希望你注意代码块中特定部分时,相关行或项目会以粗体显示:
var config = new JobHostConfiguration();
config.UseTimers();
var host = new JobHost(config);
所有命令行输入或输出均按以下方式书写:
docker images
粗体:表示新术语、重要词汇或屏幕上显示的词汇。例如,菜单或对话框中的词汇在文本中会以这种方式出现。这里有一个例子:“为此,你需要右键点击 Azure App Service 的 WebJobs 部分,并选择在门户中打开。”
警告或重要提示以这种方式显示。
提示和技巧以这种方式出现。
联系我们
我们始终欢迎读者的反馈。
一般反馈:如果你对本书的任何方面有疑问,请在邮件主题中注明书名,并通过customercare@packtpub.com联系我们。
勘误:尽管我们已尽最大努力确保内容的准确性,但错误难免。如果你发现本书中的错误,我们将非常感激你报告给我们。请访问www.packt.com/submit-errata,选择你的书籍,点击勘误提交表单链接,并输入详细信息。
盗版:如果你在互联网上发现我们作品的任何非法复制品,我们将非常感激你提供该位置地址或网站名称。请通过copyright@packt.com与我们联系,并附上相关资料的链接。
如果你有兴趣成为作者:如果你在某个主题上有专业知识,并且有兴趣撰写或参与编写书籍,请访问authors.packtpub.com。
评价
请留下评论。当你阅读并使用完本书后,为什么不在你购买书籍的网站上留下评论呢?潜在的读者可以看到并利用你的客观评价做出购买决定,我们在 Packt 也能了解你对我们产品的看法,而我们的作者则能看到你对他们书籍的反馈。谢谢!
欲了解有关 Packt 的更多信息,请访问 packt.com。
第一章:Azure 应用服务
Azure 应用服务是 Azure 云中最大、最常用的服务之一。它允许轻松开发带有多种功能的 Web 应用程序(例如支持不同平台,包括 .NET、PHP 和 Java),手动和自动扩展,以及不同的性能选项。它是一个通用平台和运行时,支持 WebJobs 和 Azure Functions 等其他服务。
本章内容包括:
-
创建并部署 Azure 应用服务
-
与不同操作系统和平台的兼容性
-
选择合适的 App Service Plan 以及它们的功能
-
使用不同的安全提供商来确保应用服务的安全
-
诊断和监控您的应用程序
技术要求
要执行本章的练习,您需要以下内容:
-
访问 Azure 订阅
-
安装了 Azure 开发工作负载的 Visual Studio 2017
-
安装 Visual Studio Code(可通过
code.visualstudio.com/下载)
创建并部署 Azure 应用服务
要开始使用 Azure 应用服务,您需要学习如何创建该服务并部署代码。您将看到 Azure 提供了许多不同的方式来完成此操作,每条路径的难易程度取决于您的当前需求和应用程序的具体要求。然而,云和平台即服务(PaaS)的强大之处在于提供了一个直接且直观的过程,用于配置系统的新组件。
使用 Azure 门户创建 Azure 应用服务
首先,我将向您展示如何使用 Azure 门户创建您的第一个 Web 应用。事实上,您只需要鼠标和键盘(因为每个应用都需要一个名称)——这里不需要外部硬件或详细的配置信息,因为 Azure 会为您完成所有操作。
从可用服务中选择 Azure Web App
要在 Azure 门户中创建 Azure 应用服务,首先需要在可用服务列表中找到它。最简单的方法是点击 + 创建资源 按钮并搜索 Web App:

您也可以选择点击 App Services,而不是使用 + 创建资源 按钮——这会将您带到不同的视图,在那里您可以通过点击 + 添加 按钮来创建一个应用服务。这适用于所有最流行的 Azure 服务,如 SQL 数据库、虚拟机和存储账户。
如您所见,Azure 门户会尽量帮助您找到与搜索字符串最相关的服务。当您点击 Web App 项时,会看到另一个屏幕,其中包含多个类似项,这些项在某种程度上都与您正在搜索的内容相关:

在本次练习中,选择 Web App*,然后点击屏幕右下方的 创建 按钮。
刚开始时,选择最通用的选项来选择服务通常会更容易。当你积累更多经验并且对可用的服务更熟悉时,你会发现 Azure 提供了许多有用的预配置设置(例如集成 Web 应用和 SQL 数据库),这些可以用来缩短开发时间,并在一个地方配置所有服务。
配置 Azure Web 应用
当你点击“创建”按钮时,你将看到一个屏幕,你可以在其中输入创建 Web 应用所需的所有信息。所有必填字段都标有*(星号)符号:

如你所见,在创建 Web 应用时,我们几乎需要填写所有字段(关于“应用程序洞察”单选按钮有一个小例外,我们将在接下来的章节中讲解)。让我们单独关注每个字段,以便更好地理解它们是如何工作的:
-
应用名称:此字段表示你应用的域名。选择一个唯一且有效的名称很重要,因为它以后不能更改。请注意,如果需要,你可以轻松地绑定自己的自定义域名。
-
订阅:如果你拥有多个订阅,你将有机会选择适合此特定资源的订阅。通过这种方式,你将能够区分不同项目之间的费用。
-
资源组:在 Azure 中,每个资源必须属于一个逻辑容器,称为资源组。这本身不会产生额外的费用,所以你不必担心创建多个资源组的问题。
-
操作系统:目前,在 Azure 中,你可以使用不同的操作系统(Windows、Linux 或 Docker 容器)来创建 Web 应用。这个选择会影响成本和性能,所以确保你选择了适合你需求的操作系统。
-
应用服务计划/位置:Azure 中的应用服务直接与应用服务计划相关联,应用服务计划根据你选择的选项提供不同的功能和性能。
通常,利用资源组并使用特定的过滤器(如资源的生命周期或给定的环境,例如生产、暂存或测试环境)来分隔资源是一个好习惯。资源组能让你更好地控制已部署的服务,并对谁可以访问某个资源进行更细粒度的控制。
由于你刚开始使用 Azure,你可能还没有创建任何应用服务计划。由于我们不能在没有应用服务计划的情况下创建应用服务,我们现在来进行排序。
创建应用服务计划
当你点击“应用服务计划/位置”时,你将看到一个屏幕,其中有一个“+ 创建新计划”按钮,允许你创建一个新的应用服务计划。它应该是这样的:

如你所见,我们需要输入三个字段:
-
应用服务计划:这是你的应用服务计划的名称,必须在一个资源组内唯一。
-
位置:通过这个设置,我们可以将我们的应用服务计划定位在特定区域。这有时意味着不同的功能可用。
-
定价层级:当你点击此项时,你将看到另一个屏幕,展示不同层级的可用功能。这个选择在功能上非常重要,并且大多数情况下会依赖于你规划的环境特性(例如开发/测试环境、生产应用程序,是否需要部署槽等):

如你在前面的截图中看到的,我们有三种应用服务计划类别:
-
开发/测试:这一层级包括 F、D 和 B 层(分别代表免费、共享和基础)。它们设计用于简单的开发/测试场景以及不需要自动缩放或备份等功能的轻量级网页应用。
-
生产模式:这个模式提供强大的机器和先进的功能,适用于许多实际场景,例如 API、电子商务和热门门户网站。
-
隔离模式:这个模式使用与生产层相同的硬件,但提供更多的功能和可能性,使你的网页应用能够隔离于外部访问。这是最昂贵的类别,但在创建无法公开访问的系统时可能会非常有用。
重要的是要记住,F 和 D 层级每天的计算时间是有限的。这意味着一旦超过了处理时间的限制(F 层为 60 分钟,D 层为 240 分钟),你的应用将变得不可用,并会被暂停,直到第二天。
为了这个练习,我建议选择开发/测试类别中的任何一个层级。选择好你满意的选项后,你可以点击“应用”按钮。例如,我的配置如下所示:

记住,你可以随时升级(或扩展)你的应用服务计划实例,例如,当你需要某个特定功能或应用程序的受欢迎程度增长时。这是云计算相比于本地部署的一大优势,在本地部署中,你必须自己购买并设置新机器。
现在,你可以点击“确定”,你将返回到 Web Appblade 页面,在这里你可以输入缺失的字段。这里,你可以看到我网页应用的完整配置:

现在只需点击创建按钮,等待几秒钟即可创建一个新资源。在此期间,Azure 会验证模板和参数,并协调多个底层控制器来创建服务。一旦新资源创建完成,您应该会看到通知,并能在您的资源中看到它。为了快速验证,点击左侧的所有资源按钮,并使用例如您创建的应用服务的名称进行筛选:

使用 Visual Studio 创建 Azure 应用服务
如果您不想使用Azure 门户来创建 Web 应用程序,您可以使用 Microsoft Visual Studio,它内置了对许多不同 Azure 服务的集成。
本练习是使用已安装 Azure 工作负载的 Microsoft Visual Studio 2017(15.6.4)创建的。如果您想配置您的实例并确保一切设置正确,请按照 docs.microsoft.com/en-us/dotnet/azure/dotnet-tools?view=azure-dotnet&tabs=windows 上的简短教程操作。
在 Visual Studio 中,点击文件 | 新建 | 项目。这将显示一个新建项目窗口,您可以在其中找到许多用于开始新应用程序的不同模板。因为我们感兴趣的是云项目,所以我们从云类别开始:

由于我们在本章中处理的是应用服务,我们感兴趣的模板是ASP.NET Web 应用程序(.NET Framework)。另一个有效的选项是ASP.NET Core Web 应用程序——如果您足够自信可以使用最新的 .NET 版本,可以随意选择,因为我们会涵盖这两种场景。当您对选择满意时,点击确定。
下一步是选择合适的模板。在这里,您有多个选项,例如:
-
空模板:最简单的选项,允许您完全控制已安装的包和整体结构
-
Web Forms:用于构建 Web 应用程序的最古老框架,使用许多内置控件并具有数据访问功能
-
MVC:一个众所周知的模型-视图-控制器(MVC)架构,它取代了Web Forms
-
Web API:用于使用 .NET 编程栈创建 RESTful HTTP 服务的模板
-
单页面应用程序:此模板带有许多用于构建客户端交互的附加工具
以上所有选项您应该都比较熟悉。不过,感谢安装了 Azure 工具集,您应该能够访问另外两个模板:
-
Azure API 应用程序:提供与不同 Azure 服务的附加集成,如 Azure AD、API 管理和逻辑应用
-
Azure 移动应用:用于构建移动后端的模板
不过,我们将在本章的后续部分讨论这两者。现在,为了继续操作,让我们选择 MVC,因为这是这里列出的所有模板中最常见且最简单的。使用该模板的默认选项并点击 确定(OK)。
你可能已经注意到一个附加按钮,我之前没有描述过,更改身份验证(Change Authentication)。它允许选择用于验证访问你 Web 应用的方式。我们将在接下来的 Azure Web 应用安全性部分中讲解该功能。
几秒钟后,Visual Studio 应该会基于所选模板生成一个项目。我相信它看起来对你来说很熟悉,因为它和从 MVC 模板创建的传统 Web 应用并没有太大区别。我敢肯定,你迫不及待地想看看它是否能正常运行——不要再等了,按下 F5 启动应用。
你应该会看到一个类似于我屏幕上的画面:

如你所见,它是一个传统项目开始时会看到的通用模板。问题是,我们如何将其部署到 Azure 以使我们的站点能够在云中运行?
现在,让我们停止本地运行的站点,回到 Visual Studio。在项目图标上右击,你会看到一个上下文菜单。在多个不同的选项中,你可以点击 发布(Publish...)。

由于这是一个云项目,你会看到除了 IIS、FTP 和 文件夹之外的额外选项:
-
App Service:用于将应用程序部署到 PaaS 服务。
-
Azure 虚拟机:用于将应用程序部署到你已配置的虚拟机。
由于本书的主题是 PaaS 服务,我们将不讨论如何将 Web 应用部署到虚拟机。不过,如果你有兴趣这么做,相关的操作指南可以在 github.com/aspnet/Tooling/blob/AspNetVMs/docs/create-asp-net-vm-with-webdeploy.md 上找到。
目前,让我们选择 App Service。你应该能看到两个不同的选项:
-
创建新应用(Create new):用于将应用程序部署到新创建的 App Service。
-
选择现有: 此选项仅在你已经部署了站点时有效。
因为我们刚刚开始,所以我们关注的选项是 创建新应用(Create new)。点击 发布(Publish...)后,你将看到另一个屏幕,在那里你可以输入所有必填参数。如果你阅读了前一节关于如何使用 Azure 门户创建 App Service 的内容,那么某些字段应该是熟悉的——实际上,你正在做的事情和在门户中操作是一样的。如果你跳过了这一节,强烈建议你返回并阅读相关描述。配置完我的 Web 应用后,我的屏幕如下所示:

请记住,你可以直接从前面的页面创建资源组和 App Service 计划。如果你不喜欢那里列出的选项,你可以点击 新建... 按钮,这将引导你完成创建新资源的过程。这也是像 Visual Studio 这样的工具的另一大优势,因为你无需离开编程环境就能使用 Azure。
如果你对当前配置满意,最后一步就是点击 创建 按钮,并稍等片刻,直到应用部署完成。此外,Visual Studio 会准备一个发布配置文件,你可以随时重复使用它。我们会查看它,因为它将在本章的下一部分帮助我们。部署完成后,你应该会看到你的 Web 应用自动在默认浏览器中打开:

恭喜!你刚刚创建并部署了你的第一个 App Service。如果你查看 URL,你会看到它包含了你在 Visual Studio 向导中设置的名称。所有 Azure 中的 Web 应用都可以通过以下 URL 格式访问:
http(s)://{appservicename}.azurewebsites.net
这也解释了为什么名称必须是唯一的:由于默认情况下,所有作为 Azure Web 应用托管的 Web 应用都是公开可用的,你必须选择一个在其他 URL 中尚未使用的名称。在下一部分,我们将尝试使用 FTP 部署我们的应用,作为使用 Visual Studio 的替代方法。
使用 FTP 部署 Azure 应用服务
使用 Visual Studio 进行部署对于测试和开发来说是一个不错的选择,但肯定不能用于生产环境的部署。上传文件到 App Service 最简单的方式是使用 FTP,且该 Azure 资源已集成 FTP。
使用用户级凭据部署 Azure 应用服务
当你进入 Azure 门户并选择之前创建的 Web 应用时,查看 概述 页面,你将看到许多关于此服务的信息,如当前状态、位置和 URL。在这些信息中,有一个 FTP 部分,包含三个不同的参数:
-
FTP/部署用户名:用于通过 FTP 客户端连接到 Web 应用时所使用的用户名
-
FTP 主机名:用于创建 FTP 连接时应使用的主机
-
FTPS 主机名:与之前相同的主机,但允许安全连接
我的 App Service 目前看起来是这样的:

所有 FTP 信息可以在整个页面的右下角找到。现在我们需要的是用于连接服务器的 FTP 客户端。对于选择这类应用程序,我没有特别的推荐。就个人而言,我偏好使用 FileZilla 来管理我的 FTP 连接和文件传输。然而,你可以使用任何你喜欢的客户端,因为它们在功能上都非常相似。在我们开始将文件上传到服务器之前,还需要一件事,那就是为用户设置密码。要生成新密码,前往 Deployment credentialsblade,位置在 App Servicefeatures 的左侧 DEPLOYMENT 部分:

在这里,你可以设置两个字段:
-
FTP 用户的用户名
-
该用户的密码
你可能会想知道这如何与之前在 Overview 屏幕上找到的用户名相关。区别其实很简单:使用 Deployment credentials,你是在创建一个新的用户,该用户将用于你 Microsoft Azure 帐户关联的所有应用程序和订阅。这意味着你将能够使用相同的凭证来部署每一个 App Service。对于你将遇到的每种场景,这并不是理想的选择,但对于本练习的目的,我们设定一个用户并用于部署。在本节的下一部分,我将向你展示如何从 Visual Studio 生成的发布配置文件中获取凭证。一旦你输入用户名和密码,点击 Save。现在,我们可以打开 FTP 客户端,并使用这些凭证来建立连接。在这里,你可以看到我的配置(请注意,用户名必须采用以下格式:<appservicename>\<ftpusername>):

一旦你连接到服务器,你将看到一个可用目录的列表。最顶层包含以下内容:
-
LogFiles: 包含有关运行中的 App Service 的诊断信息文件 -
site: 你的 Web 应用工作文件存储在这里
我们将在本章的后续部分介绍 LogFiles,它涉及监控和诊断应用程序。目前,我们关注的是 site 文件夹。当你进入该文件夹时,你会看到其他目录:deployments、locks 和 wwwroot。对于那些曾经使用过 IIS 的朋友来说,最后一个目录应该比较熟悉,因为它是存放 Web 应用程序的最常见文件夹名称。实际上,这就是你的 App Service 的工作目录,所有必要的文件都应上传到此处。这里是一个空的 Web 应用程序的完整目录结构:

现在,你已经了解了 App Service 的结构,你可以部署你的文件并查看它是否能正常工作。如果你愿意,你可以复用之前练习中的项目,或者上传一个全新的网站。
如果你想重新使用文件,你可以再次发布项目,但这次,不是直接发布到 Azure,而是创建一个新的发布配置并使用文件夹作为目标。Visual Studio 完成创建包之后,只需使用 FTP 客户端将文件从输出目录复制到 FTP 位置。
这是我之前项目中的文件,已上传到我的 FTP 服务器:

现在,当我访问我的网站 URL 时,我会看到一个正在运行的应用程序:

很好——你刚刚学会了如何利用应用服务的 FTP 功能,从任何位置和环境部署应用程序。然而,正如我之前提到的,我们正在使用用户级凭据,这些凭据对于你在订阅中部署的所有 web 应用程序都是相同的。那么,如何使用应用级用户名和密码来实现相同的结果呢?
使用应用级凭据部署 Azure App Service
使用应用级凭据部署应用程序有两种方式:
-
从 Azure 门户下载它们
-
在 Visual Studio 中配置 WebDeploy
从 Azure 门户下载应用级凭据
当你进入你的应用服务并点击概览面板时,你将看到顶部有一个获取发布配置按钮,如下截图所示:

现在,当你点击它时,浏览器将下载一个.PublishProfile文件。请打开它查看其内容。这是我从我的 web 应用中获取的一个示例文件:
<?xml version="1.0" encoding="UTF-8"?>
<publishData>
<publishProfile profileName="cloudcomrade01 - Web Deploy" publishMethod="MSDeploy" publishUrl="cloudcomrade01.scm.azurewebsites.net:443" msdeploySite="cloudcomrade01" userName="$cloudcomrade01" userPWD="LEebknaDdg0KS6SgScLuXlwtzxvwYway7ssoKxCSkCLi6Gw0HRyt2iEGMLbP" destinationAppUrl="http://cloudcomrade01.azurewebsites.net" SQLServerDBConnectionString="" mySQLDBConnectionString="" hostingProviderForumLink="" controlPanelLink="http://windows.azure.com" webSystem="WebSites">
<databases />
</publishProfile>
<publishProfile profileName="cloudcomrade01 - FTP" publishMethod="FTP" publishUrl="ftp://waws-prod-am2-197.ftp.azurewebsites.windows.net/site/wwwroot" ftpPassiveMode="True" userName="cloudcomrade01\$cloudcomrade01" userPWD="LEebknaDdg0KS6SgScLuXlwtzxvwYway7ssoKxCSkCLi6Gw0HRyt2iEGMLbP" destinationAppUrl="http://cloudcomrade01.azurewebsites.net" SQLServerDBConnectionString="" mySQLDBConnectionString="" hostingProviderForumLink="" controlPanelLink="http://windows.azure.com" webSystem="WebSites">
<databases />
</publishProfile>
</publishData>
如你所见,这是一个简单的 XML 文件,包含大量有用的信息。我们目前关注的是userName和userPWD属性。这些就是我们一直在寻找的——在应用服务创建时自动生成的应用级凭据。你可以使用这些凭据,而不是我们之前创建的用户级凭据。
在 Visual Studio 中配置 WebDeploy
要查看如何在 Visual Studio 中配置WebDeploy,请参考从使用 Visual Studio 创建 Azure App Service部分开始的所有步骤,这一部分关于从这个 IDE 发布应用程序。如果你已经完成了这些步骤,再次返回到发布屏幕。
如果你想从前一部分导入发布配置文件,则在发布屏幕上,点击新建配置...按钮,然后选择导入配置...选项,这样你就可以选择先前生成的配置文件。
当你点击配置按钮时,你将看到另一个窗口,里面包含了你部署的完整配置:

如你所见,它包含了一组完全不同的信息,且不反映你已配置的用户级设置。
请记住用户级凭证和应用级凭证之间的区别。注意,多个具有访问权限的用户可以分别使用自己的用户级凭证。此外,要使用应用级凭证,你至少需要在特定的应用服务上具有贡献者角色。如果你只有阅读者角色,则无法访问这些凭证。
在应用级凭证和用户级凭证之间的选择完全取决于你交付应用程序的过程。在大多数情况下,你无需检查和设置这些凭证,因为像 Visual Studio 或 Azure DevOps(前身为 Visual Studio Team Services)这样的工具会隐式地获取和使用它们。应用级凭证通常只有在我们需要手动部署时才会使用。
你可以随时从概述面板重置应用级凭证(例如,如果当前凭证因为某种安全问题需要被撤销)。在获取发布配置文件按钮旁边,你可以找到重置发布配置文件选项,它会设置一个新的用户名和密码。
使用 Visual Studio Code 创建 Azure 应用服务
Microsoft Visual Studio 不是唯一可以与 Azure 应用服务一起使用的 IDE。由于此 Azure 服务支持不同的技术栈,包括 .NET、JS、PHP、Java 等,你可以轻松利用它的能力来托管不同的使用不同运行时的网站。例如,假设我们有以下 PHP 代码来显示一个 Hello World 消息:
<?php
echo('Hello world from Azure App Service - PHP here!');
?>
这样一个简单的 PHP 应用程序可以在任何支持 PHP 语言的 IDE 中轻松创建。为了完成本次练习,我选择了 Visual Studio Code 这一开源编辑器,因为它可以通过许多不同的插件轻松扩展。为了简化操作,你可以安装以下扩展:

安装此插件后,你将能够轻松地从 IDE 内部部署应用程序,无需前往门户或使用其他方法。要将应用程序推送到云端,你需要进入AZURE选项卡并找到APP SERVICE部分。
在首次使用这些扩展时,你可能需要进行身份验证。请按照显示的指示操作,Visual Studio Code 将连接到你的订阅。
在我们部署简单的 PHP 应用程序之前,必须先创建一个 Azure 应用服务。为此,你需要点击 创建新 Web 应用...按钮:

向导与 Microsoft Visual Studio 略有不同,它更像是命令行,要求你依次提供所有字段和信息。在 Visual Studio Code 中,你需要输入以下内容:
-
Azure 应用服务名称
-
你选择的操作系统
-
运行时版本
在这个特定的示例中,我指定了以下内容:
-
handsonazure-euw-appservice -
Linux
-
PHP 7.2
一旦配置完成,Visual Studio Code 会询问你是否要部署应用程序。选择“确定”,然后选择要部署到的文件夹:

一切设置就绪后,你将看到一个通知,告知你现在可以浏览该网站:

当你点击“浏览网站”按钮时,你将被转发到刚刚部署的 Web 应用程序。请注意,这个扩展允许你直接在 IDE 中管理该服务,并提供不同的功能,包括应用设置、部署槽和 Azure WebJobs(后者在第二章,Azure WebJobs 中有描述)。在这里,你可以看到托管在 Azure 中的实际示例:

这里重要的是,通过使用相同的路径,你将能够在不同的 Azure 应用服务中托管多种不同的运行时。无论是 Java 应用程序、Python 脚本,还是 Node.js 后端,它们都被支持,并且可以通过像 Visual Studio Code 这样的 IDE 容易开发。
在使用带有扩展的 Visual Studio Code 时,你可能希望对资源的创建拥有更多控制。要启用“高级创建”,请进入 文件 | 首选项 | 设置 窗口,找到 扩展 部分,然后点击“应用服务:高级创建”复选框。
使用不同操作系统和平台
目前,应用服务在选择操作系统、运行时和平台时支持几种不同的配置。以下是使用应用服务运行网站的一些可能选项:
-
.NET Core
-
.NET Framework
-
Node.js
-
PHP
-
Java
-
Python
-
静态 HTML 网站
此外,你还可以选择平台(32 位或 64 位)、HTTP 版本(1.1 或 2.0)以及底层操作系统(Windows、Linux 或容器)。让我们从为应用程序选择合适的操作系统开始。
选择操作系统
要选择一个操作系统来运行你的 Web 应用程序,我们必须在 Azure 中创建一个新应用。目前,在创建应用服务后,无法更改此设置。要创建一个新网站,进入 Azure 门户并点击“+ 创建资源”。在新屏幕上,搜索Web App并选择第一个显示的项目(或者直接返回到 从可用服务中选择 Azure Web App 部分,并执行那里提到的所有步骤)。
在“Web 应用 - 创建”屏幕上,你会看到一个操作系统(OS)字段。你将有三个选项:
-
Windows: 适用于 .NET 应用程序的最常见选项,适合运行 .NET Framework、Java、Node.js 或 PHP 网站。
-
Linux:如果您有用 .NET Core 编写的应用程序,可以利用这个操作系统及其独特的功能。此外,您还可以运行 Java、Node.js、PHP 和 Python 应用程序。
-
Docker:提供 Web 应用容器,我们将在本书后面详细介绍。除了运行所有前述平台,它还允许托管用目前在应用服务中不支持的语言编写的应用程序(例如 Go)。
选择权在您。每个操作系统都有不同的特点:Linux 非常适合运行 Python 应用程序,而 Windows 在处理这种语言时有一些性能问题;另一方面,您可能有许多用 .NET Framework 编写的网站,这些网站已经针对 Windows 系统进行了优化。每个操作系统选项的定价也有所不同。让我们在这里比较 Windows 和 Linux:
| 基础 | 标准 | 高级 | 隔离 | |
|---|---|---|---|---|
| 每小时价格 (Linux) | $0.071 | $0.095 | $0.19 | 不适用 | |
| 每小时价格 (Windows) | $0.075 | $0.10 | $0.20 | $0.40 |
如您所见,这两种操作系统之间存在一些小差异。更重要的是,Linux 当前不支持免费和共享层。隔离层目前处于公开预览阶段,不应在生产工作负载中使用,但这当然未来可能会改变。当您考虑了所有优缺点后,便可以创建由您选择的操作系统提供支持的应用服务。
选择不同的平台
在上一部分中,您学会了如何选择适合您应用程序的操作系统。当然,这并不是运行网站所需的全部——如果您想部署例如 PHP 代码,您还需要启用特定的语言。为此,请前往您的应用服务(有多种方法可以做到这一点:可以从 Azure 门户左侧菜单中选择“应用服务”,然后选择您的 Web 应用,或从 资源组 窗格中选择您创建的资源组)并选择应用程序设置窗格:

一开始,您可能会对这些可用的选项感到有些不知所措,但很快,随着经验的积累,一切都会变得清晰。您可能注意到了这里的“升级以启用链接”——一些功能,如平台或始终开启,仅从 B1 等级开始可用。
记住,“始终开启”功能在某些特定场景中可能变得至关重要,因为它定义了您的应用程序是否始终运行(因此,当没有人使用时,它可以变得空闲)。正如您将在接下来的部分中学到的,设置“始终开启”为开启是运行例如持续 Web 作业或 Azure 函数时所必需的。
目前,我们对所有提到编程语言的选项感兴趣。这些选项包括以下内容:
-
.NET Framework 版本
-
PHP 版本
-
Python 版本
-
Java 版本
默认情况下,你的 App 服务支持两种语言:.NET Framework 版本和 PHP 版本。例如,要运行 Python 或 Java,你必须设置适当的设置为特定版本,例如使用 Java 版本下拉菜单启用 Java 支持。
如前所述,始终根据你为应用程序选择的语言选择正确的操作系统来支持你的 App 服务。虽然可以在 Windows 上运行 PHP 或 Python,但建议选择 Linux,因为许多库和包只能在这个特定的操作系统上运行。
使用应用程序设置
应用程序设置页面提供的不仅仅是启用或禁用可用功能。当你向下滚动时,你会看到其他部分,包括以下内容:
-
调试:如果你想启用远程调试,可以将远程调试选项切换为开启。这样你就可以设置你希望用来在本地调试应用程序的 Visual Studio 版本。
-
应用程序设置:此部分包含在应用程序运行时使用的设置。
-
连接字符串:你可以直接在 Azure 门户中为你的网站定义连接字符串。
-
默认文档:如果你希望有一个自定义的默认文档(即应用程序的起始点),你可以在此部分进行设置。
-
处理程序映射:有时你需要为特定的文件扩展名或 URL 指定自定义处理程序。在这里,你可以添加相应的配置来实现。
-
虚拟应用程序和目录:如果你需要在你的 App 服务中拥有多个应用程序,你可以在这里将虚拟路径映射到物理路径。
请记住,.NET 应用程序的应用程序设置在运行时注入,并且会覆盖存储在web.config中的现有设置。对于其他平台(如.NET、Java、Node.js),该部分的设置将作为环境变量注入,你可以引用它们。连接字符串也遵循这一规则。
Azure 中的应用程序设置在存储时始终是加密的。而且,你可以通过禁止所有用户访问它们来轻松保护它们。
针对.NET 以外的平台的连接字符串始终以适当的连接类型作为前缀。有四种可能性:SQLCONNSTR_、MYSQLCONNSTR_、SQLAZURECONNSTR_和CUSTOMCONNSTR_。
不同的 App 服务计划和功能
我们在本章开始时提到过这个话题,所以你应该对接下来要讨论的内容有所了解。正如你记得的那样,当创建 App 服务时,你必须选择(或创建)一个 App 服务计划,该计划定义了可用的性能和附加功能。让我们覆盖所有三个类别,这次重点介绍每个层级之间的差异。
开发/测试 App 服务计划
专为开发和测试环境设计的 App 服务计划可以在 Dev / Test 类别中找到:

我们有三种不同的层级可供选择:
-
F1(免费):最基本的选项,使用共享基础设施,提供 1 GB 内存和每天 60 分钟的计算时间。使用共享层时,一些应用服务的功能不可用(例如常开功能,或你选择的平台)。F1 非常适合快速测试或部署用于演示的应用程序。使用此应用服务计划不会收取费用。
-
D1(共享):与 F1 类似,但这还允许为你的应用服务设置自定义域名。更重要的是,你可以比使用免费层时多运行应用四倍的时间。不过,这仍然是共享基础设施,因此一些功能无法使用。
-
B1:建议用于运行生产工作负载的第一个层级。它保证了专用的 A 系列机器,并提供更多的内存和存储空间。它还是第一个可以进行扩展的层级——尽管只是手动扩展。基础层还提供了额外的版本(B2 和 B3),它们提供了更强的计算能力。
如果你需要在 Azure 中运行符合服务级别协议(SLA)定义的服务,请记住,不能使用免费或共享层,因为它们不支持此协议。
生产应用服务计划
在这一类别中,选择不同可用功能的选项更多。请记住,在硬件方面,基础层提供的性能与标准层完全相同:

在这里,我们可以选择以下几种:
-
标准 (S1):与 B1 使用相同的 A 系列。我们在这里得到的是自动扩展、预发布槽、备份以及使用流量管理器的可能性(将在后续章节中描述)。这是大多数生产应用程序的最佳层级,因为它支持蓝绿部署场景,并能处理更大的负载(感谢与流量管理器的集成)。如果你需要更强的计算能力,可以选择 S2 或 S3。
-
高级 (P1v2):这是新推荐的选项,替代了 P1,底层使用新的 Dv2 系列虚拟机。它在扩展方面提供了更好的性能和更高的限制(最多支持 20 个实例,而标准层为 10 个实例),并且支持更多的预发布槽。你还可以选择 P2 或 P3。
请记住,特定层级中的最大实例数量取决于可用性。在大多数情况下,这些只是软限制,联系支持后可以提升。
一般来说,标准层应满足大多数在性能、可靠性和自动化可能性方面的需求。然而,如果你计划在 Azure 中运行一个非常受欢迎的网站,可能需要高级层,因为它提供了更多的灵活性和更好的可扩展性。
需要记住的一个重要事项是,如何扩展会影响定价。通常,您有两种选择:要么向上扩展(将层级更改为更高的一个),要么横向扩展(通过部署多个相同应用实例)。例如,如果您为一个 S1 实例支付 40 美元,当您将其扩展到 10 个实例时,总共需要支付 400 美元——每个实例 40 美元。
隔离版应用服务计划
有时您可能需要比 Premium 层提供的更多功能。也许您需要将应用程序从外部网络隔离。也许您只希望为某些特定用户提供访问权限。也许 20 个实例仍然不够。这就是为什么 Azure 引入了隔离版类别:

在这个类别中,我们只有一个层级,分为三个版本:
- 隔离版 (I1/I2/I3):与 Premium 层(Dv2)中的虚拟机相同。还包括大量存储空间来存储您的文件(1 TB)、私有应用访问、集成虚拟网络(例如,您可以访问内部应用程序),以及更稳定的环境。这是最昂贵的层级,但在功能性和提供的特性范围方面提供了最多。
通常情况下,隔离版层级在处理巨大负载时是最稳定的。当 Standard 或 Premium 层级的利用率达到 100%时,它们会迅速变得无响应,而隔离版应用服务则需要更多时间才会返回HTTP 503 服务不可用的响应。如果您需要一个非常可靠、不会轻易崩溃的服务,请考虑这一点。
使用不同安全提供商保护应用服务
大多数网页应用程序必须以某种方式进行安全保护,要么使用您自己的安全系统,要么使用第三方身份提供商,如 Facebook、Google 或 Twitter。在传统的本地托管应用中,您通常需要自己配置所有内容。PaaS 解决方案,如 Azure 应用服务,已经具备此功能,并且通过身份验证/授权功能轻松访问。在本节中,您将学习如何设置它,以便提示用户登录。
在 Azure 门户中配置身份验证/授权
与大多数 PaaS 服务一样,您可以直接从门户配置应用服务的功能。由于这种方式,您可以将所有选项集中在一个地方,并轻松在它们之间切换。
使用 Azure Active Directory 保护应用服务
转到您的应用服务,然后在左侧找到“身份验证/授权”面板,位于之前提到的“应用设置”旁边。点击它后,您将看到一个配置屏幕:

如您所见,它目前处于禁用状态。当您将应用服务身份验证功能切换到“开”时,您将看到可用的新选项,您可以通过这些选项为您的网页应用配置身份验证:

将请求未通过身份验证时的操作字段更改为任何可用的值。门户将显示以下信息:
要启用身份验证 / 授权,请确保所有自定义域已绑定相应的 SSL,并且 .net 版本设置为 "4.5",管理管道模式设置为 "集成"。
由于我们现在没有自定义域,因此无需执行任何操作。同样适用于 .NET 版本和管道模式—如果你没有更改应用程序的默认参数,一切应该已经正确设置。现在,让我们选择一个身份验证提供者并进行配置——我们将从 Azure Active Directory 开始。
使用 Azure Active Directory 来与 App Service 配合工作并不需要你成为专家,特别是现在可以让 Azure Portal 为你配置它。然而,如果你想了解更多关于这个服务的信息,最好的起点是它的文档:docs.microsoft.com/en-us/azure/active-directory/active-directory-whatis。
当你点击 Azure Active Directory 选项时,系统会显示一个新屏幕,你可以在其中配置集成。首先,你需要选择管理模式:
-
关闭:Azure Active Directory 身份验证已禁用。
-
Express:使用 Azure AD 配置 App Service 身份验证的快捷方式。你需要选择一个已有的 Azure Active Directory 应用程序,或者让 Azure Portal 为你创建一个新的应用程序。
-
高级:如果 Express 不足以满足你的需求,你可以自行输入所有必要的参数。通过此选项,你将能够通过提供 Client ID、Issuer URL,以及可选的 Client Secret 来配置集成。所有这些参数都可以在浏览 Azure Active Directory 应用程序时找到。
首先,我建议使用 Express 选项,因为在 Azure Active Directory 中配置应用程序超出了本书的范围。目前,你只需要为应用程序提供一个名称并点击 OK。你将回到前一个屏幕,此时你应该可以看到已经配置了一个身份验证提供者:

现在,点击保存按钮。稍等片刻,一切应该设置完毕,你现在可以访问你的应用程序,看看安全配置是否生效。前往概览面板并点击 URL 链接,或者直接在浏览器中输入它。当加载默认页面时,你不会看到页面内容,而是会被重定向到登录页面。
对于这个特定的练习,我假设你已经部署了你的应用程序。如果没有,请回到前面的章节,使用 Visual Studio 或 FTP 部署你的代码。
由于我们将 Azure Active Directory 配置为身份验证提供者,因此系统会要求用户同意此特定应用程序访问他们的信息。
使用其他身份验证提供者
如您所见,Azure Active Directory 并不是唯一可用于应用服务的安全提供者。我们可以选择 Facebook、Google,甚至 Twitter 来为我们处理身份验证和授权。当您有一个面向公众的应用程序,供使用不同社交媒体网站的人们使用时,这一点尤其有帮助,因为他们可以使用来自其他应用程序的账户,并在进入您的网站时快速登录。要使用 Azure Active Directory 以外的身份验证提供者,您必须在其中一个提到的门户中创建一个应用程序。实际上,无论您选择 Facebook、Google 还是 Twitter,都没有区别——您需要提供两个字段:
-
Facebook 的应用 ID 和应用密钥
-
Google 的客户端 ID 和客户端密钥
-
Twitter 的 API 密钥和 API 密钥
本书不会介绍如何在其他身份验证提供者中创建应用程序。然而,您可以在developers.facebook.com/docs/apps/register/、developers.google.com/identity/sign-in/web/sign-in、developer.twitter.com/en/docs/basics/authentication/guides/access-tokens.html找到相关的详细说明。
应用服务的诊断和监控
本章的最后一部分将展示如何诊断和监控您已部署的应用服务。这些操作在应用程序正常运行时至关重要,因为错误和性能问题总是会出现,特别是在热门服务中。借助 Azure Web Apps 中的多个集成工具,您可以确保始终拥有足够的信息来发现并解决问题。
概览面板
您可能已经注意到的第一件事是“概览”面板中可见的图表:

它们提供了关于应用程序行为的基本洞察,例如数据传输、请求数量或 HTTP 500 错误。让我们点击任何一个图表——您将看到另一个重要的界面,我们现在来看一下。
度量标准
度量标准面板为您提供更详细的信息,并且能够更好地查看特定参数。在左侧,您可以选择许多不同的度量标准。通过选择多个参数,您可以创建自己的图表。
请记住,您只能选择相同单位的度量标准——例如,无法将加载的组件数量与平均响应时间连接起来。
在此屏幕上,您还可以更改图表的时间范围。这在查找相关问题时非常有用(例如,使用 数据输入 和 内存工作集 来检查应用程序处理传入数据所需的内存量)。
监控
让我们返回到 应用服务 的主屏幕。在那里,当您向下滚动时,您将看到一个包含更多有用功能的 监控 部分。
点击 日志流 面板。你将看到一个黑色的屏幕,显示以下信息:
应用程序日志已关闭。您可以通过“诊断日志”设置将其打开。
显然,目前我们没有此功能。让我们进入 诊断日志 面板。它提供了一些关于日志的有趣功能,包括以下内容:
-
应用程序日志(文件系统):收集诊断追踪
-
应用程序日志(博客):与文件系统选项相同,但这次日志存储在 Azure Storage 账户中。
-
Web 服务器日志:收集有关 Web 服务器的诊断信息。
-
详细错误消息:如果您觉得当前的消息不足以解决问题,可以启用此功能以获取更多信息。
-
失败请求追踪:收集关于失败请求的信息。
此外,您可以找到所有日志的 FTP 位置,并包含用户登录信息。由于我们需要 应用程序日志 来查看 日志流,因此让我们启用此功能。现在,我们可以返回 日志流 查看我们正在收集的信息:

如果您在 日志流 中看不到任何信息,请确保您已设置正确的日志记录级别。为了获取所有可能的信息,请使用 详细信息。
总结
在本章中,您了解了什么是应用服务,以及如何构建并部署一个可以轻松推送到 Azure 的简单应用程序。了解此特定服务的基础知识对理解本书中提到的其他主题至关重要,例如 WebJobs 或 Azure Functions。请始终记住,在测试或开发时,您可以先使用 免费版 来避免为应用程序支付费用,然后在需要时进行扩展。我强烈建议您多尝试一下 Web 应用程序,因为云组件有很多内容,某些其他功能最初并不那么显而易见。我们将在接下来的章节中介绍更高级的功能,如与 Traffic Manager、Azure SQL 数据库的集成和扩展场景。
问题
-
“应用服务”和“Web 应用程序”是否指的是相同的 Azure 服务?
-
目前在 Azure 中有多少种类别的 应用服务计划?
-
为什么不应该使用 免费版 和 共享版 来运行生产工作负载?
-
您可以在 应用服务 中设置多少个身份验证提供程序?
-
在 基础版、标准版和高级版 之间,硬件有何不同?
-
您需要启用什么功能才能在 日志流 中查看日志?
-
您可以将自定义域名附加到 应用服务 中的每个版本上吗?
-
您可以将多个 应用服务 附加到一个应用服务计划中吗?
-
App 服务支持哪些操作系统?
-
创建 App 服务后,是否可以更改操作系统?
-
是否可以使用 FTPS 将应用程序文件部署到 App 服务?可以在哪找到正确的位置地址?
-
在 App 服务中,用户级凭据与应用级凭据有什么区别?
-
扩展和横向扩展有什么区别?
-
假设你每月为一个 App 服务实例支付 50 美元。如果你将实例规模扩展到 10 个实例,你需要支付多少钱?
-
在 App 服务中使用 Isolated 层的目的是什么?
-
是否可以在 App 服务中运行 Go 应用程序?
进一步阅读
-
Azure App Service 文档:
docs.microsoft.com/en-us/azure/app-service/ -
Azure App Service 最佳实践:
docs.microsoft.com/en-us/azure/app-service/app-service-best-practices -
Web 应用的参考架构:
docs.microsoft.com/en-us/azure/architecture/reference-architectures/app-service-web-app/ -
部署槽:
docs.microsoft.com/en-us/azure/app-service/web-sites-staged-publishing
第二章:Azure WebJobs
Azure WebJobs 是 Azure App Service 的基础功能之一。它们可以轻松运行所谓的“作业”,并且可以使用不同的时间间隔,甚至可以无限运行。它们非常灵活,并提供了一个特别的 SDK,使得用户可以高效、快速地与它们进行互动。
本章将涵盖以下主题:
-
如何创建 Azure WebJobs
-
使用不同触发类型(持续触发和定时触发)
-
使用不同的文件类型进行 WebJobs
技术要求
执行本章练习时,你将需要以下内容:
-
访问 Azure 订阅
-
安装了 Azure 开发工作负载的 Visual Studio 2017
创建 WebJobs
与 WebJobs 一起工作比与 App Services 一起工作要简单得多,因为这是一个更容易配置和使用的服务。事实上,与你一起工作有两种方式:
-
使用 Azure Portal 手动部署作业
-
使用 Visual Studio 开发并手动部署
更重要的是,你可以利用 WebJobs SDK 来准备一个由外部服务触发的应用程序。这部分内容将在本章末尾介绍,并将是本书后续章节中 Azure Functions 的优秀入门。
在 Azure Portal 中创建和部署 WebJobs
开始使用 WebJobs 最简单和最快捷的方式是创建一个自定义控制台应用程序,执行一个操作,然后通过 Azure Portal 部署它。通过这个练习,你将理解托管在 App Services 中的作业的主要概念。
在 Visual Studio 中创建应用程序
打开 Visual Studio 后,进入 文件 | 新建项目。在 新建项目 窗口中,选择 Windows 经典桌面,然后选择 Console App (.NET Framework)。为你的应用程序命名,并点击 OK:

稍等片刻,你将看到一个空项目,我们可以对其进行修改。我们将尝试以间隔(比如一分钟)触发我们的作业,因此我们需要添加代码,确保作业在给定时间内完成。
首先,让我们仅显示当前日期。在应用程序的Main()方法中,添加以下代码:
using System;
namespace MyFirstWebJob
{
class Program
{
static void Main()
{
Console.WriteLine($"Current date and time is: {DateTime.Now:yyyy-MM-d dddd HH:mm:ss}");
}
}
}
现在保存并构建你的项目——我们很快就需要编译后的版本。
在 Azure Portal 中部署 WebJob
现在,当我们有了 WebJob 的代码后,我们可以尝试将其上传到云端。为此,我们需要 App Service 来托管并执行我们的代码。你可以使用现有的 Web 应用程序或创建一个新的应用程序:
- 进入你的 App Service,找到 WebJobs 面板。(如果你没有看到它,稍微向下滚动一点——它可以在左侧的设置部分找到):

如果你不知道如何创建 App Service 或配置它,请查看 第一章,Azure 应用服务,在其中我详细描述了如何使用这个 Azure 服务的过程。
- 由于你当前没有任何作业,点击+ Add 按钮。你将看到一个新屏幕,允许你配置一个新的 WebJob:

可用的字段根据所选的 Type 字段值略有不同。如果你选择了 Triggered 作业,你将看到以下字段:
-
触发器: 如果你选择了 Scheduled,一个额外的字段(CRON 表达式)将会出现。另一个选项是 Manual,这确保 WebJob 只能手动触发。
-
CRON 表达式:一个有效的 CRON 表达式,定义了作业运行的时间间隔。
CRON 表达式本身是一个相当复杂的话题,在本书中我们不会深入讨论。如果你还没听说过它们,以下两个链接将对你入门非常有用:docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer。
另一种选择是将 Triggered 字段设置为 Continuous。在这种情况下,将只显示一个额外的字段:Scale。决定是否希望始终拥有单一实例的作业,或者将其扩展到所有 App Service 实例。如果你想实现单例模式并且永不扩展作业,这会非常有用。请注意,如果你使用的是 Free 或 Shared 层,它们不支持扩展,因此无法更改此选项。
- 现在,我们将创建一个 Triggered 作业,看看 App Service 如何执行它。给它命名,并附加在前一部分中创建的控制台应用程序的可执行文件。在这里,你可以找到我的配置:

- 我决定使用
0 */1 * * * *的 CRON 表达式,每分钟运行我的作业。当你对配置满意时,点击 OK。片刻后,你应该能看到一个新的 WebJob 被添加到 App Service 并已部署:

你不必将单个文件部署为你的 WebJob。如果你的应用程序有多个文件(如额外的依赖项或静态文件),你可以将它们打包成 ZIP 文件并发布。部署后,它们将被解压并完全可用。
现在,当你点击一个作业时,菜单上方将会显示新的选项。我们想查看输出——为此,请点击 Logs。在新的屏幕上,你将看到此特定 App Service 中可用的作业列表。点击你刚刚添加的作业——应显示最近运行的列表,你可以进行分析。你可以点击任何可用的条目——你将能够看到运行作业的完整日志:

如你所见,作业已运行,并且显示了我们预期的内容——当前的日期和时间。恭喜你——你刚刚创建并部署了你的第一个 WebJob!
请记住,运行连续作业或触发作业需要至少使用基本的 App Service 计划。如果您使用免费或共享层,经过一段时间后,WebJob 将被取消,您需要手动重新启动它。
在接下来的部分中,您将学习如何直接从 Visual Studio 部署 WebJob。
从 Visual Studio 部署 WebJobs
在本章的前面部分,您已经看到如何创建一个简单的 WebJob 并从 Azure Portal 发布它。有时,您可能不想离开 IDE 并希望在其中进行部署。幸运的是,Visual Studio 与 Azure 集成,使得这种操作变得非常简单。在之前,我们开始创建一个全新的控制台应用程序,正如我们最开始所做的那样——这将是我们的起点。您可以添加任何您想要的代码——我将使用来自前面练习的代码,它会显示当前的日期和时间。一旦您对提供的功能感到满意,右键单击您的项目图标。
从上下文菜单中,点击“发布为 Azure WebJob...”:

您将看到一个新屏幕,您可以在其中为新 WebJob 选择一个名称及其运行模式。这次,我也选择了一个连续作业,因此选择了“持续运行”。当您点击“OK”时,该向导将为您的项目安装缺失的包并显示发布屏幕:

在这里,您可以创建或导入发布配置文件,这是部署 WebJob 所必需的。我们希望将作业发布到特定的位置,这就是为什么我们选择 Microsoft Azure App Service。在下一个屏幕中,您可以通过使用订阅和资源组字段来筛选并选择适当的 App Service。选择您感兴趣的选项后,点击“OK”。现在,如果您愿意,可以更改不同的属性并部署配置。我建议暂时保留默认值,然后点击“发布”。几秒钟后,您应该会在输出窗口看到一个成功消息:

现在我们可以检查我们的作业是否在 Azure 中可用。进入您的 App Service 中的 WebJobs 板块。您应该能看到您的作业与其他作业一起显示(我使用了与前一部分相同的 Web 应用,因此我有两个 WebJobs 可用):

看起来一切正常。我相信您已经看到这种方法的一个缺点——我们只能将作业的运行模式定义为按需运行或持续运行。我们这里缺少的是按计划运行作业。在接下来的部分中,我将解决这个问题,因为我们将开始使用 WebJobs SDK,以便更好地控制我们的应用程序。
实际上,可以将连续作业修改为按间隔工作。如果你查看项目,现在应该包含一个名为 webjob-publish-settings.json 的文件。其模式和描述可以在这里找到 — schemastore.org/schemas/json/webjob-publish-settings.json。
使用 WebJobs SDK 进行开发
为了简化在 Azure 中使用 WebJobs 并轻松访问其高级功能,可以使用名为 WebJobs SDK 的框架。当安装了 Azure 工作负载的 Visual Studio 中已经自带。你可以创建一个干净的控制台应用程序,或者使用之前练习中使用的应用程序 — 在这个时刻,由你决定。你还需要一个东西 — Microsoft.Azure.WebJobs 包,可以通过命令行或 NuGet 包管理器安装:

请注意,可以使用版本为 3.X 的包开发 .NET Core WebJob。然而,由于它们仍处于测试阶段,我不会在本书中涵盖它们。
当一切准备就绪时,我们可以继续并尝试创建一个新的应用程序。使用 WebJobs SDK 编写作业与之前的练习有所不同,但它确实带来了一些有趣的好处:
-
你可以使用与其他 Azure 服务(如队列)集成的一组可用触发器开始工作。
-
你有一个集成的日志记录框架,可以简化监视作业的过程。
-
开发 Azure Functions 的绝佳起点,在本书的后续部分将进行详细介绍。
要开始,你必须启动一个 JobHost 实例。它是你应用程序内运行所有作业的容器。(我们也可以称它们为 functions,正如文档中描述的那样。)样板代码如下:
using Microsoft.Azure.WebJobs;
namespace MyFirstWebJobWebJobsSDK
{
class Program
{
static void Main()
{
var config = new JobHostConfiguration();
var host = new JobHost(config);
host.RunAndBlock();
}
}
}
这些三行是你的作业应用程序的实际主机,负责处理其功能。
尝试编译和运行 — 不幸的是,会出现缺少内容的异常,如下所示:

它告诉我们,缺少 Azure 存储账户连接字符串。由于我们尚未涵盖这个主题,你需要在继续之前再完成一个练习。前往 第十一章,使用 Azure Storage - Tables, Queues, Files, and Blobs,并阅读第一节。它将指导你创建 Azure 存储账户,并告诉你在哪里找到其连接字符串。一旦完成,将其添加到 App.config 中如下:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7" />
</startup>
<connectionStrings>
<add name="AzureWebJobsDashboard" connectionString="DefaultEndpointsProtocol=https;AccountName={NAME};AccountKey={KEY}" />
<add name="AzureWebJobsStorage" connectionString="DefaultEndpointsProtocol=https;AccountName={NAME};AccountKey={KEY}" />
</connectionStrings>
</configuration>
正如你所见,我已经添加了两个必需的连接字符串 — AzureWebJobsDashboard 和 AzureWebJobsStorage。现在,当你启动主机时,应该能看到类似以下的内容:

由于我们还没有添加作业,因此找不到任何作业并启用。让我们尝试添加一个并看看它是如何工作的。
如果需要,您可以通过手动设置JobHostConfiguration对象的StorageConnectionString和DashboardConnectionString属性来直接在代码中配置连接字符串。
手动调用作业
如果您想手动触发作业,可以使用[NoAutomaticTrigger]属性告诉主机这个特定函数不会自动触发。这里有一个我创建并命名为Manual.cs的示例:
using System.IO;
using Microsoft.Azure.WebJobs;
namespace MyFirstWebJobWebJobsSDK
{
public class Manual
{
[NoAutomaticTrigger]
public static void ManualFunction(
TextWriter logger,
string value)
{
logger.WriteLine($"Received message: {value}");
}
}
}
现在让我们看看如果您从程序的主要点调用它会发生什么:
using Microsoft.Azure.WebJobs;
namespace MyFirstWebJobWebJobsSDK
{
class Program
{
static void Main()
{
var config = new JobHostConfiguration();
var host = new JobHost(config);
host.Call(typeof(Manual).GetMethod("ManualFunction"), new { value = "Hello world!" });
host.RunAndBlock();
}
}
}
您应该能够看到以下结果:

WebJobs 中的自动触发器
默认情况下,WebJobs SDK 提供了有限的触发器集。目前,它仅支持以下触发器:
-
Blob 存储
-
队列存储
-
表存储
所有这些都与 Azure 存储连接,并将在描述 Azure Functions 的章节中进行介绍。幸运的是,我们可以安装额外的 NuGet 包来扩展我们主机的功能。现在,添加Microsoft.Azure.WebJobs.Extensions包,以便我们可以使用TimerTrigger。当您拥有它时,在JobHostConfiguration对象上调用UseTimers()方法:
using Microsoft.Azure.WebJobs;
namespace MyFirstWebJobWebJobsSDK
{
class Program
{
static void Main()
{
var config = new JobHostConfiguration();
config.UseTimers();
var host = new JobHost(config);
host.Call(typeof(Manual).GetMethod("ManualFunction"), new { value = "Hello world!" });
host.RunAndBlock();
}
}
}
现在我们可以创建一个新的Timer.cs函数,它将按计划触发:
using System;
using System.IO;
using Microsoft.Azure.WebJobs;
namespace MyFirstWebJobWebJobsSDK
{
public class Timer
{
public static void TimerFunction(
[TimerTrigger("* */1 * * * *")] TimerInfo timer,
TextWriter logger)
{
logger.WriteLine($"Message triggered at {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
}
}
}
当您启动项目时,TimerFunction应该被发现并调用:

您可以安装其他包以利用其他绑定,例如Http,Notification Hub或SendGrid。搜索具有Microsoft.Azure.WebJobs.Extensions前缀的包。
发布作业
发布一个使用 WebJobs SDK 编写的作业与之前的练习相同。右键单击项目,然后单击发布为 Azure WebJob。您将再次被引导完成发布作业包的过程。在结束时,您应该能够在您的 App Service 中看到一个可用的作业:

如果您发现您的作业运行时有问题,请确保所需的连接字符串在 App Service 中可用。为此,请转到“应用程序设置”选项卡,并验证“连接字符串”部分的内容。如果有遗漏的内容,请手动添加一个名称及其值,并将其类型设置为“自定义”。
Azure WebJobs 的限制
由于 Azure WebJobs 基于 Azure App Services,并且没有办法单独托管它们(您可以使用额外的 App Service 计划来支持它们,但这通常意味着您为服务支付双倍价格),您可能会发现它们的一些限制在您的项目中相当严重。在使用此特定服务时,请记住以下事项:
-
当共同托管 Web App 和 WebJobs 时,一个可能会影响另一个的性能。换句话说,如果您的 WebJob 开始利用过多的 CPU/内存,可能会影响由相同 App Service 计划支持的 Web 应用程序的性能。
-
Azure WebJobs 提供了一组有限的绑定目录——它们目前比 Azure Functions 不那么受欢迎,也没有那么动态地发展。
-
无法使用 Azure WebJobs 的消耗模型;因此,即使它们 90%的时间什么也不做,您也必须支付全价。
使用不同的文件类型进行 WebJobs
在处理 WebJobs 时,您不限于使用 EXE 文件。目前,此服务支持以下应用程序:
-
Windows 可执行文件(
.exe,.bat和.cmd) -
Powershell(
.ps1) -
Bash(
.sh) -
Python(
.py) -
PHP(
.php) -
Node.js(
.js) -
Java(
.jar)
正如您所看到的,前面的列表与 App Services 中支持的语言相似。现在您应该明白,WebJobs 实际上是 Azure 中 Web Apps 的一个组成部分。让我们尝试一些实际操作——我们将部署一个简单的 Node.js 应用程序,将其发布到 Azure 作为 WebJob。
创建并部署 Node.js 应用程序作为 WebJob
JavaScript 是世界上最流行的编程语言之一。Azure 的优势在于它不会阻止您使用其他非微软技术。在开始之前,您必须记住一条重要信息。
运行时执行 WebJobs 的方式要求您遵循特定的约定。在搜索作业时,运行时将搜索名为run.{job_type}的文件,其中job_type是表示特定编程语言的扩展名(例如.js和.py)。如果失败,它将尝试查找具有特定扩展名的任何文件。如果这也失败,作业将被跳过。
这是我第一个用 JavaScript 编写的 WebJob 的非常简单的代码:
var myFirstWebJob = function() {
console.log("Hello, this is my first WebJob in Node.js!");
};
myFirstWebJob();
您可以使用支持 JavaScript 的任何类型的应用程序来创建它(例如,记事本或 Visual Studio Code)。将文件命名为run.js并按照之前描述的方式在 Azure 门户中发布。在这里您可以找到结果:

正如您所看到的,它是自动发现的——不需要任何额外的配置就可以使用 Node.js 运行时运行它。我强烈建议您花点时间,使用其他文件类型测试一下这个功能。
请记住,如果您的应用程序需要额外的文件(例如 Node.js 作业的额外包),您必须将它们打包为 ZIP 包然后部署。
从 Visual Studio Code 部署 Node.js Azure WebJob
不幸的是,目前无法直接从 Visual Studio Code 部署 Azure WebJob。但是,如果安装了Azure App Service扩展,您可以在 IDE 内快速导航到门户并手动上传代码。
为此,你需要右键点击你的 Azure App Service 中的 WebJobs 部分,并选择“在门户中打开”:

这样做将直接引导你进入 Azure App Service 的实例,在这里你可以通过点击“+ 添加”按钮来添加新的 WebJob:

总结
如你所见,Azure WebJobs 是 App Services 的一个非常有用和实用的功能,它让你能够快速开发持续运行或按计划触发的作业。当你拥有一个正常运行的 web 应用时,WebJobs 会展现出其优势,能够托管并异步执行多种不同的操作(例如生成报告或读取队列,之后将数据填充到应用的数据库中)。由于支持多种编程语言,你不必局限于某一特定平台。最后但同样重要的是,WebJobs 是了解 Azure Functions 的一个绝佳入门,Azure Functions 是 Azure 中最受欢迎的服务之一,进一步扩展了 WebJobs 的现有功能。在第三章《将 Web 应用程序作为容器部署》中,你将学习如何将 Web 应用部署为容器,这在托管 web 应用时,进一步扩展了 App Service 的功能。
问题
-
你可以在 Free 或 Shared 层运行 WebJobs 吗?
-
WebJobs 当前支持哪些运行模式?
-
你可以使用 WebJobs 运行 Java 应用程序吗?
-
如何确保运行时能够找到你的作业启动文件?
-
你能发布一个包含多个文件的 WebJob 吗?如果可以,你该如何操作?
-
如何确保 WebJob 不会被扩展到多个实例?
-
WebJob 可以访问托管它的 App Service 的应用设置吗?
进一步阅读
第三章:将 Web 应用程序作为容器部署
容器是 IT 行业中最热门的话题之一。它们允许将应用程序部署在“一个盒子”中,这样我们就不必担心它运行在哪个操作系统上或所需的已安装服务。虽然容器有时因对底层资源的冗余抽象而受到批评,但它们为开发和托管应用程序提供了一个稳定的环境。
本章将涉及以下主题:
-
理解容器及其最佳使用场景
-
Azure Kubernetes 服务(AKS)和使用 PaaS 组件托管 Kubernetes 环境
-
用于可扩展应用程序的 Web 应用容器
-
Azure 容器实例及如何在不管理服务器的情况下管理容器
技术要求
要开始在 Azure 中使用容器,您需要以下内容:
-
对 Docker 概念的基本理解 (
docs.docker.com/get-started/) -
Docker 开发环境(取决于您使用的操作系统——
docs.docker.com/docker-for-mac/、docs.docker.com/docker-for-windows/、或docs.docker.com/install/) -
一个 Docker Hub 账户
-
Azure CLI (
docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) -
对 Kubernetes 的基本理解 (
kubernetes.io/docs/home/) -
Kubernetes CLI (
kubernetes.io/docs/tasks/tools/install-kubectl/)
使用 AKS
AKS 通过消除需要自己维护或升级资源的步骤,简化了容器化应用程序的部署和管理过程。它是一个托管在 Azure 中的 Kubernetes 服务,提供许多有用的功能,如集成日志记录和监控、身份和安全管理以及虚拟网络集成。在本节中,我们将创建一个托管在 AKS 集群中的简单应用程序,并对其进行扩展和更新。
准备应用程序
让我们从 Docker 文档中提供的一个教程应用程序开始:
- 首先,我们需要
Dockerfile,它定义了我们的容器环境应如何构建。它包含诸如FROM(定义将用于容器的镜像)、WORKDIR(应用程序的工作目录)、ADD(将目录添加到容器中)、RUN(运行命令)、EXPOSE(暴露容器中的指定端口)、ENV(添加环境变量)和CMD(声明入口点)等关键字:
FROM python:2.7-slim
WORKDIR /app
ADD . /app
RUN pip install --trusted-host pypi.python.org -r requirements.txt
EXPOSE 80
ENV NAME HandsOnAzure
CMD ["python", "app.py"]
-
我们还需要另外两个文件:
-
requirements.txt:这个文件定义了我们的应用程序需要下载的外部依赖 -
app.py:应用程序的主要文件
-
如果您熟悉 Docker,您可以准备自己的Dockerfile和应用程序;在本节中讲解的内容很基础,您无需完全跟随。
- 在这里,您可以找到
requirements.txt的内容:
Flask
Redis
- 当然,还有
app.py,这是我们要运行的脚本。它是一个简单的 Python 应用,使用 Flask 托管一个 Web 应用,并定义了默认路由来暴露一个 HTML 网页。注意,它访问了 Dockerfile 中定义的HandsOnAzure环境变量:
from flask import Flask
from redis import Redis, RedisError
import os
import socket
# Connect to Redis
redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2)
app = Flask(__name__)
@app.route("/")
def hello():
try:
visits = redis.incr("counter")
except RedisError:
visits = "<i>cannot connect to Redis, counter disabled</i>"
html = "<h3>Hello {name}!</h3>" \
"<b>Hostname:</b> {hostname}<br/>" \
"<b>Visits:</b> {visits}"
return html.format(name=os.getenv("NAME", "HandsOnAzure"), hostname=socket.gethostname(), visits=visits)
if __name__ == "__main__":
app.run(host='0.0.0.0', port=80)
- 现在,您需要再做两件事——构建镜像并检查它是否正常工作。要构建镜像,您需要以下命令:
docker build -t {IMAGE_NAME} .
- 在 Docker 获取所有包并构建镜像后,您可以运行它。我使用了
4000端口,但您可以选择适合您的端口:
docker run -p 4000:80 {IMAGE_NAME}
- 如果一切正确,您应该能够看到一个正在运行的应用:

在下一节中,我们将创建一个容器注册表和 Kubernetes 集群来托管一个应用。
容器注册表和 Kubernetes 集群
要在 Azure 中使用 Docker 镜像,我们必须将它放入容器注册表中。为此,我们可以使用Azure 容器注册表(ACR),它是一个用于托管容器镜像的私有注册表。首先,进入 Azure 门户,点击+ 创建资源,搜索Container Registry。点击创建后,您将看到一个包含多个服务配置字段的熟悉屏幕。现在以下内容需要澄清:
-
管理员用户:如果您想通过注册表名称和管理员访问密钥登录到您的注册表,您可以启用此功能。默认情况下,它是禁用的。
-
SKU:这定义了注册表的整体性能和可用功能。首先,Basic 层应该足够用了。
以下截图显示了我当前的配置:

如果您对配置满意,可以点击确认。现在我们将推送一个带有应用的镜像到注册表,以便在 Azure 中使用它。
将 Docker 镜像推送到 Azure 容器注册表
要将镜像推送到 Azure 容器注册表,我们需要知道镜像的准确名称:
- 要列出可用的镜像,请使用以下命令:
docker images
- 执行该命令的结果是一个列出所有 Docker 可用仓库的列表:

- 我想将我之前创建的
handsonazurehello推送到 Azure。为此,我首先需要使用以下命令标记镜像:
docker tag handsonazurehello {ACR_LOGIN_SERVER}/handsonazurehello:v1
标记的原因是为了为我的容器设置一个版本,并为它赋予推送到私有注册表所需的适当名称。
- 您可以在概览面板中找到登录服务器:

- 现在,您可以尝试使用以下命令推送一个镜像:
docker push handsonazureregistry.azurecr.io/handsonazurehello:v1
- 当然,您必须推送您的镜像名称和注册表服务器登录。当您执行上面的命令时,您将看到以下结果:
$ docker push handsonazureregistry.azurecr.io/handsonazurehello:v1
The push refers to repository [handsonazureregistry.azurecr.io/handsonazurehello]
bbdbf9d56e79: Preparing
128193523190: Preparing
f78e6f8eec4b: Preparing
20f93bdcee9c: Preparing
21b24882d499: Preparing
db9dabc5cfee: Waiting
d626a8ad97a1: Waiting
unauthorized: authentication required
- 仍然有问题——我们正在将容器推送到正确的注册表,但我们尚未进行身份验证。要访问 Azure 容器注册表,我们必须使用 Azure CLI。请使用两个命令:
az login
az acr login --name {REGISTRY_NAME}
- 第一个命令用于在 Azure 中进行身份验证,第二个命令将让你与自己的容器注册表实例进行交互。成功身份验证后,你可以重试推送镜像——这次一切应该顺利运行:
$ docker push handsonazureregistry.azurecr.io/handsonazurehello:v1
The push refers to repository [handsonazureregistry.azurecr.io/handsonazurehello]
bbdbf9d56e79: Pushed
128193523190: Pushed
f78e6f8eec4b: Pushed
20f93bdcee9c: Pushed
21b24882d499: Pushed
db9dabc5cfee: Pushed
d626a8ad97a1: Pushed
v1: digest: sha256:2e689f437e1b31086b5d4493c8b4ef93c92640ad576f045062c81048d8988aa6 size: 1787
- 你可以在门户中验证它是否可用:

接下来我们需要的是一个 Kubernetes 集群——这就是我们使用 AKS 来配置托管 Kubernetes 服务的原因。
使用 AKS 创建 Kubernetes 集群
要创建 Kubernetes 服务,请执行以下步骤:
-
前往门户。
-
点击 + 创建资源 按钮并搜索
AKS。 -
当你点击 创建时,你应该看到以下屏幕:

如你所见,这里有很多不同的选项和字段。其中一些应该是显而易见的。最初,我建议对大多数字段使用默认值,比如 Kubernetes 版本或 Service principal,因为这些字段只有在你对应用程序有特定需求时才会重要。
要了解更多关于 Azure AD 中应用程序和服务主体对象的信息,可以阅读文档中的这篇简短文章:docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-application-objects。
你可以进入不同的标签页,如 Networking 或 Monitoring,查看是否需要更改某些设置——然而,当前的设置应该适用于第一个 AKS 集群。
审核完所有字段后,点击 Review + create 按钮。以下截图展示了我的配置:

当你点击 创建时,你需要稍等片刻,直到部署完成。
创建集群可能需要一些时间,尤其是在你选择同时部署多个机器时。耐心等待!
在 AKS 中运行、扩展和更新应用程序
为了在 Azure 的 AKS 中运行和部署应用程序,我们需要 Kubernetes 清单文件,该文件将定义如何部署镜像。然而,在执行这部分操作之前,我们必须先在本地配置 Kubernetes,以便它能够与我们的集群连接。为此,请执行以下步骤:
- 运行以下命令:
kubectl get nodes
- 最初,它会返回以下结果:
Unable to connect to the server: dial tcp [::1]:8080: connectex: No connection could be made because the target machine actively refused it.
- 这意味着我们尚未将
kubectl与我们刚刚创建的 AKS 集群进行配置。为此,请使用以下 Azure CLI 命令:
az aks get-credentials --resource-group {RESSOURCE_GROUP} --name {AKS_CLUSTER_NAME}
运行它应该会将集群合并为你本地配置中的当前上下文。现在,当你验证连接时,一切应该已经设置好并准备就绪。我们可以回到清单文件——初始版本可以在仓库中的Chapter03找到。一般来说,这是一个简单的 YML 文件,用于定义和配置服务及部署。
- 将文件放入你的应用程序目录中,并使用以下命令进行部署:
kubectl apply -f handsonazure.yml
- 稍等片刻,你应该看到进程的状态:
$ kubectl apply -f handsonazure.yml
deployment "handsonazurehello-back" created
service "handsonazurehello-back" created
deployment "handsonazurehello" configured
service "handsonazurehello" unchanged
- 现在,我们需要一个命令来获取服务的外部 IP:
$ kubectl get service handsonazurehello --watch
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
handsonazurehello LoadBalancer 10.0.223.94 40.118.7.118 80:30910/TCP 4m
- 最初,你可以看到
EXTERNAL-IP字段为 pending——它应该在一秒钟内改变。现在你可以验证应用的安装情况:

- 还有一种方法可以获取整个集群的状态。尝试运行以下命令:
az aks browse --resource-group {RESOURCE_GROUP} --name {AKS_CLUSTER_NAME}
- 稍等片刻后,你应该能看到整个 Kubernetes 仪表板可以在本地使用。请仔细阅读所有信息,因为它在诊断 AKS 可能出现的问题时非常有帮助:

解决身份验证问题
有时 AKS 无法访问 Azure 容器注册表,并且需要创建服务主体,这可以在 pod 部署中使用。要创建它,你需要执行以下脚本:
$ az acr show --name {REGISTRY_NAME} --query loginServer --output tsv
{YOUR_REGISTRY_NAME}
$ az acr show --name handsonazureregistry --query id --output tsv
/subscriptions/94f33c43-60b5-4042-ae74-51777f69f99a/resourceGroups/handsonazure-rg/providers/Microsoft.ContainerRegistry/registries/handsonazureregistry
{REGISTRY_ID}
$ az ad sp create-for-rbac --name acr-service-principal --role Reader --scopes {REGISTRY_ID} --query password --output tsv
{PASSWORD}
$ az ad sp show --id http://acr-service-principal --query appId --output tsv
{CLIENT_ID}
$ kubectl create secret docker-registry acr-auth --docker-server {YOUR_REGISTRY_NAME} --docker-username {CLIENT_ID} --docker-password {PASSWORD} --docker-email {DOCKER_ACCOUNT_EMAIL}
完整的脚本也可以在本章的源代码中找到。一旦密钥创建完成,你可以修改清单文件并添加imagePullSecrets行:
containers:
- name: handsonazurehello
image: handsonazureregistry.azurecr.io/handsonazurehello:v1
ports:
- containerPort: 80
resources:
requests:
cpu: 250m
limits:
cpu: 500m
env:
- name: REDIS
value: "handsonazurehello-back"
imagePullSecrets:
- name: acr-auth
扩容集群
在 AKS 中扩容是最简单的操作之一。你只需要进入 Azure 门户,找到你的 AKS 实例,然后点击 Scaleblade:

一旦在那里,你可以根据需求调整集群容量。
确保你有足够数量的节点可供你的应用使用——这些信息可以在 Kubernetes 仪表板中找到。如果机器不足,某些镜像可能无法部署。
更新应用程序
在 AKS 中更新应用程序需要做两件事:
-
将新镜像发布到 Azure 容器注册表
-
将新的镜像设置为 AKS 中的当前镜像
当你对应用进行更改时,需要使用两个命令将其更新到注册表中。首先,将其版本更改为新的版本:
docker tag handsonazurehello {ACR_LOGIN_SERVER}/handsonazurehello:v2
现在你需要做的是将这个版本推送到云端以便使用:
docker push
{ACR_LOGIN_SERVER}/handsonazurehello:v2
最后一步,告诉 Kubernetes 更新镜像:
kubectl set image deployment handsonazurehello handsonazurehello=
{ACR_LOGIN_SERVER}/handsonazurehello:v2
为了确保应用在更新过程中完全正常工作,你需要将其扩展到多个 pod。你可以使用以下命令来实现:
**kubectl scale --replicas=3 deployment/{YOUR_APPLICATION}**
Azure 容器实例
虽然 AKS 是一个完整的编排解决方案,但有时你可能更倾向于使用一个轻量级服务,它提供与运行容器相关的最关键功能。这些功能包括不需要配置和管理虚拟机、安全性以及集成的公共 IP 连接性。如果你想运行一个容器化的简单应用程序,比如 WebJob 或网站,Azure 容器实例(ACI)可以满足你的需求。
创建并部署一个应用程序和容器
我们将通过创建一个应用程序来开始我们的 ACI 之旅,这个应用程序将托管在容器中。在上一节中,我们使用了一个简单的 Python 脚本——这次我们将尝试使用 Node.js。
和往常一样,你可以在本章的适当源文件文件夹中找到源文件。
我们需要的第一件事是 Dockerfile。如你所记,它包含了如何运行应用程序的指令。以下是本次练习的示例:
FROM node:8.9.3-alpine
RUN mkdir -p /bin/
COPY ./app/ /bin/
WORKDIR /bin
RUN npm install
CMD node index.js
如果你仔细阅读,你会发现它其实非常简单——它的步骤如下:
-
安装特定的 Node.js 版本(这次是 alpine,它是一个为容器设计的小型发行版)
-
创建一个新的工作目录
-
将文件复制到容器中
-
使用
npm install命令安装所有依赖项 -
通过提供其起始点来启动应用程序
现在,我们可以使用以下命令创建一个容器:
docker build ./ -t {CONTAINER_NAME}
稍等片刻,你应该能创建并添加一个新的镜像。如果遇到问题,请确保你在正确的目录中,并且没有重复命名。
记住,你可以随时检查当前可用的镜像及其名称。为此,你可以使用以下 Docker 命令:docker images.
现在我们可以验证应用程序的工作情况。要启动它,请使用以下命令:
docker run -d -p 8080:80 {CONTAINER_NAME}
你可以访问 localhost:8080,检查应用程序是否在运行。如果一切配置正确,你应该能在屏幕中央看到 Welcome to Azure Container Instances! 消息。
有时,你在使用某些 Web 应用程序并将其本地运行时,可能会遇到问题——出于某种原因,你无法通过指定的端口连接它们。在这种情况下,重新配置应用程序并使用不同的端口通常是一个好主意。
现在我们将把镜像推送到 Azure,以便以后在 ACI 中使用。
将镜像推送到 Azure 容器注册中心
你可以在本章的 容器注册中心和 Kubernetes 集群 部分找到如何使用 ACI 的完整说明。现在,操作与使用 AKS 时相同——我们需要执行以下操作:
-
登录到 ACR
-
为镜像打标签
-
将其推送到 ACR
你将需要以下三个命令:
az acr login --name {ACR_NAME}
docker tag {CONTAINER_NAME} {ACR_LOGIN_SERVER}/{CONTAINER_NAME}:v1
docker push {ACR_LOGIN_SERVER}/{CONTAINER_NAME}:v1
推送成功后,我们可以将应用程序部署到 ACI。
将应用程序部署到 ACI
我们需要做的第一件事是创建一个 ACI 实例。像往常一样,在 Azure Portal 中点击 + 创建资源,并搜索 Container Instances。
您应该会看到类似下面的向导:

这里需要注意的是,在选择容器镜像类型时,必须选择Private。选择此选项后,将显示额外的字段,必须填写这些字段。在创建 ACI 时,您需要提供容器镜像。这就是您在将镜像推送到容器注册表时创建的 Docker 标签。在我的例子中是handsonazureregistry.azurecr.io/handsonazure-aci。镜像注册表登录服务器就是注册表的登录服务器(您可以在 ACR 的“概述”面板中找到它)。我们还需要另外两样东西——注册表用户名和密码。用户名就是注册表名称,密码可以使用以下命令获取:
az acr credential show --name {REGISTRY_NAME}--query "passwords[0].value"
要获取密码,必须启用管理员凭据。可以使用以下命令从 CMD 启用管理员凭据:
**az acr update -n {REGISTRY_NAME} --admin-enabled true**
现在我们可以进入“配置”部分:

这次,所有这些字段应该都是不言自明的,事实上,在本次操作中,我保留了默认值。您可以根据需要更改它们(例如操作系统或可用内存)。设置完成后,您可以点击OK并发布您的容器实例。
如果您在门户中部署容器时遇到问题,您可以始终使用 Azure CLI。下面是与我们在 Azure 门户中做的相同操作的完整命令:
**az container create --resource-group {RG_NAME}--name {ACI_NAME} --image {ACR_LOGIN_SERVER}/{CONTAINER_NAME}:v1 --cpu 1 --memory 1 --registry-login-server {ACR_LOGIN_SERVER} --registry-username {REGISTRY_NAME} --registry-password {REGISTRY_PASSWORD} --dns-name-label {DNS_LABEL} --ports 80**
部署完成后,进入 ACI 实例并查看“概述”面板。复制FQDN字段的值并将其粘贴到浏览器中。您应该能够看到与本地看到的完全相同的屏幕:

就是这样!如您所见,使用 Azure 容器服务是一种非常快速的方式,通过最少的功能集将应用程序部署到容器中。在接下来的部分,我们将重点介绍在应用服务中利用容器。
容器化 Web 应用
您不必使用 AKS 或 ACI 来利用 Azure 中容器的功能——目前还有一种功能可以用来部署用不受支持的语言(如 Go)编写的 Web 应用。容器化 Web 应用是应用服务的扩展,它使用 Linux 作为底层操作系统,配合 Docker 来运行构建在当前 Azure 不支持的技术栈上的服务。
创建一个托管在容器中的 Web 应用
要创建一个运行 Docker 实例的应用服务,你必须遵循与第一章 Azure 应用服务相同的步骤,回顾我们在讨论使用 Azure 门户创建 Web 应用时所做的内容。
当你进入 Web 应用创建界面时,请查看操作系统字段:

如你所见,Docker 选项是可用的。点击它后,还需要设置两个字段:
-
应用服务计划/位置:这与“传统”的应用服务相同,但有一个重要的说明。当你选择 Docker 时,所有可供选择的应用服务计划将由 Linux 操作系统提供支持。这将影响定价和功能可用性。
-
配置容器:选择此选项后,你将看到另一个屏幕,显示根据容器设置的多个不同选项。目前,我们有三个不同的选择:单容器、Docker Compose 和 Kubernetes。后两个选项目前处于预览阶段,但我们也会涵盖它们。现在,请选择单容器并使用快速入门选项。
这里是我的配置:

现在,点击创建后,Azure 将在几秒钟内完成新资源的配置。创建完成后,进入新创建的应用服务,并查看左侧的界面。你会发现其中一些已经被禁用:

如你所见,使用 Web 应用容器(Web App for Containers)不允许我们使用诸如 WebJobs 或应用程序洞察(Application Insights)等功能。然而,我们仍然能够扩展应用程序、附加自定义域名或设置备份。请注意,另一个界面——容器设置是可用的。当你点击它时,你将看到与应用服务创建时相同的界面:

让我们看看当我们更改容器的镜像时会发生什么。将镜像和可选标签(例如“image:tag”)字段更改为appsvc/dotnetcore,然后点击保存。重新启动应用程序后,你应该会看到可见日志中的变化:

此外,当你浏览你的 Web 应用时,你应该会看到一个成功的消息:

现在我们将尝试部署我们自己的代码并查看它是否能正常工作。
部署自定义应用程序
在本节中,我们将重点介绍如何在 Web 应用中创建并部署自定义应用程序到我们的 Docker 容器。为了本次练习的目的,我们将重用本章前部分讨论 ACI 时使用的容器镜像。
如果你想准备一个全新的应用程序,请按照创建和部署应用程序和容器部分中定义的步骤操作,我们在那里创建了一个 Docker 镜像并将其推送到 Azure 容器注册表。
当你再次进入容器设置面板时,你可以将镜像来源更改为三种不同的选项:
-
Azure 容器注册表:它允许你选择推送到自己实例的 ACR 中的镜像
-
Docker Hub:提供对该存储库中所有镜像的访问
-
私有注册表:你还可以定义一个私有存储库来部署你的镜像
请注意,如果你使用 ACR,其他两个来源也会指向 ACR,因为它充当你自己私有的 Docker Hub,存储着你的镜像。
选择 Azure 容器注册表标签,并根据门户提示填写所有必填字段:

片刻之后,新的镜像应该会重新加载并正常工作。如果你浏览到应用的 URL,应该能够看到更新的内容:

正如你所看到的,我在使用 ACI 时使用的同一个容器镜像,在 Web 应用容器中也是完全可用的。
总结
正如你所看到的,在 Azure 中使用容器时,你可以专注于应用的交付和形态,而不是配置或维护。当然,可用的功能不仅限于本章所涵盖的内容——你还可以利用持续部署、网络或数据卷等功能。可用的功能完全取决于你选择的服务——Azure 容器实例和 Web 应用容器是非常简洁的云组件,专注于运行应用,而 AKS 提供了更多高级功能。尽管如此,容器仍然是近几个月最受欢迎的话题,毫无疑问,构建和发展这一技能将有助于你未来的项目。
在下一章中,你将了解另一个服务,它允许你运行容器化应用并在 Azure 云中从微服务架构中获得更多的功能——Azure Service Fabric。
问题
-
什么是 Azure 容器注册表?
-
什么是 AKS 中的管理员登录功能?
-
在创建应用服务时,你需要选择哪种操作系统才能运行容器?
-
你可以在 Web 应用容器中使用来自公共注册表的镜像吗?
-
你可以在门户中扩展 AKS 吗?
-
在更新应用时,你如何降低 AKS 中应用的停机时间?
-
如果 AKS 无法验证你实例的 Azure 容器注册表身份,你需要做什么?
深入阅读
第四章:使用 Service Fabric 开发分布式应用和微服务
Service Fabric (SF) 是一个分布式应用平台,它大大简化了可扩展且可靠的应用开发与部署。它是开发云原生应用的最佳解决方案之一,让用户可以专注于开发,而非维护基础设施和组件之间的连接。它是一个下一代平台,由微软积极开发,最近受到了广泛关注。
本章将涵盖以下主题:
-
微服务架构以及如何在云中使用 SF
-
SF 的基本概念,如服务或角色
-
在 SF 中的服务间通信
-
在 SF 中管理集群并确保其安全
-
监控 SF 中的服务及如何诊断它们
技术要求
为了完成本章的练习,你将需要以下内容:
-
带有Azure 开发、ASP.NET和Web 开发工作负载的 Visual Studio 2017
-
Microsoft Azure SF SDK (
www.microsoft.com/web/handlers/webpi.ashx?command=getinstallerredirect&appid=MicrosoftAzure-ServiceFabric-CoreSDK) -
Node.js (
nodejs.org/en/)
理解微服务
你可能听说过一种叫做微服务的架构。这里没有一个单一的定义可以引用,因此本章的主要目的是更好地理解我们可以称之为微服务的内容,以及如何以这种方式开发应用程序。这与 SF 直接相关,SF 是 Azure 中最大和最先进的服务之一。如果你打算构建一个模块化、松耦合且现代的应用程序,这个云组件专为你设计。
单体应用与微服务
我们将通过将微服务与传统应用进行对比来开始我们的微服务之旅,传统应用由多个层次构成,每个层次服务不同的目的:

如你所见,我们将这些服务定义为单体应用,其中整个代码库作为一个应用程序部署。这个应用程序有多个职责:
-
提供 UI 服务
-
运行业务逻辑
-
运行额外进程(如作业)
我们也可以从不同的角度来看待它——作为一个处理不同领域功能的单一模块。让我们考虑一个电子商店,其中有以下内容:
-
支付逻辑
-
购物车逻辑
-
订单处理逻辑
-
折扣逻辑
-
许多,许多其他不同领域
现在问题来了——我们的应用程序应该在一个实例中运行这些逻辑,还是应该将其拆分成多个独立的模块,这些模块具有不同的生命周期、不同的运行方式,并且可以单独开发?也许我们还希望根据当前的工作负载或业务需求分别扩展它们。选择总是取决于你的应用程序将面临的需求。然而,如果你希望尝试微服务,SF 是最好的选择,尤其是当你打算使用云原生组件时。
微服务方法
你可能会想,微服务架构是否是你愿意选择并使用的架构。在本节中,我将重点介绍这种方法所提供的特定功能,以及如何在编写应用程序时处理这些功能,最终使用 SF。
使用不同的语言和框架
有时候,我们希望通过使用不同的编程语言或工具来解决我们的应用程序所面临的不同问题。也许主要用 C#或 Java 编写应用程序,并使用专用语言交付更复杂的功能,如领域特定的计算。也许我们有多个团队在开发不同的特性,每个团队都希望使用不同的框架。
也许整个工作是如此全球化分布,以至于将其拆分为多个较小的包(最终变成服务)是前进的方向。所有这些问题都可以通过使用一个单一应用程序来解决,但在更大规模下,这种方法可能会变得繁琐且不足够。通过利用 Azure 中的 SF 功能,我们可以将多个应用程序(每个应用程序包含多个服务)组织成一个单一平台,从一个地方进行管理并单独部署,从而节省时间和成本。
单独扩展和更新服务
我们刚才讨论了如何单独部署每个服务。得益于这种方法,你不必一次性推送整个代码库。我相信你至少有过一个项目,它是如此庞大,以至于将整个项目交付到生产环境的过程难以自动化,并且需要非常长的时间才能完成。在这种情况下,将项目拆分成更小的模块也是有益的。比如说,在上个月,只有一个团队交付了一个新特性;你不必重新进行所有平台测试。更重要的是,如果部署后系统中某个未修改的部分出现问题,你也不需要另一个团队来调查问题。这带来了以下优势:
-
交付商业价值的过程更短、更简单,因此更不容易出错
-
你可以专注于某个特定模块,并且其中的更改通常不会影响其他模块
微服务架构还有一个有趣的特点——你可以单独扩展每个组件。这意味着,如果例如有一个负责处理支付的模块,而你刚刚在网上商店进行了一次大幅度的折扣促销,导致订单量急剧增长,你不必担心订单会压垮它。在这种情况下,你可以做的是简单地扩展该模块,这样你就可以并行处理每个订单,使用尽可能多的实例来满足当前需求。本章稍后会介绍如何根据应用程序的实际结构使用不同的方法来实现这种可扩展性。下面的图表描述了微服务和单体应用程序扩展的区别——前者使你能够单独扩展每个服务,而后者则必须作为一个整体进行扩展:

使用设计良好的接口和协议
你可能会问,如果我们将应用程序划分为多个较小的模块,我们如何确保它们之间的通信既顺畅、无缝,又能尽量减少延迟?为了满足这样的要求,我们必须参考面向服务架构(SOA)所描述的多种通信模式,而这些模式实际上是微服务的基础。一般来说,你需要利用广泛应用于 IT 行业的、为成千上万的 API、设备和框架所理解的著名协议,如 HTTP 或 TCP。当然,你也可以考虑设计一个自定义的协议或标准——虽然最初这可能是个好主意,但迟早它会成为扩展应用程序的障碍,因为它与更新的服务不兼容。更重要的是,使用流行的序列化方法,如 XML、JSON,或任何容易获取并且有良好文档支持的二进制格式是很重要的。通过遵循这些指南,你可以确保你的微服务架构将易于扩展和集成。
处理状态
几乎每个应用程序都有某种状态(当然,也可以开发一个无状态的服务,它不需要在任何地方存储状态,只执行操作/返回结果;我们将在本章稍后讨论这样的服务)。
这个状态必须被管理和共享;在单体应用程序的处理过程中,这个过程非常直接且显而易见——例如,我们有一个数据库,存储着系统多个部分的所有信息。一般来说,它存储在一个地方(当然它可以扩展和共享,但我们不会自己管理这些功能),因此我们不必担心它只被部分更新(如果真的部分更新了,也总有事务来保证一致性)。当然,状态不一定要存储在数据库中——我们可以使用任何类型的存储来保存数据。
使用微服务时,结果表明每个独立的服务都有自己的状态并独立管理它。当我们必须从不同模块查询数据或将数据存储在多个存储形式中时,就会出现问题。为了解决这些问题,可以使用如最终一致性这样的模式。在 SF 中,你可以选择一个状态是应该外部化还是共置的。此外,SF 会确保它的高可用性和持久性。
诊断和监控微服务
虽然监控传统应用程序的过程相对直接和简单,但当你拥有数十个或数百个小型服务时,正确的实现方式并不那么明显。这也带来了其他影响,例如:如果一个模块出现故障,实际影响是什么?当你使用单体架构时,应用程序出现任何问题时,你会立即意识到,因为它会直接停止正常工作。而在微服务架构中,如果监控覆盖不到系统的所有领域,你可能会发现难以及时做出反应。在 SF 中,你有多层次的监控,可以按如下方式定义:
-
应用程序监控:这跟踪你的应用程序的使用情况。
-
集群监控:这允许你监控整个 SF 集群,从而验证整个服务是否按预期运行。
-
性能监控:有时候,如果不监控应用程序的性能,可能很难理解其行为。在 SF 中,追踪资源利用率并预测可能的问题更为容易。
-
健康监控:在使用微服务时,了解特定模块是否健康至关重要。在 SF 中,你可以利用健康 API 或在 SF Explorer 中提供的健康报告,更好地了解你应用程序的当前状态。
SF 中的容器、服务和参与者
在开始使用 SF 之前,我们将讨论三个主要主题,帮助你在逻辑和物理上划分应用程序:
-
容器:小型、可部署的组件,彼此隔离,使你能够虚拟化底层操作系统
-
可靠服务:SD 中可用的一种编程模型,用于编写和管理有状态和无状态的服务
-
可靠的参与者:在可靠服务之上的另一种编程模型
容器
目前,SF 支持两种类型的容器:
-
Linux 上的 Docker
-
Windows Server 2016 上的 Windows Server 容器
在 SF 中使用容器时,你可以使用任何编程语言或框架(如你所期望的那样),但与这种模型相关的最重要的事情是,你不必坚持使用内置的编程模型(可靠参与者和可靠服务)。更重要的是,这种方法非常类似于运行所谓的来宾可执行文件,你可以将现有的可执行文件部署到 SF 中。
创建一个集群
在开始使用容器之前,我们需要创建一个 SF 集群。为此,进入 Azure 门户并点击 + 创建资源。搜索 Service Fabric Cluster 并点击创建。你将看到一个熟悉的界面,需要在多个字段中填写集群配置。在 SF 中,创建过程分为四个不同的步骤,我们将逐一介绍。
在第一个屏幕上,你需要输入有关集群的基本信息:

有一个字段可能需要稍作澄清,即 操作系统。如本章开头所提到,SF 支持 Windows 和 Linux 容器,你可以在此处进行选择。选择会影响定价和可用功能,因此你必须确认自己使用的是哪个操作系统。
一旦你对设置满意,就可以继续到下一个屏幕:

现在我们开始实际的集群配置。你需要做的第一件事是选择 Node 类型计数。为了选择正确的版本,你需要理解它的实际含义。此属性定义了以下内容:
-
虚拟机(VM)大小
-
虚拟机的数量
-
虚拟机的属性
所以,简而言之——如果你需要两种不同类型的机器(例如,你有一个轻量级前端和一个重量级后端),你将选择两种不同的节点类型。
记住,在集群创建后,你始终可以添加或移除节点,但你总是需要至少保留一个节点。
在 Node 类型配置面板上,你需要选择虚拟机的大小及其容量,并为节点选择一个名称。你还可以 配置高级选项,但由于我们刚刚开始使用 SF,我不建议在那里更改任何内容。
使用少于五台虚拟机最初会将集群标记为测试集群。SF 要求你运行五台或更多虚拟机的原因,是为了确保你的解决方案能更好地应对 同时 故障。你仍然可以使用测试集群来运行生产工作负载,但不推荐这么做。
现在点击 OK 继续。倒数第二个屏幕允许你配置集群的安全功能:

你可以选择 Basic 或 Custom 配置类型——它们的区别在于,使用 Basic 时,系统会为你创建证书,而选择 Custom 时,你可以自己输入证书信息。此外, SF 要求你选择一个密钥保管库(或创建一个新的)来存储证书。
本书不会涵盖 Azure 密钥保管库。如果你想了解更多关于该服务的信息,请查看文档——docs.microsoft.com/en-us/azure/key-vault/
当一切设置好并准备就绪时,你可以点击“确定”,然后你将看到包含集群配置摘要的最终屏幕。验证显示的所有信息,如果准备好创建集群,点击“创建”。当你进入创建集群时选择的资源组,你应该会看到与我类似的设置:

如你所见,它已经包含了许多不同的服务:
-
虚拟机规模集:为了确保你能轻松扩展,SF 使用虚拟机规模集来自动化整个过程
-
Service Fabric 集群:实际的 SF 服务
-
负载均衡器:在你的机器之间分配负载
-
公共 IP 地址:使你的应用程序可公开访问
-
存储账户:用于存储数据
-
虚拟网络:为了确保机器之间的安全和便捷的通信,SF 使用 Azure 虚拟网络将虚拟机规模集中的机器连接起来
现在我们已经配置并运行了一个集群,可以继续部署 Docker 容器。
部署容器
要在 SF 中使用 Docker 镜像,我们需要在 Azure 容器注册表中有一个注册表。你可以回到 第三章,将 Web 应用程序部署为容器,在那里我详细描述了如何使用 ACR 和 Docker。
现在我们将尝试部署一个简单的 Python 应用程序——首先,我们当然需要 Dockerfile:
FROM python:2.7-slim
WORKDIR /app
ADD . /app
RUN pip install -r requirements.txt
EXPOSE 80
ENV NAME World
CMD ["python", "app.py"]
此外,我们将创建一个 Python 应用程序,显示简单的文本:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return 'This is my first Service Fabric app!'
if __name__ == "__main__":
app.run(host='0.0.0.0', port=80)
现在,运行 docker build 命令:
docker build -t handsonservicefabricapp .
我们将能够通过输入以下命令在本地进行测试和运行:
docker run -d -p 4000:80 --name handsonsf handsonservicefabricapp
如你所见,一切正常工作——我们可以继续推送镜像并部署它:

要推送容器镜像,当然你需要一个注册表。如果你想使用 Azure 容器注册表,请参考前一章的详细说明。
现在,你将需要三个 Docker 命令:
-
docker login:用于在 ACR 中进行身份验证 -
docker tag: 创建镜像的别名,并将其放入正确的命名空间 -
docker push:将镜像部署到注册表
以下是完整的语法:
docker login handsonazureregistry.azurecr.io -u {USERNAME} -p {PASSWORD}
docker tag handsonservicefabricapp handsonazureregistry.azurecr.io/sf/handsonservicefabricapp
docker push handsonazureregistry.azurecr.io/sf/handsonservicefabricapp
打包服务
为了打包我们的服务,我们将使用 Yeoman 与 SF Yeoman 容器生成器。为此,你需要先安装它们——在命令行中执行以下两个命令:
npm install -g yo
npm install -g generator-azuresfcontainer
我们还需要做一件事——因为容器镜像将从 ACR 获取,所以我们必须在 ApplicationManifest.xml 中配置其凭证。用户名可以在容器注册表的概览面板中找到,而密码则需要运行以下两个命令:
az acr login --name {REGISTRY_NAME}
az acr credential show -n {REGISTRY_NAME} --query passwords[0].value
现在,我们需要更新 Yeoman 生成的清单,以便它使用我们的凭证:
<ServiceManifestImport>
<ServiceManifestRef ServiceManifestName="HandsOnServicePkg" ServiceManifestVersion="1.0.0" />
<Policies>
<ContainerHostPolicies CodePackageRef="Code">
<PortBinding ContainerPort="80" EndpointRef="HandsOnServiceEndpoint"/>
<RepositoryCredentials AccountName="{LOGIN}" Password="{PASSWORD}" PasswordEncrypted="false"/>
</ContainerHostPolicies>
</Policies>
</ServiceManifestImport>
现在,使用以下命令登录到你的集群:
sfctl cluster select --endpoint https://{ENDPOINT}:19000 --pem {CERTIFICATE}.pem --no-verify
最后,运行 Yeoman 生成的install.ps1文件并等待片刻——你的应用程序镜像应该会被部署到云中的 SF 集群,并且完全可用。
若要获取证书,你可以从密钥保管库中的证书选项卡下载。
可靠服务
在这一部分中,我们将尝试使用 SF 创建无状态和有状态服务。这次,我们将使用 Visual Studio 创建一个 C#应用程序,并将其部署到我们的集群。你也可以从 Linux 上的可靠服务开始,但这本书不会覆盖这一部分。请参考进一步阅读部分,了解相关文档链接。
创建一个 SF 应用程序
当你打开 Visual Studio 实例时,进入文件 | 新建项目。在新窗口中搜索 Visual C# | 云模板;你应该能够找到 Service Fabric Application 选项:

如果你找不到这个选项,请确保你已经安装了 SF SDK。
在下一屏幕上,你将看到许多不同的选项,我们稍后会讨论其中的大多数。现在,选择无状态服务(Stateless Service)并点击确定(OK):

稍等片刻后,你应该能够看到一个无状态服务模板已经通过 SF 构建完成。你现在可以按F5来查看它在所有默认值下的工作情况:

如果你在启动本地 SF 实例时遇到问题,请确保你以管理员身份启动了 Visual Studio。
如你所见,每秒都会发布一条消息——Working-{N}。请看一下RunAsync()方法:
protected override async Task RunAsync(CancellationToken cancellationToken)
{
long iterations = 0;
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
ServiceEventSource.Current.ServiceMessage(this.Context, "Working-{0}", ++iterations);
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
}
你将看到它是这些消息的来源。事实上,它是你的服务的起点,当服务启动时会调用它。它还接受一个参数,cancellationToken,用于告知你以下任意一种情况:
-
你的代码中发生了致命错误,当前服务处于无效状态
-
集群中发生了硬件故障
-
正在进行升级
-
当前的服务实例不再需要
记得确保RunAsync()方法应该返回一个任务。系统会等待直到服务执行完毕,因此如果你发现请求了取消操作,务必尽可能快速地完成。
现在我们尝试添加一个有状态服务——为此,向解决方案中添加一个新的 Service Fabric 应用程序项目,但这次选择有状态服务(Stateful Service):

如果你将无状态的RunAsync()与有状态的进行对比,你将看到许多不同之处:
protected override async Task RunAsync(CancellationToken cancellationToken)
{
var myDictionary = await this.StateManager.GetOrAddAsync<IReliableDictionary<string, long>>("myDictionary");
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
using (var tx = this.StateManager.CreateTransaction())
{
var result = await myDictionary.TryGetValueAsync(tx, "Counter");
ServiceEventSource.Current.ServiceMessage(this.Context, "Current Counter Value: {0}",
result.HasValue ? result.Value.ToString() : "Value does not exist.");
await myDictionary.AddOrUpdateAsync(tx, "Counter", 0, (key, value) => ++value);
// If an exception is thrown before calling CommitAsync, the transaction aborts, all changes are
// discarded, and nothing is saved to the secondary replicas.
await tx.CommitAsync();
}
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
}
最重要的是直接引用状态——在有状态服务中,我们有状态管理器,它使你能够查询状态并在事务中执行操作。在之前的例子中,我们从中获取了一个 IReliableDictionary<> 类型的字典——这是一个可靠的集合,用来存储数据并将其复制到其他机器上。实际上,它与 IDictionary<> 是相同的字典,但这次对集合的操作是异步的,因为数据必须持久化到磁盘上。
记住,你存储在状态管理器中的所有内容都必须是可序列化的。
当你启动应用程序时,你会看到两个服务同时运行:

现在我们想将我们的简单应用程序发布到 Azure,看看它是否真的有效。
将应用程序部署到云端
如果你查看一下,你会看到我们的解决方案现在包含了三个项目:
-
无状态服务
-
有状态服务
-
SF 项目
现在右键点击 SF 项目,点击“发布...”选项:

在新屏幕上,你可以配置一些内容,如目标配置文件或应用程序参数文件,但最有趣的是连接端点,它现在是空的。实际上,如果不选择一个选项,你无法继续操作,所以让我们打开下拉菜单,看看我们的选项。在可用选项中,你会看到以下内容:
-
本地集群
-
创建新集群
-
使用试用集群
-
刷新
虽然本地/新集群选项不言自明,但你可能会想知道什么是“试用”选项。当你选择它时,你将获得一个选项来登录到所谓的 派对集群。这些是免费的 SF 集群,你可以用它们来稍微玩一下这个服务,了解它的工作原理。你不需要订阅,但一个小时后集群将被关闭。因为我们在本书中学习的是 Azure 中的服务,我不会使用该选项,但如果你决定深入了解 SF,而不仅仅是本章所能提供的内容,随时可以使用它。
当你选择“创建新集群”选项时,屏幕上会出现整个 SF 集群配置。这与门户中看到的非常相似——它具有相同的部分,如集群、证书和虚拟机详情。
从 Visual Studio 创建 SF 集群有一个缺点——你不知道推荐的值是什么,也没有直接参考文档。
以下是我从第一个标签页中的配置:

如您所见,我将节点数量设置为1——这是因为我不打算部署生产工作负载,并且不需要两种不同特征的虚拟机,因为我的两个服务基本相同。当您点击“下一步”时,您将看到第二个标签,在该标签中您将指定证书密码。如果您保留选中“导入证书”选项,则会自动创建并导入证书:

在 SF 中,证书是确保节点到节点以及客户端到节点通信安全的一种方式。这些是 X.509 证书,更重要的是,至少要保持一个有效证书——如果证书无效,甚至可能导致集群停止工作。
下一个标签是 VM 详细信息,您可以在其中指定运行集群的机器的详细信息。在提供了用户名和密码后,您需要选择将用于运行 SF 的操作系统以及每个虚拟机的大小。输入所有必需信息后,您可以点击“创建”:

部署 SF 集群可能需要一些时间,所以请耐心等待。一旦完成,您可以在 Azure 门户中查看,确认整个生态系统的所有部分都已部署。然而,当您进入 Azure 中的 SF 集群时,您会看到它没有任何应用程序或节点附加。这是因为我们刚刚创建了它,并没有部署任何内容。因此,我们需要返回 Visual Studio,再次点击“发布”,这次选择我们刚刚创建的集群:

现在,让我们去 Azure 并探索我们的应用程序。在我们的 SF 实例的概览页面上,有一个“Explorer”按钮:

当您点击它时,新的窗口将在您的浏览器中打开,您将被要求选择一个证书,该证书将用于安全连接。记得选择在 SF 集群创建过程中创建并导出的那个证书。接受后,您应该能够看到 Service Fabric Explorer:

恭喜——您刚刚使用 SF 创建了您的第一个微服务架构!
可靠演员
在上一节中,我们创建了一个由两个服务组成的应用程序——一个有状态服务和一个无状态服务。在 SF 中,有许多不同的框架来构建系统——另一个是可靠演员。它旨在创建一个可以并发和独立运行的分布式服务平台——因为每个演员是隔离的,一个实例的问题不会影响其他正在运行的实例。您可能会想知道什么时候选择可靠服务,什么时候选择可靠演员?经验法则可以这样定义:
-
如果您需要将工作分配给多个工作者(例如成百上千个),请选择“可靠演员”
-
如果你想隔离你的工作并希望一个单线程环境以简化操作,选择可靠演员
-
如果你的业务领域要求在事务中执行工作,选择可靠服务
-
如果你的服务必须是可靠且高可用的,选择可靠服务
当然,上述原因并不能涵盖所有可能的场景,但你现在应该能够理解它们之间的区别了。一般来说,你不能期望演员们会非常耐用,因为其核心思想是生成新的演员,并将工作负载转移给它们,而不是确保它们能够无限期地工作。
创建一个演员项目
我们将通过在 Visual Studio 中创建一个全新的项目来开始我们与可靠演员的旅程——为此,点击文件 | 新建项目,再次选择 SF 模板。在“新建服务”屏幕中,选择“演员服务”模板并点击确定:

在你的项目初始化后,你会发现它与可靠服务的项目略有不同——最重要的是,它现在包含了 .Interfaces 项目,目前该项目仅包含一个文件,内容如下:
[assembly: FabricTransportActorRemotingProvider(RemotingListener = RemotingListener.V2Listener, RemotingClient = RemotingClient.V2Client)]
namespace ReliableActor.Interfaces
{
/// <summary>
/// This interface defines the methods exposed by an actor.
/// Clients use this interface to interact with the actor that implements it.
/// </summary>
public interface IReliableActor : IActor
{
/// <summary>
/// TODO: Replace with your own actor method.
/// </summary>
/// <returns></returns>
Task<int> GetCountAsync(CancellationToken cancellationToken);
/// <summary>
/// TODO: Replace with your own actor method.
/// </summary>
/// <param name="count"></param>
/// <returns></returns>
Task SetCountAsync(int count, CancellationToken cancellationToken);
}
}
它将作为演员和其客户端之间的通信点。你可以把它看作是一个契约聚合器。现在,检查一下主要演员项目(在我的例子中是 ReliableActor)——你会在那里找到之前接口的当前实现。以下是当前代码:
namespace ReliableActor
{
[StatePersistence(StatePersistence.Persisted)]
internal class ReliableActor : Actor, IReliableActor
{
public ReliableActor(ActorService actorService, ActorId actorId)
: base(actorService, actorId)
{
}
protected override Task OnActivateAsync()
{
ActorEventSource.Current.ActorMessage(this, "Actor activated.");
return this.StateManager.TryAddStateAsync("count", 0);
}
Task<int> IReliableActor.GetCountAsync(CancellationToken cancellationToken)
{
return this.StateManager.GetStateAsync<int>("count", cancellationToken);
}
Task IReliableActor.SetCountAsync(int count, CancellationToken cancellationToken)
{
return this.StateManager.AddOrUpdateStateAsync("count", count, (key, value) => count > value ? count : value, cancellationToken);
}
}
}
每个演员实现都带有 [StatePersistence] 特性。它有三种不同的选项:
-
持久化:在这里,状态会被持久化到磁盘并复制到副本(三个或更多)。这是最耐用的选项,即使在整个集群故障的情况下也能防止丢失数据。
-
易失性:与其将状态持久化到磁盘,不如将其复制并存储在三个或更多副本的内存中。这是一个较不耐用的选项,类似于仅将数据保存在 RAM 中,一旦断电,数据会丢失。
-
无:如果你不需要持久化状态,可以选择此选项。
在这里没有最佳选择——一切取决于你的演员需求。请再注意一点——演员本身并不局限于某种“硬性”契约;你可以自己定义代码,SF 会尽最大努力复制它,持久化状态(如有需要),并进行横向扩展以满足你的需求。目前,我们只有一个工作者——我们还需要一个客户端来测试我们的服务。
创建一个演员的客户端
为了创建一个客户端,我们将使用最传统的控制台应用程序。再次点击文件 | 新建项目并搜索,或者直接右键点击解决方案并点击添加 | 新建项目。在我们编写代码之前,你需要再添加两个内容:
-
添加对
.Interfaces项目的引用,因为我们必须知道要调用哪些方法 -
安装
Microsoft.ServiceFabric.Actors包
我们将编写一个简单的应用程序,该应用程序调用我们的 Actor,获取当前的计数值并更新它。以下是我的示例代码:
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.ServiceFabric.Actors;
using Microsoft.ServiceFabric.Actors.Client;
using ReliableActor.Interfaces;
namespace ReliableActor.Client
{
class Program
{
static void Main()
{
MainAsync().GetAwaiter().GetResult();
}
static async Task MainAsync()
{
IReliableActor actor = ActorProxy.Create<IReliableActor>(ActorId.CreateRandom(), new Uri("fabric:/ReliableActors/ReliableActorService"));
while (true)
{
var count = await actor.GetCountAsync(CancellationToken.None);
Console.Write($"Current count is: {count}\r\n");
await actor.SetCountAsync(++count, CancellationToken.None);
Thread.Sleep(1000);
}
}
}
}
正如你所看到的,我做了三件事:
-
我通过使用
fabric协议和我创建的特定接口来获取对我的 Actor 服务的引用。 -
为了获取计数值,我在我的引用上调用了
GetCountAsync()方法。 -
我通过调用
SetCountAsync()来更新状态。
以下显示了同时运行 Actor 和客户端的结果:

很好——一切按预期工作。你可能会好奇 Actor 实例是如何在 SF 集群中分配的,以及我们是如何实现数百个实例的分配的。其实,这一切都由 SF 运行时处理,它通过对实例进行分区并将它们附加到集群中的不同节点来实现。正因为如此,你可以期待负载会得到平衡——更重要的是,你可以通过 Actor 的 ID 来引用它(与展示的ActorId.CreateRandom()方法不同),但并不总是推荐这么做,因为你必须确保不会使某个 Actor 超负荷。
服务之间的通信
你现在已经知道如何使用 SF(服务框架)来处理可靠服务和可靠 Actor。下一个重要话题是关于服务实例之间的通信。正如我们在本章开始时讨论的,构建微服务时最好的选择是创建一个平台,使其在选择最佳的通信方式和接受传入请求时保持中立。在 SF 中,你并没有单一的消息交换方式——相反,它为你提供了一个完整的框架,让你可以按照自己的方式进行操作。在这一部分,我们将重点构建一个简单的通信通道来支持你的服务。
创建通信通道
要创建一个通道,你需要实现以下接口:
public interface ICommunicationListener
{
Task<string> OpenAsync(CancellationToken cancellationToken);
Task CloseAsync(CancellationToken cancellationToken);
void Abort();
}
正如你所看到的,关于使用的技术或框架没有任何信息——这完全取决于你。我们将尝试在我们的服务中开启 HTTP 协议。由于本书无法覆盖所有类型的服务,我们将重点介绍无状态服务。如果你从关于它的章节打开项目,在服务的主文件中,你可以找到以下方法:
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new ServiceInstanceListener[0];
}
目前,它返回的是一个空数组——我们需要提供一个自定义的ServiceInstanceListener实现并将其添加到这里。虽然当然可以这么做,但描述它的细节会花费太多时间;因此,我们使用一个 NuGet 包,它包含了一个正确实现的监听器,并且是基于 ASP.NET Core 构建的。
目前,可以使用 Http Sys 或 Kestrel 来解决通信问题。请随意进行实验,因为这些技术有所不同,且它们总是可以互相替代(例如,Http Sys 目前并不适用于有状态服务)。
对于这个练习,请安装以下包:Microsoft.ServiceFabric.AspNetCore.HttpSys。安装后,你可以如下修改CreateServiceInstanceListeners()方法:
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new[]
{
new ServiceInstanceListener(serviceContext =>
new HttpSysCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
new WebHostBuilder()
.UseHttpSys()
.ConfigureServices(
services => services
.AddSingleton<StatelessServiceContext>(serviceContext))
.UseContentRoot(Directory.GetCurrentDirectory())
.UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
.UseStartup<Startup>()
.UseUrls(url)
.Build()))
};
}
如你所见,它提供了完整的监听器实现和处理请求的管道。你还需要实现Startup类,来处理通信:
public class Startup
{
public Startup(IHostingEnvironment env)
{
}
public Startup(IApplicationBuilder appenv, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
}
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app)
{
app.Run(context => {
return context.Response.WriteAsync("Hello From Service Fabric!");
});
}
}
这看起来可能很熟悉,因为这是一个简单的 ASP.NET Core 堆栈,你会以相同的方式实现,创建一个不托管在 SF 中的 Web 应用程序。目前只有一件事需要修改——我们必须修改ServiceManifest.xml并指定我们的端点是由服务暴露的:
<Resources>
<Endpoints>
<Endpoint Name="ServiceEndpoint" Protocol="http" Port="80" />
</Endpoints>
</Resources>
记住,端点名称必须与在代码中定义的名称匹配。
现在,当你运行应用程序时,你应该能够通过使用暴露的端点来调用它,该端点可以在 Service Fabric Explorer 中找到:

以下是从 Postman 应用程序调用我的服务的结果:

现在的问题是:你可以利用这样的功能做些什么?实际上,有很多可能性——你可以在服务之间交换消息,可以查询某个服务当前执行的任务状态,或者可以更改某个状态,以便在运行工作负载时选择不同的路径。这是一个非常强大的功能,除了 SF 为分布式系统带来的所有优势外,你还可以确保你的通信是可靠的,并且完全在你的控制之下。
SF 中的集群
我们已经讨论了 SF 中的集群,但你如何真正理解在该服务中的这一概念呢?你可能还记得,在创建集群时,我们需要选择节点类型及其特性——虚拟机的数量及其类型。如果你选择有三个节点,每个节点包含五台机器,那么你最终会得到一个总共十五台机器的集群。SF 会自动进行负载均衡,因此如果你改变集群大小,所有服务将会重新部署,以达到最大利用率。本章将简要讨论安全性、可用功能和可扩展性。
以下是集群组织的概念图:

集群安全
如果你回到集群的创建过程,你会注意到我们总是需要创建或导入证书——没有它,就无法继续。你必须记住,确保你的环境安全并防止未经授权的访问是你自己的责任。正如文档所述,创建不安全的集群是不可能的——这当然是正确的。然而,如果你将端点公开暴露(特别是在生产工作负载中),总有可能有人会发现它并开始滥用。
节点间安全
假设你有三个不同的节点,分别用于不同类型的工作负载:

这些节点可能会暴露(或不暴露)给外部网络。现在,你可能会想知道 SF 是如何确保通信以安全的方式处理的。实际上,有两种可能性:
-
证书安全性:在这种场景下,客户端(节点)会将凭证附加到每个请求中,并用私钥签名消息
-
Windows 安全性:基于 Kerberos 协议
最终的解决方案取决于你的实际需求。
客户端与节点的安全性
除了在集群内进行通信外,你还可能需要允许授权用户与单个节点交换消息。事实上,这与 节点间安全性 相似——在这种情况下,你可以选择使用证书或 Active Directory(AD)安全性。在这种场景下使用 AD 的优势是什么?有一个非常重要的方面——在大多数情况下,你不想与客户端共享证书(尤其是当证书数量很多时,这会变得非常繁琐)。AD 安全性可以通过在 ARM 模板中提供额外的选项进行设置:
"azureActiveDirectory": {
"tenantId": "<guid>",
"clusterApplication": "<guid>",
"clientApplication": "<guid>"
}
扩展
扩展是 SF 最重要的功能之一,因为微服务架构的核心就是能够在需要时进行扩展,并在此过程中保持稳定的环境。在大多数情况下,你需要水平扩展(通过增加更多的机器来运行工作负载),但当然,也可以进行垂直扩展(这样更强大的机器就能投入使用,并且在某些情况下,这比水平扩展更合适)。通常,是否选择水平扩展或垂直扩展,取决于你的代码需要执行的工作:
-
如果你的工作可以并行化,选择水平扩展
-
如果你的工作涉及大量计算并且 I/O 密集型,且这些操作无法分布式处理,选择水平扩展
更重要的是,SF 中的每个节点类型都是一个独立的虚拟机扩展集。这意味着你可以根据需要独立地扩展节点。这是一个非常重要的特性——如果只有系统的某一部分需要更多的计算能力,而你还需要更新整个集群,你可能不会很高兴。
在 SF 中,每个节点在扩展时有特定的要求,因为保持正确数量的节点运行生产工作负载是很重要的。详细信息可以在文档中找到;你可以在本章末尾的 进一步阅读 部分找到相关链接。
扩展集群的规模
一般来说,不建议对集群进行上下扩展,因为这是一项危险的操作(特别是如果你想更改主节点的 VM SKU)。如果你想知道原因,请考虑以下操作——你即将缩减一个节点。这是一项基础设施操作,需要为你的应用程序更改可用硬件。如果你没有正确监控和编排所有操作,你可能会导致你的有状态服务丢失数据(例如,暂时无法访问数据库),甚至无状态服务也可能变得不稳定。实际上,要安全地进行上下扩展,你应该首先创建一个新的节点类型,然后逐渐减少旧节点的实例计数,以便 SF 能够在关闭旧节点之前正确地分发工作负载。
SF 文档指出,在运行主节点的 VM 上更改 SKU 是非常不建议的。然而,当小心操作时,这是可能的;你可以在本章末尾的Further reading部分找到一篇指南的链接。
监视和诊断
在本章的最后一节,我们将涵盖一些关于在 SF 中监视和诊断你的服务的主题。正如你可能记得的那样,我将这些功能视为微服务中最重要的之一,因为你必须始终能够知道每个服务如何工作,并在需要时执行必要的操作(例如扩展、重启或终止实例)。在 SF 中,有几个监控层次,我们将在这里简要描述。
应用程序监控
在大多数情况下,你希望监视你的应用程序如何工作,用户流量是多少,以及你的服务如何相互通信。虽然你可以自己带入框架,但也可以利用应用程序洞察(AI)集成——这将确保你获取所有必要的日志和可用的诊断消息。你可以在创建 SF 集群时设置 AI 集成:

集群监控
在工作过程中,你的集群会发出各种事件,这些事件映射到其中的一个特定实体:
-
集群
-
应用程序
-
服务
-
分区
-
副本
-
容器
你可以通过利用 SF 中提供的EventStore服务查询这些事件。可以对它们进行关联,以便找出一个实体如何影响其他实体。以下是从特定时间范围返回事件的 API 示例请求:
http://{CLUSTER}:19080/EventsStore/Cluster/Events?api-version=6.2-preview&StartTimeUtc=2018-04-03T18:00:00Z&EndTimeUtc=2018-07-04T18:00:00Z
健康监控
除了监视你的应用程序和集群外,你还需要检查特定服务的工作情况。为了检查其健康状况,SF 引入了 Service Fabric Explorer,你可以访问以验证以下内容的健康状况:
-
节点
-
应用程序
-
服务
-
分区
-
副本
你可以在本地和云端访问 Explorer。它呈现了一个不错的用户界面,显示了大量有用的信息,你可以利用这些信息来确定系统的当前状态,发现潜在问题,并获取必要的细节以进一步调查问题:

总结
本章中,我们只是简单介绍了 SF 及其构建的微服务。你已经学习了 SF 的基本概念,例如可靠服务和可靠演员,以及如何实现一个通信协议以便在服务与客户端之间交换消息。记住,基于微服务构建应用程序并非易事,它要求遵循许多重要规则,以避免处理状态、监控或扩展时出现问题。将 SF 作为构建分布式应用程序的框架,它确保应用程序的可靠性和高可用性。
最后但同样重要的是:如果你在使用 SF 时遇到问题,或者被其众多选项和配置所困扰,不要灰心——这项服务确实有一个相当陡峭的学习曲线,但在阅读完整本章节后,你应该能够毫不费力地开始编写自己的服务。
在第三章《将 Web 应用程序部署为容器》中,你将了解 Azure 中另一个 PaaS 服务:Azure Search,它允许你使用自己的搜索引擎对存储的文档进行索引和查询。
问题
-
可靠服务和可靠演员之间有什么区别?
-
无状态服务和有状态服务之间有什么区别?
-
为了在 SF 中引入自己独立的通信通道,必须实现哪些内容?
-
SF 中的节点类型是什么?
-
你可以单独扩展(向上/向外)节点类型吗?
-
创建集群时,你可以选择虚拟机 SKU 吗?
-
SF 中有哪两种类型的节点安全性?
-
集群、应用程序、服务、分区和副本之间有什么区别?
-
为什么 SF 建议创建至少包含五个虚拟机的节点?
-
SF 中的可靠性等级是什么?
进一步阅读
-
docs.microsoft.com/en-us/azure/service-fabric/service-fabric-technical-overview -
docs.microsoft.com/en-us/azure/service-fabric/service-fabric-cluster-creation-via-arm -
docs.microsoft.com/en-us/azure/service-fabric/service-fabric-cluster-scaling -
docs.microsoft.com/en-us/azure/service-fabric/service-fabric-work-with-reliable-collections
第五章:使用 Azure 搜索
当需要使用搜索引擎时,使用那些经过测试且在市场上存在已久的知名解决方案总是一个不错的选择。其中一种解决方案是 Azure 搜索,它提供了一个“搜索即服务”的云解决方案,并为开发者提供了 API,使得用户能够专注于开发一个有效的解决方案,而无需管理基础设施或配置。随着最近加入的认知搜索 API,我们现在可以利用人工智能功能增强搜索功能,从而将非结构化内容转化为可搜索的内容。
本章将涵盖以下主题:
-
在你的项目中使用 Azure 搜索
-
根据需求使用全文搜索
-
使用语言学分析
-
使用索引、索引定义和索引器
-
新的认知搜索 API 支持 AI 的索引工作负载
技术要求
要执行本章中的练习,你需要以下资源:
-
访问 Azure 订阅
-
发送 HTTP 请求的工具(cURL 和 Postman)
创建 Azure 搜索服务
创建 Azure 搜索是一个简单的任务,不会花费超过几分钟。这里需要记住的重要一点是,如果你需要重新创建服务,那么无法备份和恢复数据——如果你在这里犯错,所有的工作都必须从头开始。
重建的原因通常是更改搜索索引列的数据类型或其他搜索属性。虽然在本次练习中不会有问题,但在创建生产工作负载时请牢记这一点。
使用 Azure 门户
与 Azure 中的大多数服务一样,我们将从在门户中创建 Azure 搜索开始。为此,点击 + 创建资源并搜索 Azure Search。在介绍页面,点击创建按钮,这将带你进入此服务的配置界面:

如你所见,我们这里提供了相当标准的选项,现在应该不会让你感到困惑。然而,仍然有一个选择定价层级的选项,目前有不同的选项可以选择:
-
免费版:这是最基础的版本,没有可用的副本,且资源是共享的。
-
基础版:该版本提供负载均衡、扩展功能和专用资源。
-
标准版:这是三个不同的层级,提供更多的计算能力和分区。
-
高密度版:与标准版相同,但提供更多的索引(且分区较少)。这个层级特别为 SaaS 开发者设计。
在我们继续之前,你需要了解这些概念:
-
副本:这是你的搜索服务的一个单独实例,承载一个索引的副本。如果你有多个副本,可以对查询进行负载均衡。
-
索引:你可以将其理解为一个包含多行(或者换句话说,文档)的表格,并带有由服务使用的额外元数据。
-
搜索单元(SU):这是 Azure Search 的计费单位,它是副本和分区的组合(SU = 副本 * 分区)。
-
分区:每个分区为您的 Azure Search 实例提供存储和 I/O,因此扩展此单元将提供更多存储和 I/O。
副本和分区之间的主要区别在于,如果您需要更多的计算资源,可以增加分区,而对于更大的查询量,则需要更多的副本(以便查询能够负载均衡)。
填写完所有字段并选择一个层级后,点击创建按钮,稍等片刻,直到服务创建完成。创建后,点击它并进入概览页面。在那里,选择导入数据,这样我们可以先使用一些示例数据,再深入了解 Azure Search:

在下一个页面上,您将看到多个导入记录的选项——您可以选择数据源和索引,并启用认知搜索,后续章节将讲解这个功能:

请记住,虽然可以删除或更改用于索引数据的字段,但这些操作需要重新索引所有文档。重新索引意味着您需要删除然后重新创建索引。然而,您可以在不重新索引文档的情况下添加新字段——在这种情况下,旧文档中的新列值将为 null。
由于我们选择了示例数据,索引已经填充了一些默认值。当您点击确定时,这些值将被验证,索引器页面将变得可用。您可能会好奇什么是索引器。其定义非常简单——它是一个爬虫,查看您的数据源并根据索引和存储信息之间的映射提取所有内容。例如,如果您选择了名为 Status 的字段作为索引的一部分,索引器将搜索所有包含该字段的记录,并将其推送到索引中。
索引器可以配置为一次性抓取数据或按计划抓取。我们将在关于索引和文档索引的部分详细讨论定期索引器。
如果您对导入配置满意,点击确定按钮,稍等片刻,直到数据被导入并索引。现在,我们可以测试服务的运行——在概览页面,您可以找到搜索探索器按钮。当您点击它时,您将看到一个新页面,在那里可以输入查询字符串和请求 URL,您可以在应用程序中使用它们来获取结果:

Azure Search 中的全文搜索
Azure Search 的强大之处在于,当您需要执行全文搜索以找到满足查询的相关文档时。这项 Azure 服务在底层使用 Apache Lucene,这是一个广泛使用的高性能搜索引擎,采用 Java 编写。
你可以在这里找到关于 Lucene 的更多信息:lucene.apache.org/core/。这是一个开源项目,任何人都可以下载。
在本章中,你将学习如何执行全文搜索,了解其语法,并识别潜在问题。
发送请求
在本章的第一部分,你创建了 Azure Search 实例并看到了搜索资源管理器,它使你能够发送简单的查询。现在,我们将扩展我们的请求,以便你可以选择哪些字段用于查询分析、过滤结果以及按特定属性排序。以下是你将用于所有请求的基本 URL:
https://handsonazuresearch.search.windows.net/indexes/realestate-us-sample/docs?api-version=2016-09-01&search=*
当然,这会根据你的 Azure Search 实例名称、索引名称和使用的版本有所不同。URL 模板可以定义如下:
https://[service name].search.windows.net/indexes/[index name]/docs?[query parameters]
正如你所看到的,在这个示例中,我使用了 *,这基本上意味着我对所有已编制索引的文档感兴趣。然而,在继续之前,我们需要做一件事——与大多数 API 一样,Azure Search 是有安全保护的,需要一个密钥来授权请求。如果你没有发送密钥,你将收到一个 HTTP 403 response。要获取密钥,请访问 Azure Portal 并选择 Keys 面板:

现在,每次向你的 API 发送请求时,你都需要使用 api-key 头部并提供适当的值。这里是一个示例:
GET /indexes/realestate-us-sample/docs?api-version=2016-09-01&search=* HTTP/1.1
Host: handsonazuresearch.search.windows.net
api-key: 38B4F66ACD480406328C62273C056CA4
Cache-Control: no-cache
然而,在大多数情况下,我们并不关心所有可用文档——我们有特定的参数想要使用。假设你想搜索一个特定的城市。在这种情况下,我们必须使用另一个端点并传递有效的负载,这将用于构建查询:
POST /indexes/realestate-us-sample/docs/search?api-version=2016-09-01 HTTP/1.1
Host: handsonazuresearch.search.windows.net
api-key: {API_KEY}
Content-Type: application/json
Cache-Control: no-cache
{
"search": "Sammamish",
"searchFields": "city"
}
如你所见,我将 HTTP 方法改为 POST,并为我的请求使用了 /search 端点。然而,最重要的是请求体——目前我使用了两个字段:
-
search:这是我们的查询字符串,我们用它告诉 Azure Search 我们感兴趣的内容 -
searchFields:在这里我们传递了字段,这些字段应包含我们的查询字符串
请记住,请求体中传递的字段是区分大小写的,如果有多个单词,你应该遵循 camel case 规则。
如果你在示例索引上运行前面的查询,你应该能够看到返回的一些结果。如果你搜索的城市不在已编制索引的文档中,你将看到空结果:
{
"@odata.context": "https://handsonazuresearch.search.windows.net/indexes('realestate-us-sample')/$metadata#docs",
"value": []
}
你可能会问,选择搜索字段的规则是什么——唯一的要求是它必须被标记为Searchable。看看如果我使用 beds 来搜索特定数量的记录会发生什么:
{
"error": {
"code": "",
"message": "The field 'beds' in the search field list is not searchable.\r\nParameter name: searchFields"
}
}
看起来我们不能随意使用任何字段。你可以在索引屏幕中查看哪些字段可以用于搜索:

实际上,你不能将任何 Edm.Int32 类型的字段用作 Searchable。还有其他一些类型也不被支持(例如,Edm.GeographyPoint)——你可以在构建或修改用于索引的字段时找到有关它们的信息。
为了解决前面提到的问题,你可以使用过滤器——这些是基于 OData 语法的表达式,你可以利用它们来搜索你感兴趣的实体。唯一的要求是将你想用作过滤器的字段标记为 filterable。在这里,你可以找到所有可以在此类 HTTP 请求中使用的字段:
{
"count": true | false(default),
"facets": ["facet_expression_1", "facet_expression_2", ...],
"filter": "odata_filter_expression",
"highlight": "highlight_field_1, highlight_field_2, ...",
"highlightPreTag": "pre_tag",
"highlightPostTag": "post_tag",
"minimumCoverage": #( % of index that must be covered to declare query successful; default 100),
"orderby": "orderby_expression",
"scoringParameters": ["scoring_parameter_1", "scoring_parameter_2", ...],
"scoringProfile": "scoring_profile_name",
"search": "simple_query_expression",
"searchFields": "field_name_1, field_name_2, ...",
"searchMode": "any" (default) | "all",
"select": "field_name_1, field_name_2, ...",
"skip": #(default 0),
"top": #
}
我们不会涵盖所有内容,因为这将占用整个章节,然而,我们将稍微关注一下发送到 Azure Search 的查询的实际语法。你可能还记得,这项服务使用 Lucene 搜索引擎来索引数据并处理请求。Lucene 支持多种不同的查询类型,例如模糊搜索、通配符搜索等。你可以通过发送 queryType 参数并选择其中一个可用值——simple 或 full(Lucene),来决定使用哪种解析器。
你可以通过阅读以下页面来了解 Lucene 支持的查询操作:docs.microsoft.com/pl-pl/rest/api/searchservice/lucene-query-syntax-in-azure-search。
全文搜索中的语言分析
在使用全文搜索时,你必须了解执行这些操作的规则。搜索引擎必须对搜索查询进行词法分析,才能提取重要信息并将其传递给查询树。在本节中,我们将讨论最常见的词法分析,即语言分析,帮助你理解 Azure Search 如何工作,以及如何执行正确的查询。
Azure Search 中的分析器
为了进行语言分析,Azure Search 支持多种不同的分析器,可以在索引定义中指定。然而,在我们开始定义这些分析器之前,你需要了解我们所谈论的内容。在创建索引时,每个搜索服务必须分析所有输入文档,并决定在执行搜索时哪些内容是重要的。
此外,每个搜索查询应该根据一些常见规则进行调整,以便搜索引擎能够理解。必要的操作可以描述如下:
-
所有非必要的词应被删除(例如,英语中的 "the")。
-
所有单词应转换为小写字母。
-
如果一个词包含多个单词(例如 "up-front"),它应当被拆分成原子单词。
现在,假设你使用以下搜索查询来搜索一个公寓:
Spacious apartment with 4 and the Red Kitchen
你的分析器必须在将查询传递给搜索引擎之前执行所有上述操作,事实上,这里你可以找到这种分析的结果:
{
"@odata.context": "https://handsonazuresearch.search.windows.net/$metadata#Microsoft.Azure.Search.V2016_09_01.AnalyzeResult",
"tokens": [
{
"token": "spacious",
"startOffset": 0,
"endOffset": 8,
"position": 0
},
{
"token": "apartment",
"startOffset": 9,
"endOffset": 18,
"position": 1
},
{
"token": "with",
"startOffset": 19,
"endOffset": 23,
"position": 2
},
{
"token": "4",
"startOffset": 24,
"endOffset": 25,
"position": 3
},
{
"token": "and",
"startOffset": 26,
"endOffset": 29,
"position": 4
},
{
"token": "the",
"startOffset": 30,
"endOffset": 33,
"position": 5
},
{
"token": "red",
"startOffset": 34,
"endOffset": 37,
"position": 6
},
{
"token": "kitchen",
"startOffset": 38,
"endOffset": 45,
"position": 7
}
]
}
如你所见,每个单词都有其特定的位置和偏移量。为了获得与之前相似的结果,你可以发送以下查询:
POST /indexes/[index name]/analyze?api-version=2016-09-01 HTTP/1.1
Host: [service name].search.windows.net
api-key: [api key]
Content-Type: application/json
Cache-Control: no-cache
{
"text": "Spacious apartment with 4 and the Red Kitchen",
"analyzer": "standard"
}
在请求的正文中,你必须提供要分析的文本并使用分析器。请注意,我在这里使用了standard,这意味着使用的是标准的 Lucene 分析器。
Azure Search 支持多种不同语言的分析器——更重要的是,Microsoft 和 Lucene 版本都可用。要获取完整的列表,请访问文档页面:docs.microsoft.com/pl-pl/rest/api/searchservice/language-support。
如果你精通语言分析和语法,你可以创建一个自定义分析器用于文本分析。此类分析器可以在索引创建时定义,然而,我们在本书中不会讨论这个主题,因为它是一个高级练习,超出了我们目前的范围。你可以在本章的进一步阅读部分找到该教程的链接。
分析器选择
你可以在创建索引时或在编辑索引时为特定字段选择一个分析器。如果你进入 Azure 门户并选择你的 Azure Search 实例,你可以点击一个索引并选择“字段”部分。它应该显示该索引中所有字段的列表:

现在,当你向下滚动时,你会看到你能够添加一个新字段。如果你想选择一个分析器,你需要执行以下操作:
-
在该界面顶部选择复选框分析器
-
将“可搜索”选项选择为该字段的选项
现在,你应该看到一个下拉列表,你可以从中选择一个与自定义分析器不同的分析器:

请注意,当字段包含多种语言时,选择不同于自定义分析器的分析器非常重要。在这种情况下,你应该选择适合所使用语言的分析器。
在 Azure Search 中进行索引
索引是 Azure Search 中最重要的构造之一。我们将其定义为包含所有导入文档的表格,表格中定义了可搜索的数据。在本章的开头,你学习了如何创建它并添加或编辑字段。在本节中,你将进一步了解如何修改它,因为索引并不是一个无法更改的固定实体,你可以根据需要进行调整。
导入更多数据
总是需要将更多数据推送到你的索引中——随着应用程序的发展,你的文档存储会越来越大,尤其是当你正在创建文档库,并且希望能够在最新的文档中找到你所搜索的内容时。实际上,有两种方式可以向你的索引中添加数据:
-
推送模型
-
拉取模型
我们将在接下来的章节中讲解它们。
推送模型
推送模型是满足低延迟要求的应用程序的最佳解决方案。与拉取模型不同,对于推送模型,在使用 RESTful API 推送文档后,您的文档将立即被索引。
目前,除使用 RESTful API 或.NET SDK 执行操作外,无法使用其他方法实现推送模型。在拉取模型中,也可以使用 Azure 门户获取数据。
在这里,您可以找到推送文档的示例请求:
POST /indexes/realestate-us-sample/docs/index?api-version=2016-09-01 HTTP/1.1
Host: [service name].search.windows.net
api-key: [api key]
Content-Type: application/json
Cache-Control: no-cache
{
"value": [
{
"listingId": "12344234",
"@search.action": "upload",
"price": 250.0,
"description": "The very apartment in Warsaw",
"city": "Warsaw",
"tags": ["pool", "view", "wifi", "gym"],
"beds": 4,
"location": { "type": "Point", "coordinates": [52.237049, 21.017532] }
}
]
}
如果一切正确,您应该能够看到成功的结果:
{
"@odata.context": "https://handsonazuresearch.search.windows.net/indexes('realestate-us-sample')/$metadata#Collection(Microsoft.Azure.Search.V2016_09_01.IndexResult)",
"value": [
{
"key": "12344234",
"status": true,
"errorMessage": null,
"statusCode": 201
}
]
}
现在,我想检查我的文档是否已被索引并可用:
POST /indexes/realestate-us-sample/docs/search?api-version=2016-09-01 HTTP/1.1
Host: [service name].search.windows.net
api-key: [api key]
Content-Type: application/json
Cache-Control: no-cache
{
"search": "Warsaw",
"searchFields": "city"
}
结果应为包含我们在推送请求中传递的所有字段的文档。
拉取模型
拉取模型与推送模型略有不同,因为它使用索引器来实际获取数据。在使用它时,您需要配置数据源以及数据拉取的频率。与推送模型不同,拉取模型可以在使用 Azure 门户时进行配置和访问。
请注意推送和拉取之间的一个重要区别——在使用推送时,您可以使用任何您想要的数据源。而在使用拉取模型时,您仅限于 Blob 存储、表存储、CosmosDB 和 SQL 数据库(无论是 Azure 上的还是虚拟机中的)。
在这里,您可以找到当使用表存储作为数据源时,拉取数据的索引器配置:

请记住,在使用示例数据时,无法配置拉取策略。
更重要的是,您可以通过点击“索引器”按钮,在概述面板中访问索引器配置和当前状态:

认知搜索 – 为索引工作负载添加 AI
创建和管理索引时,您始终需要确保已选择所有必需字段,并在需要时将其标记为可搜索。此外,我们受到当前服务功能的限制,因此无法使用图像或自然语言处理等功能。幸运的是,Azure 服务即将开始支持认知搜索功能,它将 AI 添加到您的索引操作中,从而使用更多的向量进行更丰富的分析。在本章中,您将学习如何配置它,以便从一开始就能开始使用。
配置认知搜索
认知搜索可以在导入数据时为您的服务进行配置。当您点击“导入数据”按钮时,您会看到其中一个可用的部分正是该功能:

在撰写本文时,此功能仅在美国南中部和西欧地区可用。
有一份可以用于为数据编制索引的认知技能列表。是否使用这些技能取决于你的实际需求——在本练习中,我选择了检测语言。你还可以自定义字段的名称;如果你的索引中包含另一个同名字段,这一点尤为重要,因为它将被添加到查询结果中。当你完成索引配置后,可以将之前的索引与通过 AI 丰富的最新索引进行比较:

如你所见,由于我们没有使用语言字段,因此这里缺少该字段。让我们将其与最新的索引进行比较:

此外,当我使用较新的索引查询服务时,结果中会包含已填充的语言字段:
{
(...)
"location": {
"type": "Point",
"coordinates": [
-122.388,
47.576
],
"crs": {
"type": "name",
"properties": {
"name": "EPSG:4326"
}
}
},
"price": 762048,
"thumbnail": "https://searchdatasets.blob.core.windows.net/images/bd2bt2apt.jpg",
"tags": [
"condominium",
"dream home",
"lake access",
"no outlet",
"miele appliances",
"wall of windows",
"guest room"
],
"language": "en"
}
总结
Azure 搜索是一个出色的服务,适合你拥有自己的搜索解决方案,并且不打算维护其基础设施和配置。凭借其灵活性和直观性,你可以快速开发应用程序,利用推送/拉取模型、定时索引或支持不同数据源等功能。此外,即使对于生产工作负载,从免费层开始也能逐步进展,随着需求的变化调整成本,按实际需求扩展解决方案。在第六章,《使用通知中心的移动通知》中,我们将讨论如何通过 Azure 通知中心处理移动应用程序和推送通知的相关主题。
问题
-
什么是索引?
-
推送模型和拉取模型有什么区别?
-
是否可以使用自定义间隔安排索引器?
-
Azure 搜索默认使用什么分析器?
-
是否可以实现自定义分析器并在 Azure 搜索中使用?
-
分区和副本之间有什么区别?
-
用于授权请求到 Azure 搜索的头部名称是什么?
进一步阅读
-
Azure 搜索文档:
docs.microsoft.com/zh-cn/azure/search/ -
Azure 搜索的认知技能:
docs.microsoft.com/zh-cn/azure/search/cognitive-search-predefined-skills -
Apache Lucene 文档:
lucene.apache.org/core/ -
Azure 搜索中的筛选器:
docs.microsoft.com/zh-cn/rest/api/searchservice/odata-expression-syntax-for-azure-search
第六章:使用通知中心进行移动通知
推送通知是许多移动应用的主要功能之一。它们帮助用户了解等待操作的通知,或者关于仅在应用内存在几分钟的临时折扣。虽然每个移动操作系统供应商都有自己的通知服务,但将此类功能集中配置在一个地方总是很好的,这样我们就不必担心底层 API 或所需参数的变化。Azure 通知中心通过提供一个单一的服务,作为我们移动应用的单一端点,极大简化了开发和测试过程。
本章将涵盖以下主题:
-
通知中心及其好处
-
通知架构以及发送通知到移动应用的最佳模式
-
在通知中心注册设备并保持注册信息
-
向多个供应商发送通知
-
通过通知中心发送富媒体内容的通知
技术要求
要进行本章的练习,你需要以下资源:
-
一个有效的 Azure 订阅
-
安装了通用 Windows 应用工具的 Visual Studio 2017
-
一个 Windows Store 账户
使用通知中心的理由
如果你曾经有机会开发一个与任何通知系统集成的应用程序,你可能会了解在创建这样的产品时所面临的挑战。在本章中,我将尝试向你介绍一些基本概念,如 PNS、推送通知和设备注册。这样,我们就能轻松开始开发利用通知中心功能的应用程序,专注于学习细节和隐藏功能。
本章的练习是为通用 Windows 平台(UWP)应用程序编写的——不过,所介绍的概念也适用于其他平台,如 Android 或 iOS。
应用设计的挑战
假设你有如下架构:

在这里,我们有一个后端系统,将消息发送到三个不同的平台:
-
iOS
-
Android
-
Windows
现在,如果这些消息是推送通知,我们的后端将需要与三个不同的服务进行通信:
-
苹果推送通知服务(APNS)
-
Firebase 云消息推送(FCM)
-
Windows 通知服务(WNS)
这些服务被称为平台通知服务(PNS)。它们的责任是接受发送通知的请求并将其发送到相应的设备。它们还处理希望接收通知的设备的注册(例如,APNS 中的令牌)。这种解决方案的缺点是,这些服务没有共同的接口——我们无法在后台引入一个简单的封装器以相同的方式处理每个请求。解决这种问题的方法是稍微调整我们的架构,使其包含一个能够汇总每个 PNS 逻辑并与其通信的服务:

通知中心就是这样一个服务;它是不同平台通知服务(PNS)的抽象层,能够处理不同的设备注册。我们还可以考虑另外两个问题——扩展性和路由。需要知道的是,根据 PNS 的指南,设备令牌必须在每次应用启动时刷新。现在,如果这是你的后台责任,你可能最终会得到一个试图处理刷新请求的解决方案,而不是专注于业务逻辑。
此外,如果你想向特定设备发送通知,你必须将其标识符存储在某个地方,以便能够将消息路由到该设备。所有这些责任都可以转移到通知服务上,这样所有的额外负担都可以从后台移除。
推送通知架构
创建一个完全依赖推送通知的系统并不是一件简单的事。除了确保你不单独处理每个 PNS 的逻辑,并提供可靠的设备注册和路由系统外,你还必须引入一个消息传递管道,将消息从系统的一个部分传递到最终设备。在本节中,我们将重点介绍如何将通知中心集成到几个参考架构中,这将帮助你理解它的作用以及不同云服务之间的连接。
直接连接
我们能想到的最简单架构是后台与通知服务之间的直接连接:

在这种情况下,每个发送通知请求都由通知服务处理,该服务与不同的 PNS 进行通信。每个 PNS 单独处理请求,并将通知发送到已注册的设备。设备注册由通知服务处理——每个设备必须在通知服务中注册,才能接收通知。请注意,即使在这种简单的场景中,通知服务(在我们的案例中是通知中心)也负责两件重要的事情:
-
为不同的 PNS 提供统一接口
-
处理设备注册和路由
设备永远不会直接与 PNS 本身通信——它们仅通过向通知服务发送请求来接收推送通知。
排队通信
有时,将 Notification Service 暴露给后端并不是最佳选择——它可能变得无响应,出现性能问题,或者只是过载。在这种情况下,拥有一个可以缓存消息并在所有问题解决之前存储它们的服务总是好的。让我们通过添加一个服务来修改之前的架构:

通过引入带有读取器的队列,你可以将后端从处理与 Notification Service 的通信中解脱出来,并将消息传递的责任转移给它们。现在,后端不需要知道如何处理未发送的消息,也不会知道存储它们的存储位置。这个解决方案比之前的方案更容易扩展,因为你不必担心丢失消息——队列应该能够根据需要缓存它们。
确保你的缓存机制在你所处理的业务领域中是合理的。例如,缓存一个通知请求一天,并在那时之后发送通知,在诸如地理定位、特定时间或短期折扣等场景中可能没有意义。
触发的通信
有时,你可能希望根据一些特定的参数或事件触发来发送通知。假设你希望每次上传照片到存储时发送通知:

在这个异步场景中,你有一个事件监听器,它监听事件发布,并根据传递的元数据执行相应的操作。它向 Notification Service 发送请求,后者与 PNS 通信,发送包含适当内容(可能是关于上传状态的信息)的通知。再次,我们看到了拥有作为 PNS 代理服务的优势——整个通信可以是异步的,每个组件都有自己的职责。
在 Notification Hub 中注册设备
要能够实际发送通知,你必须在 PNS 中注册一个设备。如果不使用类似 Notification Hub 这样的服务,你将不得不了解每个 PNS 的个别逻辑,并将设备数据存储在某个地方。这样的挑战在大多数情况下是有问题的,因为通常你不希望自己处理外部依赖关系;而是你的目标是简化整体系统逻辑。在本节中,你将学习如何在 Notification Hub 中处理设备注册以及如何监控它。
Notification Hub 设备注册
当你在 Notification Hub 中注册设备时,实际上是将设备与通知模板和标签关联起来。为了创建这样的链接,你需要一个 PNS 句柄,可以理解为一个特定厂商的标识符(例如令牌或 GCM 注册 ID)。事实上,注册设备有两种方式:
-
使用注册:你需要传递标识符、标签和模板
-
使用安装:增强的注册,包含一组额外的与推送相关的属性
请注意,目前,如果您想使用安装功能,是无法使用 .NET SDK 的—您只能使用服务的 REST API。
我们还需要描述标签和模板,以便充分理解这一过程:
-
标签:这是一种将通知路由到特定设备组(或所有设备)的方法。它允许您对用户进行分段,从而轻松决定谁是消息的接收者;例如,您可以使用
version_Beta将通知发送到使用您应用程序预览版本的有限设备组。 -
模板:这是一种特定的数据模式,设计用于发送到客户端应用程序。它根据使用的推送通知服务(PNS)有所不同,从 JSON 数据到 XML 文档不等。通过使用通知中心,您可以创建一个平台无关的模板,这个模板可以在不同平台之间重复使用。
现在我们将尝试使用两种方法注册设备,并了解它们之间的区别。
创建通知中心
在我们开始发送通知之前,我们必须先有一个已配置并正常运行的通知服务。要创建一个通知中心实例,进入门户并点击 + 创建资源按钮。搜索“通知中心”并点击“创建”。在这里,您可以看到一个已完成的配置:

如您所见,屏幕上没有什么意外情况—需要澄清的唯一事项是定价层级和命名空间:
-
命名空间:您可以在同一个命名空间中拥有多个通知中心。命名空间是您通知中心的逻辑容器,并持有可用推送次数的限制。
-
定价层级:根据选择的层级(免费、基础或标准),您将拥有不同的功能和不同数量的可用推送次数。此外,它还定义了额外推送次数的价格和激活设备的数量。更重要的是,标准层级提供了便利的企业功能,如多租户或定时推送。
对于本次练习,免费层级就足够了。一旦您对配置满意,点击创建按钮,并等待片刻,直到服务创建完成。当创建完成后,您可以进入其页面,看到概览页面:

在那里,您可以点击您创建的中心,查看其功能。我们将在本章稍后介绍这些功能。
在应用程序中注册
在这一部分,我们将尝试使用 Visual Studio 中的 UWP 应用程序进行注册。首先,从本章的源代码中打开 HandsOnAzureApp 项目—您应该能看到一个带有基本代码的空白 UWP 应用程序。
我们在这里将使用一个 UWP 应用程序,因为它是开始使用和操作通知中心的最简单方式。然而,如果你是移动开发者,你可以使用任何你想要的项目类型。
要开始注册,你需要安装一个包来与通知中心进行交互——使用 NuGet 包管理器,搜索WindowsAzure.Messaging.Managed包,该包包含了本练习所需的所有组件。在App.xaml.cs文件中,你需要添加以下代码:
private async void RegisterADevice()
{
var channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();
var hub = new NotificationHub("<hub-name>", "<connection-string>");
var result = await hub.RegisterNativeAsync(channel.Uri);
if (result.RegistrationId == null) return;
var dialog = new MessageDialog("Registration successful: " + result.RegistrationId);
dialog.Commands.Add(new UICommand("OK"));
await dialog.ShowAsync();
}
我们在这里做的事情可以描述如下:
-
我们正在创建一个通知通道,用于注册
-
我们正在定义一个中心,供我们处理通知和注册
-
我们正在注册一个设备,并在操作成功时显示一个对话框
你可能想知道如何获取用于通信的连接字符串;要获取它,请前往 Azure 门户,选择你的通知中心命名空间,然后点击访问策略(Access Policies)选项卡——在那里你会看到一个名为RootManageSharedAccessKey的策略,你可以从中复制连接字符串。
我仅为本练习使用根策略。在生产环境中,最好为每个应用程序创建一个独立的策略,并仅选择所需的权限。
当你输入你的中心名称并粘贴连接字符串后,调用RegisterADevice()方法,放在OnLaunched()中。现在,你可以尝试启动应用程序。如果一切顺利,你应该会看到一个类似于我屏幕的界面:

恭喜你——你刚刚在通知中心注册了你的第一个设备!
检查可用的注册
一旦注册了设备,你需要确保它确实在通知中心中可用。最简单的方式是使用服务器资源管理器(Server Explorer)检查设备注册情况,可以通过在视图(View)菜单中点击它(视图 | 服务器资源管理器)或使用Ctrl + W + L快捷键组合来访问:

现在,当你双击你想要检查的通知中心实例时,你会看到一个新屏幕,包含两个标签——测试发送(Test Send,稍后我们会讲解)和设备注册(Device Registrations)。
通过点击后者,你可以验证所有可用的注册:

使用安装
安装是一个较新的功能,它允许你使用稍有不同的语法和工具处理每个设备注册。它相较于注册有几个重要的优势:
-
虽然可以重复注册(通过两次注册相同的设备),但安装是幂等的。这意味着多次发送相同的安装不会导致创建多个注册记录。
-
使用
HTTP PATCH,你可以更新安装中的特定参数。 -
执行单个推送操作更为简单,因为每个安装包会自动标记安装标识符。在注册时,您需要自己创建这样的标签,并以某种方式维护它,以便获得相同的功能。
正如我在本书的前面部分所说,目前不能在客户端使用 .NET SDK 来使用安装包——要检查此功能,我们必须使用通知中心 RESTful API 或使用后台 SDK。这里,您可以找到调用 API 方法的示例请求:
PUT /<hub>/installations/12234?api-version=2015-01 HTTP/1.1
Host: <namespace>.servicebus.windows.net
Authorization: <authorization token>
Content-Type: application/json
Cache-Control: no-cache
{
"installationId": "12234",
"platform": "wns",
"pushChannel": "<push channel>",
"templates": {
"myTemplate" : {
"body" : '<toast><visual lang="en-US"><binding template="ToastTest01"><text id="1">$myTextProp1</text></binding></visual></tile>',
"headers": { "X-WNS-Type": "wns/toast" },
"tags": ["foo", "bar"]
}
}
}
要生成授权令牌,您需要生成一个 SAS 令牌。您可以在这里找到如何生成它的指南 msdn.microsoft.com/library/azure/dn495627.aspx。
发送通知
通知中心的主要功能是将通知发送到一组已注册的设备。您会看到,使用它的 SDK 和门户,您可以轻松开始使用此功能,而无需了解不同推送通知服务(PNS)的内部逻辑。通过这一部分,您应该能够顺利使用通知中心并将其集成到您的应用程序中。
发送测试通知
在开发应用程序时,您总是需要一种测试它的方法。使用通知中心时,您有两种发送测试通知的选择——要么使用门户,要么使用其 SDK。这两种方式都能达到类似的效果;然而,使用 SDK 更加灵活,因为它能更轻松地找到您希望发送通知的所有设备,或者添加任何逻辑。
在 Azure 门户中的测试通知
当您进入已创建的中心时,您会看到在页面顶部有一个 Test Send 按钮:

当您点击它时,您会看到一个用于测试发送功能的屏幕。这里有一些字段,所有这些字段都依赖于所选的平台。以下是 Windows 平台的示例请求:

现在,如果您点击“发送”按钮,通知中心将选择十个不同的已注册设备,这些设备将接收到通知。如果您愿意,您可以更改发送的类型和负载。更重要的是,您还可以通过指定“发送到标签表达式”选项,将消息发送给特定的设备集合。
SDK 中的测试通知
也可以使用通知中心 SDK 发送测试通知。要使用它,您需要安装以下包:Microsoft.Azure.NotificationHubs。请参考以下示例:
var hub = NotificationHubClient.CreateClientFromConnectionString(
"<connection string>",
"<hub>", true);
最后一个参数允许发送测试通知。这意味着,每次您使用 SDK 发送通知时,它将发送到最多十个已注册的设备。此外,您还会看到每个操作的结果(是否成功或失败)。
请记住,当启用测试模式时,每个对通知中心的请求都会被限流。这意味着您无法过度加载通信通道,因为发送操作将被排队并以受控方式执行。
您可以通过检查 NotificationHubClient 对象上的一个属性来检查是否启用了测试发送:
var hub = NotificationHubClient.CreateClientFromConnectionString(
"<connection string>",
"<hub>", true);
if (hub.EnableTestSend)
{
// Do something....
}
使用 SDK 发送通知
通知中心 SDK 提供了许多不同的方法来发送通知,具体取决于配置和期望的输出。在这里,您可以找到 SDK 中所有可用的方法:
hub.SendAdmNativeNotificationAsync();
hub.SendAppleNativeNotificationAsync();
hub.SendBaiduNativeNotificationAsync();
hub.SendDirectNotificationAsync();
hub.SendNotificationAsync();
hub.SendTemplateNotificationAsync();
hub.SendGcmNativeNotificationAsync();
hub.SendWindowsNativeNotificationAsync();
如您所见,我们有两种不同的类别:
-
原生通知:仅用于向特定平台发送通知的方法
-
通用通知:一组用于向特定标签发送通知的方法
我强烈建议您尝试和测试不同的可能性,因为每种方法略有不同。这里您可以看到调用 SendAppleNativeNotificationAsync() 并序列化输出的结果:
var hub = NotificationHubClient.CreateClientFromConnectionString(
"<connection string>",
"<hub>", true);
hub.SendAppleNativeNotificationAsync("{\"aps\":{\"alert\":\"Notification Hub test notification\"}}");
在我的情况下,结果将如下所示:
{
"Result": {
"Success": 8,
"Failure": 0,
"Results": [{
"ApplicationPlatform": "apple",
"PnsHandle": "<pns handle>",
"RegistrationId": "1013412858828458675-3388525925469165319-3",
"Outcome": "The Notification was successfully sent to the Push Notification System"
}, {
"ApplicationPlatform": "apple",
"PnsHandle": "<pns handle>",
"RegistrationId": "4629243313258036270-2338090353657828558-2",
"Outcome": "The Notification was successfully sent to the Push Notification System"
}, {
"ApplicationPlatform": "apple",
"PnsHandle": "<pns handle>",
"RegistrationId": "5538320565569680693-6905916546981709583-3",
"Outcome": "The Notification was successfully sent to the Push Notification System"
}, {
"ApplicationPlatform": "apple",
"PnsHandle": "<pns handle>",
"RegistrationId": "5711668963446635284-8967913844749790004-1",
"Outcome": "The Notification was successfully sent to the Push Notification System"
}, {
"ApplicationPlatform": "apple",
"PnsHandle": "<pns handle>",
"RegistrationId": "5728263539515349341-3583197654290557965-2",
"Outcome": "The Notification was successfully sent to the Push Notification System"
}, {
"ApplicationPlatform": "apple",
"PnsHandle": "<pns handle>",
"RegistrationId": "6986970356553456728-8953287549645821249-1",
"Outcome": "The Notification was successfully sent to the Push Notification System"
}, {
"ApplicationPlatform": "apple",
"PnsHandle": "<pns handle>",
"RegistrationId": "7231787013272625417-8398074035919763615-3",
"Outcome": "The Notification was successfully sent to the Push Notification System"
}, {
"ApplicationPlatform": "apple",
"PnsHandle": "<pns handle>",
"RegistrationId": "8026985566358875763-8860727728212773916-1",
"Outcome": "The Notification was successfully sent to the Push Notification System"
}]
},
"Id": 9,
"Exception": null,
"Status": 5,
"IsCanceled": false,
"IsCompleted": true,
"CreationOptions": 0,
"AsyncState": null,
"IsFaulted": false
}
如您所见,我们得到了向一组注册设备发送通知的完整结果。您可以利用该输出与您的应用程序进行交互,例如显示适当的状态或报告。
富内容通知
在本章的最后部分,我们将讨论另一种类型的通知,称为 富内容通知。有时您可能希望发送的不仅仅是纯文本。在通知中心中,您可以发送例如图像来增强应用程序的外观和感觉。
请注意,接收富内容通知需要在客户端进行更改。我们将在本章中不涉及该内容,但在章节结束时,您将找到一个链接,其中详细描述了如何进行此类操作。
创建并发送富内容通知
要创建并发送富内容通知,您需要两个东西:
-
通知模型
-
通知负载和内容
这个思路是以一种方式发送通知,使客户端应用程序能够获取富内容并在客户端处理。事实上,最简单的方法是拥有一个 API 提供两个操作:
-
发送通知
-
获取通知数据
在接下来的内容中,您可以找到两个操作的示例代码:
public class HubController : ApiController
{
public static Lazy<NotificationHubClient> Hub = new Lazy<NotificationHubClient>(() =>
NotificationHubClient.CreateClientFromConnectionString("<connection string>", "<hub>"));
[HttpPost]
public async Task<HttpResponseMessage> Send()
{
var notification = new Notification("Hey, check this out!");
var fullNotification = "{\"aps\": {\"content-available\": 1, \"sound\":\"\"}, \"richId\": \"" + notification.Id +
"\", \"richMessage\": \"" + notification.Message + "\", \"richType\": \"" +
notification.RichType + "\"}";
await Hub.Value.SendAppleNativeNotificationAsync(fullNotification, "<tag>");
return Request.CreateResponse(HttpStatusCode.OK);
}
public HttpResponseMessage Get(string id)
{
var image = Notification.ReadImage(id);
var result = new HttpResponseMessage(HttpStatusCode.OK) {Content = new StreamContent(image)};
result.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/{png}");
return result;
}
}
如您所见,我们需要做的唯一事情就是保持正确的架构,以便向特定的 PNS 发送通知。在之前的示例中,我使用了 APNS,但当然也可以使用其他供应商(只要他们的软件支持通过推送通知接收图像或音频)。您可以在本章的源代码中找到示例。
总结
在本章中,你已经了解了什么是通知中心,以及如何使用它将推送通知集成到应用程序中。我们介绍了一些参考架构和可能的场景,这应该有助于你理解该服务的目的,并且当向多个 PNS 和设备发送通知时,它是如何解决问题的。
本章结束了本书的第一部分。在接下来的章节中,我们将重点介绍无服务器组件和架构。
问题
-
什么是 PNS?
-
不同平台(iOS、Android 和 Windows)是否有不同的 PNS?
-
注册和安装之间有什么区别?
-
在免费和基础套餐中,你能否注册相同数量的设备?
-
检查通知中心中已注册的设备的最简单方法是什么?
-
如何发送测试通知?
-
什么是富媒体内容通知?
深入阅读
-
Azure 通知中心文档:
docs.microsoft.com/zh-cn/azure/notification-hubs/ -
Azure 通知中心中的注册管理:
docs.microsoft.com/zh-cn/azure/notification-hubs/notification-hubs-push-notification-registration-management
第七章:无服务器和 Azure Functions
Azure Functions 是 Azure 无服务器架构的主要产品。它们允许在完全托管的运行时内执行小段代码,因此我们不需要关心性能和可扩展性。它们是开源的,支持扩展,并建立在 App Services 之上,因此它们提供类似于 WebJobs 的体验。微软非常重视 Azure Functions 的新特性开发,并且在社区的极大支持下,它是快速开发简单和复杂应用程序的最佳工具之一。
本章将涵盖以下主题:
-
理解 Azure Functions
-
配置本地开发环境以开发 Azure Functions
-
创建一个函数
-
Azure Functions 特性
-
Azure Functions 中的工作流——持久化函数
-
将函数与其他服务集成
技术要求
要开始使用 Azure Functions 并执行本章中的练习,你需要以下内容:
-
已安装 Azure 工作负载的 Visual Studio 2017
-
Azure Functions 和 Visual Studio 的 WebJobs 工具扩展
理解 Azure Functions
Azure Functions 是 Azure 云中可用的所谓无服务器组件的一部分。在你开始学习这个特定服务之前,你需要理解无服务器究竟意味着什么。虽然最初你可能会认为这个概念意味着完全没有服务器,但你很快会重新评估自己的思维方式(毕竟,我们距离完全不使用任何类型的机器来处理我们的应用和工作负载还有一段距离)。
实现“无服务器”
你可以轻松找到许多描述“无服务器”这一术语的不同文章——老实说,我不想推动一种唯一正确的定义,因为这个话题目前仍然模糊,很难找到最好的描述。然而,我的目标是给你一些提示和最佳实践,帮助你以最适合自己的方式理解它。
云供应商的责任
我们将从以下截图开始:

在之前的截图中,你可以看到关于供应商责任的无服务器架构与两种最流行的云模型的比较。我使用五个不同的领域进行了对比:
-
数据中心:数据中心基础设施、安全性、维护和人员配置
-
网络安全:实现有关网络的正确和安全的解决方案(防火墙、渗透测试和抗 DDoS 解决方案)
-
操作系统:更新、维护和配置
-
开发工具:为程序员和管理员开发和交付多个功能(例如 IDE 扩展、管理门户以及管理服务的相关工具)
-
应用托管:托管和运行我们应用的特定运行时(例如应用服务计划)
正如您所看到的,唯一的区别(至少在使用描述的特征时)是应用主机。在涉及无服务器组件时,您向解决方案提供的唯一内容是您的代码(或某种配置,用于设置服务)—其余内容由您的云供应商提供和处理。当然,这不是定义这个想法的唯一方式。
定价模型
无服务器服务和架构最受欢迎的功能之一是支付执行次数和使用的计算能力的可能性。这种定价模型与最常见的预付费模型完全相反,在预付费模型中,您根据一组配置字段(如使用的 VM 数量或集群大小)支付固定价格。在这里,您可以找到描述 Azure Functions 定价的表格:
| 计量单位 | 价格 | 免费赠款(每月) |
|---|---|---|
| 执行时间 | €0.000014/GB/s | 400.000 GB/s |
| 总执行次数 | €0.169 每百万次执行 | 100 万次执行 |
现在,您可能想知道如何理解这一点,以便计算您解决方案的预估成本。为了使您的计算正确,您必须理解两件事:
-
执行:这是一个持续N秒的单个函数执行
-
消耗:这定义了您的函数在固定时间内消耗的资源(CPU 和内存)
现在,如果您将前述术语与表格进行比较,您将看到它们略有不同。这是因为 Azure Functions 的定价不直接定义消耗的价格,而是使用执行时间。
您可能已经注意到定价表中的免费赠款列。请记住,它仅适用于消费模型—对于预付费模型不起作用。
现在,让我们假设您已经估算了以下内容:
-
您每月需要对您的函数进行 1000 万次执行
-
每次执行持续约 80 毫秒
-
每次执行您使用 145 MB 的内存
要计算使用 Azure Functions 的整体价格,您可以使用以下公式:

在前面的公式中,适用以下内容:
-
Rc:资源消耗定义为消耗的内存和执行时间的乘积(以 GB/s 为单位)
-
Te: 总执行次数(以百万为单位)
现在,如果您输入正确的值并计算公式,您将得到以下结果:

这将为您带来以下费用:5.19 欧元。然而,您可能会发现之前的公式有点令人困惑——为什么我使用256(而不是 128)作为内存消耗量,1 Ms(而不是 800 Ks)作为执行时间?嗯,在使用消费计划时有一件重要的事情要记住:最小执行时间为 100 毫秒,而在资源消耗方面,总是向最近的 128 MB 四舍五入。
实际上,在函数执行时,你无法低于 100 毫秒和 128MB 的内存使用量。这在计算可能的成本优化时非常重要,因为通常你不应专注于优化函数,而应集中精力于整体算法的改进(例如批处理或更好的序列化方法)。
Azure Functions 概念
现在你对无服务器架构有了些许了解,我们可以开始学习 Azure Functions 的其他内容。为了继续,你需要理解以下主题之间的区别:
-
函数应用
-
函数
-
触发器和绑定
函数应用
多个函数的逻辑容器称为函数应用。一个函数应用可以托管一个或多个函数,它们将共享配置、设置和运行时版本。可以在同一个函数应用中使用多种语言运行函数。
在这里,你可以看到一个单独的函数应用的样子,里面托管着多个独立的函数:

如果你需要同时使用两种定价计划(按需计费和应用服务),你必须创建两个不同的函数应用,因为单个函数应用不支持这种场景。
函数
托管你代码的 Azure Functions 的单个可执行部分称为函数。每个函数都可以执行用不同支持的语言编写的代码(有的可以使用 C#,而其他的则可以利用 Python 的特性)。目前支持的语言如下:
-
C#
-
JavaScript
-
F#
在第二版运行时(v2)中,Java 也应该可以使用。
请注意,在本书编写时,v1 版本是唯一支持生产工作负载的版本。
还有使用其他语言(例如 Powershell、PHP 或 Batch)的可能性,但它们处于实验模式,不应在生产环境中使用。这里,你可以看到一个包含一些模板代码的示例函数:
[FunctionName("QueueTrigger")]
public static void Run(
[QueueTrigger("myqueue-items")] string myQueueItem,
TraceWriter log)
{
log.Info($"C# function processed: {myQueueItem}");
}
请注意,之前的代码是使用 Visual Studio 生成的——在 Azure Portal 中生成的模板代码看起来稍有不同。
如你所见,一个函数由以下组件构成:
-
函数装饰器:
[FunctionName],它允许运行时找到一个函数,并提供所需的元数据 -
触发器:
[QueueTrigger]——每个函数都需要配置触发器才能正确运行 -
附加绑定:
TraceWriter,将在运行时注入 -
函数代码: 每次调用函数时将执行的实际逻辑
当然,函数的某些部分会根据你使用的功能有所不同——在之前的示例中,我们使用了 Azure 存储队列的触发器,但也有其他可能性(例如 HTTP 请求、Azure 服务总线或 Azure CosmosDB);此外,你可以使用其他绑定,并在每次调用时提供自定义代码。我们将在本章的后续部分介绍所有这些主题。
触发器和绑定
Azure Functions 的强大之处在于考虑到所有可能的集成,它们可以无缝地使用,并且几乎无需额外的工作。事实上,可用的触发器和绑定的列表相当令人印象深刻:
-
Azure Storage
-
Azure CosmosDB
-
Azure Event Grid
-
Azure Event Hub
-
HTTP
-
Microsoft Graph
-
Azure Mobile Apps
-
Azure Notification Hub
-
Azure Service Bus
-
定时器
-
Twilio
-
SendGrid
此外,你还可以访问一些实验性的触发器和绑定,虽然这些可能不被官方支持,但如果你决定使用,它们可以在你的应用程序中使用(例如外部文件和外部表)。
请记住,一些实验性的触发器和绑定永远不会达到 GA(正式发布)状态,因为有一些特定的建议(例如使用Azure Logic Apps),在大多数情况下应遵循这些建议。
当然,你可以引入自定义触发器和绑定,因为 Azure Functions 提供了完整的 SDK,可以用于扩展运行时。但是,这属于高级主题,本书中不会涉及——你可以在进一步阅读部分找到相关教程的参考。在这里,你可以找到一个自定义绑定的示例,供我用于用户授权:

定价模型
在 Azure Functions 中,有两种定价模型可供选择:
-
消费模型:在前面部分已经描述过,你按函数的执行次数和使用的计算能力付费
-
应用服务计划模型:在这里,你选择一个应用服务计划版本,无论你执行多少次函数,都有固定的价格
扩展
服务器无状态组件和架构最重要的特点之一是,它们能够随着负载的增加而扩展。与传统的 PaaS 服务不同,你通常需要担心可用实例或扩展配置。无服务器架构允许无缝处理传入的请求,即使服务遭遇意外的高流量。在这一部分中,我们将讨论 Azure Functions 的扩展能力,重点讲解消费模式和应用服务模型之间的差异。
消费模型下的扩展
当你在 Azure Functions 中使用消费模型时,你并未为服务定义任何可用实例,也无法配置自动扩展设置。事实上,在该模型中,你完全无法知道有多少台机器在运行你的工作负载(不过,如果你将函数与 Azure Application Insights 集成,你将能够查看通过查看实时流面板,了解创建了多少实例)。
在消费模型下,每次执行函数时,内存有固定的限制——为 1.536 MB。函数是否能扩展,取决于当前内存和 CPU 的利用率。
该计划的优势在于能够轻松扩展到数百个函数,同时并行运行相同的代码。当然,这一切都取决于函数使用的实际触发器——例如,使用 HTTP 触发器时,必须扩展才能处理多个请求,而使用事件中心触发器时,则会自动增加每个分区使用的工作实例数量。另一方面,你不能总是依赖消费计划来确保不会出现响应延迟或暂时不可用的情况——即使是即时扩展,也不一定能得到保证,因此,当你的应用需要应对快速流量高峰时,这种定价计划并不总是最好的解决方案。
请记住,当前扩展函数应用的最大限制为 200。此外,还值得注意的是,运行时将在每 10 秒内分配新的实例。
应用服务模型中的扩展
使用应用服务模型有其优势,尤其是在处理消费计划未涵盖的某些扩展情况时。如前所述,如果你必须确保能够处理传入的负载,通常使用这种特定模型是一个更好的选择,因为它确保为你的函数应用提供某些固定资源。此外,你可以 100%确保为你的应用提供的硬件是经过预配的——这在消费模型中并不总是如此,因为你无法获得有关所提供的机器及其特性的保证。更重要的是,你可以确保你的运行时始终运行——因为在消费模型中,当函数未被使用时,其资源将被解除分配,你可能会遇到如冷启动等常见问题。
配置本地开发环境以开发 Azure 函数
要开始使用 Azure Functions,我们需要一个环境,它能让我们测试函数并快速、无缝地开始开发。幸运的是,这项 Azure 服务配备了多种工具,帮助我们在本地编程和运行函数。我还将描述一些额外的应用程序,它们应有助于你分析和调试可能的问题,并在部署到云端之前测试触发器。
在本地开始使用 Azure Functions
如果你已经安装了本章开头提到的所有必需软件,你应该能够在无需额外配置的情况下开始开发。为了开始,我们将创建一个简单的函数,并尝试运行它,以确保一切都已设置好并准备就绪。
打开你的 Visual Studio 实例后,点击文件 | 新建项目。在新屏幕中,搜索 Cloud | Azure Functions 模板:

这是确保你这边一切配置正确的第一步。当你点击 OK 时,你将看到另一个屏幕,允许你选择一些不同的选项:
-
运行时版本:你可以选择 v1 或 v2。在本书中,我们将专注于 v1,因为 v2 仍处于预览阶段。
-
触发器类型:根据你所拥有的 SDK 版本,你将会有不同的选项。当然,这不是 Azure Functions 可用触发器的完整列表。
-
存储账户:大多数函数都需要一个存储账户才能正常工作。幸运的是,你可以在本地使用 Storage Emulator,它是一个在你计算机上安装的
LocalDB实例下运行的简单数据库。
Azure 的新版本 SDK 应该会自动安装 Storage Emulator。如果由于某些原因你没有安装它,可以访问以下页面并安装缺失的组件:docs.microsoft.com/en-us/azure/storage/common/storage-use-emulator#get-the-storage-emulator。
为了本练习的目的,我选择了 HTTP 触发器,并将所有字段保持为默认值:

当你点击 OK 并稍等片刻,你将看到一个新的函数文件已被创建,并且一些模板代码已插入。由于我将在下一节解释如何创建函数及其特性,所以此时我不会详细解释。为了确保一切正常工作,按下 F5 并等待项目编译完成。你将注意到两点:
-
Storage Emulator 将在后台启动以处理函数请求。
-
将打开一个带有控制台应用程序的新窗口,显示一些关于 Azure Functions 的诊断消息。
前者是实际的 Azure Functions 运行时,它在你与函数通信时处理所有工作。在这里,你可以看到它在我的计算机上的样子:

需要注意几点:
-
它显示了运行时监听传入请求的端口。
-
它告诉你配置文件是从哪个位置获取和加载的。
-
它会通知你已加载的自定义扩展(正如我所说,确实可以引入自定义绑定,它们将在运行时加载)。
-
它显示了所有找到的函数的名称(以及在 HTTP 触发器的情况下,URL)。
当你向下滚动时,你应该能看到你刚刚创建的函数的端点:

现在,我们将尝试调用它,看看它是否能正常工作(我正在使用 Postman,但你可以使用任何你熟悉的工具):

正如你所看到的,它工作正常——它返回了一个结果(尽管结果本身不是成功的——我们缺少一个必需的字段,但现在这不是问题)。如果你发送相同的请求,你应该能够看到相同的结果。如果由于某种原因你无法这样做,请按照以下步骤操作:
-
确保你的函数主机仍在工作并且没有显示错误
-
确保运行时监听传入请求的端口是开放的
-
确保 Azure Functions CLI 没有被防火墙阻塞
-
确保你正在调用正确的端点
在接下来的部分中,我将详细描述函数的结构,这样你就能继续进行更高级的场景和功能。
创建一个函数
我们讨论了整体的无服务器(serverless)方法,并通过本地配置来确保我们对 Azure Functions 有一些基本的了解,并知道如何开始使用它们。在本章的接下来的部分,我将向你展示这个服务究竟提供了什么,以及如何在日常工作中使用它。这将帮助你开始开发完整的项目,涵盖从最简单到最先进的功能。
使用 Visual Studio
在前面的部分中,你使用 Visual Studio 的向导创建了一个函数。如果你返回到这个特定的项目并打开它的文件,你会看到一些常见的代码,这些代码是使用这个特定模板创建的。在这里,你可以找到相同的代码,但没有它所引入的自定义代码:
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
namespace HandsOnAzure.Function
{
public static class Function1
{
[FunctionName("Function1")]
public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]
HttpRequestMessage req, TraceWriter log)
{
}
}
}
正如你所看到的,我删除了整个函数体——这是唯一不属于服务的部分(还记得我们的 IaaS、PaaS 与无服务器的对比吗?)。我们可以看到一些属性,它们装饰了 C# 方法及其参数——这些都是运行你函数的运行时的一部分。让我们将其与一个由 Azure 存储队列触发的函数进行比较:
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
namespace HandsOnAzure.Function
{
public static class Function2
{
[FunctionName("Function2")]
public static void Run([QueueTrigger("myqueue-items", Connection = "connection-string")]
string myQueueItem, TraceWriter log)
{
}
}
}
在这里,你可以看到我们仍然有[FunctionName]属性和某种触发器属性。不同之处在于触发器参数的类型——在 HTTP 中,我们使用的是HttpRequestMessage,而在队列中,我们使用一个简单的string参数。这个参数(及其类型)直接定义了传递给函数的消息类型。总的来说,这是很清楚的——每个 HTTP 请求都会被反序列化并作为HttpRequestMessage(例如在 Web API 中)传递,而每个队列服务和每个消息都是一个字符串。然而,以下签名又如何呢:
using System.Net.Http;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
namespace HandsOnAzure.Function
{
public static class Function3
{
[FunctionName("Function3")]
public static HttpResponseMessage Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "Function3/name/{name}")]
HttpRequestMessage req, string name, TraceWriter log)
{
}
}
}
正如你所看到的,前面的示例引入了一个额外的参数——name,它是一个字符串,尽管整个函数是由 HTTP 请求触发的。这个特定的参数将在绑定过程中使用,绑定过程会发现该函数的路由在其 URL 模板中包含它。这与传统的 MVC/Web API 框架中的模型是完全一样的,它们提供相同的功能。
绑定过程本身相当复杂,主要取决于使用的触发器类型。不幸的是,这超出了本书的范围,所以我不会详细介绍——幸运的是,Azure Functions 是开源的,因此您可以直接查看主机是如何工作的。
如果您想快速将新函数添加到您的项目中,请执行以下步骤:
-
在 Visual Studio 中右键单击您的项目,然后搜索添加 | 新建 Azure 函数...菜单项。
-
这将显示一个屏幕,您可以在其中输入新的函数名称。
-
当您点击添加时,您将看到另一个屏幕,允许您选择函数类型,比我们最初看到的选项更多:

使用 Azure 门户
与所有其他 Azure 服务一样,您也可以通过直接在 Azure 门户中创建来创建一个函数应用实例:
-
登录后,点击+ 创建资源并搜索函数应用。
-
当您点击创建时,您将看到一个屏幕,里面有几个字段需要在处理之前填写:

如您所见,前面的表单与我们在创建应用服务实例时使用的表单相似。这是因为,从底层上讲,Azure Functions 由这个特定服务提供支持,多个可用功能在它们之间共享。如您所见,您可以选择操作系统,这决定了您将能够使用的运行时。
如果您有兴趣使用 .NET Core,您可以将操作系统设置为 Linux。目前这是预览版,但它允许使用 v2 版本的运行时。它有许多增强功能,并使用最新的 .NET 技术栈,因此在许多情况下,它比 v1 更快。
在托管计划下拉菜单中,您可以选择是否使用按需计划或应用服务模型进行定价。我们在本章前面部分讨论了这两者的区别,所以您应该能够自己决定使用哪一个。记住,您至少需要选择B1等级。
Azure 门户不允许您使用共享或免费等级,因为 Azure Functions 需要启用始终在线功能——您可能还记得,只有基本等级及更高级别才可用。虽然可以使用,例如,免费等级(通过 ARM 模板等)创建函数应用,但它不会正常工作。
这个向导还为您提供了启用应用程序洞察集成的选项。由于我们尚未讨论这个特定服务,我将在本章中跳过它。不过,如果您有兴趣监控您的函数,它比集成的监视功能要好得多——它能提供更多细节,并且在日常工作中更加直观。
启用 Application Insights 对于你的 function app 来说,可能会显著改变整个服务的费用,因为最初每个函数都会生成许多不同的追踪和日志。对于生产环境,始终建议降低日志消息的严重性—你可以在此处找到更多关于配置的信息:docs.microsoft.com/en-us/azure/azure-functions/functions-monitoring。
当你对设置满意时,可以点击 Create。Azure 门户将验证所有字段并启动服务提供程序的过程。几秒钟后,你的 function app 应该就绪。当你进入它时,你将看到仪表板,这是访问所有 Azure Functions 功能的起点:

现在,如果你想创建一个函数,将鼠标光标移动到 Functions 部分并点击加号 (+)。这将显示一个新的向导页面,你可以选择从一个预制的函数开始,或者创建一个自定义的函数。为了完成本练习,我选择了一个用 JavaScript 编写的定时器函数:

点击 Create this function 按钮后,你会看到一些函数代码已经生成。Azure 门户还允许你直接在浏览器窗口中编辑函数,因此如果你想尝试一些自定义代码,完全没有阻碍:

此外,你可以点击运行—这就是所谓的手动触发,它使你能够立即启动一个函数。运行函数的结果将在 Logs 窗口中显示:
2018-07-22T09:15:36 Welcome, you are now connected to log-streaming service.
2018-07-22T09:15:57.252 [Info] Function started (Id=63d9f8ff-b807-4805-8b24-5f90edfc0134)
2018-07-22T09:15:57.377 [Info] JavaScript timer trigger function ran!: 2018-07-22T09:15:57.377Z
2018-07-22T09:15:57.377 [Info] Function completed (Success, Id=63d9f8ff-b807-4805-8b24-5f90edfc0134, Duration=128ms)
2018-07-22T09:17:36 No new trace in the past 1 min(s).
2018-07-22T09:18:36 No new trace in the past 2 min(s).
恭喜你—你已经学会了如何通过 Visual Studio 和 Azure 门户创建函数。在下一节中,我将介绍更多高级场景,并重点讲解进一步了解 Azure Functions 特性。
Azure Functions 特性
Azure Functions 不仅仅是提供可执行代码,这些代码将由运行时处理。它还支持更高级的场景,使得这个服务成为你希望快速开始开发并且配置最小的理想选择。在本节中,我将展示如何利用函数的更多高级功能,并帮助你在使用这个 Azure 组件时不断提升技能。
平台特性
如你所知, Azure Functions 是建立在 App Service 之上的,这使得你可以使用多个已知的功能,如 自定义域、应用设置和身份验证/授权。要访问所有可用的 平台特性,进入你的 function app 在 Azure 门户中,并点击相应的标签:

正如你所见,我们提供了多种不同的功能——你关注的内容完全取决于你的具体需求。然而,有一个特定于功能的特性我想描述一下:函数应用设置。
当你点击这个链接时,新的标签页会打开,里面包含一些可以设置的关键选项:

在前面的截图中,你可以看到大多数功能要么未启用,要么不可用。这取决于你的函数应用的状态——设计上,所有有问题的功能都是可选择退出的,因此它们不会干扰你的函数。不管怎样,我会在这里描述它们,以便你决定是否需要它们。
-
每日使用配额(GB-秒):如果你想为函数应用使用设置硬性限制,可以在这里设置。这样,你可以确保它不会超出你预定的配额。
-
运行时版本:此设置定义了当前你的函数应用所使用的运行时版本。请注意,无法将 v1 更改为 beta(此情况下为 v2),因为更新的版本可能会引入一些变化,从而破坏你的应用。
-
函数应用编辑模式:如果你决定通过任何形式的 CI/CD 管道部署你的函数,这个设置会自动被设置为“只读”。这样可以确保在运行时不会进行更改,除非通过自动化流程。
-
插槽(预览):如果你想执行蓝绿部署(以便在出现问题时快速回滚),这可以让你将新版本作为新实例进行部署,并立即与现有实例交换。
安全性
我们还没有涉及另一个重要话题——Azure 函数的安全性。虽然可以使用例如 Azure Active Directory 或社交提供商作为身份来源(从而为函数应用添加身份验证),但默认情况下,函数是通过其密钥进行保护的。你可以在点击“管理”标签时查看可用的密钥:

根据函数触发的方式,可能会有不同的选项可用。在这里,你可以看到另一个由 HTTP 触发器而非定时器触发的函数应用:

如你所见,我们有两种类型的密钥可供选择:
-
函数密钥:这些是为特定函数设计的
-
主机密钥:这些密钥允许调用函数应用中的任何函数
你可以使用密钥作为一种简单的方式来实现函数应用的授权。你可以为每个客户端生成新的密钥,撤销它们,并设置特定的值。
请注意,函数密钥是为由 HTTP 请求触发的函数设计的——无法用于其他类型的触发器。
使用函数密钥授权请求有两种方式。你可以将它们放入查询字符串中:
https://handsonazurefunctionapp.azurewebsites.net/api/HttpTriggerJS1?code=awKhkdPqyQvYUwzn6zle6V4hqk460YwOBs9RyaQUthX/AWGBMjtRIA==
或者,你可以使用标头并引入x-functions-key标头,其中将包含一个密钥:
GET /api/HttpTriggerJS1 HTTP/1.1
Host: handsonazurefunctionapp.azurewebsites.net
Content-Type: application/json
x-functions-key: awKhkdPqyQvYUwzn6zle6V4hqk460YwOBs9RyaQUthX/AWGBMjtRIA==
Cache-Control: no-cache
监视
每次调用和执行函数都会被监控并保存。当你点击“监视”标签时,你将看到一个屏幕,显示下次执行以及一些诊断数据。
如果你没有看到列表,可能会提示你启用 Application Insights 集成。要访问标准视图,点击“切换到经典视图”按钮。
在这里,你可以看到由定时器触发的函数执行日志:

如你所见,它包含了每次执行的相关信息,包括成功和错误计数以及调用详情。当你选择一个特定项时,你还将看到该函数的所有日志。
“监视”功能对于快速分析问题非常有用。对于更详细的错误和日志,你需要启用 Application Insights 并使用其功能。
Host.json
当你创建一个函数时,会看到一个host.json文件被自动创建。虽然最初为空,它是一个全局配置文件,用于定义触发器和函数的行为。在这里,你可以找到一个包含大多数功能的示例文件,例如绑定配置和通用功能:
{
"aggregator": {
"batchSize": 1000,
"flushTimeout": "00:00:30"
},
"applicationInsights": {
"sampling": {
"isEnabled": true,
"maxTelemetryItemsPerSecond" : 5
}
},
"eventHub": {
"maxBatchSize": 64,
"prefetchCount": 256,
"batchCheckpointFrequency": 1
},
"functions": [ "QueueProcessor", "GitHubWebHook" ],
"functionTimeout": "00:05:00",
"healthMonitor": {
"enabled": true,
"healthCheckInterval": "00:00:10",
"healthCheckWindow": "00:02:00",
"healthCheckThreshold": 6,
"counterThreshold": 0.80
},
"http": {
"routePrefix": "api",
"maxOutstandingRequests": 20,
"maxConcurrentRequests": 10,
"dynamicThrottlesEnabled": false
},
"id": "9f4ea53c5136457d883d685e57164f08",
"logger": {
"categoryFilter": {
"defaultLevel": "Information",
"categoryLevels": {
"Host": "Error",
"Function": "Error",
"Host.Aggregator": "Information"
}
}
},
"queues": {
"maxPollingInterval": 2000,
"visibilityTimeout" : "00:00:30",
"batchSize": 16,
"maxDequeueCount": 5,
"newBatchThreshold": 8
},
"serviceBus": {
"maxConcurrentCalls": 16,
"prefetchCount": 100,
"autoRenewTimeout": "00:05:00"
},
"singleton": {
"lockPeriod": "00:00:15",
"listenerLockPeriod": "00:01:00",
"listenerLockRecoveryPollingInterval": "00:01:00",
"lockAcquisitionTimeout": "00:01:00",
"lockAcquisitionPollingInterval": "00:00:03"
},
"tracing": {
"consoleLevel": "verbose",
"fileLoggingMode": "debugOnly"
},
"watchDirectories": [ "Shared" ],
}
正如你所看到的,它包含了日志记录设置、函数超时值和特定触发器配置等内容。在进一步阅读部分,你将找到一个链接,详细描述了host.json文件的每个部分。
发布
Azure Functions 的发布方式与 App Service 完全相同,因为它们共享许多共同的部分。如果你在 Visual Studio 中右键单击你的函数项目并选择“发布”,你将看到一个与我们在处理 App Service 时看到的界面相似的屏幕:

传统上,你可以选择创建一个新的函数应用或使用现有的。当你使用现有的并点击“发布”按钮时,你将能够在特定的资源组中找到一个函数应用:

现在,当你点击“确定”时,一个新的发布配置文件将被创建,整个应用程序将被部署。
Azure Functions 中的工作流 – 持久化函数
在大多数情况下,处理函数的最佳方法是保持它们无状态。这样可以简化许多操作,因为你不必担心共享资源和存储状态。然而,也有一些情况你可能需要访问并在不同实例之间分配它。在这种情况下(例如编排工作流或调度任务),一个更好的起点是利用持久化函数的能力,这是一种对主运行时的扩展,它会稍微改变你的工作方式。
它改变了 Azure Functions 的工作方式,因为它允许你从执行暂停或停止的地方恢复,并引入了将一个函数的输出作为输入传递给另一个函数的可能性。我们不会详细讨论这个内容,因为这本书不仅仅是关于 Azure Functions,但你会略知一二,这将帮助你自己启动它。
要开始,你不需要任何额外的扩展——你唯一需要的是一个额外的 NuGet 包,名为Microsoft.Azure.WebJobs.Extensions.DurableTask。
协调过程和活动
Durable Functions 的主要元素是协调过程和活动。它们之间有一些显著的区别:
-
协调过程:这些旨在协调不同的活动。它们应该是单线程的并且具有幂等性,它们只能使用非常有限的一组异步方法。它们的扩展基于内部队列的数量。此外,它们控制一个或多个活动的执行流。 -
活动:这些应该包含你应用程序的大部分逻辑。它们像典型的函数一样工作(没有协调的限制)。它们会扩展到多个虚拟机。
在这里,你可以找到两种类型函数的代码:
[FunctionName("Orchestration")]
public static async Task Orchestration_Start([OrchestrationTrigger] DurableOrchestrationContext context)
{
var payload = context.GetInput<string>();
await context.CallActivityAsync(nameof(Activity), payload);
}
[FunctionName("Activity")]
public static string Activity([ActivityTrigger] DurableActivityContext context)
{
var payload = context.GetInput<string>();
return $"Current payload is {payload}!";
}
如你所见,它们都被 [FunctionName] 属性装饰,就像典型的函数一样——不同之处在于使用的触发器。
协调器客户端
要开始一个协调过程,你需要为它提供一个主机。在 Durable Functions 中,这个主机是协调器客户端,它使你能够对协调过程执行以下操作:
-
启动它
-
终止它
-
获取其状态
-
触发事件并将其传递给协调过程
客户端的基本代码非常简单:
[FunctionName("Orchestration_Client")]
public static async Task<string> Orchestration_Client(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "start")] HttpRequestMessage input,
[OrchestrationClient] DurableOrchestrationClient starter)
{
return await starter.StartNewAsync("Orchestration", await input.Content.ReadAsStringAsync());
}
从上面的代码可以看到,我们通过提供名称并传递一些有效载荷来启动一个协调过程,这些有效载荷将被反序列化并解码。在这里,你可以找到一个客户端的示例,它已被托管,通过传递其标识符来终止一个实例:
[FunctionName("Terminate")]
public static async Task Terminate(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "terminate/{id}")] HttpRequestMessage input,
string id,
[OrchestrationClient] DurableOrchestrationClient client)
{
var reason = "Manual termination";
await client.TerminateAsync(id, reason);
}
协调历史记录
Durable Functions 的工作方式确保,如果任何活动被重放,它的结果将不会再次被评估(这就是协调过程必须具有幂等性的原因)。在这里,你可以找到一张图表,展示框架如何详细工作:

长话短说,我将过程分为四个部分:
-
调度器:这是框架的内部部分,负责调用协调过程、执行重放并保存状态
-
协调器函数:这是一个调用活动的协调过程
-
存储:这是一个存储协调历史记录的地方
-
队列:这是一个内部队列(使用 Azure 存储队列实现),用于控制协调过程的执行流
Durable Functions 的工作方式如下:
-
调度器运行一个协调过程,它调用
Activity1并等待其结果 -
控制权返回给调度器,调度器提交工作流历史中的状态并将消息推送到队列中。
-
在此期间,工作流被解除分配,从而节省内存和处理器资源。
-
从队列中获取消息并完成任务后,调度器会重新创建工作流并重新播放所有活动。
-
如果它发现这个特定的活动已经完成,它只获取其结果并继续到下一个活动。
前述过程持续进行,直到所有活动都被处理完毕。执行历史的信息可以在名为DurableFunctionsHubHistory的表中找到,该表存储在你函数应用使用的 Azure 表存储中。
计时器
有时,你可能想要在特定的延迟后安排工作。使用传统函数时,你必须创建一个自定义解决方案,以便在特定时间触发工作流。而在持久化函数中,这就像写一行代码一样简单。考虑以下示例:
[FunctionName("Orchestration_Client")]
public static async Task<string> Orchestration_Client(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "start")] HttpRequestMessage input,
[OrchestrationClient] DurableOrchestrationClient starter)
{
return await starter.StartNewAsync("Orchestration", null);
}
[FunctionName("Orchestration")]
public static async Task Orchestration_Start([OrchestrationTrigger] DurableOrchestrationContext context, TraceWriter log)
{
log.Info($"Scheduled at {context.CurrentUtcDateTime}");
await context.CreateTimer(context.CurrentUtcDateTime.AddHours(1), CancellationToken.None);
await context.CallActivityAsync(nameof(Activity), context.CurrentUtcDateTime);
}
[FunctionName("Activity")]
public static void Activity([ActivityTrigger] DurableActivityContext context, TraceWriter log)
{
var date = context.GetInput<DateTime>();
log.Info($"Executed at {date}");
}
在前面的示例中,我使用了context.CreateTimer()方法,它允许在函数执行中创建延迟。如果先前的工作流已执行,等待计时器后,它将把控制权返回给调度器。得益于此,你不会为这次特定的函数执行收费,因为它将在等待特定时间间隔后被解除分配并重新创建。
外部事件
在持久化函数中,可以在继续执行工作流之前等待外部事件。这对于创建交互式流程特别有用,在这种流程中,你可以在一个地方启动一个过程,并要求等待某人的决定。要触发一个事件,可以使用以下函数:
[FunctionName("Orchestration_Raise")]
public static async Task Orchestration_Raise(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "start_raise/{id}/{event}")] HttpRequestMessage input,
string id,
string @event,
[OrchestrationClient] DurableOrchestrationClient starter)
{
await starter.RaiseEventAsync(id, @event, await input.Content.ReadAsStringAsync());
}
这里是一个等待事件的示例:
[FunctionName("Orchestration")]
public static async Task<string> Orchestration_Start([OrchestrationTrigger] DurableOrchestrationContext context)
{
var @event = await context.WaitForExternalEvent<int>("Approved");
if (@event == 1)
{
var result = await context.CallActivityAsync<string>(nameof(Activity), @event);
return result;
}
return "Not Approved";
}
这个工作的方式可以描述如下:第一个函数允许你通过传递适当的参数来触发自定义事件。第二个函数在等待context.WaitForExternalEvent()函数时被暂停。如果你发送一个Approved类型的事件,函数将恢复并继续执行。此外,你还可以传递事件的有效载荷,该载荷将作为WaitForExternalEvent()的结果传递。此方法的工作方式与计时器和其他持久化函数相同,这些函数可以在DurableOrchestrationType中使用——在等待期间,控制权返回到调度器,并且函数本身被解除分配。
与其他服务集成函数
在本章的最后部分,我们将稍微关注一下 Azure Functions 如何与其他 Azure 服务集成。我们将查看可用的触发器和绑定,并尝试弄清楚它们的最佳使用场景以及它们的实际工作原理。本节的设计旨在通过对 Azure Functions 工作方式的共同理解,帮助你进一步探索。
函数文件
当您查看您的bin目录时,您会发现其中的结构与传统应用程序有所不同,其中包含已编译的函数。
在这里,您可以找到我在本章练习中的文件夹:

如您所见,它包含了Function1目录,其中有一个名为function.json的文件。在这里,您可以找到它的内容:
{
"generatedBy": "Microsoft.NET.Sdk.Functions-1.0.14",
"configurationSource": "attributes",
"bindings": [
{
"type": "httpTrigger",
"methods": [
"get",
"post"
],
"authLevel": "function",
"name": "req"
}
],
"disabled": false,
"scriptFile": "../bin/HandsOnAzure.Function.dll",
"entryPoint": "HandsOnAzure.Function.Function1.Run"
}
它定义了一些元数据,稍后将被函数的运行时和bindings字段使用,后者是已使用触发器的定义。如果将其与代码属性进行比较,您会发现它们非常相似:
[FunctionName("Function1")]
public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]
HttpRequestMessage req, TraceWriter log)
{
}
当您编译项目时,编译器会为代码中定义的每个函数生成一个function.json文件。在这里,您可以找到事件中心触发器的输出:
{
"generatedBy": "Microsoft.NET.Sdk.Functions-1.0.0.0",
"configurationSource": "attributes",
"bindings": [
{
"type": "eventHubTrigger",
"path": "myhub",
"connection": "EhConnection",
"name": "myEventHubMessage"
}
],
"disabled": false,
"scriptFile": "..\\bin\\FunctionsTest.dll",
"entryPoint": "FunctionsTest.Hub.Run"
}
如您所见,它有相同的结构,只有bindings字段不同,因此它反映了另一个触发器类型。
请注意,function.json的内容和结构可能会根据使用的 SDK 版本而有所不同。为了避免出现缺乏向后兼容性的问题,请不要直接在您的应用程序中使用它。
输入/输出绑定
一些绑定是双向的,而一些只能单向使用。更重要的是,并非每个绑定都能作为触发器使用。一个既是双向的又是触发器绑定的例子是 Azure Blob Storage. 在这里,您可以找到一个作为触发器工作方式的示例:
[FunctionName("BlobTriggerCSharp")]
public static void Run([BlobTrigger("my-blobs/{name}")] Stream myBlob, string name, TraceWriter log)
{
}
将其与一个由队列触发但接受 Blob 作为输入的函数示例进行比较:
[FunctionName("BlobInput")]
public static void BlobInput(
[QueueTrigger("myqueue-items")] string myQueueItem,
[Blob("samples-workitems/{queueTrigger}", FileAccess.Read)] Stream myBlob,
TraceWriter log)
{
}
如您所见,除了一个细节外,我在这两种情况下都使用了相同的语法——我声明了FileAccess.Read,以告诉运行时这不是一个触发器。这里是另一个输出示例:
[FunctionName("ResizeImage")]
public static void ResizeImage_Run(
[BlobTrigger("sample-images/{name}")] Stream image,
[Blob("sample-images-sm/{name}", FileAccess.Write)] Stream imageSmall,
[Blob("sample-images-md/{name}", FileAccess.Write)] Stream imageMedium)
{
// There goes your code...
}
如您所见,语法仍然相似——唯一改变的是FileAccess的值。还有其他使用函数返回值的可能性。在这里,您可以了解如何使用属性来定义结果:
[FunctionName("QueueTrigger")]
[return: Blob("output-container/{id}")]
public static string QueueTrigger_Run([QueueTrigger("myqueue")] string input, TraceWriter log)
{
return "Some string...";
}
自定义绑定
虽然 Azure Functions 为许多不同的场景提供了各种不同的绑定,但有时您可能需要一种没有现成提供的自定义功能。在这种情况下,您可以创建一个自定义绑定,正如我在本章前面提到的。要生成它,您需要以下内容:
-
使用
[Binding]属性装饰的属性 -
IBindingProvider接口的实现 -
IBinding接口的实现 -
IExtensionConfigProvider接口的实现
通过提供所有前面的实现,您的绑定将会被运行时自动获取并启用。如果一切设置正确,您将在运行时启动时收到通知:

在前面的截图中,您可以看到运行时从我的代码中提取了IdentityExtensionConfig Provider,这个将被用于稍后解析我的自定义绑定。
总结
在本章中,你已经学到了很多关于 Azure Functions 的内容,以及如何使用这个无服务器组件。你阅读了定价模型的差异、可扩展性问题,以及基本的触发器和绑定。我们探讨了有关监控、部署和开发这个 Azure 服务的一些简单场景。
在本书的后续章节中,你将学到更多关于无服务器服务的知识。接下来我们将更深入地探讨函数,因为它是 Azure 中最受欢迎的组件之一,可以轻松与其他工具和产品集成。
问题
-
App Service 和消费定价模型有什么区别?
-
什么是 GB/s?
-
你可以使用 Azure Functions 创建有状态的服务吗?
-
用于函数的容器叫什么名字?
-
你可以在 Azure Functions 中使用 Python 吗?
-
一个绑定可以同时充当触发器和输出吗?你能提供一个例子吗?
-
为什么有时候会将一个 function app 模式设置为只读?
-
你可以像在 App Services 中一样使用 Application Settings 功能吗?
深入阅读
-
Azure Functions 概述:
docs.microsoft.com/zh-cn/azure/azure-functions/functions-overview -
host.json描述:docs.microsoft.com/zh-cn/azure/azure-functions/functions-host-json -
functions.json文件的架构:json.schemastore.org/function -
Azure Functions 的触发器和绑定:
docs.microsoft.com/zh-cn/azure/azure-functions/functions-triggers-bindings -
Durable Functions:
docs.microsoft.com/zh-cn/azure/azure-functions/durable-functions-overview
第八章:使用 Logic Apps 集成不同的组件
Logic Apps 是一种主要的企业级集成服务,允许我们将组织内的流程作为工作流进行自动化。它们允许通过多个连接器简单地连接不同的服务和应用程序。此外,通过利用无服务器模型,它们减少了成本并缩短了开发有效解决方案所需的时间。
本章将涵盖以下主题:
-
什么是 Azure Logic Apps,它是如何工作的
-
Logic Apps 的连接器
-
创建一个 Logic App 并将其与其他服务集成
-
B2B 集成及其工作原理
技术要求
要执行本章的练习,你需要以下内容:
-
Visual Studio 2017
-
Azure SDK 2.9.1 或更高版本
-
Azure Logic Apps for Visual Studio:
marketplace.visualstudio.com/items?itemName=VinaySinghMSFT.AzureLogicAppsToolsforVisualStudio-18551#overview
什么是 Azure Logic Apps?
有时你需要集成多个服务并自动化任务,例如发送电子邮件、创建文件或根据某些输入数据(可能是数据库表或社交媒体数据)生成报告。如果你使用的是特定的云供应商(在本例中是 Microsoft Azure),能够快速开发可以版本控制并与多个云服务原生集成的工作流就显得至关重要,而且这种工具不需要学习许多不同的概念就能上手。这样的服务就是 Azure Logic Apps,你将在本章中学习到它。
Azure Logic Apps——它是如何工作的
在上一章中,你了解了 Azure Functions,它需要触发器来执行。Azure Logic Apps 的情况类似——你需要定义特定的标准,告诉运行时何时应执行 Logic App 实例。在执行过程中,还会执行更多操作:
-
输入数据会被转换,以满足初始要求
-
所有条件流都会执行,以便评估特定的执行流程
-
临时变量被赋予值
以下展示了一个每日执行的流程示例,它会设置一个变量,用于移除过时的 blobs:

如你所见,它包含三个不同的块,这些块是流程的初始部分(事实上,整个工作流要大得多,因为它包含了循环、不同的条件和动作——我们稍后会介绍这些内容):
-
第一个定义了 Logic App 实例应该多频繁执行
-
第二个定义了一个变量,该变量被后续步骤使用——它指定了 Azure Blob 存储中文件的最大年龄
-
第三个将一个可用 blob 列表传递给下一个步骤(
for each循环),以供特定存储帐户和容器使用
你可能注意到的另一点是——工作流是通过图形块构建的,这些块可以通过定义多种关系来连接。虽然这种解决方案在创建和修改 Logic Apps 时非常方便,但在进行版本控制和团队开发时可能会有问题。幸运的是,每个流程也可以通过 JSON 来定义:
{
"$connections": {
"value": {
"azureblob": {
"connectionId": "/subscriptions/<subscription-id>/resourceGroups/handsonazure-rg/providers/Microsoft.Web/connections/azureblob",
"connectionName": "azureblob",
"id": "/subscriptions/<subscription-id>/providers/Microsoft.Web/locations/westeurope/managedApis/azureblob"
}
}
},
"definition": {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"actions": {
"For_each": {
"actions": {
"Condition": {
"actions": {
"Delete_blob": {
(...)
}
}
}
}
}
}
}
}
借助这样的表示方法,你可以将你的 Logic Apps 添加到任何版本控制系统(例如 Git 或 SVN)中,并在需要时进行修改。此外,你还可以通过即时生成代码文件来自动创建不同的 Logic Apps。
Azure Logic Apps – 优势
你可能会好奇,当你有其他选择(如 Azure Functions 和自定义工作流)时,Azure Logic Apps 的实际应用场景是什么。如果你仔细查看其功能,你会注意到以下几点:
-
你不必是云开发者才能开发工作流——即使是非技术用户(如 IT 专业人员、IT 管理员和数据科学家)也能在不需要学习太多关于该服务的知识的情况下创建所需的工作流。
-
你无需担心扩展问题——因为 Azure Logic Apps 也是 Azure 中无服务器服务的一部分,你可以专注于提供业务价值,而不是服务器配置和能力。
-
一般来说,你不需要编写代码——然而,你并不局限于一个“无代码”环境,因为它也可以托管在 Azure Functions 中,并根据需要执行。
-
你能够实现 B2B 集成,利用与消息交换和通信相关的企业标准,如 AS2 或 EDIFACT
以下是 Azure Logic Apps 的当前定价:
-
每个操作执行的费用为 $0.000025
-
每个连接器执行的费用为 $0.000125
为了充分理解这一点,我们需要描述这两个术语:
-
操作:这是触发器之后执行的每个步骤(例如,列出文件、调用 API 或设置变量)
-
连接器:这是绑定到多个外部服务(如 Azure Service Bus、SFTP 或 Twitter),你将在工作流中使用它们
请注意,当频繁执行复杂工作流时,Azure Logic Apps 可能会非常昂贵。在这种情况下,可以考虑使用其他服务(如 Azure Functions),这些服务当然需要更多的开发时间,但另一方面提供更好的定价。
Logic Apps 的连接器
Azure Logic Apps 的主要概念是连接器。连接器既是操作也是触发器,你可以在工作流中使用它们来获取数据、转换数据,并扩展你应用程序的当前功能。目前,Azure Logic Apps 提供 200 种不同的连接器,允许你与多个 Azure 服务(如 Azure API 管理或 Azure Functions)、其他系统(如 OneDrive 和 Salesforce)甚至本地系统进行集成。
连接器类型
在 Azure Logic Apps 中,连接器分为两类:
-
内置 连接器:这些连接器旨在与 Azure 服务一起工作,创建工作流,并围绕处理应用程序逻辑和数据进行组织
-
托管 连接器:这些连接器简化了与其他系统和服务的集成
托管连接器被划分为更加详细的组别:
-
托管的 API 连接器
-
本地连接器
-
集成账户连接器
-
企业连接器
在这一节中,我们将通过多个不同类型的连接器示例,使你能够理解它们的使用场景和功能。
内置连接器
以下是你可以在 Azure Logic Apps 中使用的内置连接器的示例:
-
计划:用于在特定时间表上运行 Logic Apps 或暂停它们的执行
-
HTTP:用于通过 HTTP 协议与端点通信
-
请求:用于使 Logic App 可被其他服务调用或发送响应
-
批处理:用于批量处理消息
-
Azure Functions:用于运行自定义代码片段
-
Azure API Management:用于集成其他服务定义的触发器和操作
-
Azure App Services:用于调用 API 应用和 Web 应用
-
Azure Logic Apps:用于调用其他 Logic Apps
如你所见,我们这里有更多通用的连接器(如计划、HTTP 和请求)以及那些特定于某个服务的连接器(如 Azure Functions 或 Azure App Services)。通常,这些连接器构成了大多数 Azure Logic Apps 的基础——在创建工作流时,我们通常需要调用 API 或执行其他 HTTP 请求。
请注意,每个连接器都有详细的文档,描述了其使用场景以及如何使用它开发工作流。你可以在本章的进一步阅读部分找到相关链接。
托管 API 连接器
使用托管的 API 连接器时,你将能够集成需要配置连接的服务和系统;这些连接器可以在执行 Logic App 实例时使用。以下是一个 Logic App 在资源组中与定义为 API 连接的附加资源的示例:

在文档中,描述了许多不同的 API 连接器——我们来回顾一些示例:
-
Azure Service Bus
-
SQL Server
-
Office 365 Outlook
-
Azure Blob 存储
-
SFTP
-
SharePoint Online
如你所见,使用这些连接器,我们可以访问多种不同的 Azure 服务和其他系统(这里是 Office 365 和 SharePoint),这些都可以在工作流中被利用,以扩展你的应用程序逻辑。
本地连接器
正如我之前所描述的,使用 Azure Logic Apps,你还可以集成本地服务,如不同的数据库系统(Oracle、MySQL 和 SQL Server)、商业分析系统(Teradata)或文件系统。
要访问本地数据,你需要创建一个名为本地数据网关的资源。关于如何操作的说明可以在此找到:docs.microsoft.com/en-us/azure/logic-apps/logic-apps-gateway-connection。
集成帐户连接器和企业连接器
Azure 逻辑应用还允许你使用集成帐户和利用各种高级连接器构建所谓的 B2B 解决方案,例如 XML 转换和 X12 编码/解码,甚至访问企业系统如 SAP。对于大多数用户来说,这些功能并不是特别有用(因为这些是相对高级的话题,大多数人并不熟悉),但能够构建允许合作伙伴之间无缝通信的逻辑应用是一个有趣的功能。我们将在本章末尾详细讨论 Azure Logic Apps 中的 B2B 集成。
创建逻辑应用并集成服务
由于 Azure Logic Apps 也面向非开发人员,创建实例和使用它们的过程相当简单。在本章的这一部分,你将学习如何在 Azure 门户和 Visual Studio 中使用它们,如何集成多个服务并使用操作来控制工作流。
在 Azure 门户中创建逻辑应用
要创建一个逻辑应用实例,请按照以下步骤操作:
- 点击 + 创建资源,然后搜索 Logic App。当你点击“创建”按钮时,你将看到一个简单的表单,允许你创建一个新的逻辑应用实例:

-
实际上,你在这里唯一可以配置的额外项是启用 Azure 日志分析。由于本书中不会涉及此特定服务,我将在本章跳过它。当你点击“确定”按钮时,Azure 将开始创建一个新的逻辑应用。
-
创建完成后,你可以进入该服务,查看它最初的样子:

- 现在你可以点击左侧的 Logic App Designer 按钮,进入一个新的面板,允许你创建全新的工作流。最初,你将看到很多不同的模板可用—如果你对这个服务不熟悉,这些模板是一个很好的起点,它展示了许多不同的可能性和配置,你可以通过 Azure Logic Apps 实现这些功能。要开始,请在模板部分,从类别下拉菜单中选择“计划”选项,然后选择“调度器 - 添加消息到队列”:

- 当你点击“使用此模板”时,你将看到一个设计窗口,在那里你可以完成工作流的配置。请注意,最初我们看不到所有的模块——我们需要先设置所有缺失的配置值(如队列名称或消息),然后才能继续。配置完成并准备就绪后,你可以点击“继续”,这将允许你开始处理你的工作流:

创建与 Azure Queue 的连接将要求你创建一个存储帐户。如果你不熟悉该服务,请参阅第十一章,使用 Azure 存储 - 表、队列、文件和 Blob,了解 Azure 存储的相关内容。
- 要完成配置,你需要输入所有缺失的值——队列名称和消息。一旦你完成这些设置,就可以保存 Logic App,并尝试运行它。
这个特定的连接器如果队列不存在,不会创建队列。确保在启动 Logic App 实例之前已有队列。如果你不知道如何操作,请参阅第十一章,使用 Azure 存储 - 表、队列、文件和 Blob,我们将在该章节中讨论 Azure 存储功能和队列。
如果你点击“运行”按钮,你将能够看到整个工作流的执行过程:

我们可以将之前的执行与失败的执行进行比较,这样你可以看到它们之间的差异(例如消息内容或在工作流执行过程中创建的动态变量)。Logic Apps 的调试非常迅速,因为它们提供了详细的错误消息,并直接指向出现问题的地方:

恭喜,你已经创建了你的第一个 Azure Logic App!在下一部分,你将看到如何在 Visual Studio 中使用它。
在 Visual Studio 中使用 Azure Logic Apps
虽然通过 Azure Portal 使用 Logic Apps 是完全可以的,但你并不总是想要登录并使用它的 UI。由于可以安装 Microsoft Visual Studio 扩展,使你能够与 Azure Logic Apps 配合使用,我们将在本章中检查它的工作原理:
- 要在 Visual Studio 中开始使用 Logic Apps,你需要打开 Cloud Explorer(视图 | Cloud Explorer)窗口。它将显示可用的订阅和其中的资源列表。
如果 Cloud Explorer 窗口为空,请确保你安装了最新版本的 Visual Studio 和 Azure SDK,并且已经登录到你的 Azure 帐户。
- 在其他服务类型中,你应该能够找到 Logic Apps 部分:

这是主要视图,允许你开始使用特定的 Azure 组件。当你点击感兴趣的实例时,你将看到一个附加菜单,显示在资源下方。
你也可以右键点击你想要使用的资源——它会显示一个包含可用操作的菜单。
- 如下截图所示,我点击了“用 Logic App 编辑器打开”,它显示的是你在门户中看到的相同视图:

你可以像在 Azure 门户中一样,在 Visual Studio 中使用你的 Logic App。此外,你还可以查看它的历史记录,并禁用或删除它。
当你双击运行历史中的一个项目时,你将看到与 Azure 门户中相同的视图,在那里你可以调试特定的调用。这非常有帮助,因为你可以快速开发和测试你的 Logic Apps,并且可以在不离开 IDE 的情况下处理应用程序的其他部分。
我强烈建议你自己在 Visual Studio 中稍微试用一下 Azure Logic Apps,因为这是对整个服务的一个极好补充,并且感觉和门户一样。
B2B 集成
在本章中,我们已经略微涉及了 Azure Logic Apps 的 B2B 集成,但我想提供更多关于这个话题的信息,因为这个服务并不总是被认为是一个企业集成工具。令人惊讶的是,Azure Logic Apps 在交换信息和数据方面,尤其是在合作伙伴之间,提供了许多有趣的功能,相关细节可以在本节中找到。
在 Azure Logic Apps 中启动 B2B 集成
要开始 B2B 集成,你需要一个集成账户——这是你将要使用的集成工件的特殊容器。通常,它允许你将各种不同的项目(证书、架构和协议)存储在一个地方,以便在 Azure Logic Apps 中使用。
要创建这样的账户,请点击“+ 创建资源”,然后搜索集成账户:

如你所见,它提供了一个简单的表单,事实上,你需要做的唯一事情就是选择正确的定价层级。
选择的层级会改变最大存储的工件数量。当然,你可以稍后更改它。
一旦你有了集成账户实例,你需要将其与 Logic App 链接。为此,请在你的 Azure Logic Apps 实例中进入“工作流设置”面板,并搜索集成账户部分:

现在你应该能够使用需要集成账户才能工作的连接器了。
通常,如果你的 Logic App 没有链接集成账户,当你添加需要此功能的步骤时,你需要提供一个自定义名称。一旦连接可用,你将不再需要提供额外信息。
在本章的进一步阅读部分,你将找到更多链接,帮助你获取关于 Azure Logic Apps 中 B2B 集成的更多信息。
总结
在本章中,你已经了解了 Azure Logic Apps,这是一项简单但非常有用的服务,其使用并不限于 Azure 高手。你阅读了关于不同连接器的信息,并且学习了多种与 Logic Apps 配合工作的方式——通过 Azure 门户(包括设计器和代码编辑器)以及 Visual Studio。此外,你还应该了解有关该服务中的 B2B 集成及其启动方法的一些知识。
在第九章,瑞士军刀 - Azure Cosmos DB,我们介绍了 Azure CosmosDB——一个无服务器数据库,允许用户在同一服务中使用不同的数据库模型。
问题
-
Azure Logic Apps 的定价模式是什么?
-
我们可以在 Logic App 工作流中使用循环吗?
-
在 Visual Studio 中打开 Logic App 需要什么?
-
我们如何调试某个特定的 Logic App 执行?
-
Logic App 能否直接将消息推送到队列,例如 Azure Service Bus 或 Azure Storage Queue?
-
我们如何对多个 Logic App 进行版本控制?
进一步阅读
获取更多信息,你可以参考以下链接:
-
Azure Logic Apps 概览:
docs.microsoft.com/en-us/azure/logic-apps/logic-apps-overview -
交换 AS2 消息:
docs.microsoft.com/en-us/azure/logic-apps/logic-apps-enterprise-integration-as2 -
将 Azure Logic Apps 与企业解决方案集成:
docs.microsoft.com/en-us/azure/logic-apps/logic-apps-enterprise-integration-overview -
Azure Logic Apps 连接器:
docs.microsoft.com/en-us/connectors/
第九章:瑞士军刀 - Azure Cosmos DB
在存储方面,我们经常需要使用多个数据库存储多个数据模式。由于需要使用多个服务,管理我们的解决方案变得繁琐,并且需要具备相当的技能才能以正确的方式进行。感谢 Azure Cosmos DB,我们可以使用不同的数据库模型(如 MongoDB、表存储或 Gremlin)存储记录,同时仅为我们商定的内容付费——吞吐量、延迟、可用性和一致性,这一切都得益于无服务器模型。
本章将涵盖以下主题:
-
Azure Cosmos DB 是什么,它与其他存储系统相比如何
-
分区、吞吐量和一致性
-
不同的 Azure Cosmos DB 数据库模型
-
安全功能
技术要求
要执行本章中的练习,您需要以下内容:
-
Microsoft Visual Studio 或 Visual Studio Code
-
Azure 订阅
理解 Cosmos DB
在处理存储时,您可能听说过不同种类的存储:关系型数据库、NoSQL 数据库、图数据库、文档数据库。存储数据时,有很多不同的模型可供选择,每种模型具有不同的特点。如果您需要轻松地维护表之间的关系,通常情况下您会选择像 SQL Server 这样的数据库。另一方面,也许您想将每个记录保存为 JSON 文件格式,那么最合适的解决方案就是 MongoDB 实例。虽然选择完全取决于您,但最大的问题是,您需要有不同种类的服务来实现相同的目的——存储数据。这就是 Azure Cosmos DB 发挥作用的地方。凭借其多模型功能、灵活性和可扩展性,它是全球分布式和高响应应用的理想选择。在本节中,您将学习如何开始使用此服务及其主要功能。
在门户中创建 Cosmos DB 实例
我们将通过在 Azure 门户中创建 Azure Cosmos DB 开始我们的旅程:
- 当您点击 + 创建资源并搜索
Azure Cosmos DB时,您将看到一个简单的表单,允许您选择该服务的基本功能:

然而,也有一些不太明显的功能,需要稍微解释一下:
-
API:如前所述,Azure Cosmos DB 允许您在创建时使用几种不同的 API。目前,有五种可用的 API:SQL、MongoDB、Cassandra、Azure Table 和 Gremlin。根据所选择的 API,您将拥有不同的功能(而且更重要的是,应用程序代码中与数据库通信时需要不同的软件包)。
-
启用地理冗余:通过选择此选项,您的数据将分布在两个配对区域之间(取决于您在位置下拉菜单中选择的区域),例如西欧和北欧,或美国中部和美国东部 2。
-
启用多主模式:这是一个新的(目前处于预览阶段)功能,你可以在全球范围内拥有多个主数据库,而不仅仅是单一的主数据库。这大大减少了读取已保存数据时的延迟(因为你不必等待数据传播),并提高了一致性和数据完整性(因为你可以将数据写入特定区域的主实例)。
-
虚拟网络:根据你选择的模型,你可以通过将 Azure Cosmos DB 实例放入特定的虚拟网络和子网中,来限制对其的访问。目前,这对于两种数据库模型是可行的:SQL 和 MongoDB。
- 当你对所有输入的数据感到满意时,可以点击“创建”按钮来创建它。服务创建完成后,你可以访问“概览”面板,查看它的初始工作状态:

如你所见,中心显示了一个地图,告诉你数据在不同区域是如何复制的。如果你点击地图,你将能够重新配置初始设置。如果你点击“添加新区域”按钮,你将能够搜索特定区域并将其选择为额外的读取区域。或者,你可以直接点击一个区域图标:

在当前设置中,你无法添加额外的写入区域。要做到这一点,你必须使用我之前提到的多主模式功能。
一旦保存了额外的区域,手动故障转移和自动故障转移将会变为激活状态。故障转移的概念很简单——如果你的写入区域出现故障并变得不可用,另一个可用的读取区域可以代替它。唯一的区别是,你是希望手动执行故障转移,还是自动执行。
如果你选择了自动故障转移,你可以决定读取和写入区域之间的切换顺序。如果你想,比如,首先从北欧切换到西欧,那么西欧必须在列表中成为最高优先级。
如果你返回到“概览”面板,你会注意到一些额外的功能:
-
监控:在这里,你可以轻松找到所有对数据库的请求及其状态。
-
启用地理冗余:如果你在创建 Azure Cosmos DB 实例时没有启用此功能,你现在可以启用。
-
数据浏览器:点击此按钮,你可以轻松访问一个浏览器,允许你插入和修改数据。
此外,你还可以前往“快速入门”面板,在那里你将能够开始使用此 Azure 服务开发应用程序:

根据所选的数据库模型,您将能够访问不同的初始配置。此外,如您所见,您可以选择是否使用 .NET、Node.js、Java 或 Python——这些语言都可以轻松与 Azure Cosmos DB 集成,使其在创建多平台应用程序时成为一个更好的选择。
在 Visual Studio 中使用 Azure Cosmos DB
除了在门户中控制 Azure Cosmos DB,您还可以直接在您的代码和 IDE 中访问它,例如 Visual Studio。像许多其他服务一样,您可以使用 Cloud Explorer 浏览您的订阅中所有可用的数据库实例:

现在让我们尝试通过一个简单的应用程序与它进行通信。虽然 Cosmos DB 的实例最初是空的,但我们可以迅速为其添加一个表。
在本节中,我们将使用 Cosmos DB 中的表 API。如果您想使用其他类型,您需要查看 进一步阅读 部分的教程。
请参考以下代码片段:
using System;
using Microsoft.Azure.CosmosDB.Table;
using Microsoft.Azure.Storage;
namespace HandsOnAzureCosmosDB
{
internal class Program
{
private static void Main()
{
// You can get the connection string from the Quick start blade mentioned previously
var connectionString = "<connection-string>";
var storageAccount = CloudStorageAccount.Parse(connectionString);
var tableClient = storageAccount.CreateCloudTableClient();
var reference = tableClient.GetTableReference("handsonazure");
var result = reference.CreateIfNotExists();
Console.ReadLine();
}
}
}
在前面的代码中,我们正在创建一个空表,该表应该会在 Cosmos DB 实例中立即可用。现在,如果我再次检查 Cloud Explorer,我看到实际上它是正确的:

现在我们可以向其中添加一条记录。我们稍微修改代码如下:
using System;
using Microsoft.Azure.CosmosDB.Table;
using Microsoft.Azure.Storage;
namespace HandsOnAzureCosmosDB
{
internal class Program
{
private static void Main()
{
// You can get the connection string from the Quick start blade mentioned previously
var connectionString = "<connection-string>";
var storageAccount = CloudStorageAccount.Parse(connectionString);
var tableClient = storageAccount.CreateCloudTableClient();
var reference = tableClient.GetTableReference("handsonazure");
var result = reference.CreateIfNotExists();
var executionResult = reference.Execute(TableOperation.Insert(new TableEntity("handsonazure", Guid.NewGuid().ToString())));
Console.WriteLine(executionResult.Result);
Console.ReadLine();
}
}
}
现在我们通过门户中的 Data Explorer 来查看 Visual Studio 和 Azure 门户之间的差异。我们应该能够看到我们刚刚插入的实体:

如您所见,开始使用具有多种数据库模型的数据库,且能够快速配置以实现地理冗余并在全球范围内进行扩展,只需几行代码。
Azure Cosmos DB 的定价
Azure Cosmos DB 是 Azure 中的一部分无服务器服务。这意味着配置和提供服务器以运行它的机会要么极为有限,要么不可用。如您所见,我们无法定义希望运行多少实例的服务(或节点或集群)。相反,我们必须为每个集合单独定义 吞吐量:

一个简单的计算器显示屏还估算了每小时和每天的集合成本。Azure Cosmos DB 中的吞吐量单位是 请求单位(RUs)。在创建容器(或集合——您可以使用这两种定义)时,您还需要指定其类型——是 固定 容量还是 无限制。
一旦定义了集合类型,您无法再更改它。
通过选择不同的选项,您可以为 RUs 选择不同的限制:
-
对于 固定,您可以选择从 400 到 10,000 的 RUs。
-
对于 无限制,您可以从 1,000 RUs 到最多 100,000 RUs 之间进行选择。
关于定价,你支付的是存储的数据量($0.25 GB/月)和预留的 RUs(每 100 RUs 每小时 $0.008)。有了这些值,我们可以迅速计算出最小账单金额——大约是 $23。现在有一个非常重要的警告:你是按每个集合/表/容器收费的。这意味着,如果你有例如 20 个不同的表,你需要支付 20 * $23 = $462。在这种情况下,有时最好将数据库建模为可以将所有数据存储在一个容器中的方式。
尽管 Azure Cosmos DB 看起来是一项相当昂贵的服务,但请记住,它为你做了很多事情,比如地理冗余、多个读取区域、多主模式等。你始终需要计算出最适合你的选项(并且如果能通过类似的方式获得相同的结果)。为此,可以查看 进一步阅读 部分中的容量规划器。
分区、吞吐量和一致性
现在我们已经了解了有关 Azure Cosmos DB 的一些基本知识——它是如何工作的以及其最常见的功能——接下来我们可以稍微关注一下这个服务中的三个非常重要的主题:分区、吞吐量和一致性。这些因素在选择数据库引擎来支持你的应用程序时至关重要。它们直接告诉你系统的性能表现、能够处理多少请求以及在数据完整性方面适用的保证。
Azure Cosmos DB 中的分区
分区与 Azure Cosmos DB 中的扩展性密切相关,因为它可以为传入请求进行负载均衡。事实上,在此服务中有两种不同类型的分区:
-
物理:这些是固定存储和可变计算资源的组合。这种类型的分区由 Cosmos DB 完全管理——你无法直接影响数据是如何物理分区的,也无法控制服务如何处理这些分区。实际上,你也不知道当前有多少分区正在被使用。因此,你不应该针对这个特定概念设计容器。
-
逻辑:这种分区包含具有相同分区键的数据。因为你可以定义该键(通过在每个实体中指定它),所以你可以控制数据的分区方式。
请记住,逻辑分区的最大限制为 10 GB。此外,所有属于一个逻辑分区的数据必须存储在同一个物理分区内。
现在你可能会想知道 Azure Cosmos DB 中的分区是如何工作的。这可以通过几个步骤来描述:
-
每次创建一个新容器时(并且你提供了若干 RUs),Cosmos DB 都需要创建物理分区,这些分区将能够处理指定数量的请求。
-
结果可能是指定的 RUs 数量超过了每个分区每秒处理的最大请求数。在这种情况下,Cosmos DB 将预配所需的分区数量,以满足你的要求。
-
接下来需要做的是为分区键哈希分配空间。所有已预配的分区都必须分配相同的空间(以便均匀分配)。
-
现在,如果某个分区在一段时间后达到了存储限制,它将被拆分为两个新的分区,数据将在它们之间均匀分配。
当然,用于分区的分区键因数据库模型而异——对于表来说,它是分区键,对于 SQL,它将是一个自定义路径。一般而言,这个操作根据数据库类型略有不同,但整体概念是相同的。
记住,如果你的容器对其中所有实体只有一个分区键,那么 Cosmos DB 将无法对该分区进行拆分。这意味着,你可能会达到每个分区最大 10GB 的限制,并且无法再添加更多数据。
分区键的最佳值完全取决于你的应用数据规范。通常,你需要选择一个具有较大差异性的值(这样才可以进行分区)。另一方面,你不应为每个记录创建唯一的分区键(尽管可能做到,但成本非常高)。对于这个问题没有统一的解决方案——你总是需要分析每个场景,并选择最适合你的方案。
在大多数情况下,值得在过滤数据时包括分区键,因为它可以支持高并发。
Azure Cosmos DB 中的吞吐量
在进一步阅读部分,你将找到一个容量计算器——这是一种工具,可以帮助你规划你的 Cosmos DB 实例并估算所需的 RUs。如前所述,在这个特定服务中,你并不需要定义实例或集群的数量。相反,在创建容器时,你需要指定该特定集合(或一组集合)期望的吞吐量。得益于 Azure Cosmos DB 的服务水平协议(SLA),该值将得到保障。此外,即使你将数据库复制到另一个区域,你也可以预期一个区域的问题不会影响其他区域。
1 个请求单位有一个重要的定义——它是处理能力,允许你使用简单的 GET 请求读取 1KB 的实体。对于插入或删除等操作则不同,因为这些操作需要更多的计算能力来执行。
如果你想知道一个特定操作消耗了多少 RUs,你需要查看来自 Cosmos DB 实例的响应中的x-ms-request-charge头部。它将告诉你此操作的成本——当然,你需要记住,这个成本可能会根据返回的记录数量而有所不同。在文档中,你可以找到以下表格:
| 操作 | 请求单位费用 |
|---|---|
| 创建项 | ~15 RU |
| 读取项 | ~1 RU |
| 按 ID 查询项 | ~2.5 RU |
这些是针对大小为 1 KB 的实体执行操作时的值。如你所见,值会根据操作类型的不同而完全不同。你还可以看到,仔细检查所有操作要求是至关重要的——如果你没有做到这一点,可能会遇到 HTTP 429 响应,表示你已经超过了预留的吞吐量限制。在这种情况下,你应该遵守x-ms-retry-after-ms头响应,这样可以轻松实施重试策略。
Azure Cosmos DB 中的一致性
除了不同的数据库模型外,Azure Cosmos DB 还提供了不同级别的一致性。你可能会想知道一致性是什么,它如何影响你的数据。
我们可以按如下定义它:
一致性是数据库系统的一个参数,反映了事务如何影响数据。它定义了当不同的约束或/和触发器影响写入数据库的数据时所应用的规则。
基本上,它告诉你,如果一组操作影响了你的数据,它将不会被损坏,你可以依赖它。以下是 Cosmos DB 中可用的一致性模型:
-
STRONG
-
BOUNDED STATELESS
-
SESSION
-
CONSISTENT PREFIX
-
EVENTUAL
在上述列表中,每个低于 STRONG 的一致性级别都会给你带来较少的一致性。这对于 EVENTUAL 尤其如此,你可能通过一个名为“最终一致性”的主题有所了解。一般来说,你为你的账户设置默认的一致性级别——然后可以针对每个请求覆盖它(当然,如果你愿意的话)。如果你想了解每个一致性级别具体如何工作,请参阅进一步阅读部分。要在你的 Cosmos DB 实例中设置特定级别,请点击“默认一致性”选项卡:

如你所见,它允许你根据需求轻松切换到另一个一致性级别。更重要的是,它显示了一个漂亮的动画,描述了在多个区域中读取/写入如何适应这个特定级别。下面的截图展示了最终一致性的动画:

在此截图中,每个单独的节点代表在特定区域中的单次读取或写入操作。此外,在这个界面中,你还可以在选择了 BOUNDED STALENESS 级别时设置最大延迟(时间)。
CosmosDB 数据模型和 API
如前所述,Azure Cosmos DB 提供了五种不同的数据库模型,它们共享相同的基础设施和概念。这是一个很棒的特点,使得该服务非常灵活,可以服务于多种不同的目的。在本节中,我将简要描述每个数据库模型,以便你能够选择最适合你需求的模型。
SQL
如果你想到 SQL,你可能会想到一个包含表格、关系和存储过程的关系型数据库。当你使用 Cosmos DB 中的SQL API时,实际上你会使用可以通过 SQL 语法查询的文档。假设你想使用以下调用查询文档:
SELECT * FROM dbo.Order O WHERE O.AccountNumber = "0000-12-223-12"
这里是一个用 C#编写的查询示例:
var order =
client.CreateDocumentQuery<Order>(collectionLink)
.Where(so => so.AccountNumber == "0000-12-223-12")
.AsEnumerable()
.FirstOrDefault();
如你所见,这完全是通过一个简单的 LINQ 查询,它允许你使用特定的属性来过滤数据。由于所有 Cosmos DB 中的记录都是以 JSON 文档的形式存储的,你可以轻松地将它们从表格形式转换为文档表示(并可能进行非规范化处理)。
使用文档数据库与存储关系型数据库中的数据完全不同。始终记住要根据数据库的能力恰当地建模你的数据。
MongoDB
由于 Cosmos DB 实现了 MongoDB 的协议,你可以轻松将当前使用 MongoDB 文档数据库的所有应用程序迁移到新的 Azure Cosmos DB 实例,而无需更改任何内容(除了连接字符串)。虽然它目前还无法完全模拟 MongoDB(支持的操作的完整列表可以在进一步阅读部分找到),但在大多数情况下,你将能够无缝地使用它。由于 Cosmos DB 在安全性方面有严格要求,因此你在与其通信时必须使用 SSL:
mongodb://username:password@host:port/[database]?ssl=true
这里你可以看到一个连接字符串的模板,ssl=true已经包含在其中——它是在与此 Azure 服务通信时必需的。而且,你将无法在未认证请求的情况下设置通信。
图形
Azure Cosmos DB 支持 Gremlin 作为图形数据库模型。如果你不熟悉图形数据库,你可以将其看作由顶点和边组成的结构。它们能够非常方便地展示图形中不同元素之间的关系,因为你可以迅速遍历这些连接,发现元素 A 通过元素 C 间接了解了元素 B。更具体地说, Cosmos DB 支持一种被称为属性图的更具体的图形数据库模型。以下是 Gremlin 查询的示例:
:> g.V('thomas.1').out('knows').out('uses').out('runsos').group().by('name').by(count())
上述示例取自文档,直观地回答了这个问题:用户 ID 为 thomas.1的关系使用了哪些操作系统?图形数据库非常适用于社交媒体门户或物联网中心等应用。
表格
虽然你可以将 Azure Storage Table 用于你的应用程序(这一部分将在接下来的章节中讲解),你也可以利用 Cosmos DB 的 Table API,并考虑使用该服务处理更复杂的场景。这两种服务之间存在一些差异:
-
当前, Azure Storage Tables 的最大操作限制为每秒 20,000 次操作,而 Cosmos DB 则能够达到数百万次操作
-
你不能为 Storage Table 启动故障转移
-
在 Cosmos DB 中,数据会根据所有属性进行索引,而不仅仅是分区键和行键
-
不同的定价(存储与吞吐量)
-
不同的一致性级别
使用 Cosmos DB Table API 进行开发与使用 Azure Table Storage 是相同的。以下是一个 C#代码示例,从表中检索实体:
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
CloudConfigurationManager.GetSetting("StorageConnectionString"));
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference("people");
TableQuery<CustomerEntity> query = new TableQuery<CustomerEntity>().Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "Smith"));
foreach (CustomerEntity entity in table.ExecuteQuery(query))
{
Console.WriteLine("{0}, {1}\t{2}\t{3}", entity.PartitionKey, entity.RowKey,
entity.Email, entity.PhoneNumber);
}
Cassandra
在 Azure Cosmos DB 中,最后一个可用的模型是 Cassandra。Cassandra 是一个可扩展、持久化和去中心化的数据库,用于存储海量数据。现在,如果你将其与 Cosmos DB 一起使用,你可以专注于开发,而不是操作、性能管理或一致性选择。虽然当前此模型处于预览阶段,但你可以进行测试,并查看它带给你的优势。底层使用 Cassandra API,因此可以使用 Cassandra 查询语言与数据进行交互。此模型与 MongoDB 相似——你可以使用与当前 Cassandra 实例相同的工具,且不应注意到任何差异。
Cosmos DB 的不同功能
Azure Cosmos DB 具有多个不同的功能,可以帮助你降低费用、保障实例安全或与其他服务集成。在本节中,我们将快速浏览其中的大部分功能,帮助你全面理解此服务的基础知识,并能自主推进。
账户级吞吐量
有时,你可能希望为整个账户设置一个固定的吞吐量值,而不是为每个集合单独定义吞吐量。如果你有许多不同的容器,且不希望为每个容器单独付费(正如你记得的——每月超过$20),你可以选择账户级吞吐量并为整个账户设置吞吐量:

该功能的唯一限制是,你目前不能在账户中创建表。如果启用此功能,所有对所有表的请求将共享相同数量的吞吐量(这样你可以支付更少的费用,但如果遇到“贪婪”的集合,你可能会耗尽 RUs)。该功能的缺点是,无论是否创建了集合,你都将为已配置的吞吐量付费。
请注意,上述功能仅适用于 Table API。
数据库级吞吐量
在 Azure Cosmos DB 中,也可以直接为数据库配置吞吐量。为此,你需要在创建数据库时选中“配置吞吐量”复选框:

启用该功能后,所有配置的 RUs 将跨数据库中所有可用的集合共享。
防火墙和虚拟网络
如果你在创建 Cosmos DB 时配置了虚拟网络功能,在此面板中你将能够进一步配置它。更重要的是,还可以配置防火墙——因此你可以限制特定 IP 范围的访问或禁止来自其他 Azure 数据中心的连接。一般来说,你不希望数据库被任何人访问,因此如果此功能可用,我强烈建议你使用它。
请注意,目前防火墙和虚拟网络(VNets)仅适用于 SQL API 和 Mongo API。其他 API 的支持应该很快就会提供。
Azure Functions
您可以通过使用添加 Azure Function 面板,轻松将 Azure Cosmos DB 与 Azure Functions 集成:

创建一个来自 Cosmos DB 的函数将向您的函数应用程序添加以下代码:
#r "Microsoft.Azure.Documents.Client"
using Microsoft.Azure.Documents;
using System.Collections.Generic;
using System;
public static async Task Run(IReadOnlyList<Document> input, TraceWriter log)
{
log.Verbose("Document count " + input.Count);
log.Verbose("First document Id " + input[0].Id);
}
这是 CSX 代码,我们没有涵盖过——不过,除了语法上的一些微小变化,它是纯 C#。这个函数将监听您在创建过程中选择的集合的变化——接下来它做什么由您决定。一般来说,这是将这两个服务集成的快捷且简单的方式。更重要的是,您可以为您的集合或表生成不止一个 Azure Function。现在,如果我添加一个文档,我可以看到它触发了一个函数:
Pause Clear Copy logs Expand
2018-08-07T06:01:28 Welcome, you are now connected to log-streaming service.
2018-08-07T06:01:34.556 [Info] Function started (Id=5fb63ab3-e128-45ad-b7a8-4ccfdad38c82)
2018-08-07T06:01:34.573 [Verbose] Document count 1
2018-08-07T06:01:34.573 [Verbose] First document Id test_document
2018-08-07T06:01:34.591 [Info] Function completed (Success, Id=5fb63ab3-e128-45ad-b7a8-4ccfdad38c82, Duration=21ms)
存储过程
Azure Cosmos DB 允许创建可以单独执行的存储过程,并可以包含您不想共享的额外逻辑。
如果您进入数据资源管理器中的集合,您将看到“新建存储过程”标签和创建存储过程的选项:

存储过程是用 JavaScript 编写的——这使您可以轻松访问文档的架构(因为它们都是 JSON 格式的)。更重要的是,它们是按集合注册的。这里有一个最简单的存储过程示例:
function sample(prefix) {
var context = getContext();
var response = context.getResponse();
response.setBody("Hello, World");
}
用户定义的函数和触发器
为了扩展查询语言,您可以编写自己的用户定义函数(UDF)并在查询中使用。请注意,您不能在存储过程中使用这些函数。UDF 用于扩展 Azure Cosmos DB 中的 SQL 查询语言,并且只能在查询内部调用。而触发器则分为两类:
-
前触发器
-
后触发器
此外,您可以选择此触发器所引用的操作:
-
全部
-
创建
-
删除
-
替换
这里您可以找到一个触发器的示例,它会在文档创建之前更新文档中的时间戳:
var context = getContext();
var request = context.getRequest();
var documentToCreate = request.getBody();
if (!("timestamp" in documentToCreate)) {
var ts = new Date();
documentToCreate["my timestamp"] = ts.getTime();
}
request.setBody(documentToCreate);
触发器当然也可以从数据资源管理器中获取:

总结
在这一章中,您了解了另一个无服务器 Azure 组件——Azure Cosmos DB。您看到了该服务支持的多种数据库模型,以及多个不同的功能,例如地理冗余和轻松扩展、引入新的读取区域(数据将在这些区域中进行复制)。更重要的是,您现在了解了多个一致性模型,并且知道如何在 Azure 门户中更改它们。
在下一章中,您将学习另一个热门话题:与 Azure Event Grid 的反应式架构。
问题
-
目前 Azure Cosmos DB 支持哪些 API?
-
Azure Table Storage 和 Cosmos DB 中的 Table API 的功能之间有差异吗?
-
可用的一致性模型有哪些?
-
哪种一致性模型更一致——界限一致性、过时一致性,还是最终一致性?
-
是否可以将 Azure Cosmos DB 的访问限制为仅来自单一 IP 的访问?
-
SQL API 和 SQL Server 是一样的吗?
-
使用存储过程的原因是什么?
-
是否可以为整个账户而不是每个集合单独配置 Azure Cosmos DB 的吞吐量?
进一步阅读
-
在 Azure Cosmos DB 中分区数据:
docs.microsoft.com/en-us/azure/cosmos-db/partition-data -
Azure Cosmos DB RUs:
docs.microsoft.com/en-us/azure/cosmos-db/request-units -
一致性级别:
docs.microsoft.com/en-us/azure/cosmos-db/consistency-levels -
Mongo DB 支持:
docs.microsoft.com/en-us/azure/cosmos-db/mongodb-feature-support#mongodb-protocol-support -
图形 API 和 Gremlin:
tinkerpop.apache.org/docs/current/reference/#intro
第十章:使用 Event Grid 的响应式架构
Azure Event Grid 是 Azure 中另一个代表无服务器服务的云组件。它可以被视为事件网关,既能加速我们的解决方案,又能反转控制,使得我们的服务不必等待其他服务,从而避免浪费可用资源。它也是一个很棒的路由工具,能够快速分配负载并进行扩展,帮助任务更快完成。
本章将涵盖以下内容:
-
Azure Event Grid 与响应式架构
-
通过 Azure Event Grid 连接到其他服务
-
Azure Event Grid 的安全功能
-
向 Azure Event Grid 发布自定义事件
-
将 Azure Functions 与 Azure Event Grid 集成
技术要求
要完成本章中的练习,你需要:
-
一个 Azure 订阅
-
一个兼容 Visual Studio 2017 的 IDE
Azure Event Grid 与响应式架构
在云中使用多个服务时,通常需要一个集中式服务,负责将事件路由到不同的端点。这使得数据交换变得非常简单——你不需要维护不同的 API URL,因为可以利用通用的事件架构和基于事件类型等的自定义路由配置。在 Azure 中,这样的服务被称为 Azure Event Grid——一个无服务器事件网关,是可用的较新云组件之一。采用按需付费的定价模式,你可以快速构建一个响应式架构,反转服务之间的通信,使它们变得被动。在本章中,你将学习如何使用 Event Grid,并将其与其他 Azure 组件集成。
响应式架构
要开始使用,让我们考虑以下架构图所示的内容:

在此图中,你可以看到上传示例流,比如从用户上传头像图像。文件通过Azure App Service 传输,并存入Azure Blob Storage。然后,它被Azure Functions 处理。虽然这样的设置完全可以,但考虑以下缺点——为了处理图像,Azure Functions 必须收到有关新文件已上传的通知。
由于Azure Blob Storage 无法做到这一点(至少在公开功能中无法实现),实现这一目标的唯一方式是轮询存储并以某种方式保持已处理的文件。尽管从概念上讲,这不是高科技,但你必须记住,在云中,使用资源时需要为所消耗的时间付费。因此,在前述场景中,即使没有文件上传到存储,你也需要支付费用,因为Azure Functions 中的触发器(这里是 Blob 触发器)必须保持文件的状态,并定期检查是否有新内容出现,因此你往往会为无事发生的情况付费。现在,考虑以下更改:

如你所见,我在Azure Blob 存储和Azure Functions之间放置了一个Azure Event Grid。它改变了什么呢?好吧,处理 Blob 的函数不再需要轮询存储以获取上传文件的信息。这是由于 Azure Storage 的版本 2(你可以在进一步阅读部分找到描述的链接)支持的功能——它可以将事件发布到Azure Event Grid,然后这些事件可以被转发到所有该事件类型的订阅者。感谢这一点,Azure Functions可以保持被动——它们会在需要时由Azure Event Grid调用,因此如果没有上传文件,你就不需要支付费用。当然,这也是无服务器架构的一个元素——按需付费使得这种设置成为可能。
记住,如果你只使用 Azure Functions 的消费计划,你将不会被收费。如果你必须为函数使用应用服务计划,那么你将无法通过前面的架构节省资金——另一方面,你将节省一些计算资源,这些资源可以用于其他工作负载,因此反应式架构的概念仍然有效。
这就是我们所说的反应式架构——一种模型,在这种模型中,组件可以保持空闲状态,等待即将到来的请求。
主题和事件订阅
在使用Azure Event Grid时,有五个主要的主题:
-
事件
-
事件处理程序
-
事件源
-
主题
-
事件订阅
在本节中,我们将逐一了解这些服务,以更好地理解该服务。
事件源
目前,Azure Event Grid支持以下事件源:
-
Azure Blob 存储
-
Azure 媒体服务
-
Azure 订阅
-
资源组
-
Azure 事件中心
-
Azure IoT 中心
-
Azure 服务总线
-
自定义主题
-
容器注册表
-
存储通用版本 v2(GPv2)
如你所见,在使用 Event Grid 时,集成了许多不同的服务并且可以使用。虽然我们知道可以使用哪些事件源,但我们仍然没有定义什么是事件源。请看下面的图示:

在这种场景下,上传到Azure Blob 存储的文件触发一个事件,然后该事件被Azure Event Grid获取并进一步传递给消费者。事件源是事件的来源,之后由 Event Grid 处理。当使用此服务时,所有事件源都有发布事件并与Azure Event Grid进行通信的方法。还有一个额外的事件源——自定义主题。你可以将自己的自定义事件直接发布到 Event Grid 端点——我们将在本章后面介绍这个。
事件处理程序
在前面的示例中,我们介绍了事件源。我们来看看一个类似的场景:

再次强调,我们有 Azure Blob Storage 作为发布者,但这一次,事件被转发到 Azure Functions 和 Azure Event Hub。在该架构中,右侧展示的服务是事件处理器。以下是当前支持的服务列表:
-
Azure Functions
-
Azure Logic Apps
-
Azure 自动化
-
WebHooks
-
Azure Queue Storage
-
混合连接
-
Azure Event Hubs
-
Microsoft Flow
那么,事件处理器究竟是什么?你可以把它看作是一个事件处理器——根据配置,Azure Event Grid 会将事件转发到处理器,在那里它们将被反序列化和分析。
通常,Azure Event Grid 在将事件传递给处理器时使用通用的事件模式。更重要的是,它可以一次交付多个事件——你需要准备好可能收到一批事件。
主题和订阅
主题是一个通用的消息传递概念,允许一对多的通信。它与订阅的工作方式如下——你将消息发布到消息服务中的一个主题,然后让消费者订阅它。在 Azure Event Grid 中,你负责创建一个主题——这意味着你必须发布一个自定义应用程序来处理发布者和 Event Grid 端点之间的通信。你可以有一个应用程序,或者多个应用程序——这取决于你的设计和预期吞吐量。此外,你还需要配置订阅——在下一节中,你将看到如何做这一步,并设置适当的过滤条件。一般结构可能如下所示:

上图的左侧代表发布者和主题(发布者与 Azure Event Grid 之间的连线)以及带有处理器的订阅。每条线代表一个不同的主题和订阅。整个配置和路由都位于 Event Grid 内,可以在这里进行管理。
Azure Event Grid 会处理未送达消息的重试。可以通过自定义策略配置重试规则。此外,当使用自定义主题时,事件必须以批量方式发布,才能正常工作。
总结一下,我们可以像下面这样定义一个主题和一个订阅:
-
主题:服务与 Azure Event Grid 之间的一个通道,允许前者将事件推送到 Azure 服务。
-
订阅:Azure Event Grid 和服务之间的一个通道,用于从前者检索事件。
通过 Azure Event Grid 连接服务
现在你已经了解了一些关于 Azure Event Grid 的知识以及它的工作原理,我们将尝试测试它并创建一个有效的解决方案。我们将从在 Azure Portal 中创建实例并配置它以接收和路由事件开始。你还将看到事件的模式是什么,以及如何利用它发送自定义事件,这些事件将由 Event Grid 处理。
在 Azure Portal 中创建 Azure Event Grid
要开始使用 Azure Event Grid,请在 Azure Portal 中执行以下操作:
-
点击“+ 创建资源”,然后搜索
Event Grid。从列表中选择 Event Grid 主题并点击“创建”。 -
你会看到一个非常简单的表单,在其中你需要输入服务实例的名称:

- 当你点击“创建”并稍等片刻后,服务实例将被创建。完成后,你可以进入你的资源查看空实例:

如你所见,尚未创建订阅。而且,更重要的是,也没有主题,主题才是发送事件到我们实例的地方。在我们继续之前,先来看一下“概述”面板。除了创建订阅的选项外,还有另一项重要内容——主题端点。你将使用它来发布来自自定义主题的事件。还有一个重要的面板——访问密钥。点击它,你将看到两个密钥,可以用来授权访问 Azure Event Grid:

现在,让我们尝试创建一个带有订阅的主题。为此,我们将使用以下代码片段:
$myEndpoint = "<my-endpoint>"
Set-AzureRmContext -Subscription "<subcription-name>"
New-AzureRmEventGridSubscription `
-Endpoint $myEndpoint `
-EventSubscriptionName "<event-subscription-name>"
-ResourceGroupName "<rg-name>"
这段 PowerShell 代码应创建一个资源组主题,将事件推送到 $myEndpoint 变量中定义的端点。然而,如果你执行这段代码,以下错误将发生:
New-AzureRmEventGridSubscription : Long running operation failed with status 'Failed'. Additional Info:'The attempt to validate the provided endpoint <your-endpoint>. For more details, visit https://aka.ms/esvalidation.'
发生了什么?嗯,事实证明我们无法创建订阅,因为我们的端点没有经过验证。我们该如何验证端点,以便能够创建订阅呢?我会很快解释。
Azure Event Grid 安全性
除了访问令牌,Azure Event Grid 还会检查端点是否有效且安全。以下处理程序类型将不会进行验证:
-
Azure 逻辑应用
-
Azure 自动化
-
当使用
EventGridTrigger时的 Azure Functions
其余的端点(尤其是那些通过 HTTP 请求触发的端点)必须进行验证才能使用。下面是这种类型的验证是如何处理的:
-
首先,
SubscriptionValidationEvent会发送到一个包含多个字段的端点,比如主题、验证代码等。此外,还会发送一个特殊的aeg-event-type: SubscriptionValidation头部。 -
其次,Event Grid 期望收到一个成功响应,响应中包含请求中发送的验证代码。
这是一个验证事件的示例:
[{
"id": "3d178aaf-364c-67b-bq0c-e34519da4eww",
"topic": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"subject": "",
"data": {
"validationCode": "512d38b6-c7b8-40c8-89fe-f46f9e9622b6",
"validationUrl": "<validation-url>"
},
"eventType": "Microsoft.EventGrid.SubscriptionValidationEvent",
"eventTime": "2018-08-10T10:20:19.4556811Z",
"metadataVersion": "1",
"dataVersion": "1"
}]
在这种情况下,要验证一个端点,你需要返回以下响应:
{
"validationResponse": "512d38b6-c7b8-40c8-89fe-f46f9e9622b6"
}
之后,你应该能够创建一个订阅。
正如你可能已经注意到的,验证事件中还包含了 validationUrl 属性。它允许你手动验证订阅,而不是重新部署带有正确应用逻辑的代码。
创建订阅
现在你已经熟悉了端点验证主题,我们可以再次尝试创建订阅:
- 为此,我创建了一个由 HTTP 请求触发的函数。我在 CSX 中快速写了它,这样就不需要手动编译和部署:
#r "Newtonsoft.Json"
using System.Net;
using Newtonsoft.Json;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
var @event = JsonConvert.DeserializeObject(await req.Content.ReadAsStringAsync());
log.Info(@event.ToString());
return req.CreateResponse(HttpStatusCode.OK);
}
多亏了前面的代码,我可以看到一个验证事件被发送到一个端点。现在,根据你拥有的工具集版本,你将在负载中看到 validationUrl 的值。
- 要利用此功能,你必须为 Azure CLI 2.0 安装 Event Grid 扩展——下载链接可以在 进一步阅读 部分找到。要在没有此功能的情况下继续进行,我们将需要稍微修改一下我们的函数代码:
#r "Newtonsoft.Json"
using System.Net;
using Newtonsoft.Json;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
var @event = JsonConvert.DeserializeObject<ValidationEvent[]>(await req.Content.ReadAsStringAsync())[0];
return req.CreateResponse(HttpStatusCode.OK, new {validationResponse = @event.Data.ValidationCode} );
}
public class ValidationEvent {
public ValidationEventData Data {get;set;}
}
public class ValidationEventData {
public string ValidationCode {get;set;}
}
- 请注意,我正在将验证事件反序列化为
ValidationEvent[],所以它实际上是一个事件数组。记住这一点很重要,以避免可能出现的问题。
如果事件被发送到一个未经验证的端点,批处理将被分为两部分,一部分包含一个单独的验证事件,另一部分包含实际的事件。
- 现在,如果你执行之前失败的 PowerShell 代码,你应该能够创建一个订阅。为了检查是否一切正常,你可以运行以下命令:
Get-AzureRmEventGridSubscription
- 在这里,你可以看到我这边的结果:
PS C:\Users\kamz> Get-AzureRmEventGridSubscription
Name Topic ProvisioningState SubjectBeginsWith SubjectEndsWith Endpoint
---- ----- ----------------- ----------------- --------------- --------
rg-subscription /subscriptions/<id> Succeeded https://handsonazure-function.azurewebsites.net/api/HttpTriggerCSharp1
请注意,通过 API 创建的订阅在门户中是不可见的。这应该很快就会改变,但在这个问题没有解决之前,主要还是依赖 命令行界面 (CLI) 来维护 Azure Event Grid。
- 现在,假设你在资源组中创建了一个将事件发布到 Event Grid 的资源,类似以下的事件将会发生:
{
"subject": "/subscriptions/.../Microsoft.Storage/storageAccounts/handsonazure",
"eventType": "Microsoft.Resources.ResourceWriteSuccess",
"eventTime": "2018-08-10T08:51:32.3888833Z",
"id": "37f85f91-1af9-4ee3-84a6-ee1955c74edc",
"data": {
"authorization": {
"scope": "/subscriptions/.../handsonazure-rg/providers/Microsoft.Storage/storageAccounts/handsonazure",
"action": "Microsoft.Storage/storageAccounts/write",
"evidence": {
"role": "Subscription Admin"
}
},
"claims": {
"aud": "https://management.core.windows.net/",
(...)
}
}
}
- 也可以在没有 CLI 的情况下创建这样的连接——如果你进入你的资源组,你将看到 Eventsblade:

- 当你点击 + 事件订阅按钮时,你将看到一个表单,使整个过程变得更加简便。如果你更喜欢在门户中配置服务,而不是使用 CLI,可以使用这个表单:

将自定义事件发布到 Azure Event Grid
到目前为止,我们已经讨论了如何通过已经内置的发布者和主题来集成 Azure Event Grid,使用资源组作为示例。我在本章开始时提到过,这个服务能够处理自定义主题,使其成为一个非常灵活的解决方案,可以作为事件网关。在本节中,我们将讨论这个话题,并尝试使用 Event Grid 作为我们的路由器来处理和维护事件的路由。
事件网关概念
让我们看看以下的图示:

在这里,你有一个单一的事件生产者和四个不同的处理器。现在,如果你想象一下Publisher仅发布自定义事件,你会看到 Event Grid 能够将它们分发到N个不同的处理器(这些处理器不必是相同类型——它们可以是任何支持的处理器的混合)。当然,这个概念也可以用于像资源组、Azure Blob Storage 或 Azure Event Hub 这样的发布者——就个人而言,我认为它对自定义场景更有用。让我们举个例子——你正在发布一个OrderCreated事件。通过 Azure Event Grid,你现在可以将其分发到不同的处理器:
-
OrderConfirmation:例如,用于通过邮件发送确认。 -
OrderProcessor:用于处理订单的实际逻辑。 -
OrderNotification:用于通知某人有一个需要验证的订单。
当然,前面的步骤仅依赖于你的逻辑——不过,它应该能给你一个关于如何使用 Azure Event Grid 进行事件路由和分发的提示。
处理自定义事件。
在我们发送自定义事件之前,我们需要查看 Event Grid 事件架构:
[
{
"topic": string,
"subject": string,
"id": string,
"eventType": string,
"eventTime": string,
"data":{
object-unique-to-each-publisher
},
"dataVersion": string,
"metadataVersion": string
}
]
如你所见,它是一个简单的 JSON 数组,包含许多不同的事件。让我们在这里描述每个字段:
-
topic:这定义了一个事件源的完整路径(例如,Azure Blob Storage)。 -
subject:这定义了事件主题的路径(因此,在发布来自资源组的事件的情况下,这可能是指向 Azure 资源的完整路径,或者在 Azure Blob Storage 的情况下,这将是一个 Blob 路径)。 -
id:这是事件的唯一标识符。 -
eventType:已发布事件的类型(例如Microsoft.Resources.ResourceWriteSuccess)。 -
eventTime:这定义了事件发布的时间,使用发布者的 UTC 时间。 -
data:事件的有效载荷。 -
dataVersion:由发布者定义的事件架构版本。 -
metadataVersion:由 Event Grid 提供的事件元数据架构版本的修订号。
现在,如果你想发布一个事件,你需要做以下操作:
-
即使你发布的是单个事件,也始终使用事件数组。
-
要么使用一个空的主题,要么使用以下语法,它反映了每个主题也是一个 Azure 资源
/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.EventGrid/topics/<eventgrid-name> -
使用
aeg-sas-key或aeg-sas-token通过提供访问密钥中的密钥来授权请求。
这里,你可以找到一个请求示例:
POST /api/events HTTP/1.1
Host: handsonazure-eventgrid.westeurope-1.eventgrid.azure.net
Content-Type: application/json
aeg-sas-key: <sas-key>
Cache-Control: no-cache
[
{
"subject": "example",
"id": "1",
"eventType": "SectionFinished",
"eventTime": "2018-08-12T07:41:00.9584103Z",
"data":{
"section": 3
},
"dataVersion": "1"
}
]
如果一切正常,你应该会看到一个HTTP 200响应。现在,你可能会想知道如何接收这样的请求。如果你进入你的 Azure Event Grid 实例并点击+事件订阅按钮,你会看到一个可以创建新订阅的表单:

你之前见过这个表单,当我们讨论如何直接从资源组创建订阅时。 然而,有一些重要的事项需要提到:
-
订阅所有事件类型:你可以选择将所有事件类型路由到终端,或者仅将你定义的事件类型路由到终端(取消勾选复选框后,你可以输入任何你想要的事件类型)。这对于正确的事件路由非常有用。
-
终端类型:你可以选择不同的终端类型,包括 WebHook、Azure Event Hub 和混合连接。
-
事件模式:你可以选择使用 Event Grid 模式或更常见的 Cloud Events 模式,后者是一个开放的标准规范,可以用于为系统中的所有组件引入自定义模式。
-
过滤器:你还可以通过过滤主题字段值来进一步过滤特定类型的事件。
当你填写所有值后,点击创建按钮来实际创建它。
记住,必须验证终端才能成功创建订阅!
现在,如果你发送一个示例请求,你应该能够在你的处理程序中接收到它(在我的案例中是 Azure Functions):
2018-08-12T08:17:25.263 [Info] Function started (Id=8216a64d-19c5-436f-8cce-69fc49a3cff2)
2018-08-12T08:17:25.404 [Info] [
{
"subject": "example",
"id": "1",
"eventType": "SectionFinished",
"eventTime": "2018-08-12T07:41:00.9584103Z",
"data": {
"section": 3
},
"dataVersion": "1",
"metadataVersion": "1",
"topic": "/subscriptions/.../resourceGroups/handsonazure-rg/providers/Microsoft.EventGrid/topics/handsonazure-eventgrid"
}
]
2018-08-12T08:17:25.404 [Info] Function completed (Success, Id=8216a64d-19c5-436f-8cce-69fc49a3cff2, Duration=138ms)
你可能会想,如果事件处理程序没有返回成功响应给 Azure Event Grid,会发生什么?在这种情况下,将执行重试操作。
Azure Event Grid 只将 HTTP 200 和 HTTP 202 响应视为成功。
默认情况下,Event Grid 使用指数回退重试策略。这意味着每次连续的重试将在前一次和下一次重试之间增加延迟。你可以通过提供自定义重试策略来定制此行为。关于该功能的链接可以在进一步阅读部分找到。
将 Azure Functions 与 Azure Event Grid 集成
我们将要讨论的 Azure Event Grid 的最后一个内容是与 Azure Functions 的集成。如前所述,如果你使用 Event Grid 向由 HTTP 触发器触发的 Azure Functions 发布事件,你必须验证终端。这并不是最佳解决方案,但幸运的是,使用 EventGridTrigger 是可行的,它允许我们在配置服务时跳过终端验证步骤。这个话题本身非常大,因此我们不会覆盖所有可能的问题;不过,我会将你指引到文档的特定部分,帮助你更好地理解这个主题。
Azure Functions 中的 EventGridTrigger
一般来说,将 Azure Functions 与 Azure Event Grid 集成的最简单方法是使用 HttpTrigger:
[FunctionName("HttpTriggerCSharp")]
public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequestMessage req,
TraceWriter log)
{
(...)
}
这是最通用的设置。它提供了对请求消息的直接访问,并使你能够控制其特定部分。然而,也有一个替代方案——我们可以使用 EventGridTrigger:
[FunctionName("EventGridTriggerCSharp")]
public static void Run([EventGridTrigger]JObject eventGridEvent, TraceWriter log)
{
log.Info(eventGridEvent.ToString(Formatting.Indented));
}
在这里,使用EventGridTrigger,您可以直接访问请求的有效负载,这对于不关心其余部分的情况非常有用。此外,您不需要验证端点。如果使用 Azure Functions 运行时的版本 2,前面的函数可以稍作不同:
[FunctionName("EventGridTest")]
public static void EventGridTest([EventGridTrigger]EventGridEvent eventGridEvent, TraceWriter log)
{
log.Info(eventGridEvent.Data.ToString());
}
如您所见,这里您可以访问一个明确定义的EventGridEvent,而不是绑定到JObject。
即使在 Azure Functions 运行时版本 1 中,也可以使用EventGridEvent。为此,您需要通过安装Microsoft.Azure.EventGrid NuGet 包来手动引用Microsoft.Azure.EventGrid.Models.EventGridEvent。
您可以通过 Azure 门户轻松创建一个由 Event Grid 触发的函数,如下所示:

创建函数后,您将看到一个代码片段,可以从这里开始编写函数。重要的是添加事件网格订阅,您需要用它来将函数与 Azure Event Grid 集成:

生成的代码取决于您的 Function App 的运行时版本——在我的示例中,我使用的是版本 1,因此我使用了JObject而不是EventGridEvent。
当您点击它时,您将看到一个表单,您可以填写该表单来创建订阅。实际上,它非常类似于您在 Event Grid 实例中创建订阅时看到的表单:

唯一的区别是一些字段会自动填充。创建订阅后,您可以通过向 Event Grid 发送例如自定义事件来进行测试。更重要的是,刚创建的订阅应能在您的 Azure Event Grid 实例的概览页面上看到:

测试 Azure Event Grid 和 Azure Functions
您可能在考虑如何在本地测试 Azure Event Grid 和 Azure Functions。事实上,当前您有两种方法可以实现这一点:
-
捕获并重新发送事件到您的应用程序
-
使用 ngrok(可在
ngrok.com/上找到)将请求转发到本地计算机
您选择的这些方法将取决于您的能力(例如,ngrok 会暴露您计算机的端口,这可能带来安全问题),因此您需要自己判断哪种选项最适合。两种方法在进一步阅读部分提到的链接中都有描述。然而,Azure Functions 有一个有趣的功能,可以用来在本地测试 Event Grid。它可以通过以下端点找到:
http://localhost:7071/admin/extensions/EventGridExtensionConfig?functionName={functionname}
这里是一个请求的示例:
POST /admin/extensions/EventGridExtensionConfig?functionName=Function1 HTTP/1.1
Host: localhost:7071
Content-Type: application/json
aeg-event-type: Notification
Cache-Control: no-cache
[
{
"subject": "example",
"id": "1",
"eventType": "SectionFinished",
"eventTime": "2018-08-12T07:41:00.9584103Z",
"data":{
"section": 3
},
"dataVersion": "1",
}
]
这里有一个重要的事项——您必须将aeg-event-type设置为Notification。如果未设置,您将收到HTTP 400响应。通过这样的设置,您可以模拟您的函数在部署到 Azure 时的行为。
总结
本章介绍了什么是反应式架构以及如何与 Azure Event Grid 一起使用。你将不同的事件生产者与事件处理程序集成,并使用自定义主题发布自定义事件。此外,你现在还掌握了如何将 Azure Event Grid 与 Azure Functions 集成并进行本地测试的知识。
本章结束了本书的第二部分,内容涉及无服务器服务和架构。在下一部分中,我们将介绍不同的存储选项、消息传递和监控服务,这将进一步拓宽你在 Azure 上的专业技能。
问题
-
Azure Event Grid 中支持的事件模式有哪些?
-
如何在发布自定义事件时授权请求访问 Event Grid 端点?
-
验证端点时需要返回什么?
-
何时不需要验证端点?
-
如果端点没有返回成功响应,会发生什么?
-
如何在 Azure Event Grid 中过滤事件?
-
如何测试 Azure Event Grid 与 Azure Functions 的集成?
进一步阅读
-
Azure 存储 V2 账户:
docs.microsoft.com/en-us/azure/storage/common/storage-account-options#general-purpose-v2-accounts -
Azure CLI:
docs.microsoft.com/en-us/cli/azure/azure-cli-extensions-list?view=azure-cli-latest -
CloudEvents 标准规范:
cloudevents.io/ -
Azure Event Grid 中的事件传递:
docs.microsoft.com/en-us/azure/event-grid/manage-event-delivery -
Azure Function 与 Azure Event Grid 绑定:
docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-event-grid
第十一章:使用 Azure 存储 - 表格、队列、文件和 Blob
Azure 中的 PaaS 不仅仅是关于应用服务或容器。这个云平台提供了更多功能,尤其是在存储、消息传递解决方案或监控方面。通过 Event Hub、Azure 存储或 Application Insights 等服务,我们得到了一个完整的云组件集,它们提供了极大的灵活性,并简化了开发完整、可扩展且易于维护的应用程序。
本章将涵盖以下主题:
-
使用 Azure 存储解决方案
-
使用 Azure 存储表格存储结构化数据
-
实现 Azure 存储文件的完全托管文件共享
-
使用 Azure 存储队列的队列
-
使用 Azure 存储 Blob 进行对象存储
技术要求
要执行本章的练习,您需要以下内容:
-
一个 Azure 订阅
-
Visual Studio 2017
-
Azure 存储资源管理器,网址:
azure.microsoft.com/en-us/features/storage-explorer/ -
Azure 存储模拟器,网址:
azure.microsoft.com/en-us/downloads/
在解决方案中使用 Azure 存储
大多数应用程序在没有存储解决方案的情况下无法工作。存储解决方案可以是任何类型的数据库——关系型、文档型、文件型或图形型。大多数情况下,它们都需要一些技能来配置并开始使用它们。目前,我们已经介绍了 Azure 中的一种存储解决方案,即 Azure Cosmos DB,它是一个无服务器数据库,唯一需要做的就是设置正确的吞吐量值。当然,Azure 提供了更多存储服务,其中最常见的是 Azure 存储。它是一个 PaaS 云组件(虽然有些人将其定义为无服务器,主要是因为没有服务器),可以通过四种不同的方式使用。在本章中,我们将覆盖所有这些方式,帮助您熟悉它们的功能和特点。
不同的 Azure 存储服务
Azure 存储由四个不同的服务组成:
-
表格存储
-
队列存储
-
Blob 存储
-
Azure 文件
它们各自有不同的用途,提供不同的功能和限制。虽然它们的名称直观易懂,但您会发现每个服务都是完全不同的,虽然它们可以互相配合使用,但需要不同的技能才能高效地做到这一点,并且必须遵循最佳实践。此外,Azure 存储还提供了一个额外的服务——磁盘存储,这是虚拟机使用的功能。由于这个原因,本书将不涉及此部分内容。不过,您可以在进一步阅读部分找到相关文档的链接。
不同类型的存储账户
Azure 存储提供三种不同类型的存储账户:
-
通用标准型:支持表格、Blob、文件和队列,且支持三种不同类型的 Blob:块 Blob、页 Blob 和追加 Blob
-
通用高级:仅限于块存储,支持页块存储
-
支持热/冷访问层的 Blob 存储:仅限于块存储,并支持块存储和追加块
你将在接下来的章节中学习更多关于不同种类块存储的内容。目前的问题是:标准帐户和高级帐户之间的区别是什么——当然,除了定价之外?你可以这样定义它们:
-
标准:最常见的选择,具有合理的性能并支持所有类型的数据。这些帐户使用磁盘存储。
-
高级:性能更好的帐户,得益于使用 SSD 硬盘——推荐用于虚拟机以及当你需要快速访问存储在其中的数据时。
如果你想比较两种帐户的性能,以下是 128 GB 磁盘的对比:
-
标准:500 I/O 操作/秒,吞吐量 50 MB/秒,月费 €3,78
-
高级:500 I/O 操作/秒,吞吐量 100 MB/秒,月费 €15,12
所以,如你所见,高级选项的吞吐量大约是标准选项的两倍。
保护 Azure 存储
一般来说,有两种方法可以保护对存储帐户的访问:
-
Azure AD 与 RBAC
-
SAS 令牌
此外,块存储可以公开访问(当然,前提是你决定这么做)。根据你的需求,某个选项可能更符合你的要求——这当然取决于你的应用程序特点。以下是这两种保护 Azure 存储的方法的区别:
-
RBAC:此方法用于保护对帐户的管理操作。你可以限制对服务特定功能的访问,仅限于 Azure AD 中定义的特定组。然而,你无法使用此方法保护块存储或表存储(尽管你可以通过保护对 SAS 令牌的访问间接做到这一点)。
-
SAS 令牌:这些是长字符串,存储描述访问资源的不同参数。它们指定服务类型、权限和令牌的有效期,或限制对 IP 地址的访问。
以下是一个 SAS 令牌的示例:
https://myaccount.blob.core.windows.net/securecontainer/blob.txt?sv=2015-04-05&st=2015-04-29T22%3A18%3A26Z&se=2015-04-30T02%3A23%3A26Z&sr=b&sp=rw&sip=168.1.5.60-168.1.5.70&spr=https&sig=Z%2FRHIX5Xcg0Mq2rqI3OlWTjEg2tYkboXr1P9ZUXDtkk%3D
如你所见,它限制了对存储在 securecontainer 容器中的 blob.txt 文件的访问。它定义了诸如服务版本(sv)、过期时间(se)或令牌的实际签名(sig)等参数。一般来说,使用 SAS 令牌,你可以限制对帐户或服务的访问(因此,也可以限制对表存储中一系列实体的访问)。
复制
使用云服务时,你必须预期任何服务都可能随时宕机。尽管 Azure 存储被认为是最耐用的服务之一(因为许多 Azure 服务依赖于它),但它仍然可能会发生故障。为了缓解与此类故障相关的问题,它提供了四种不同的复制方式:
-
本地冗余存储 (LRS):在同一数据中心内有三份数据副本
-
区域冗余存储 (ZRS):在同一区域内有三份数据副本
-
地理冗余存储(GRS):在同一数据中心内存储三份数据,再加上在另一区域内存储三份数据
-
读取访问地理冗余存储(RA-GRS):在同一数据中心内存储三份数据,再加上在另一区域内存储三份数据,并且可以从该区域进行读取
在使用 Azure Storage 构建应用时,你必须仔细设计其可用性要求。根据你的期望,可能需要选择不同的模型。
当使用将数据复制到另一个数据中心的模型时(基本上是 GRS 和 RA-GRS),需要考虑不同区域之间传输数据的成本。
你可能会想知道 LRS 与其他复制模型相比有多耐用。要了解这一点,你需要理解数据是如何在单个数据中心内存储的。实际上,Azure Storage 的磁盘被安装在机架内,这些机架构成了一个更大的概念,叫做 stamp。Stamps 被配置成使用不同的电源线和网络,正因为如此的设置,数据可以存储在不同的故障域中,确保如果一个故障,其他两个仍然能工作。微软表示,LRS 的设计目标是提供至少 99.999999999%的耐用性。如果这还不够,你可以考虑其他模型。
使用 RA-GRS 时,不要轻视如果发生故障时轻松写入备用区域的能力。启动故障转移是微软的责任(与 Azure Cosmos DB 不同,后者由你来决定),因此恢复时间目标(RTO)包括微软做出决策的时间和更改 DNS 条目以指向另一区域的时间。
使用 Azure Storage Tables 存储数据
我们将通过了解 Table Storage 开始我们的 Azure Storage 能力之旅。如果你希望存储几乎无限容量的非结构化数据,并且对可用性和耐用性有很高的要求,那么这个服务适合你。在这一部分,你将学习如何开始使用 Table Storage 开发应用程序,并了解存储数据和实现最佳性能的最佳实践,无论是写入还是读取。你还将学习如何高效地查询数据,以及在设计使用此 Azure Storage 功能的服务时需要注意的事项。
创建 Azure Storage 服务
要开始使用,首先我们需要实际创建一个 Azure Storage 实例。为此,请按照以下步骤操作:
-
进入 Azure 门户并点击+创建资源。搜索
storage account,然后点击创建按钮。 -
你会看到一个典型的表单,你需要在其中配置一个新的服务实例。以下是我选择的一个示例:

现在我想介绍一些这里提供的更神秘的选项:
-
部署模型:根据当前需求,您可以选择不同的部署模型。一般来说,几乎每个新的存储账户,默认选项是使用资源管理器。
经典模式设计用于传统部署,使用经典虚拟网络。这个选择还限制了在选择账户种类和一些额外功能,如性能层时可用的选项。 -
账户种类:这里有三个选项可用(通用、V1/V2 和 Blob)。如果想要使用包括表、队列、Blob 在内的多种功能的存储账户,请选择存储。选择
V2允许您定义访问层(冷或热),这直接关联到访问账户内数据的频率。 -
需要安全传输:使用Azure 存储可以要求启用安全连接选项。为了您的生产工作负载,务必打开此选项,以防止任何人通过例如 HTTP 而非 HTTPS 访问存储在账户中的数据。
-
性能:可以选择
标准或高级性能层。正如前面提到的,这会影响使用的硬件,标准层使用常规磁盘,而高级层使用 SSD。 -
虚拟网络:与许多其他服务一样,可以在虚拟网络中配置Azure 存储,以进一步限制对其的访问。
一切准备就绪后,您可以点击创建按钮并稍等片刻——您的账户将被创建,并很快您将能够开始使用它。
管理表存储
当您转到概述刀片时,您将看到一个包含有关您账户的基本信息的仪表板:

正如您所见,它显示了您在创建时定义的信息,如位置、性能层或复制类型。此外,当您向下滚动时,还将看到监视部分,其中显示整个服务的运行情况:

在本节中,我们介绍表存储,因此请在左侧找到表刀片并点击。最初,您可能根本看不到任何表——当然,这是我们预期的情况,因为此服务的实例刚刚被配置。尽管如此,这是一种检查账户内实际存储内容的方法之一:

要创建新表,只需点击+表按钮——您将被要求提供表名,这是开始的全部内容。也许您还记得,我描述过表存储作为存储非结构化数据的能力。这就是为什么在开始一个表时没有其他选项的原因——您只需依赖此服务工作的内部规范。以下展示了创建新容器时的情况:

上面的截图展示了一个orders表及其 URL——你可能会想知道这个 URL 到底是什么。由于有多种方式来管理和使用 Azure 服务,Azure 存储允许你通过不同的方法使用其功能,如 REST、Powershell、Azure CLI 或 Azure 门户。当你使用 SDK 并阅读其源代码时,你会发现它们实际上只是对简单 REST API 的封装。这使得该服务非常容易入门并在日常工作中使用。我们已经谈了些关于表存储的基础知识——现在是时候描述它们的架构了。
在表存储中存储数据
表存储中的每条记录都有一个包含多个列的行结构。每一行有以下基本列:
-
PartitionKey:行分区的标识符 -
RowKey:行的标识符 -
Timestamp:此列表示行最近被修改的时间 -
ETag:表存储实现了乐观并发控制模型,并使用 ETag 来控制是否修改实体
当然,你并不局限于上述列——你可以创建任何额外的列,并为每个列指定类型。然而,在进一步进行之前,你需要完全理解这种设计的含义。这里有一个例子,展示了存储在同一个表中的实体:
2018-07-13T11:56:11.108Z
PartitionKey |
RowKey |
Timestamp |
Name |
Price |
Created |
CustomerId |
Quantity |
|---|---|---|---|---|---|---|---|
| 订单 | 16Hbs6gs8s | 2018-07-13T11:56:11.108Z | 2018-07-13T11:36:11.108Z | customer-001 | |||
| 16Hbs6gs8s | 1 | 2018-07-13T11:57:17.108Z | 海绵 | 3.00 | 2018-07-13T11:36:11.108Z | 3 |
在前面的例子中,你的数据存储在多个分区中,尽管只使用了一个表,但仍然可以使用多个架构,因此无需使用额外的容器。此外,我使用了一个简单的模式,这使你能够引入 1:n 关系——每个订单都有一个唯一的RowKey,可以用作与之相关的实体的分区键(这使得查询数据变得非常简单)。
PartitionKey
表存储通过分区来分配、加载和处理请求。表中分区键的数量会影响它们的平衡能力。虽然可以为每个表使用单个分区,但在大多数情况下,这是一种无效的方法,会降低存储帐户的性能。分区键的大小限制为 1 KB,并且在表中必须唯一(因此,一旦实体分配了分区键,所有使用相同值的实体将存储在同一存储中)。它们也必须是字符串类型。
RowKey
每个行键是分区内一行的唯一标识符(因此,只要它们有不同的PartitionKey,你可以拥有使用相同RowKey列值的多行)。更重要的是,每个表按行键值升序排序。这要求你在设计时要聪明,例如,当你只需要读取部分顶端行而不想提供它们的行键时(我们将在本章后面讲到)。像PartitionKey一样,RowKey也有 1 KB 的限制,并且必须是字符串。
时间戳
这个列是由服务器端维护的,并且是一个DateTime值,每次实体被修改时都会更改。它也被内部用于提供乐观并发控制,并且不能被修改。即使你设置了它,值也会被忽略。
实体的一般规则
表存储在存储数据时有一些硬性限制:
-
最大列数为 255
-
实体的最大大小为 1 MB
-
默认情况下,每个实体列被创建为字符串类型——在创建时可以覆盖这个默认值
-
不可能存储
null值——如果你没有提供列的值,实体会被认为没有这个列
查询表存储中的数据
要在表存储中查询数据,你需要一个简单的应用程序(可以是控制台应用程序)和该服务的 SDK。你还需要一个 Azure 存储实例——它可以是 Azure 中配置的存储实例,也可以是本地的存储实例(如果你已安装存储模拟器)。
要开始使用存储模拟器,只需搜索可执行文件(例如,开始 | 输入Storage Emulator)并运行它。它会初步创建一个用于存储数据的数据库,并在后台运行,所以你不必担心意外关闭它。
为了开始,我们需要使用 NuGet 包管理器安装WindowsAzure.Storage包。它包含了开始在.NET 中使用 Azure 存储所需的所有内容。这里有一个创建表的代码示例:
using Microsoft.WindowsAzure.Storage;
namespace TableStorage
{
internal class Program
{
private static void Main()
{
var storageAccount = CloudStorageAccount.Parse("<connection-string>");
var tableClient = storageAccount.CreateCloudTableClient();
var table = tableClient.GetTableReference("orders");
table.CreateIfNotExists();
}
}
}
我们可以简要描述一下这段代码的作用:
-
它解析连接字符串,以便在接下来的方法中使用
-
它创建了一个
CloudTableClient类的实例,这是与表存储交互的主要类 -
它获取到一个
order表的引用,无论它是否存在 -
最后,它会创建一个
orders表(如果表尚不存在的话)
你也可以使用Create()方法来代替CreateIfNotExists(),不过如果表已经创建,使用Create()方法可能会出错。
现在我们需要获取连接字符串,具体取决于你想要使用的存储账户,你可以:
-
需要进入 Azure 门户,找到你的存储账户,然后从
Access keys选项卡中复制连接字符串 -
使用
UseDevelopmentStorage=true值来连接存储模拟器
当你执行一个应用程序时,表应该能够顺利创建。现在,当我们有了表后,我们希望实际往表中插入一些数据。为此,你需要以下代码:
var op = TableOperation.Insert(new DynamicTableEntity("orders", Guid.NewGuid().ToString(), "*",
new Dictionary<string, EntityProperty>
{
{"Created", EntityProperty.GeneratePropertyForDateTimeOffset(DateTimeOffset.Now)},
{"CustomerId", EntityProperty.GeneratePropertyForString("Customer-001")}
}));
table.Execute(op);
在这里,我们创建了一个新的TableOperation,它接受一个参数,即TableEntity的实例。TableEntity是一个包含所有行属性的基类,必须传递给表(如PartitionKey或RowKey)。当然,除了使用DynamicTableEntity,你还可以从TableEntity派生并引入自定义实体类。
在前面的示例中,我们使用了Insert()操作,但对于并发请求来说,这可能不是最好的选择。在这种情况下,有时更好的做法是使用InsertOrReplace()或InsertOrMerge()。
最后需要做的是查询表格。要在.NET 中执行此操作,你将需要像这样的一些代码:
var query = new TableQuery();
var result = table.ExecuteQuery(query);
foreach (var entity in result)
{
Console.WriteLine($"{entity.PartitionKey}|{entity.RowKey}|{entity.Timestamp}|{entity["Created"].DateTimeOffsetValue}|{entity["CustomerId"].StringValue}");
}
我们刚刚执行了一个基本查询,这将返回表中的所有行。虽然它现在能够工作,但使用这样的查询来查询表中的所有数据并不是最佳选择——在大多数情况下,你将使用以下类似的查询:
var query =
new TableQuery().Where(
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "orders"));
上述查询将返回表中所有具有orders分区键的行。你可以通过生成进一步的过滤条件,按照自己的需求扩展这些查询。
记住,为了获得最佳性能,你的查询应该同时包括PartitionKey和RowKey。仅使用PartitionKey会导致结果较差,但仍然是可以接受的。仅使用RowKey将导致读取整个分区。没有使用这些列将导致读取整个表。
你也可以使用 Azure Storage Explorer 查看表中存储的内容:

Azure Cosmos DB 中的表 API
可以使用 Azure Cosmos DB 来利用表存储的高级服务。使用这个选项有以下优点:
-
自动和手动故障转移。
-
二级索引(对行内所有属性进行索引的能力)。
-
跨多个区域的独立扩展。
-
不同的一致性级别。
-
每个表的专用吞吐量。
虽然仅使用表存储也可以实现故障转移,但其余的功能仅适用于 Azure Cosmos DB,并且当你喜欢这个服务的简易性,同时希望面对更复杂的场景时,它将是一个很好的解决方案。
使用 Azure Files 实现完全托管的文件共享
当你需要创建一个可以供不同人访问的文件共享时,你通常需要购买一些硬件,这些硬件将为这种功能进行设置和配置,或者使用第三方解决方案,这些解决方案可能难以定制或者价格昂贵。使用 Azure Storage,你可以快速开发一个几乎无限制容量、提供行业标准协议、并且可以快速配置和投入使用的解决方案。
Azure Files 概念
Azure Files 有一些基本概念,构成了整个服务的框架。实际上,它旨在取代当前的本地文件服务器,在功能和性能方面表现优异。Azure Files 与“传统”解决方案的主要区别在于可访问性(因为您可以设置访问令牌并将 URL 设置为私有)。此外,它是操作系统无关的,允许您在不同的机器上使用相同的文件共享,无论是 Linux、Windows 还是 macOS。当然,它还继承了其他 Azure 存储的概念,因此您可以以同样的可靠性和耐用性来使用它。Azure Files 的主要特点是支持 SMB 协议。这是一种非常常见的协议(也是一个成熟的协议,因其设计于 1980 年代中期),用于共享计算机资源,也用于打印机和其他网络设备。我们可以这样总结 Azure Files:
-
完全托管:这是一项完整的云服务,您无需担心操作系统或其配置。
-
耐用性和弹性:使用 Azure Files,您无需担心无法访问存储的数据,或担心电力故障等停机事件对资源的安全性造成影响。
-
常用开发工具:由于系统 I/O API、适当的 SDK,甚至 REST API,访问 Azure Files 变得非常简单。
使用 Azure Files
当您进入 Azure 门户并打开您的 Azure 存储实例时,您可以找到“文件”刀片。它与将要讨论的 Blob 存储非常相似,显示了可用的文件共享列表,如下图所示:

在此屏幕上,您可以通过点击+文件共享按钮来创建新的文件共享。这里最重要的是“配额”字段的值,它决定了文件共享的最大容量。
文件共享的配额最大值为 5,120 GB。
要获取有关如何连接到文件共享的信息,您可以点击右侧的更多按钮并选择连接:

它会显示一些简短的指令,说明如何快速从计算机连接到特定的文件共享。这里您可以看到一个在 PowerShell 中为 Windows 编写的命令示例:
$acctKey = ConvertTo-SecureString -String "<key>" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential -ArgumentList "Azure\handsonazurestore", $acctKey
New-PSDrive -Name Z -PSProvider FileSystem -Root "\\handsonazurestore.file.core.windows.net\handsonazure" -Credential $credential -Persist
您可以使用-Name参数指定驱动器的字母(在前面的示例中,它是Z)。
映射驱动器是一项可能需要额外权限的操作——请确保以管理员身份运行所有这些命令。
现在,我可以比较在 Azure 门户中显示的文件共享内容:

我在虚拟机上挂载的磁盘:

整个设置只用了几分钟——这是该服务的优势,因为通常我需要花费很多小时来设置所有内容并达到相同的可移植性和可靠性水平。它还为你提供了无限的存储容量——没有任何东西会阻止你附加多个 Azure Files 共享空间并将所有文件存储在其中。
Blob 存储与 Azure Files
事实上,Azure Blob 存储和 Azure Files 都有类似的目的——它们用于存储和共享文件。然而,在使用场景上,它们之间有一些根本性的差异,例如:
-
如果你想为公司创建一个通用的文件共享空间,你将使用 Azure Files
-
如果你希望为用户通过你的网站上传的文件提供一个存储空间,你将使用 Blob 存储
-
如果你希望文件完全私密,你将使用 Azure Files
-
如果你想在 Blob 或容器级别配置安全性,你将使用 Blob 存储
这两项服务也有不同的定价模型(例如,当按每 GB 数据收费时,Azure Files 要比 Blob 存储贵得多)。
Azure 队列存储中的队列
Azure 存储——除了作为存储多种不同类型数据的服务外——还可以用作队列。队列存储是另一个功能,允许你快速开发一个需要简单队列解决方案的应用程序,并且能够在队列中存储数百万条消息而不影响性能。在本节中,你将了解如何使用队列存储开发应用程序,以及使用该功能时需要注意的事项。此外,我假设你已经拥有一个存储账户。如果没有,可以查看使用 Azure 存储表存储数据一节,里面描述了创建账户的过程。
队列存储功能
一般来说,队列存储有两个使用场景:
-
异步处理消息
-
在不同服务之间交换通信(Azure Functions、传统的 Web 角色/Worker 角色)
这是一个非常简单的队列解决方案,能够存储和处理任何格式的消息,每条消息的大小限制为 64 KB。消息的保留时间为七天——超过这个时间,消息将丢失。队列的容量基本上等于你的存储账户的容量。通常,你不必担心存储空间用尽的问题。队列存储与其他 Azure 存储功能共享许多附加功能,如虚拟网络、SAS 令牌等。因此,我们在本节中不再重新介绍这些功能。
使用队列存储开发应用程序
为了展示队列存储,我创建了两个应用程序:
-
生产者
-
消费者
生产者将创建并推送消息,随后将被消费者消费。这里是生产者应用程序的代码:
using System;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Queue;
namespace QueueStorage.Producer
{
internal class Program
{
private static void Main()
{
var storageAccount = CloudStorageAccount.Parse("UseDevelopmentStorage=true");
var queueClient = storageAccount.CreateCloudQueueClient();
var queue = queueClient.GetQueueReference("orders");
queue.CreateIfNotExists();
var message = new CloudQueueMessage($"New order ID: {Guid.NewGuid()}");
queue.AddMessage(message);
}
}
}
当然还有消费者应用程序:
using System;
using Microsoft.WindowsAzure.Storage;
namespace QueueStorage.Consumer
{
internal class Program
{
private static void Main()
{
var storageAccount = CloudStorageAccount.Parse("UseDevelopmentStorage=true");
var queueClient = storageAccount.CreateCloudQueueClient();
var queue = queueClient.GetQueueReference("orders");
var message = queue.GetMessage();
Console.WriteLine(message.AsString);
Console.ReadLine();
}
}
}
当您向队列发布消息时,您可以随时取回它—如前所述,您有七天时间从队列中取出消息。这里您可以看到存储在队列中的消息样子:

对象存储解决方案 – Azure 存储 Blob
Azure 存储的最后一个功能是 Blob 存储。在前面的章节中,我们使用该服务通过表存储存储非结构化数据,通过队列存储推送消息,并通过文件存储创建文件共享。在本章的最后一部分,我们将重点开发存储所谓 blob 的解决方案。您可能会想知道什么是 blob—其实并没有一个单一的定义。一般来说,blob 是各种类型的文件,比如文本文件、图像或音频文件。您将了解如何在应用程序中使用它们,如何保护它们,以及如何实现最大性能。
Blob 存储概念
在深入了解服务之前,您需要了解 Blob 存储的基本概念。这里有一个图表,清楚地定义了三个主要主题:

如您所见,我们有三个不同的概念:
-
账户:即您的存储账户,存储所有 Blob 存储中的数据。
-
容器:一个逻辑实体,可以容纳无限数量的 blob。一个账户可以拥有无限数量的容器。
-
Blob:存储在容器中的文件。
此外,还有三种不同类型的 blob:
-
块 blob:最大大小为 4.7 TB 的文本或二进制数据。这样的 blob 由更小的块组成。
-
附加 blob:一种更为特定的 blob 类型,最适合用于日志数据、存储事件或事务日志等场景。它们针对追加操作进行了优化。
-
页面 blob:用于存储虚拟机(VM)使用的 VHD 文件。
使用最新版本的存储账户(v2),可以使用该服务的最新功能。最有趣的新增功能之一是访问层。现在可以选择使用热层或冷层,选择取决于数据访问的频率—如果你希望频繁读取数据,热层是最佳选择;否则,使用冷层或通用账户会更好。
上述的各个存储层在选择 Blob 作为存储账户类型时可用,普通账户不可使用这些存储层。
还有一个层级:归档层—用于存储很少访问的 blob—尽管它仅在 blob 层级可用。您可能会想知道这些层级之间的差异。这里有一张表格,定义了它们的定价:
| 热层 | 归档层 | 冷层 | |
|---|---|---|---|
| 前 50 TB/月 | 每 GB $0.0184 | 每 GB $0.01 | 每 GB $0.002 | |
| 下一步 450 TB/月 | 每 GB $0.0177 | 每 GB $0.01 | 每 GB $0.002 | |
| 每月超过 500 TB | 每 GB $0.017 | 每 GB $0.01 | 每 GB $0.002 |
就存储而言,你可以看到热存储是最贵的,其他层级要便宜得多,特别是归档存储。现在让我们来看一下 10,000 次读取操作的价格:
-
热存储:$0.004
-
冷存储:$0.01
-
归档:$5
哎呀—这里的差别可大了!这就是选择正确层级如此重要的原因——如果你误用了 Blob 存储层级,你最终可能会得到一个花费非常高的解决方案。
向 Blob 存储中插入数据
现在我们将尝试向 Blob 存储中实际添加一些内容。这里有一段代码,可以帮助你上传一个文件到容器:
using System;
using Microsoft.WindowsAzure.Storage;
namespace BlobStorage
{
internal class Program
{
private static void Main()
{
var storageAccount = CloudStorageAccount.Parse("UseDevelopmentStorage=true");
var cloudBlobClient = storageAccount.CreateCloudBlobClient();
var container = cloudBlobClient.GetContainerReference("handsonazure");
container.CreateIfNotExists();
var blob = container.GetBlockBlobReference("foo.txt");
blob.UploadText("This is my first blob!");
Console.ReadLine();
}
}
}
和前面几个例子一样,这个也看起来非常相似。你需要按照以下步骤操作:
-
首先,你必须创建一个
CloudStorageAccount实例。 -
然后,你需要获取容器的引用,如果容器不存在,则创建它。
-
最后,你必须获取一个 Blob 的引用,并上传一些内容。
如果我打开 Azure Storage Explorer,我可以看到一个新的 Blob 已经上传到容器中:

当然,如果我打开文件,我会看到它包含我上传的文本:

容器和权限
在访问 Blob 存储中的容器时,你可以选择适当的访问级别。如果你进入 Azure 门户并打开你的 Azure Storage 服务,你可以找到 Blobs 磁贴。在其中,你可以点击 + 容器按钮,这将打开一个小窗口:

如你所见,除了为容器提供名称外,你还可以选择公共访问级别。目前,你有三种不同的选项可供选择:
-
私有:禁止匿名访问
-
Blob:Blob 层级的匿名访问
-
容器:容器级别的匿名访问
你可以点击你创建的容器,看到另一个屏幕,在那里你可以进行管理。我将用它来实际上传一个文件,看看有哪些其他选项可用。这里展示的是文件上传后,在门户中点击它的样子:

现在我可以看到关于文件的附加元数据,管理如获取租约或生成 SAS 令牌等操作。
如果你想将文件设为只读,点击“获取租约”按钮——虽然仍然可以更改它,但这种操作需要提供租约 ID。
更重要的是,存在一个可用的 URL 属性,可以直接访问 Blob,例如,使用浏览器访问。在我的例子中,它是这样的:
https://handsonazurestore.blob.core.windows.net/blob/11047_01_01.PNG
现在你可能想知道 Blob 和容器访问之间的区别是什么。为了找出答案,我们将使用以下代码:
using System;
using Microsoft.WindowsAzure.Storage.Blob;
namespace BlobStorage
{
internal class Program
{
private static void Main()
{
var container = new CloudBlobContainer(new Uri("<container-uri>"));
var blobs = container.ListBlobs();
foreach (var blob in blobs)
{
Console.WriteLine(blob.Uri);
}
Console.ReadLine();
}
}
}
我已经创建了两个不同的容器——一个具有 Blob 访问权限,一个具有容器访问权限。如果我执行前面的代码,针对具有完全公共访问权限的容器,我将看到如下内容:

现在让我们对一个仅具有 Blob 公共访问权限的容器运行它:

正如你所看到的,当容器的访问级别是 Blob 或私有时,容器级别的操作不可用。当然,如果你使用例如访问密钥进行授权,你可以列出容器中的所有 Blob,即使它是私有的。当然,也可以直接在代码中设置容器级别的权限:
using System;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
namespace BlobStorage
{
internal class Program
{
private static void Main()
{
var storageAccount = CloudStorageAccount.Parse("UseDevelopmentStorage=true");
var cloudBlobClient = storageAccount.CreateCloudBlobClient();
var container = cloudBlobClient.GetContainerReference("blob");
container.CreateIfNotExists(BlobContainerPublicAccessType.Blob);
var blob = container.GetBlockBlobReference("foo.txt");
blob.UploadText("This is my first blob!");
Console.ReadLine();
}
}
}
Blob 存储:附加功能
Blob 存储的一个最新且最酷的特性是软删除功能。它允许你执行称为软删除的操作。这意味着什么?在某些情况下,你可能希望删除一个文件,但又能在固定时间内轻松恢复该删除操作。在 Blob 存储中,通过软删除功能可以实现这一点:

如果你启用此功能,任何已删除的 Blob 将在存储中保留一段设定的天数(但不可检索或修改)。Blob 存储还具有两个其他功能,可以与两个其他 Azure 服务一起使用:
-
Azure CDN:一个为客户提供静态内容的内容分发网络服务——我们将在本书后续章节中详细介绍。
-
Azure 搜索:如前所述,你可以轻松地将 Blob 存储设置为搜索引擎的数据源。
正如你所看到的,这是一个非常灵活且实用的 Azure 存储功能,可以用于文件存储、作为 Azure 搜索文档存储、日志数据库等多种用途。
总结
在这一章中,你学习了关于 Azure 最重要的服务之一——Azure 存储的一些基础知识。我们为表格、队列、文件和 Blob 开发了一些解决方案——每种方案都可以实现不同的功能,从异步消息处理到创建文件共享。你还了解了不同的冗余模型,以及这个服务的可靠性和耐用性。在进一步阅读部分,你会找到更多的资源,这些资源将帮助你在使用这个 Azure 服务时提升技能,如表格存储模式、性能目标和 REST API 参考。在接下来的章节中,你将学习一些数据处理服务,比如 Azure 事件中心和 Azure 流分析。
问题
-
在创建帐户时,选择 Blob 作为帐户类型时,提供哪些存储层?
-
要在查询表格存储时实现最佳性能,必须包含哪些内容?
-
存储帐户有哪些可用的冗余模型?
-
Blob 存储和文件存储有什么区别?
-
你能使用 Blob 存储来存储二进制文件吗?
-
消息在队列存储中能存活多久才会被移除?
-
Queue 存储中消息的最大大小是多少?
-
PartitionKey列值的最大大小是多少? -
表存储中实现了什么并发模型?
-
Azure Files 存储和本地文件系统之间有什么区别?
深入阅读
-
磁盘存储:
docs.microsoft.com/en-us/azure/virtual-machines/windows/about-disks-and-vhds -
SAS 令牌参考:
docs.microsoft.com/en-us/azure/storage/common/storage-dotnet-shared-access-signature-part-1 -
ARM 与经典部署:
docs.microsoft.com/en-us/azure/azure-resource-manager/resource-manager-deployment-model -
表存储数据模型:
docs.microsoft.com/en-us/rest/api/storageservices/Understanding-the-Table-Service-Data-Model -
Blob 存储定价:
azure.microsoft.com/en-us/pricing/details/storage/blobs/ -
文件存储性能目标:
docs.microsoft.com/en-us/azure/storage/files/storage-files-scale-targets -
表存储指南:
docs.microsoft.com/en-us/azure/storage/tables/table-storage-design-guidelines
第十二章:大数据管道 - Azure Event Hub
Azure Event Hub 是引入几乎无限吞吐量入口点的最佳解决方案之一。它专为大数据工作负载设计,能够处理每秒数百万条消息。它提供了非常简单的配置,并且凭借可用的 SDK,您可以轻松地将其调整为几乎任何在云中开发的解决方案。它还与其他 Azure 组件本地集成,使得创建托管在云中的完整平台变得轻而易举。
本章将涵盖以下主题:
-
高效使用 Azure Event Hub
-
不同的概念,例如发布者、分区、吞吐量单元或消费者组
-
Azure Event Hub 安全概念
-
Azure Event Hub 捕获功能
技术要求
要执行本章中的练习,您需要以下内容:
-
Microsoft Azure 订阅
-
Visual Studio 2017
Azure Event Hub 服务和概念
如今,我们收集越来越多的数据,这些数据必须进行聚合、处理并存储到某个地方。这意味着需要使用能够处理不断增加的负载、适应日益增长的需求并提供最小延迟的服务。所有这些要求在构建所谓的大数据管道时都会被提到——这是一个旨在处理尽可能多数据的系统部分,以便以后可以通过 Hadoop、Spark、ML、AI 等工具访问。如果您正在寻找一个能够处理每秒数百万条消息的 Azure 服务,Azure Event Hub 是正确的选择。在本章中,您将学习此 Azure 组件的基础知识,并熟悉 Azure 中的消息传递解决方案。
Azure Event Hub 概念
一般来说,Azure Event Hub 是一个简单的服务,基于两个概念构建:
-
事件发布者
-
事件处理器主机
当然,这些并不是我们在这里将讨论的唯一主题。不过,在继续之前,我想先稍微关注一下发布者和处理器之间的区别:
-
发布者:这是向 Azure Event Hub 实例发送数据的实体。它可以使用两种可用协议(HTTP 或 AMQP),并且不关心当前 Event Hub 的能力。
-
处理器:一个从 Azure Event Hub 中读取事件的实体,它在事件可用时读取。它使用 AMQP 进行通信,并依赖于诸如消费者组和分区等附加概念。
以下展示了 Azure Event Hub 的工作原理:

如您所见,这里还提到了另外两个概念:
-
分区:每个分区是一个独立的事件日志,单独存储数据。一般来说,Event Hub 负责确保每个共享相同分区键的事件按顺序存储在同一个分区内。当然,您可以自行设置这个值——在这种情况下,您必须确保不会过度负载某个特定分区。
-
消费者组:如果你希望允许不同的处理器分别消费事件,你必须使用不同的消费者组来实现。
如你所见,Azure Event Hub 并未使用诸如实例主题之类的方式来分配数据——它充当一个单一的事件管道,你可以随时以高吞吐量进行读取。为了定义这一值,Event Hub 使用一个名为吞吐量单元(TU)的概念。1 TU 的定义如下:
-
最多 1MB/s 或 1,000 个事件的入站
-
最多 2MB/s 或 4,096 个事件的外发
请注意,Azure Event Hub 为你使用的所有消费者组共享 TU。如果你有 1 个 TU 和 5 个消费者组,最大外发量将会在所有消费者之间进行分配(因此,当所有 5 个消费者同时读取事件时,每秒最多将有 400 个事件可用)。
如果你恰好超过了可用的限制,Event Hub 将开始限制你的请求,最终返回ServerBusyException。然而,这仅适用于传入事件——对于外发,你不能读取超过当前 TU 值允许的数量。
默认情况下,每个 Event Hub 命名空间最多不能有超过 20 个 TU。然而,这只是一个软限制——你可以通过联系 Azure 支持来扩展它。
现在,让我们稍微关注一下分区。Event Hub 中的每个 Hub 最多可以有 32 个分区。你可能会想知道这意味着什么——实际上,这提供了一些额外的选择:
-
由于每个分区都可以有一个对应的消费者,默认情况下,你可以使用 32 个消费者并行处理消息。
-
因为在 Hub 创建后不能更改分区数,你必须在最初阶段仔细设计。
-
默认使用最大数量的分区并不总是最佳选择——它应当反映你计划支持的读者数量。如果你选择的分区数过多,它们会开始争夺分区的租赁。
以下是数据在 Hub 内不同分区之间如何全球存储的示意:

如前所述,每个分区可以独立增长——更重要的是,每个分区都有一个独立的offsetvalue。什么是offsetvalue?你可以将其视为日志中的某个特定位置的指针——如果它存储的是从 1 到 10,000 的事件,并且你已经读取了 1,000,那么offsetvalue将是 1,001。在这种情况下,意味着读者应从第 1,001 个事件开始读取数据。
事实上,offset和消费者组在概念上是相互关联的——每个消费者组都有一个独立的offset值;这就是为什么通过引入它,你可以再次读取所有可用日志的原因。
然而,记住,为了设置偏移量,消费者必须执行一个检查点。如果没有执行,下一次连接时,它将重新读取所有数据。如果你想避免处理重复数据,这一点非常重要——你要么需要实现一个非常可靠的事件处理过程,以确保即使出现故障也能执行检查点,要么你需要有一个机制来检测重复数据。
如果有需要,你可以通过在启动处理器时提供你感兴趣的偏移量值,轻松读取以前的事件。
目前需要考虑的最后一件事是 Azure Event Hub 对存储事件的保留策略。默认情况下(或者换句话说,使用基础层时),事件只能存储 24 小时以供消费;超过这个时间后,事件会丢失。当然,通过使用标准层,你可以延长保留时间,最多可延长至事件保留后的 7 天。一般来说,你应该避免将此服务作为标准队列或缓存使用——它的主要目的是提供每秒聚合数千条消息并进一步推送的功能。
Azure Event Hub 的耐久性
在许多场景中,Azure Event Hub 是系统的主要入口点之一,成为一个关键组件,应该进行复制并确保高可用性。在这个特定的服务中,地理灾难恢复功能在选择标准层时可用,并且需要你设置并配置合适的环境。为此,你需要理解以下主题:
-
别名:你可以使用别名来通过一个稳定的连接字符串进行连接,而不是提供多个连接字符串。
-
故障转移:这是在命名空间之间发起切换的过程。
-
主/辅助命名空间:在使用 Azure Event Hub 的地理灾难恢复功能时,你必须定义哪个命名空间是主命名空间,哪个是辅助命名空间。这里需要注意的是,你可以将事件发送到两个命名空间,但第二个命名空间保持被动——这意味着来自活动命名空间的事件不会被转移。
现在,为了在事件中心实现这个功能,你需要做两件事:
-
监控你的主命名空间以检测任何异常
-
启动故障转移
当然,如果发生灾难,完成故障转移后,你需要创建一个新的配对。
你必须知道“停机”和“灾难”之间的区别,停机通常是指数据中心内部的临时问题,而灾难通常意味着永久性损坏和可能的数据丢失。地理灾难恢复功能是为灾难设计的;在停机情况下,你应该实施其他处理方式,如本地缓存数据。
使用 Azure Event Hub
现在,您已经熟悉了一些基本概念,我们可以继续并开始使用真实的 Azure Event Hub 实例。在这一部分,您将学习如何在 Azure 门户中创建和访问 Event Hub,并使用其 SDK 进行操作。实际上,使用此服务既可以通过门户进行(因为许多 Azure 组件与之无缝集成,且无需额外配置),也可以通过提供自定义实现的消费者来读取和进一步处理数据。
在 Azure 门户中创建 Azure Event Hub
要创建一个 Azure Event Hub,我们将像大多数情况下那样,点击 + 创建资源 按钮。输入 Event Hub 并从搜索结果中选择该服务。在这里,您可以看到我 Event Hub 实例配置的示例:

现在让我们全球聚焦于我们所拥有的内容:
-
名称:这是您的 Event Hub 实例的唯一名称。请注意,它必须在所有 Azure 支持的实例中是唯一的。
-
定价层:您可以选择 Basic 和 Standard 层。实际上,还有一个额外的层——Dedicated,不过仅在您直接申请时才可使用。Basic 和 Standard 层之间在功能和吞吐量上差异很大;我们稍后会详细介绍。
-
启用 Kafka:这是一个新功能,允许您将 Azure Event Hub 用作 Apache Kafka 实例。通过这个功能,您可以在不重新配置与 Kafka 实例通信的应用程序的情况下,切换到此 Azure 服务。
-
使此命名空间具备区域冗余:如果您愿意,可以为 Event Hub 利用可用性区域,使整个命名空间具备区域冗余。这可以提高服务实例的可用性且无需额外费用(不过,您仍然需要为额外的实例付费)。目前,此功能已在三个位置启用——美国中部、美国东部 2 和法国中部。
-
订阅:将在其中创建实例的订阅。
-
资源组:在此资源组中将创建 Event Hub 实例。
-
区域:Azure Event Hub 将在此区域创建。
-
吞吐量单位:此设置定义了整个命名空间的吞吐量。在 Azure 中,每个 Event Hub 实例将在命名空间内与其他所有 Hub 共享可用的 TUs。根据需要,您可以稍后更改该值。
-
启用自动扩展:您可以启用自动扩展,而不是手动调整命名空间的吞吐量。此功能会随着负载增长自动扩展命名空间。但请注意,它不会自动缩减。通过启用该功能,您还可以定义最大 TUs 值,因此您不会被收取超过预期的费用。
请记住,您会为每个 TU 付费——这意味着如果您选择了 Basic 层并且需要 10 个 TUs,那么您每月需要支付 9.41 EUR * 10 = 94.1 EUR!
当您点击 Create 按钮时,Azure 将为您创建一个 Azure Event Hub 实例。
在门户中使用 Azure Event Hub
以下截图显示了一个全新的 Azure Event Hub 实例:

如您所见,它包含一些基本信息,如指标、元数据和连接字符串的访问权限。当然,这是命名空间的视图——我们还没有创建任何 Event Hub。在此之前,我想先关注一下我们当前拥有的内容。在左侧,您可以找到 SETTINGS 部分,其中包含附加功能:
-
共享访问策略:在 Azure Event Hub 中,访问策略有两个级别——它们可以分配给命名空间或集线器。通过这些策略,您可以与三种权限组合共享访问密钥——管理(Manage)、监听(Listen)和发送(Send)。
-
扩展:如果您觉得需要更多的吞吐量,您可以转到此页面并将命名空间的规模扩大(或者如果需要更少的 TUs,则缩小)。在该屏幕上,您还可以更改层级——例如,选择 Standard,以便启用自动扩展(Auto-Inflate)。
-
地理恢复(Geo-recovery):如果您需要使 Event Hub 高可用并且选择了 Standard 层级,您可以通过此页面启动与其他区域的配对。
现在让我们创建一个集线器:
- 点击 Event Hubs 页面:

- 点击 + Event Hub 按钮,查看一个表单,该表单允许您配置新的集线器实例:

请注意,某些字段目前是灰色的。这是因为我在此示例中使用了 Basic 层级; Message Retention(允许您将事件可用期延长至最多七天)和 Capture(将在后面描述)是 Standard 层级的功能。
- 点击 Create 按钮以启动创建一个集线器的过程。
一旦集线器创建完成,您可以点击它并访问它,如下所示:

请注意,这个视图与命名空间的视图略有不同;虽然它也包含一些元数据和指标,但可用的附加功能是有限的。
请注意,对于 Basic 层级,消费者组(consumer groups)也不可用。使用该层级时,只有默认组—命名为 $Default—可以使用。
使用 Azure Event Hub 开发应用程序
我们在 Azure 门户中创建并配置了我们的 Azure Event Hub 实例;现在是时候使用我一开始提到的概念——Event Processor Host。在本节中,您将学习:
-
如何将事件发送到 Azure Event Hub
-
如何通过实现您自己的 Event Processor Host 来接收事件
然而,在我们开始编写代码之前,你必须理解这一概念背后真正的原理。与竞争消费者不同,后者每个消费者都使用相同的消息通道,Azure Event Hub 采用了事件处理器主机(Event Processor Host)的概念,这是一个智能代理,能够在不同的分区消费者之间分配事件。你可能会好奇,当这个概念被实现时是如何工作的;为了理解这一点,你可以看到第一个场景的示意图:

在这个示意图中,你可以看到有一个生产者和四个不同的消费者。每个消费者都实现了 IEventProcessor——这是 SDK 提供的一个接口,使得接收事件成为可能。每个消费者覆盖一个分区并获得对该分区的租约。现在让我们查看另一个场景:

唯一的变化是分区的数量——现在 Event Hub 有八个。事件处理器主机确保负载被均匀分配,每个消费者将消费两个分区。为了让事情更加复杂,我们还需要考虑第三个场景:

这一次,我们有更多的消费者而不是可用的分区。在这种情况下,你会注意到一种情况,某个消费者无法工作,因为没有它可以处理的分区。在这种情况下还有一个需要注意的地方;因为租约持续时间(一个分区与特定消费者关联的时间)不是无限的,在某个时刻,当前处于空闲状态的消费者可能会剥夺其他消费者的控制权,并接管一个分区。现在,当我们描述了事件处理器主机的概念后,我们可以检查如何编写一些代码来与 Azure Event Hub 交互。以下是一个事件生产者的代码:
using System;
using System.Text;
using Microsoft.ServiceBus.Messaging;
namespace HandsOnAzure.Sender
{
internal class Program
{
private const string ConnectionString = "<connection-string>";
private static void Main()
{
var eventHubClient = EventHubClient.CreateFromConnectionString(ConnectionString);
try
{
var message = Guid.NewGuid().ToString();
Console.WriteLine("{0} > Sending message: {1}", DateTime.Now, message);
eventHubClient.Send(new EventData(Encoding.UTF8.GetBytes(message)));
}
catch (Exception exception)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("{0} > Exception: {1}", DateTime.Now, exception.Message);
Console.ResetColor();
}
Console.ReadLine();
}
}
}
要使用这个代码示例,你需要一个连接字符串。为了获取它,我访问了我的 Hub 实例,进入了共享访问策略(Shared access policies)界面,并创建了一个仅具有发送权限(Send)的新策略:

现在,当我执行我的应用程序时,我会看到它成功发送事件:
23.08.2018 11:20:50 > Sending message: 1a09038b-1aeb-4729-ace0-104f26c7d376
我们有一个生产者,现在我们需要一个消费者!我再次创建了一个访问策略,这次仅用于监听(Listen):

要创建一个消费者,你需要 Microsoft.Azure.ServiceBus.EventProcessorHost NuGet 包。安装后,你将能够像这样实现 IEventProcessor:
public class MyFirstEventProcessor : IEventProcessor
{
private Stopwatch _checkpointStopWatch;
public Task OpenAsync(PartitionContext context)
{
Console.WriteLine("SimpleEventProcessor initialized. Partition: '{0}', Offset: '{1}'", context.Lease.PartitionId, context.Lease.Offset);
_checkpointStopWatch = new Stopwatch();
_checkpointStopWatch.Start();
return Task.FromResult<object>(null);
}
public async Task ProcessEventsAsync(PartitionContext context, IEnumerable<EventData> messages)
{
foreach (var eventData in messages)
{
var data = Encoding.UTF8.GetString(eventData.GetBytes());
Console.WriteLine($"Message received. Partition: '{context.Lease.PartitionId}', Data: '{data}'");
}
if (_checkpointStopWatch.Elapsed > TimeSpan.FromMinutes(5))
{
await context.CheckpointAsync();
_checkpointStopWatch.Restart();
}
}
public async Task CloseAsync(PartitionContext context, CloseReason reason)
{
Console.WriteLine("Processor Shutting Down. Partition '{0}', Reason: '{1}'.", context.Lease.PartitionId, reason);
if (reason == CloseReason.Shutdown)
{
await context.CheckpointAsync();
}
}
}
如你所见,它有三个方法:OpenAsync、ProcessEventsAsync 和 CloseAsync。为了能够实际使用这样的处理器,你需要启动整个事件处理主机:
using System;
using Microsoft.ServiceBus.Messaging;
namespace HandsOnAzure.Receiver
{
internal class Program
{
private const string EventHubConnectionString = "<connection-string>";
private const string EventHubName = "<event-hub-name>";
private const string StorageAccountName = "<storage-account-name>";
private const string StorageAccountKey = "<storage-account-key>";
private static void Main()
{
var storageConnectionString =
$"DefaultEndpointsProtocol=https;AccountName={StorageAccountName};AccountKey={StorageAccountKey}";
var eventProcessorHostName = Guid.NewGuid().ToString();
var eventProcessorHost = new EventProcessorHost(eventProcessorHostName, EventHubName, EventHubConsumerGroup.DefaultGroupName, EventHubConnectionString, storageConnectionString);
Console.WriteLine("Registering EventProcessor...");
var options = new EventProcessorOptions();
options.ExceptionReceived += (sender, e) => { Console.WriteLine(e.Exception); };
eventProcessorHost.RegisterEventProcessorAsync<MyFirstEventProcessor>(options).Wait();
Console.WriteLine("Receiving. Press enter key to stop worker.");
Console.ReadLine();
eventProcessorHost.UnregisterEventProcessorAsync().Wait();
}
}
}
现在,当你运行应用程序时,你应该能够看到传入的事件。
请注意,事件处理器主机需要你创建一个存储帐户实例。它用于内部管理租约和偏移量。
在这里,你可以看到来自我的处理器的日志:
Registering EventProcessor...
Receiving. Press enter key to stop worker.
MyFirstEventProcessor initialized. Partition: '4', Offset: ''
MyFirstEventProcessor initialized. Partition: '9', Offset: ''
MyFirstEventProcessor initialized. Partition: '11', Offset: ''
MyFirstEventProcessor initialized. Partition: '8', Offset: ''
Message received. Partition: '9', Data: '5e0b2a73-ca9d-418d-8d47-43c7b7feb17e'
Message received. Partition: '4', Data: '1a09038b-1aeb-4729-ace0-104f26c7d376'
Message received. Partition: '4', Data: '859cce28-76e1-4a68-8637-a2349d898e8b'
MyFirstEventProcessor initialized. Partition: '15', Offset: ''
Message received. Partition: '15', Data: '36f13819-46d6-42c9-8afe-6776264e7aab'
MyFirstEventProcessor initialized. Partition: '1', Offset: ''
MyFirstEventProcessor initialized. Partition: '5', Offset: ''
MyFirstEventProcessor initialized. Partition: '0', Offset: ''
MyFirstEventProcessor initialized. Partition: '7', Offset: ''
MyFirstEventProcessor initialized. Partition: '12', Offset: ''
MyFirstEventProcessor initialized. Partition: '3', Offset: ''
MyFirstEventProcessor initialized. Partition: '14', Offset: ''
MyFirstEventProcessor initialized. Partition: '10', Offset: ''
MyFirstEventProcessor initialized. Partition: '2', Offset: ''
MyFirstEventProcessor initialized. Partition: '6', Offset: ''
MyFirstEventProcessor initialized. Partition: '13', Offset: ''
注意,一个接收方如何处理我为这个特定中心使用的所有 16 个分区。现在你可以查看如果我引入另一个消费者会发生什么:
Microsoft.ServiceBus.Messaging.ReceiverDisconnectedException: New receiver with higher epoch of '4' is created hence current receiver with epoch '3' is getting disconnected. If you are recreating the receiver, make sure a higher epoch is used. TrackingId:628871df00003ffd002d0cc25b7fd487_C1655342710_B13, SystemTracker:handsonazure:eventhub:handsonazurehub~2047|$default, Timestamp:8/24/2018 9:49:09 AM
at Microsoft.ServiceBus.Common.AsyncResult.EndTAsyncResult
at Microsoft.ServiceBus.Messaging.MessageReceiver.RetryReceiveEventDataAsyncResult.TryReceiveEnd(IAsyncResult r, IEnumerable`1& messages)
at Microsoft.ServiceBus.Messaging.MessageReceiver.EndTryReceiveEventData(IAsyncResult result, IEnumerable`1& messages)
at Microsoft.ServiceBus.Messaging.EventHubReceiver.<ReceiveAsync>b__61_1(IAsyncResult result)
at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.ServiceBus.Common.TaskHelpers.EndAsyncResult(IAsyncResult asyncResult)
at Microsoft.ServiceBus.Messaging.IteratorAsyncResult`1.<>c.<CallTask>b__24_1(TIteratorAsyncResult thisPtr, IAsyncResult r)
at Microsoft.ServiceBus.Messaging.IteratorAsyncResult`1.StepCallback(IAsyncResult result)
Processor Shutting Down. Partition '0', Reason: 'LeaseLost'.
如你所见,Partition 0已经被另一个接收方占用,该接收方将开始从该分区处理事件:
Registering EventProcessor...
Receiving. Press enter key to stop worker.
MyFirstEventProcessor initialized. Partition: '0', Offset: ''
Message received. Partition: '0', Data: '3c3bb090-2e0c-4d06-ad44-1d0ad4a106a7'
Message received. Partition: '0', Data: '54fed07a-a51e-4f36-8f26-f2ded2da9faa'
Message received. Partition: '0', Data: '69b8b291-8407-466a-a2c1-0b33a2ef03ad'
Message received. Partition: '0', Data: 'ec45d759-01bb-41db-ab51-de469ee5da55'
Message received. Partition: '0', Data: 'fcf41b0e-cd6b-465a-ac20-100ba13fd6af'
Message received. Partition: '0', Data: '2f05104a-c4a2-4a8f-8689-957f2dca6c71'
Message received. Partition: '0', Data: '63d77b4c-584f-4db3-86d0-9f73179ccb9f'
Message received. Partition: '0', Data: '03c70d22-4efa-4bd6-9c5c-f666c2922931'
Message received. Partition: '0', Data: '96f4c8be-831c-415c-8aa7-0a5125458f16'
Message received. Partition: '0', Data: 'af2e8a21-d9ce-4256-a8eb-73483387912c'
Message received. Partition: '0', Data: '73d9f92b-686b-44d1-b01a-50c0c63426ee'
Message received. Partition: '0', Data: 'bf53ea8f-dd34-405f-a6a6-0e947ce2473b'
它将逐渐接管一半可用分区,直到负载平衡。
Azure Event Hub 安全
我们已经涵盖了使用和开发应用程序时与 Azure Event Hub 相关的一些话题——现在是时候深入了解该服务的安全功能了。在本章的前面部分,你使用了共享访问策略,这是当你想要限制对中心的访问时最简单的选项(例如收听事件、发送事件或管理 Event Hub)。现在,我将向你展示更多关于安全模型的内容,并通过 IP 过滤来限制对整个命名空间的访问。
事件发布者
在创建 Event Hub 命名空间实例时,你需要选择一个层级——可以选择Basic和Standard层级。除了诸如消费者组或消息保留等功能外,Standard层级还提供了一项额外功能——创建事件发布者的能力。事件发布者作为发送消息到中心的虚拟端点。事实上,它通过将 SAS 令牌与发送者的身份结合来增强安全性。要生成令牌,你必须使用以下方法:
public static string SharedAccessSignatureTokenProvider.GetSharedAccessSignature(string keyName, string sharedAccessKey, string resource, TimeSpan tokenTimeToLive)
要正确执行它,你将需要:
-
密钥名称:SAS 策略的名称
-
共享访问密钥:为策略生成的密钥
-
资源:命名空间的 URL,格式如下:
sb://<NAMESPACE>.servicebus.windows.net/<EVENT_HUB_NAME>/publishers/<PUBLISHER_NAME> -
令牌有效期:令牌的有效时间
记住,PUBLISHER_NAME对于每个客户端来说应该是唯一的。
当你生成令牌时,它将采用以下格式:
SharedAccessSignature sr=%2f%2fZvZExXejq40LO5vmRIikSpWLn9YlKMZ5cwC2Nk83%2bnE%3d.servicebus.windows.net%2fhandsonazurehub%2fpublishers%2fhandsonazurepublisher&sig=UraqQnVck9O64h3pd8dcX9KdZZa2rb%2bxfR%2blyod2Ep2Q%3d&se=1535279857&skn=handsonazuresend
为了能够实际使用它,你需要使用EventHubSender而不是EventHubClient:
private static EventHubSender CreateSender()
{
var publisher = "handsonazurepublisher";
var token = SharedAccessSignatureTokenProvider.GetSharedAccessSignature(KeyName, SASKey,
$"sb://{Namespace}.servicebus.windows.net/{HubName}/publishers/{publisher}", TimeSpan.FromHours(24));
var connectionString =
ServiceBusConnectionStringBuilder.CreateUsingSharedAccessSignature(
new Uri($"sb://{Namespace}.servicebus.windows.net"), HubName, publisher, token);
var eventHubSender = EventHubSender.CreateFromConnectionString(connectionString);
return eventHubSender;
}
这是因为使用事件发布者时,你只能发送事件——它们不能用于其他 Event Hub 操作。
请注意,一般情况下,客户端不应该了解此类生成的 SAS 令牌所提供的额外功能。最重要的是,它们不应该由客户端生成;相反,你应该引入一个服务,客户端可以在其中请求完整的连接字符串并使用它。
现在,当你控制谁或什么可以访问 Azure Event Hub 时,还有一件事你可以做——撤销发布者,以使其无法再访问中心。为此,你将需要以下方法:
var nsm = NamespaceManager.CreateFromConnectionString(manageString);
nsm.RevokePublisher(eventHubName, publisherId);
一旦你撤销一个发布者,它在尝试发送事件时会收到PublisherRevokedException。
IP 过滤
通过引入 IP 过滤器,可以限制对 Azure Event Hub 的访问;这个功能(如下所示)允许你通过知道哪些 IP 地址应该被拒绝,来保护整个命名空间。默认情况下,IP 过滤器是空的,这意味着 Event Hub 接受任何连接(这相当于将其设置为批准 0.0.0.0/0 IP 地址):

你可以通过点击 + 添加 IP 过滤规则 来轻松创建规则:

当我限制对我计算机 IP 的访问时,当我尝试发送事件时,将收到以下消息:
25.08.2018 13:11:39 > Sending message: 0a7dd971-6600-458c-816d-fbbbee0d81cb
25.08.2018 13:11:40 > Exception: Ip has been prevented to connect to the endpoint. TrackingId:9421f06c-3a1c-4e4e-8a25-fb76f1cacee6, SystemTracker:AmqpGatewayProvider, Timestamp:8/25/2018 11:11:36 AM
你可以选择限制来自某些特定 IP 地址的访问,或者允许一个特定的子集。
Azure Event Hub Capture 特性
Azure Event Hub 有一个功能需要单独一节来深入描述。它就是 Capture,一个允许你使用预定义的存储解决方案(如 Azure Storage 或 Azure Data Lake)自动存储事件并进一步处理的功能。不幸的是,这个特性常常被误用,因为它的使用场景并不那么明显;此外,它的工作原理有时也可能不太清楚。
Azure Event Hub Capture 如何工作
在 Event Hub 的常见使用场景中,你需要一个 生产者 和一个 消费者 来获取数据并处理它。让我们考虑以下场景:

在这种情况下,我们有两个消费者:
-
消费者 1 用于一些通用处理
-
消费者 2 用于事件归档
我们还引入了 存储 来存储事件日志。如你所见,那个解决方案的缺点是,你需要维护两个消费者——两个代码库和两个实例。使用 Event Hub Capture 后,我们现在考虑的场景会发生一点变化:

现在我们不再需要额外的消费者,因为将由 Event Hub Capture 负责存储数据。你可能会想知道,在这种情况下,数据是如何存储的;一般来说,它是基于一个时间窗口,当时间窗口结束时,会触发数据捕获。
这个场景可以通过一个例子来轻松描述。假设你将时间窗口设置为 10 分钟;在该时间间隔之后,所有存储在 Azure Event Hub 中的数据将被捕获并使用 Apache Avro 格式存储到选定的数据库中。
一个重要的事情是 Capture 的定价;它的费用是每个吞吐单元每小时 €0.085。这意味着,如果你有启用了 Capture 的 Azure Event Hub 和 1 个吞吐单元,你将支付 80 欧元,而不是 18 欧元。若有 2 个吞吐单元,则需支付 160 欧元,而不是 37 欧元。
启用 Event Hub Capture
Event Hub Capture 是单个 Event Hub 的功能,而不是整个命名空间的功能。要启用它,你需要进入你的 Hub 并搜索 Capture 页签。
现在,当你启用 Capture 时,你将看到该功能的完整配置,我们现在将尝试理解它:

如你所见,它包含以下设置:
-
时间窗口:它定义了多少分钟后触发捕获。
-
大小窗口:或者,可以在窗口达到大小限制后触发捕获。是否触发取决于时间或大小,哪一个先达到限制。
-
捕获提供者:你可以选择 Azure Storage 和 Azure Data Lake Store。选择权在你,因为这并不意味着额外的功能或限制。
-
Azure Storage 容器/数据湖存储:根据你的选择,你需要选择不同类型的容器。
-
捕获文件名格式:此 Event Hub 功能有一组预定义的文件存储格式。不幸的是,无法完全自定义它,因为它必须包含
{Namespace}、{EventHub}、{PartitionId}、{Year}、{Month}、{Day}、{Hour}、{Minute}和{Second}字段。
一旦你对选择满意,就可以保存表单。在一段时间后,你的生产者发送数据,你会看到每个分区的数据被捕获:

更重要的是,它们包含以下格式的数据文件:
Objavro.codecnullavro.schema{"type":"record","name":"EventData","namespace ":"Microsoft.ServiceBus.Messaging","fields":[{"name":"SequenceNumber","type ":"long"},{"name":"Offset","type":"string"},{"name":"EnqueuedTimeUtc","type ":"string"},{"name":"SystemProperties","type":{"type":"map","values":["long ","double","string","bytes"]}},{"name":"Properties","type":{"type":"map","v alues":["long","double","string","bytes","null"]}},{"name":"Body","type":[" null","bytes"]}]}
这些数据可以轻松转换为 JSON:
{
"definitions" : {
"record:Microsoft.ServiceBus.Messaging.EventData" : {
"type" : "object",
"required" : [ "SequenceNumber", "Offset", "EnqueuedTimeUtc", "SystemProperties", "Properties", "Body" ],
"additionalProperties" : false,
"properties" : {
"SequenceNumber" : {
"type" : "integer",
"minimum" : -9223372036854775808,
"maximum" : 9223372036854775807
},
"Offset" : {
"type" : "string"
},
"EnqueuedTimeUtc" : {
"type" : "string"
},
"SystemProperties" : {
"type" : "object",
"additionalProperties" : {
"oneOf" : [ {
"type" : "integer",
"minimum" : -9223372036854775808,
"maximum" : 9223372036854775807
}, {
"type" : "number"
}, {
"type" : "string"
}, {
"type" : "string",
"pattern" : "^[\u0000-y]*$"
} ]
}
},
"Properties" : {
"type" : "object",
"additionalProperties" : {
"oneOf" : [ {
"type" : "integer",
"minimum" : -9223372036854775808,
"maximum" : 9223372036854775807
}, {
"type" : "number"
}, {
"type" : "string"
}, {
"type" : "string",
"pattern" : "^[\u0000-y]*$"
}, {
"type" : "null"
} ]
}
},
"Body" : {
"oneOf" : [ {
"type" : "null"
}, {
"type" : "string",
"pattern" : "^[\u0000-y]*$"
} ]
}
}
}
},
"$ref" : "#/definitions/record:Microsoft.ServiceBus.Messaging.EventData"
}
你可以在进一步阅读章节中了解更多关于 Avro 的信息。
总结
在本章中,你已学到许多关于 Azure Event Hub 的内容——它是如何工作的,分区的用途,以及如何利用更高级的功能,如消费者组或 Event Hub 捕获功能。我强烈建议你尝试一下,亲自体验这个 Azure 服务,因为它是一个处理每秒成千上万事件的强大工具。它也非常易于使用,且不需要花费太多时间来上手。
在下一章,你将了解另一项处理大量事件的服务,并且能够实时分析和转换它们——Azure Stream Analytics。
问题
-
消费者组是做什么的?
-
使用 1 TU 每秒可以处理多少事件?
-
每个 Event Hub 应该使用多少个分区?
-
TUs 是分配给命名空间还是特定的 Event Hub?
-
你可以为访问策略分配哪三种不同的权限?
-
事件发布者可以使用其令牌监听传入的事件吗?
-
如果消费者数量超过分区数量,会发生什么?
进一步阅读
Event Hub 灾难恢复的完整文档可以在这里找到:docs.microsoft.com/en-us/azure/event-hubs/event-hubs-geo-dr。
Apache Avro 文档可以在这里找到:avro.apache.org/。
第十三章:实时数据分析 - Azure Stream Analytics
虽然一些 Azure 组件使我们能够将数据传输到云中,但在大多数情况下,我们还需要某些专门用于分析和查询流数据的工具。其中一个服务是 Azure Stream Analytics,它是一个实时数据分析工具,能够读取所有通过 Event Hub 等传送的消息,并使用预定义的输出之一进行转换和保存。
本章将涵盖以下主题:
-
与 Azure Stream Analytics 的协作
-
可用的输入和输出类型
-
使用查询语言查询数据
-
确保传入数据的正确顺序,并执行检查点或重播操作
技术要求
要进行本章的练习,你将需要:
-
Visual Studio 2017 实例
-
一个 Azure 订阅
-
Azure Stream Analytics 工具—
docs.microsoft.com/en-us/azure/stream-analytics/stream-analytics-tools-for-visual-studio-install
Azure Stream Analytics 介绍
在上一章中,我们讨论了 Azure Event Hub,这是一个用于接收和处理每秒数千条消息的解决方案,介绍了事件处理器主机的实现。尽管它非常适合大数据管道或物联网场景等工作负载,但它并不是所有问题的解决方案,尤其是当你想避免托管虚拟机时。扩展此类架构可能会繁琐且不直观;这就是为什么有 Azure Stream Analytics,它是一个为大量数据设计的事件处理引擎。它弥补了其他服务(如 Event Hub 或 IoT Hub)表现不佳的空白(或者如果要做到这一点,它们需要更多的技能和/或更复杂的架构),特别是在实时分析、异常检测和地理空间分析方面。它是一个为高级任务设计的先进工具,将大大提高你的云技术和消息处理能力。
流式数据摄取与流式分析
为了开始,我们将比较两个主题:
-
流摄取:这是一个引入服务/API 用于接收来自生产者的消息的过程。这类服务只设计用于摄取数据—它不做其他事情(如转换或分析)。要对摄取的数据执行任何分析,你必须引入自己的处理器。
-
流分析:这是一个实际分析数据的过程。你会搜索异常、重复或格式错误的数据,处理它,并将其推送到其他服务进行存储、展示和触发其他操作。
为了更清楚地说明这一点,我们可以查看以下图示:

它展示了数据处理的四个步骤:
-
生成:数据实际上是由不同的服务、设备和客户端产生的地方
-
摄取:这是从不同的来源消费数据的过程
-
Analyze: 在此步骤中,数据被分析、转换并路由到适当的服务和组件。
-
Use: 在其他服务中进一步存储、显示和处理数据,例如 PowerBI、Azure Functions 等等。
在 Azure Event Hub 或 Azure IoT Hub 作为摄入步骤的一部分时,Azure Stream Analytics 负责分析。
请注意,在摄入数据时,您并不限于 Azure 服务。在这种情况下,只要它能够处理每秒数千个事件,您也可以使用任何类型的队列或 API。
Azure Stream Analytics 概念
在 Azure Stream Analytics 中,最重要的概念是流。您可以将其视为携带数据的许多事件的流——它们不一定相同或共享架构。分析这样的流不是一件简单的任务。如果您需要解码数十万个事件,该过程必须快速、稳健且可靠。我们将讨论该服务的主要概念,以验证它是否能够作为我们的分析解决方案和主要事件处理器:
-
Fully managed: Azure Stream Analytics 是一个完全托管的平台即服务(PaaS),因此您无需担心资源配置和扩展问题——运行时会自行处理,这样您就可以专注于为数据分析提供最佳查询。
-
An SQL-based query language: 为了分析数据,Azure Stream Analytics 使用基于 SQL 的查询语言,使开发人员能够快速构建高级程序,从流中精确提取所需内容。此外,您可以引入自己的扩展,如 ML 解决方案或用户定义的聚合,以执行额外的计算,使用服务不可用的工具。
-
Performance: Azure Stream Analytics 专注于流单元(SUs)而不是一些硬编码的 CPU 或内存值。这是因为它设计用于提供稳定的性能和反复执行时间。此概念使得您可以轻松扩展解决方案以满足需求。
-
Low cost of ownership: 在 Azure Stream Analytics 中,您只需支付您选择的内容。由于定价取决于每小时的 SUs 数量,因此在总体付款中不会增加额外费用。
在本章后续部分,我们将涵盖一些额外的技术概念(例如输入/输出类型、检查点或重播)。要了解使用 Azure Stream Analytics 的整个管道的全貌,请查看以下图片:

当然,在这张图片上可能还有其他参考信息(附加服务、用户功能和分析器),但为了简单起见,我没有包括它们。
输入和输出类型
Azure Stream Analytics 提供与一些本地 Azure 服务的无缝集成,例如 Azure Event Hub、Azure IoT Hub 或 Azure Blob Storage。此外,它还可以轻松配置,将数据输出到 SQL 数据库、Blob 或 Azure Data Lake Store。为了利用这些功能,你需要定义你感兴趣的输入和输出类型。这使得数据可以轻松地以流的形式被接收,从而你的作业可以在数千个事件上进行工作,进行分析和处理。在本节中,你将学习如何开始使用 Azure Stream Analytics,并定义输入和输出。
在 Azure 门户中创建 Azure Stream Analytics
要开始使用,你需要创建一个 Azure Stream Analytics 实例。为此,你需要点击+ 创建资源并搜索Stream Analytics job。这将显示一个表单,你可以在其中输入所有必要的数据来创建一个服务:

有两个字段,初看之下你可能会忽视它们:
-
托管环境:Azure Stream Analytics 可以通过两种方式托管:作为本地 Azure 服务或部署到本地 IoT Edge 网关设备。IoT Edge 是本书超出范围的主题,因此自然的选择将是云。
-
流处理单元(1 到 120):你需要选择为一个作业分配多少个 SUs 来处理事件。所需的 SUs 数量取决于你作业的特性,并且可能会根据你选择的输入类型有所变化。在进一步阅读部分有一个链接,详细描述了你可能需要多少 SUs 来处理你的作业。
记住,你将为每个选择的 SU 支付€0.093/小时,即使它没有在作业上工作。
一旦你点击创建并打开概览面板,你将看到一个空的仪表盘:

如你所见,Inputs 和 Outputs 目前都是空的——我们需要更改这些设置,这样我们才能在查询中使用它们。两项功能可以在左侧的 JOB TOPOLOGY 部分找到:

添加输入
要添加输入,点击 Inputs 面板。它将显示一个空白的屏幕,你有两种选择:
-
- 添加流输入:在这里,你可以添加一个链接,连接到支持流数据接收的服务。目前可用的 Azure 组件包括 Azure Event Hub、Azure IoT Hub 和 Azure Blob Storage。输入可以是实时的(也可以不是),并且这种连接支持压缩(例如,你可以传输使用 GZip 或 deflate 压缩的流)。
-
- 添加引用输入:您不仅可以从实时流中获取数据,还可以使用 Azure Blob Storage 并添加对其的引用,从而获取所谓的引用数据。在这种情况下,Azure Stream Analytics 会将整个数据加载到内存中,以便进行查找。这是处理静态或变化缓慢的数据的理想解决方案,并且支持最大为 300 MB 的数据。
在这里,您可以找到将 Event Hub 配置为输入的示例:

根据您的选择(是否在您的订阅中有 Event Hub,是否存在 Event Hub),会有不同的选项可用。在之前的示例中,我配置了一个新的 Hub(它是不存在的)作为我的数据来源。下面有一些字段,我现在想解释一下:
-
Event Hub 消费者组:如果您希望 Azure Stream Analytics 从头开始读取数据,请在此输入消费者组。默认情况下,它将使用
$Default,这是 Azure Event Hub 中的默认消费者组。 -
事件序列化格式:您可以选择 JSON、Avro 和 CSV。这可以根据使用的序列化格式自动反序列化事件。
-
事件压缩类型:如果您使用的是 GZip 或 Deflate,在这里您可以选择正确的选项,这样输入将会自动反序列化。
请注意,您需要一个实际的 Azure Event Hub 命名空间,才能通过 Azure Stream Analytics 自动创建一个 Hub。
填写所有必填字段后,您将能够点击“创建”按钮,初始化创建一个新的输入。当然,您可以添加多个输入,因为它们都会出现在输入流中,您将能够处理传入的事件。在开始作业之前,您至少需要一个输出,接下来我们将添加输出。
添加输出
要添加输出,您必须点击“输出”页面。它类似于“输入”页面,但有不同种类的输出可用:

如您所见,有许多不同类型的输出可用,这使得 Azure Stream Analytics 在将获取的数据推送到不同服务时非常灵活。我们可以将它们分为不同类别:
-
存储:SQL 数据库、Blob 存储、Table 存储、Cosmos DB 和 Data Lake Store
-
报告:Power BI
-
计算:Azure Functions
-
消息传递:Event Hub、Service Bus
根据类别,您将有不同的选项可以对处理过的事件进行操作:
-
存储:用于进一步操作、归档和事件日志的数据存储
-
报告:近实时报告
-
计算:实现无限集成能力的简单解决方案
-
消息传递:将事件推送到不同的管道和系统
在这里,您可以找到将 Azure Table 存储配置为输出的示例:

可用字段很大程度上取决于所选的输出类型,因此我在本章中不会重点讨论这些内容。你可以在进一步阅读部分找到相关参考。
Azure Stream Analytics 查询语言
除了 Azure Stream Analytics 丰富的 Azure 服务选择,可以无缝集成外,其强大之处还在于查询语言,它允许你轻松地分析输入流并将其输出到所需的服务。由于它是类似 SQL 的语言,它应该对大多数使用该服务的开发者来说直观且容易学习。即使你不熟悉 SQL,提供的许多示例和简单的语法也应该使你容易掌握。
编写查询
在 Azure 门户中,Azure Stream Analytics 的查询窗口可以在概览或查询面板中找到:

在上面的示例中,你可以看到一个简单的类似 SQL 的查询,它执行以下三项操作:
-
使用给定的别名从输入中选择数据
-
选择特定的列
-
将它们推送到特定的输出中
你也可以点击“编辑查询”链接,这样你将被引导到查询页面:

如你所见,要实际使用查询,你需要同时拥有输入和输出,否则你将无法保存它。一般来说,查询由三个元素组成:
-
SELECT:你从输入中选择你感兴趣的列 -
INTO:你告诉引擎你感兴趣的输出 -
FROM:你选择要从中提取数据的输入
当然,上述语句并不是唯一可用的—你可以使用许多不同的选项,如 GROUP BY、LIKE 或 HAVING。一切取决于输入流和传入数据的模式。对于某些任务,你可能只需要进行简单的转换并提取必要的列;而对于其他任务,你可能需要更复杂的语法来精确获取所需内容。你可以在进一步阅读部分的链接中找到常见的查询模式。在上面的示例中,在查询的 SELECT 部分,我选择了三个在分析 Azure Event Hub 事件时可用的列。而且,我使用了 AS 构造,告诉引擎实际重命名字段以匹配 Outputs 部分中定义的字段。当我运行作业时,我可以看到它确实将事件传递给了我的表:

然而,当前配置存在一些问题:
-
我们依赖于 Event Hub 字段,这些字段未来可能会发生变化。
-
我们缺少事件的实际数据。
-
存在重复的列。
假设每个事件具有以下结构:
{"Id":"165e0206-8198-4f21-8a6d-ad2041031603","Date":"2018-09-02T12:17:48.3817632+02:00"}
当然,特定数据会随时间变化。我们可以快速修改查询:
SELECT
PartitionId,
Id,
Date
INTO
[handsonazure-tablestorage]
FROM
[handsonazure-alias]
并调整配置,稍微改变输出:

然而,基本构造只占该服务整体能力的一小部分。还有一些内置函数,可以在每个查询中轻松使用以增强查询,具体如下:
- 数学函数:
SELECT FLOOR(input.x) AS "The FLOOR of the variable x" FROM input
SELECT SQUARE(input.x) AS "The SQUARE of the variable x" FROM input
- 聚合函数:
SELECT COUNT(*) FROM Input
SELECT SUM (Income) FROM Input
SELECT AVG (Income) FROM Input
- 分析函数:
SELECT ISFIRST(mi, 10) as first FROM Input
- 地理空间函数:
SELECT ST_DISTANCE(input.pos1, input.pos2) FROM input
- 字符串函数:
SELECT SUBSTRING (SerialNumber ,1,3 ), FROM Input
除了这些,还有一些其他函数,如记录函数、日期/时间函数、转换函数或数组函数。上述示例当然并不是所有可用的函数。你可以在进一步阅读部分找到所有的函数。这里需要记住的重要一点是,某些函数是确定性的(这意味着,如果使用相同的输入值,它们总是返回相同的结果),而有些则不是——这在处理高负载并试图避免可能的异常时尤其重要。
记住,你可以合并不同的数据流并将它们推送到单个输出(或者反过来——有一个输入并将其分发到多个输出)。这是此服务的一个非常强大的功能,使得数据的摄取和处理变得更加容易。
事件排序、检查点和重放
在前面的章节中,我们介绍了 Azure Stream Analytics 的一些基础主题:如何配置输入和输出、查询数据以及使用该服务。在本章的最后部分,我将向你展示它的一些更高级的功能,如事件排序、检查点和重放,这些功能确保事件以你预期的方式被精确处理。这些话题实际上是许多不同消息传递解决方案中的常见内容,因此你可以将本章中的知识应用到其他项目中。
事件排序
在事件排序时有两个概念:
-
应用时间(或事件时间)
-
到达时间
它们之间有明显的区别:
-
应用时间:这是事件在客户端(或应用程序)端生成的时间戳。它告诉你事件发生的确切时间。
-
到达时间:这是一个系统时间戳,在原始负载中不存在。它告诉你事件何时被服务接收并开始处理。
根据输入类型, 到达时间和 应用时间将是不同的属性(EventEnqueuedUtcTime或EnqueuedTime表示到达时间,而应用时间通常将是一个通用属性)。你需要记住的是,根据所选择的场景,你可以按顺序处理事件但有延迟,或者按乱序处理事件。这个可以通过以下事件序列轻松描述:
-
到达时间:
2018-09-02T12:17:49应用时间:2018-09-02T12:17:48 -
到达时间:
2018-09-02T12:17:50应用时间:2018-09-02T12:17:44 -
到达时间:
2018-09-02T12:17:51应用时间:2018-09-02T12:17:46
如果按事件流到来的顺序处理,它们将被无序处理——实际上,它们发生的顺序不同,因此有可能某些数据会变得过时。另一种选择是按应用时间对事件进行排序;在这种情况下,处理会延迟,但顺序将得以保留。
是否需要按顺序处理事件,取决于数据模式和已处理事件的特征。按顺序处理事件更耗时,但有时你根本无法采用其他方式。
Azure Stream Analytics 具有一个名为事件排序的功能,允许你决定如何处理无序或过时的事件:

有两种可用选项:
-
延迟到达的事件:这一选项允许你在定义的时间窗口内处理过时的事件(即应用时间与最后处理的事件时间不匹配的事件)。
-
无序事件:可能会发生Azure Stream Analytics 将一些事件视为无序事件的情况(例如,如果发送方的时钟不一致)。在这种情况下,你可以设置一个时间窗口,在该时间窗口内可以接受此情况。
此外,你可以定义一个操作,如果事件迟到或无序,则会执行该操作——对于丢弃(Drop)操作,它将被简单移除,如果选择调整(Adjust),当这种情况发生时,处理将暂停一段时间。
检查点和重放
实际上,Azure Stream Analytics 是一个有状态服务,能够跟踪事件处理进度。这使得它适用于以下场景:
-
作业恢复
-
有状态查询逻辑
-
不同的作业启动模式(现在、自定义和上次停止时)
当然,在检查点之后和重放时,情况是有所不同的。当检查点中存储的数据不足时,可能需要进行完整的重放;然而,这取决于你的查询。实际上,它依赖于查询的并行化因素,可以通过以下公式描述:
[输入事件速率] x [间隔长度] / [处理分区数]
处理器越多,出现问题时恢复得越快。一个好的经验法则是,当作业失败并且你需要快速填补间隙时,增加更多的 SUs。
重放数据时需要考虑的重要因素是查询中使用的窗口函数(滚动窗口、跳跃窗口、滑动窗口或会话窗口)——它们允许你在不同类型的窗口中处理数据,但也使得重放机制变得复杂。
总结
在本章中,我们介绍了 Azure Stream Analytics,这是一个用于近实时处理数据流的服务。你已经了解了可用的输入和输出,并学习了如何配置它们。而且,你也能够编写你的第一个查询,并查看查询语言如何用于分析和处理传入事件。如果你需要一个能够快速读取和转换事件,并将其推送到多个不同 Azure 服务的 PaaS,Azure Stream Analytics 就是你需要的服务。
在下一章,我们将讲解 Azure Service Bus,这是一种企业级的消息传递解决方案,实际上是我们之前讨论的 Azure Event Hub 的基础。
问题
-
Azure Stream Analytics 的付费模型是什么?
-
流和引用输出有什么区别?
-
应用时间和到达时间有什么区别?
-
需要使用哪个查询构造来选择输入中的 ID 并将其推送到输出?
-
你能在同一个查询中处理不同的输入吗?
-
何时认为事件是无序的?
-
是否可以从查询中的某个属性提取子字符串?如果可以,应该使用哪个函数?
进一步阅读
-
扩展和 SUs:
docs.microsoft.com/en-us/azure/stream-analytics/stream-analytics-streaming-unit-consumption -
不同的输出类型:
docs.microsoft.com/en-us/azure/stream-analytics/stream-analytics-define-outputs -
常见查询模式:
docs.microsoft.com/en-us/azure/stream-analytics/stream-analytics-stream-analytics-query-patterns -
窗口函数:
docs.microsoft.com/en-us/azure/stream-analytics/stream-analytics-window-functions
第十四章:企业级集成 - Azure Service Bus
有时候,为了使用消息传递解决方案集成我们的应用程序,我们需要的不仅仅是一个简单的管道,因为管道在分发数据和过滤数据时功能有限。Azure Service Bus 提供了主题、过滤器等更多功能,是一个为提供可靠、可扩展和高效的消息发送方式而设计的企业级解决方案,可以向多个接收者发送消息。
本章将涵盖以下主题:
-
使用 Azure Service Bus 进行工作
-
基础知识——队列、主题和中继
-
保护 Azure Service Bus 安全
-
高级功能,如地理复制、会话或死信队列
-
处理停机和灾难
技术要求
要完成本章的练习,你将需要:
-
访问 Microsoft Azure 订阅
-
一个 Visual Studio 2017 实例
-
Visual Studio Code(如果你没有 Visual Studio 2017 实例)
Azure Service Bus 基础
你已经学习了其他消息传递解决方案,这些方案帮助你简化了服务之间的通信,它们都有不同的特性。在 Azure Event Hub 中,你能够每秒处理数千条消息,而在 Azure 存储队列中,你得到了一个可靠且持久的解决方案,可以用于异步处理接收到的数据。在本章中,我们将讨论 Azure Service Bus,这是一种多租户云消息传递服务,引入了先进的概念,如先进先出(FIFO)消息传递、死信队列或事务。它是一个企业级的云组件,能够集成许多不同的服务和应用程序。
Azure Service Bus 与其他消息传递服务的对比
在前几章中,我们讨论了以下服务,它们允许我们处理消息:
-
Azure Event Hub
-
Azure 存储队列
-
Azure Event Grid
它们都有相似之处,但设计上服务于不同的功能并提供不同的能力。我们经常交替使用事件和消息这两个概念。实际上,它们之间有细微的区别,理解这一点对于成功使用不同的消息传递服务至关重要:
-
事件:它携带某些事情发生的信息——某人或某物产生了一个事件,并不意味着对事件的处理有任何预期。一般来说,事件是轻量级的信息载体,不会将完整的数据传递给接收者。
-
消息:与事件不同,当生产者发送消息时,它对消息的处理有一定的预期(因此生产者和消费者之间存在某种契约)。而且,消息携带的是原始数据,而事件表示某事发生了;消息表明组件已初始化通信,应该以常规方式处理。
现在,您可以回想一下您所学的内容,例如,Azure Event Grid 或 Azure Event Hub——它们的名称中都有“事件”二字,但工作方式却大不相同:
-
Azure Event Grid:它旨在分发事件并响应变化。它仅传递元数据,实际的消息必须单独获取;因此,可以说它分发的是事件。
-
Azure Event Hub:它作为大数据管道工作,将事件流式传输到其他服务。根据您的实现方式,它可以同时流式传输事件和消息。
现在,让我们来对比一下 Service Bus:
- Azure Service Bus:它是为支持关键业务流程而创建的,这些流程对处理顺序和消息服务的可靠性有很高要求。您可以在消息不能丢失或重复的情况下使用它。它不涉及“事件”的概念——相反,它允许您推送整个数据,供消费者读取。
Azure Service Bus 和 Azure Storage Queues
您可能会想,Azure Service Bus 和 Azure Storage Queues 有什么区别。事实上,它们都是消息传递解决方案,可靠、持久并且能够同时处理多条消息。然而,仔细观察,您会发现它们是完全不同的服务,采用不同的概念并且有不同的目的:
-
Azure Storage Queues 强制轮询队列以接收消息,而通过 Azure Service Bus,您可以通过 TCP 建立长轮询接收操作。
-
在 Azure Storage Queue 中,您可以存储最多 64 KB 的消息——而 Azure Service Bus 将该限制提升至 256 KB。
-
Azure Service Bus 队列的存储数据量小于 Azure Storage Queues——最大支持 80 GB。
-
Azure Service Bus 支持批量消息消费。
-
在 Azure Storage Queues 中,安全模型相对简单——Azure Service Bus 在保障队列安全时支持 RBAC 模型。
-
Azure Storage Queues 不支持事务行为。
如您所见,Azure Service Bus 提供了许多高级功能,这些功能对于集成不同系统和应用程序的应用非常有帮助,也适用于第三方应用。当然,这些附加功能需要额外费用,因为它们要求使用更贵的服务层级。在 Azure Service Bus 中,您有三种服务层级可供选择:
-
基础版:仅支持队列和定时消息。
-
标准版:所有功能均可用。
-
高级版:最大消息大小扩展至 1 MB,并且包含代理连接。此级别还保证更高的吞吐量和更好的性能。
如果您只需要基本功能(不需要主题、事务或会话),那么 Azure Service Bus 实例可能比使用 Azure Storage Queues 更便宜。具体取决于您的系统需求。
Azure 门户中的 Azure Service Bus
要创建一个 Azure 服务总线实例,你需要在市场中搜索Service Bus服务。你将看到一个简短的表单,在其中填写最关键的信息,如服务名称、定价层级和位置:

目前,你只需填写这些内容——点击创建按钮,稍等片刻,服务就会创建完成。概述面板显示了更多信息,但如你所见,它与在使用 Azure 事件中心时看到的非常相似:

在前面的截图中,你会看到+ 主题按钮被灰色禁用——这是因为我为本次练习选择了基本层。点击+ 队列按钮,你将能够创建一个新的队列:

这里的事情变得有些复杂:
-
名称:这是队列的唯一名称。
-
最大队列大小:你可以决定队列的最大大小(与 Azure 存储队列中的固定 80 GB 大小不同)。
-
消息存活时间:在 Azure 存储队列中,消息的最大存活时间为 7 天。在这里,你可以指定消息在被删除(或移动到死信队列)之前的自定义存活时间。
-
锁定持续时间:当消费者提取消息时,消息会在固定时间段内被锁定,以避免重复读取。在这里,你可以自定义锁定时间(最多为 5 分钟)。
-
启用重复检测:如果你想确保在固定时间段内实现“恰好一次”的投递模式,可以启用此选项。它允许你配置一个重复检测窗口,在该窗口内会保留已处理消息的历史记录。
-
启用消息过期时的死信处理:如果消息过期,它会被自动删除。若要将其推送到死信队列,而不是删除,请启用此选项。
-
启用会话:Azure 服务总线中的会话确保 FIFO(先进先出)消息处理。为了确保最早推送到服务的消息是最先处理的消息,请启用此功能。
-
启用分区:此选项将队列从单一的消息存储中分离出来,实际上你将得到多个队列。此选项确保即使存储出现故障,整个队列或主题也不会出现停机。然而,关于此功能有一些限制——其中之一是启用分区后,你不能在单个事务中发送属于不同会话的消息。更重要的是,每个命名空间最多只能拥有 100 个分区队列或主题。
在 Azure 服务总线的高级层中,不支持分区队列和主题。
启用分区时,队列的样子如下:

如你所见,队列的最大大小显示为 16 GB——这是因为启用分区后,我们将得到 16 个分区,每个分区托管一个最大为 1 GB 的队列。
由于单个队列的最大大小被设置为 5 GB,你可以通过使用分区来实现最大大小 80 GB。启用此功能后,最大大小将为 5 GB * 16 个分区 = 80 GB。
队列、主题和中继
Azure Service Bus 支持三种不同类型的实体:
-
队列
-
主题
-
中继
这三种方式为你提供了不同的通信处理选项。
队列
队列是服务中最简单的实体。你可以如下定义它:

在前面的示例中,你可以看到我们有以下概念:
-
生产者:一个应用程序或服务,将消息推送到队列
-
队列:消息容器
-
消费者:一个应用程序或服务,通过拉取模型从队列读取消息
拉取模型意味着生产者实际上需要向队列请求接收消息。当然,可以有多个生产者和多个消费者——此时锁定持续时间功能特别有用,因为它确保在任何时刻只有一个消费者读取消息。
主题
主题与队列略有不同,因为它们允许你实现发布/订阅通信模型。当队列是点对点通信时,主题让你有机会将不同的消息分发到不同的队列:

这种模型使得可能对消息进行过滤并将其隔离,以便消费者只读取他们感兴趣的消息。
请记住,主题在基础层不可用——你必须至少使用标准层。
中继
队列和主题都是模型,旨在仅提供单向通信——生产者发送消息,接收者读取消息。如果你想实现双向通信,你需要使用中继:

Azure Relay 实际上是一个独立的服务,我们不会在本章中讨论它。然而,仍然有许多很棒的功能,你可能会在应用程序中找到它们的帮助:
-
它旨在安全地暴露托管在公司网络内的服务。
-
它允许不同的通信模型,如单向、发布/订阅和双向通信。
-
它不像 VPN 那样改变网络,因此更加稳定,并且限制于单个应用程序端点。
Azure Service Bus 设计模式
Azure Service Bus 通常是许多不同云服务的集成中心——它可以用于多种场景,包括数据集成、信息广播,甚至双向通信。由于该服务具有丰富的功能,你可以使用它实现各种责任。在本章的进一步阅读部分,你可以找到许多 Azure Service Bus 设计模式的示例。
使用 Azure Service Bus SDK 开发解决方案
GitHub 上有一个丰富的数据库,包含许多不同的示例,用于与 Azure 服务总线合作(你可以在进一步阅读部分找到链接),所以我们将在本章中只介绍基本的示例。这里是将消息发送到队列的最简单方法:
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.ServiceBus;
namespace HandsOnAzure.ServiceBus
{
internal class Program
{
private static void Main()
{
MainAsync().GetAwaiter().GetResult();
}
private static async Task MainAsync()
{
var client = new QueueClient("<connection-string>", "<queue-name>");
var message = "This is my message!";
await client.SendAsync(new Message(Encoding.UTF8.GetBytes(message)));
}
}
}
如你所见,所需的只是使用一个QueueClient实例(至少要实现基本功能)。如果你想使用主题,则可以使用TopicClient:
var client = new TopicClient("<connection-string>", "<topic-name>");
事实上,你需要做的就是安装Microsoft.Azure.ServiceBusNuGet 包。在我运行上述代码三次并检查我的队列后,我在门户中看到的内容如下:

如你所见,有三条活跃消息。这意味着我已经成功发布了它们,并且它们准备好被拉取。有多种不同的拉取消息方式——这里有一个使用PeekAsync的示例:
using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.ServiceBus.Core;
namespace HandsOnAzure.ServiceBus.Reader
{
internal class Program
{
private static void Main()
{
MainAsync().GetAwaiter().GetResult();
Console.ReadLine();
}
private static async Task MainAsync()
{
var receiver =
new MessageReceiver(
"<connection-string>",
"<queue-name>");
while (true)
{
var message = await receiver.PeekAsync();
if(message == null) continue;
Console.WriteLine($"New message: [{message.ScheduledEnqueueTimeUtc}] {Encoding.UTF8.GetString(message.Body)}");
await Task.Delay(100);
}
}
}
}
然而,如果你只是查看消息,而不是接收消息,你将不会创建消息存储。要做到这一点,你必须使用ReceiveAsync:
var message = await receiver.ReceiveAsync();
当你使用两种方法读取消息时,差异会变得明显。PeekAsync不会改变消息的状态(即使你将ReceiveMode选项设置为ReceiveAndDelete,它们仍然会显示为活跃)。ReceiveAsync将使用ReceiveMode选项的值,并可能充当原子操作CompleteAsync。
在使用PeekAsync后,要将消息标记为已读,你可以使用CompleteAsync。
本章稍后我们将讨论更高级的场景。
Azure 服务总线安全性
由于 Azure 服务总线被描述为一个企业级的云服务,旨在集成不同的服务,因此对其提供的安全功能有很高的期望。除了共享访问令牌外,预览版中还有一些新功能,使访问管理更加灵活。
托管服务身份
托管服务身份(MSI)是 Azure 云中的一个功能,它简化了服务之间的身份验证,而无需在代码中存储凭证。完整描述可以在进一步阅读部分的链接中找到。当涉及到与 Azure 服务总线一起使用时,不需要额外的面板——你需要做的只是找到访问控制(IAM)面板中的身份:

现在,你可以使用以下代码,而不是使用 SAS 令牌或访问策略:
var tokenProvider = TokenProvider.CreateManagedServiceIdentityTokenProvider();
var sendClient = new QueueClient($"sb://{namespace}.servicebus.windows.net/", {queue-name}, tokenProvider);
await sendClient.SendAsync(new Message(Encoding.UTF8.GetBytes(messageInfo.MessageToSend)));
await sendClient.CloseAsync();
如你所见,流程变得更简单了,因为你不需要存储凭证或密钥,而是让提供商来处理身份验证。
RBAC
在 Azure Service Bus 中,您还可以利用在 Azure AD 中定义的角色授予服务访问权限。整个功能依赖于一个假设,即用户能够负责授予访问 Service Bus 实例的权限。第一步与 MSI 认证完全相同:您需要将用户添加到服务中,以便它可以获得访问权限并开始推送和接收消息。完整的说明可以在 进一步阅读 部分找到。
请注意,通过明确告诉用户或应用程序如何访问 Azure Service Bus,您可以更好地控制消息的发布和接收。这是相较于 Azure Storage Queues 的一个重要改进,因为在 Azure Storage Queues 中没有此类功能。
还可以使用 RBAC 认证来授予一个服务访问另一个服务的权限(如果 MSI 不可用)。在这种情况下,将不需要交互式登录,因为所有操作都由 Azure AD 处理。
即使需要交互式登录,它也不会由应用程序处理,因此您可以确保它不会直接处理任何凭据。
Azure Service Bus 的高级功能
我们已经涵盖了 Azure Service Bus 的一些基础知识,如 SDK、最重要的概念和安全性考虑事项。现在我们将更多地关注一些更高级的用例,如死信、性能、会话和事务。所有这些话题对于开发一个可靠且重要的服务(集成多个不同的应用程序和系统)至关重要。同时,别忘了查看 进一步阅读 部分中的 Azure Service Bus 示例,它指向一个 GitHub 仓库,您可以在其中找到许多使用此服务时的不同用例和概念。
死信
一般来说,死信指的是队列中被认为是“死”的消息(因为没有接收者愿意拉取它们),而且您有两种选择来处理这些消息:
-
或者将它们永久删除
-
将它们推送到一个额外的队列,名为死信队列
在 Azure Service Bus 中,您有两种选择将消息推送到死信队列:
-
设置消息的最大生命周期—一旦过期,它将自动移到死信队列
-
使用
DeadLetterAsync方法在MessageReceiver上,如下所示:
await receiver.DeadLetterAsync("<lock-token>", "<reason>");
这里您可以找到完整的示例,并可以找到锁定令牌:
while (true)
{
var message = await receiver.ReceiveAsync();
if(message == null) continue;
Console.WriteLine($"New message: [{message.ScheduledEnqueueTimeUtc}] {Encoding.UTF8.GetString(message.Body)}");
await receiver.DeadLetterAsync(message.SystemProperties.LockToken, "HandsOnAzure - test");
await Task.Delay(100);
}
一旦您将消息推送到死信队列,其状态将在门户中显示:

当然,您也可以从死信队列中提取消息。要获取队列名称,您可以使用以下方法:
var deadLetterQueueName = EntityNameHelper.FormatDeadLetterPath("<entity-path>");
灾难恢复
当发生灾难时,您可能会丢失部分或全部数据。一般来说,灾难是指整个服务的暂时性或永久性丧失,且无法保证它会再次可用。此类灾难包括洪水、地震或火灾,仅举几例。灾难通常发生在单一区域(不同区域同时发生灾难的概率非常小),因此,通常您需要两个不同的数据中心来实现灾难恢复(DR)。
请记住,如果两个数据中心彼此距离较近,单靠这两个数据中心可能不足够——您必须选择能够满足您需求的两个数据中心,但同时它们之间的距离应尽可能远。
在实现 Azure Service Bus 中的灾难恢复时,流程与 Azure Event Hub 中的流程相同:
-
创建主区域
-
创建辅助区域
-
创建配对
-
定义故障转移触发器
通常,要创建配对,您需要以下代码片段:
var client = new ServiceBusManagementClient(creds) { SubscriptionId = subscriptionId };
var namespace2 =
await client.Namespaces.CreateOrUpdateAsync(
"<resource-group-name>",
"<secondary-namespace>",
new SBNamespace { ... params ... });
ArmDisasterRecovery drStatus =
await client.DisasterRecoveryConfigs.CreateOrUpdateAsync(
"<resource-group-name>",
"<primary-namespace>",
"<alias>",
new ArmDisasterRecovery { PartnerNamespace = namespace2.Id })
上述示例使用了Microsoft.Azure.Management.ServiceBus NuGet 包来操作命名空间。
一旦配对配置并创建完毕,您需要触发并启动故障转移。为此,您只需要以下代码:
client.DisasterRecoveryConfigs.FailOver("<resource-group-name>", "<secondary-namespace>", "<alias>");
请注意,故障转移是在辅助区域启动的——这一点至关重要,因为在执行操作时,主区域可能不可用。
一旦故障转移完成,您可以开始使用辅助区域处理消息。然而,有一件重要的事情需要记住:如果再次发生故障,您希望能够再次进行故障转移。因此,设置另一个辅助命名空间(并将当前命名空间设为主命名空间)并重新配对以确保安全也非常重要。
会话
在 Azure Service Bus 中,会话用于实现 FIFO 保证。一般而言,服务不控制消息之间的关系,因此即使在大多数情况下顺序得以保持,但并不保证。要将消息放入会话中,您必须利用SessionId属性:
await client.SendAsync(new Message(Encoding.UTF8.GetBytes(message)) { SessionId = Guid.Empty.ToString()});
要在接收方处理会话,必须在QueueClient实例上使用RegisterSessionHandler方法:
var client = new QueueClient("<connection-string>", "<queue-name>");
client.RegisterSessionHandler((session, message, ct) => Task.FromResult(new SessionHandler()), args => Task.CompletedTask);
此外,您还必须实现IMessageSession**。
事务
Azure Service Bus 中的事务是一个广泛的话题,涉及许多不同的实体,您可以在此服务中与之进行交互:
-
客户端(
QueueClient、TopicClient) -
消息(通过使用诸如
Complete、Defer、Abandon等操作) -
会话(
GetState/SetState)
如您所见,这里没有列出接收操作;这是因为假设它们在设计时是原子的。
通常,在拉取消息并在循环或OnMessage回调中打开事务范围时,要求使用ReceiveMode.PeekLock模式。
您可以参考以下代码片段,以便更好地理解我们在本节中讨论的内容:
var message = receiver.Receive();
using (scope = new TransactionScope())
{
var newMessage = // transfer
sender.Send(newMessage);
message.Complete();
scope.Complete();
}
在上述示例中,处理器(同时负责生成消息)将消息标记为完成,同时将新消息传输到另一个队列。整个模型利用了 Azure Service Bus 的自动转发功能。以下是主题的一个示例:
var subscription = new SubscriptionDescription(sourceTopic, subscriptionName);
subscription.ForwardTo = destinationTopic;
namespaceManager.CreateSubscription(subscription);
当执行事务时,只有当整个事务成功时才会提交到队列日志中;否则,处理过的消息将不会留下任何痕迹。
处理停机和灾难
如果你将 Azure Service Bus 作为架构的中心 —— 一个负责集成数十个服务和处理通信的服务,你必须确保其进行了复制并且不易受到灾难影响。这里有两个主题需要考虑:灾难恢复和处理停机。由于这些术语是完全不同的概念,你必须理解它们并能够在意外问题和事故发生时实施解决方案。在本章的最后一节,你将学习如何将 Azure Service Bus 打造成一个可靠的云组件,你和你的应用都可以依赖它。
处理停机
尽管灾难通常意味着部分数据丢失,停机可能被描述为服务暂时不可用。这就是为什么一旦解决了这个问题,你可能希望同步两个服务总线命名空间。尽管这个过程是自动的,但可能需要一些时间。文档中指出,每分钟只能传输 50-100 个实体。因此,你可能需要考虑主动/被动复制的概念:
-
主动模式:在这种方法中,你拥有两个活动的命名空间,可以主动接收消息。然后接收者始终接收两者中的消息 —— 你必须使用相同的唯一标识符正确标记它们,以便检测重复(你可以使用
MessageId或Label属性中的任何一个)。 -
被动模式:与同时使用两个队列(或主题)相比,只有在无法将消息传递到主要命名空间时,才会使用第二个。然而,这种方法也有其注意事项:可能会导致消息传递延迟(甚至丢失)或重复。
在这里,你可以找到被动复制的一个例子:
private async Task SendMessage(BrokeredMessage message1, int maxSendRetries = 10)
{
do
{
var message2 = message1.Clone();
try
{
await _activeQueueClient.SendAsync(message1);
return;
}
catch
{
if (--maxSendRetries <= 0)
{
throw;
}
lock (_swapMutex)
{
var client = _activeQueueClient;
_activeQueueClient = _backupQueueClient;
_backupQueueClient = client;
}
message1 = message2.Clone();
}
}
while (true);
}
如你所见,这清楚地展示了消息副本如何传递到备份队列。主动复制的一个例子略有不同:
var task1 = primaryQueueClient.SendAsync(m1);
var task2 = secondaryQueueClient.SendAsync(m2);
try
{
await task1;
}
catch (Exception e)
{
exceptionCount++;
}
try
{
await task2;
}
catch (Exception e)
{
exceptionCount++;
}
if (exceptionCount > 1)
{
throw new Exception("Send Failure");
}
在这里,我们将同一消息发送到两个命名空间,即使其中一个失败。处理停机的另一个需要考虑的事情是使用分区发送者(尽管高级版中不可用)。使用它们时,一旦单个消息存储停机,你仍然可以使用其他分区发送和接收数据。以下示例展示了在主题上启用分区:
var ns = NamespaceManager.CreateFromConnectionString(myConnectionString);
var td = new TopicDescription(TopicName);
td.EnablePartitioning = true;
ns.CreateTopic(td);
摘要
在这一简短的章节中,你学习了 Azure Service Bus 的基本概念,包括队列、主题、SDK 以及更高级的功能,如死信、会话和事务。还有许多内容需要学习:异步消息传递、高级消息队列协议(AMQP)以及高级事务场景。总体来说,这是一个非常适合简单和关键场景的优秀服务,因为它为大多数应用提供了足够的灵活性,同时也很容易学习如何入门。记住,你可以使用基础层来处理最简单的用例,它为你提供了一个便宜且可靠的解决方案,比 Azure 存储队列提供了更加丰富的选项。在下一章中,我们将重点介绍使用 Azure Application Insights 进行监控服务*。
问题
-
队列和主题有什么区别?
-
你可以在基础层中使用主题吗?
-
使用死信队列的原因是什么?
-
Azure Service Bus 中的会话是什么?
-
启用分区的队列的最大大小是多少,当单个队列的最大大小为 1 GB 时?
-
活跃复制和被动复制有什么区别?
-
如何在 Azure Service Bus 中实现灾难恢复?
深入阅读
-
MSI 概述:
docs.microsoft.com/pl-pl/azure/active-directory/managed-identities-azure-resources/overview -
RBAC 认证:
docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-role-based-access-control -
Service Bus 示例:
github.com/Azure/azure-service-bus/tree/master/samples/DotNet -
Azure Service Bus 设计模式:
msdn.microsoft.com/en-us/magazine/mt845652.aspx
第十五章:使用应用程序洞察监控你的应用程序
Azure 不仅仅是关于开发应用程序。一旦我们将解决方案部署到云端,我们就必须以某种方式进行监控和诊断。Azure 应用程序洞察服务提供了一个完整的工具集,用于维护应用程序,提供多种语言和平台的 SDK,警报,查询语言,并与许多本地 Azure 服务集成。它简化了应用程序的登录,并消除了在使用来自多个地方的数据分析问题时的多个数据源。
本章将涵盖以下主题:
-
使用 Azure 应用程序洞察服务
-
监控不同的平台
-
使用分析模块
-
自动化 Azure 应用程序洞察
技术要求
要执行本章的练习,你将需要:
-
一个 Azure 订阅
-
安装了以下工作负载的 Visual Studio——ASP.NET、Web 开发和 Azure 开发
使用应用程序洞察服务
在开发应用程序时(尤其是托管在云中的应用程序),最重要的功能之一就是能够轻松地进行监控,并在早期阶段检测到任何潜在问题或缺陷。为此,你需要一个完整的日志记录、存储和报告工具的架构,这些工具需要集成、配置并每天维护。这要求你的团队具备额外的技能,当然也需要时间——你的应用程序越大,所需的工作量就越多。使用 Azure 应用程序洞察,所有这些操作变得更加简单:你只需要一个服务和一个端点来记录所有需要的信息,剩下的工作自动完成。
在云中记录数据
假设你有以下架构:

它包含了许多不同的 Web 应用程序、不同的存储能力(如 SQL 数据库或 Azure 存储),还有 Azure Functions。如果我们希望能够监控所有这些服务,我们将需要以下组件:
-
一种保存日志的工具(能够使用不同的输出,如存储或文件,可能是跨平台的)
-
能够存储数 GB 日志数据的存储
-
某种仪表板,将从存储中获取数据,并使用不同的筛选器和参数显示它
考虑到所有这些因素后,你可能会发现以下一些警告:
-
存储原始数据是不够的,因为你还需要某种投影视图,能够快速提取,并且不需要额外的转换或处理。
-
你需要找到一种存储数据的方法,使得用户可以根据不同的向量随意查询——追加日志可能非常适合检查最近的记录,但对于创建动态参数的索引却不太理想。
-
你需要实施某种数据保留策略——大多数日志在固定时间段后就没有用了。
-
应用程序和报告解决方案的性能应具有可重复性,并且不应随时间变化(例如,存储的数据量增加时)。
-
为问题跟踪设置专门的团队可能并不是最合理的资源分配——具有报告和数据分析技能的人在处理实际业务数据时会更有价值。
对于前述问题,理想的解决方案是一个能够完成我们之前提到的所有任务的单一组件:

在 Azure 中,类似的组件是 Azure Application Insights,我们将在本章中介绍。
Azure Application Insights 基础知识
连接到 Azure Application Insights 服务可能会因您的情况而异。通常,您在集成该服务时有多种选择:
-
与门户的无缝集成——无需额外的步骤,您只需启用并配置一个功能,剩下的已经实现
-
根据平台使用适当的 SDK
-
将遥测事件直接发送到服务端点
根据您使用的概念,将使用不同的配置:
-
对于门户,您无需额外的步骤,因为您已通过身份验证,并且可以从下拉菜单中选择一个资源。
-
对于 SDK,您需要一个仪表密钥,可以在门户中找到——我们将在本章后续部分讨论这一话题。
-
使用 REST API,有多种选项可用,例如 App ID、密钥或 OAuth2 流程。
请注意,根据使用的日志记录方法,可能会有不同的功能和能力。这对于发送自定义事件或自定义日志逻辑尤其如此——此类操作通常需要使用专用的 SDK。
除了一些显而易见的功能(如记录和存储有关请求或异常的信息的能力),Azure Application Insights 还实现了许多不同的功能:
-
请求遥测:您可以自动收集有关请求次数、平均延迟和失败率的信息。例如,如果您将此服务与 Azure App Services 一起使用,只需实现 SDK 即可获得对您的 Web 应用程序的全面洞察。
-
依赖项:除了常规的遥测信息,Azure Application Insights 还可以提供有关您的依赖项(如 Azure Table Storage 和 Azure SQL)如何执行的信息。如果您集成了多个服务,并且想知道哪个服务对延迟的影响最大,这一点尤为重要。
-
异常:获取有关失败请求或依赖项的信息是一回事,但显示有关错误的汇总数据的详细仪表板则更有用。在 Azure Application Insights 中,您可以轻松查看哪些类型的错误与某些特定子集的请求相关联。这使您更好地理解应用程序内部发生了什么,并知道从哪里开始修复。
-
用户遥测:你想知道到底有多少用户吗?你是否对他们使用应用时的流程感兴趣?在 Azure Application Insights 中,还有其他功能可以提供关于用户和会话数量、他们的行为以及整体活动的信息。
当然,我没有列出所有可用的功能——还有更多的功能,比如收集有关 AJAX 调用、页面视图和网页性能的信息;性能计数器(用于虚拟机),以及来自 Docker 的主机诊断。实际上,功能的可用性也取决于你选择的服务——为 Azure Functions 和 Azure App Services 收集的遥测数据是不同的。
实际上,你可以通过类似的图表和诊断,使用大多数服务实现与你的日志相同粒度的分析。不同的是所需的工作量——一个服务与 Azure Application Insights 集成得越少,你需要做的事情就越多。
在门户中创建 Azure Application Insights
要创建一个 Azure Application Insights 实例,你需要在市场中搜索该服务。
你需要填写以下简单的表单以继续操作:

事实上,它包含了一个你可能还没遇到过的字段:
- 应用类型:根据你的选择(ASP.NET Web 应用、Java、App Center、Node.js 或通用),将选择不同的图表集。这个选项不会影响服务的可用功能。它只是确保,对于特定实例,你将只看到你最感兴趣的信息。
你的实例能够做的其他事情完全取决于与它集成的服务以及发送到它的数据量。
与大多数 Azure 服务不同,在 Azure Application Insights 中,并没有要求为所有实例提供一个全球唯一的服务名称——在这里,你只需要避免在一个服务内为多个实例使用相同的名称。这是因为它不使用 DNS 名称来工作并与其他应用连接。
当你点击创建时,应该不会花费太长时间来配置一个新的服务实例,它看起来应该与以下内容相似:

如你所见,有一个默认隐藏的 Essentials 部分——它包含一些常见的元数据,更重要的是,包含一个仪器密钥——以及一个标识符,它唯一地标识你的服务实例。我们将使用它来实际连接到 Azure Application Insights——本章后续会详细介绍其他功能。
监控不同平台
Azure Application Insights 的优势在于它能够同时监控不同平台。你可以选择从 ASP.NET 页面、Java、Node.JS,甚至是 Python 或 Ruby(不过,有些语言和框架是 Application Insights 团队官方支持的,有些则是社区支持的)。关键是,它是与平台无关的。实际上,当你需要实现通信通道时,只需提供一个仪表密钥,就可以轻松将数据发送到服务实例,而无需额外的密钥和复杂的配置。在本节中,我们将专注于从不同平台发送信息,因此你将能够轻松开始在自己的项目中进行集成。
.NET
在 .NET 应用程序中,开始的唯一操作是安装最新的Microsoft.ApplicationInsights包。最简单的方法是使用以下代码片段:
TelemetryConfiguration.Active.InstrumentationKey = "<instrumentation-key>";
var telemetryClient = new TelemetryClient();
这段代码做了两件事:
-
通过提供仪表密钥来设置当前配置。连接到服务所需的全部内容。
-
初始化一个
TelemetryClient实例——这个类是一个代理,用于与服务进行通信。
然后,你可以使用不同的方法来记录一些数据:
telemetryClient.TrackTrace("Hello World!");
telemetryClient.TrackException(new Exception());
telemetryClient.TrackDependency(new DependencyTelemetry());
当然,还有更多可用的方法——你可以在进一步阅读部分找到它们。在第一个代码片段中,我们使用了TelemetryConfiguration类型——实际上,它提供的配置中存储的数据最初是从ApplicationInsights.config文件中获取的——它看起来可能是这样的:
<?xml version="1.0" encoding="utf-8"?>
<ApplicationInsights >
<InstrumentationKey>8sad7asd-asd876asf-jr323jsd-3hshjahj</InstrumentationKey>
<TelemetryInitializers>
<Add Type="Microsoft.ApplicationInsights.DependencyCollector.HttpDependenciesParsingTelemetryInitializer, Microsoft.AI.DependencyCollector"/>
</TelemetryInitializers>
<TelemetryModules>
<Add Type="Microsoft.ApplicationInsights.DependencyCollector.DependencyTrackingTelemetryModule, Microsoft.AI.DependencyCollector">
<ExcludeComponentCorrelationHttpHeadersOnDomains>
</ExcludeComponentCorrelationHttpHeadersOnDomains>
<IncludeDiagnosticSourceActivities>
<Add>Microsoft.Azure.ServiceBus</Add>
<Add>Microsoft.Azure.EventHubs</Add>
</IncludeDiagnosticSourceActivities>
</Add>
</TelemetryModules>
<TelemetryChannel Type="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.ServerTelemetryChannel, Microsoft.AI.ServerTelemetryChannel"/>
</ApplicationInsights>
如果文件不存在(或者它没有包含所有值),配置将不正确;在这种情况下,你需要手动提供它。
请注意,上述指令仅适用于使用 .NET 框架的情况。
这里值得提一下的是,设置在不同平台之间有所不同。我们讨论了最基本的(控制台应用)设置,但我们还可以使用其他应用类型,比如 Windows 桌面应用:
public partial class Form1 : Form
{
private TelemetryClient _telemetryClient = new TelemetryClient();
private void Form1_Load(object sender, EventArgs e)
{
_telemetryClient.InstrumentationKey = "<instrumenation-key>";
_telemetryClient.Context.User.Id = Environment.UserName;
_telemetryClient.Context.Session.Id = Guid.NewGuid().ToString();
_telemetryClient.Context.Device.OperatingSystem = Environment.OSVersion.ToString();
_telemetryClient.TrackPageView("Form1");
}
}
或者我们可以使用与 Azure Application Insights 无缝集成的 Web 应用。
Node.js
要在 Node.js 中开始使用 Azure Application Insights,你需要以下命令:
npm install applicationinsights
它将安装一个 NPM 包——Application Insights,这个包允许你与该服务进行交互。以下是这个包接口的完整示例:
let http = require("http");
let appInsights = require("applicationinsights");
appInsights.setup("<instrumentation-key>");
appInsights.start();
let client = appInsights.defaultClient;
client.trackEvent({name: "my custom event", properties: {customProperty: "custom property value"}});
client.trackException({exception: new Error("handled exceptions can be logged with this method")});
client.trackMetric({name: "custom metric", value: 3});
client.trackTrace({message: "trace message"});
client.trackDependency({target:"http://dbname", name:"select customers proc", data:"SELECT * FROM Customers", duration:231, resultCode:0, success: true, dependencyTypeName: "ZSQL"});
client.trackRequest({name:"GET /customers", url:"http://myserver/customers", duration:309, resultCode:200, success:true});
http.createServer( (req, res) => {
client.trackNodeHttpRequest({request: req, response: res});
}).listen(1337, "127.0.0.1");
console.log('Server running at http://127.0.0.1:1337/');
尝试运行示例并查看洞察结果——你会看到最初的请求已经被记录:

Azure Functions
Azure Application Insights 还提供与另一个 Azure 服务——Azure Functions 的无缝集成。启用集成有两种方式:在创建服务时开启,或者通过在设置中手动提供仪表密钥。
以下是可以启用此功能的表单:

然而,这种设置有一个警告:如果你已经有一个服务实例,则无法选择另一个实例。要做到这一点,你需要通过在功能应用的设置中提供APPINSIGHTS_INSTRUMENTATIONKEY来手动启用集成。
还有一个选项。你可以点击任何功能的“监视”标签,然后点击“配置”,而不是手动提供密钥。如果经典视图尚未启用,这个选项将会可用。
启用集成后,你将能够分析所有查询的执行情况:

分析模块
将 Azure Application Insights 与不同服务(以及自定义应用程序)集成的多种方式并不是此服务的唯一大特点。另一个重要且至关重要的部分是分析模块中提供的分析语言。这是一种交互式查询语言,使你能够使用简单直观的语法轻松探索日志数据。它的另一个优点是,你不需要任何额外的工具就可以开始使用——一旦存储了跟踪、异常或请求数据,它就可以开箱即用——你需要做的唯一事情就是编写查询。在本节中,我们将介绍查询语言和模块,这样你就可以开始编写自己的查询,并发现存储日志中可用的许多不同维度。
访问分析模块
使用分析模块非常简单。转到你的 Azure 应用程序分析实例的概览页面,然后点击分析按钮:

它会显示一个新窗口,显示新的选项,例如输入查询、使用预定义的查询,或者仅仅探索可用的不同维度:

这里最重要的是查询窗口。它是一个交互式功能,使你能够编写查询,并提供额外的功能,如语法验证器和建议。我们先从一个简单的查询开始,显示过去几天的请求计数:
requests
| summarize totalCount=sum(itemCount) by bin(timestamp, 30m)
| render timechart
如你所见,它分为三个部分:
-
requests:你查询的维度 -
summarize:定义你想从该维度获取的数据的函数 -
render:一个可选函数,根据数据绘制图表
当然,查询可以有不同的结构;你可以找到一个稍微复杂一点的查询:
requests
| summarize RequestsCount=sum(itemCount), AverageDuration=avg(duration), percentiles(duration, 50, 95, 99) by operation_Name
| order by RequestsCount desc
从前面的示例来看,将不会显示图表——相反,它会显示一个表格:

重要的是查询的日期范围,因为在这个范围内你可以过滤数据。你必须选择你感兴趣的日期。为此,点击“时间范围”按钮,该按钮位于“运行”按钮旁边,然后选择正确的选项:

Azure 应用分析中的查询语言非常丰富,它定义了许多不同的数据类型和操作的函数(例如,您可以使用许多不同的窗口函数,像是next())。相关的参考资料可以在进一步阅读部分找到。
此外,当执行查询时,您可以为其选择一个额外的过滤器(基于结果),从而选择特定的记录:

另一个有用的分析模块功能是智能分析。它是一组附加功能,通过在数据分析中引入机器学习元素来扩展分析能力。目前,提供以下功能:
-
自动聚类:此功能自动将数据分成若干集群,以便更易于理解。
-
篮子:此功能会自动在查询结果中寻找有趣的数据。
-
差异模式:此功能操作于存储真/假数据的列上,并尝试找到对应于它们之间差异的模式。
-
定时系列:此功能(
make-series)将数据转换为单行,便于分析问题的根本原因。 -
线性回归:使用此功能,您可以轻松地根据结果(例如,异常数量是否增长)找到数据趋势。
-
异常值检测:此功能可以发现数据中某个值与其他值的异常差异。
使用这些高级功能,您可以大大提高数据分析的效果。它们结合了许多有用的功能,使整个服务更加灵活。而且,您不必是数据科学家就可以进行数据分析,发现趋势和异常。
应用洞察自动化
监控并不是您每天都愿意花时间做的事。事实上,服务的自动化程度越高,您可以获得的结果就越好。让机器查看不同的维度并根据预设规则找出问题,总是比自己来做更快捷、更细致。在 Azure 应用洞察中,您有许多自动化选项:ARM 模板、门户中的警报,或者集成外部服务(例如 Microsoft Flow)。在本章的最后部分,您将学习如何开始使用自动化,并确保将精力集中在开发上,而不是日志分析和服务维护。
警报
警报是一项功能,可以在发生异常时通知您。在设置警报时有很多不同的选项,首先从 ARM 模板开始:
{
"name": "[variables('myFirstAlertName')]",
"type": "Microsoft.Insights/alertrules",
"apiVersion": "2014-04-01",
"location": "[parameters('appLocation')]",
"dependsOn": [
"[resourceId('Microsoft.Insights/components', parameters('myApplicationName'))]"
],
"tags": {
"[concat('hidden-link:', resourceId('Microsoft.Insights/components', parameters('myApplicationName')))]": "Resource"
},
"properties": {
"name": "[variables('responseAlertName')]",
"description": "response time alert",
"isEnabled": true,
"condition": {
"$type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.ThresholdRuleCondition, Microsoft.WindowsAzure.Management.Mon.Client",
"odata.type": "Microsoft.Azure.Management.Insights.Models.ThresholdRuleCondition",
"dataSource": {
"$type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.RuleMetricDataSource, Microsoft.WindowsAzure.Management.Mon.Client",
"odata.type": "Microsoft.Azure.Management.Insights.Models.RuleMetricDataSource",
"resourceUri": "[resourceId('microsoft.insights/components', parameters('myApplicationName'))]",
"metricName": "request.duration"
},
"threshold": "[parameters('responseTime')]",
"windowSize": "PT15M"
},
"actions": [{
"$type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.RuleEmailAction, Microsoft.WindowsAzure.Management.Mon.Client",
"odata.type": "Microsoft.Azure.Management.Insights.Models.RuleEmailAction",
"sendToServiceOwners": true,
"customEmails": []
}]
}
}
虽然最初这样的 ARM 模板可能有些令人生畏,但实际上它包含了一组固定的参数,可以通过资源浏览器轻松找到(您可以在进一步阅读部分找到相关链接)。在这里,您可以了解如何发现现有的警报规则:

在上面的截图中,我展示了在一个特定资源组内创建的 Azure Application Insights 实例中可用的警报规则。当你点击它时,你将看到 Azure 中该资源的完整描述。
资源浏览器是一个非常好的工具,尤其是在使用 ARM 模板并寻找某个特定资源的引用时。它显示了从 ARM 视角描述服务的所有参数。
在这里,你可以找到服务部署时创建的默认警报:
{
"id": "/subscriptions/.../resourceGroups/handsonazure-rg/providers/microsoft.insights/alertrules/Failure Anomalies - handsonazure-ai",
"name": "Failure Anomalies - handsonazure-ai",
"type": "Microsoft.Insights/alertRules",
"location": "West Europe",
"properties": {
"name": "Failure Anomalies - handsonazure-ai",
"description": "",
"isEnabled": true,
"condition": {
"$type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.ThresholdRuleCondition, Microsoft.WindowsAzure.Management.Mon.Client",
"odata.type": "Microsoft.Azure.Management.Insights.Models.ThresholdRuleCondition",
"dataSource": {
"$type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.RuleMetricDataSource, Microsoft.WindowsAzure.Management.Mon.Client",
"odata.type": "Microsoft.Azure.Management.Insights.Models.RuleMetricDataSource",
"resourceUri": "/subscriptions/.../resourcegroups/handsonazure-rg/providers/microsoft.insights/components/handsonazure-ai",
"resourceLocation": null,
"metricNamespace": null,
"metricName": "...",
"legacyResourceId": null
},
"operator": "GreaterThan",
"threshold": 2,
"windowSize": "PT1H",
"timeAggregation": null
},
"action": null,
"lastUpdatedTime": "2018-09-14T12:04:57.6355645Z",
"provisioningState": "Succeeded",
"actions": [
{
"$type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.RuleEmailAction, Microsoft.WindowsAzure.Management.Mon.Client",
"odata.type": "Microsoft.Azure.Management.Insights.Models.RuleEmailAction",
"sendToServiceOwners": true,
"customEmails": []
}
]
}
}
为了让事情更容易理解,我们来看一下如何在门户中设置警报。你可以在“配置”部分访问“警报”面板:

要开始,你需要点击“+ 新建警报规则”按钮。你将看到一个详细的向导,提供设置警报的快捷方式。这里最重要的是条件——它们定义了警报的触发方式:

当然,警报可以有多个触发条件,因为这取决于它的特性。更重要的是,条件可以非常复杂;它们可以涉及多个资源,并引入复合警报规则的概念。接下来在这里定义的是警报的详细信息——你需要提供一些描述它们的元数据:

重要的事项是警报的严重性:它越高,警报就越重要。向导的最后一部分是实际的操作,你可以选择多个选项:
-
邮件/SMS/推送/语音
-
Azure Function
-
LogicApp
-
Webhook
-
ITSM
-
自动化运行簿
其中一些要求你已经有一个处理警报的服务——你需要选择最适合你需求的选项。当你创建一个警报时,它将会处于活动状态,并在服务中可见:

总结
在本章中,你了解了一个适用于 Azure 的监控解决方案:Azure Application Insights。我们讨论了资源的配置、创建警报以及与其他服务的集成等内容。除了本章中提到的功能外,这个 Azure 组件还提供了许多附加功能——例如智能检测、数据的持续导出和详细的使用日志。我强烈鼓励你进一步探索它,因为它极大地简化了监控活动和解决问题的过程。在下一章中,我们将介绍本书范围内的最后一个 Azure 服务:Azure SQL,它是一个 PaaS 服务,是 Azure 版本的知名数据库引擎。
问题
-
识别 Azure Application Insights 实例并连接到它需要什么?
-
是否可以在 Node.js 应用程序中使用 Azure Application Insights?
-
什么是智能分析模块?
-
如何查询存储在 Azure Application Insights 中的日志?
-
如何在服务中自动创建警报?
-
是否可以将短信作为触发警报的动作?
进一步阅读
-
TelemetryClient 参考:
docs.microsoft.com/pl-pl/dotnet/api/microsoft.applicationinsights.telemetryclient?view=azure-dotnet -
Azure 日志分析参考:
docs.loganalytics.io/index -
智能分析:
docs.loganalytics.io/docs/Learn/Tutorials/Smart-Analytics/Understanding-Autocluster -
资源浏览器:
resources.azure.com/
第十六章:Azure 中的 SQL - Azure SQL
Microsoft SQL Server 是最流行的数据库之一,通常是许多流行应用程序的核心。借助 Azure,我们可以跳过整个集群设置、安装和维护的过程,使用 Azure SQL —— 这是 SQL Server 的云版本,具有相同的功能。得益于灵活的定价,我们可以根据性能和可用功能选择任何我们想要的选项。我们也不需要担心地理复制和存储备份 —— 所有这些功能都可以在云中轻松配置和自动化。
本章将涵盖以下主题:
-
Microsoft SQL Server 和 Azure SQL 之间的差异
-
在 Azure 门户中使用 Azure SQL
-
Azure SQL 的安全特性
-
扩展和监控 Azure SQL
技术要求
要执行本章中的练习,你将需要:
- 一个 Azure 订阅
Microsoft SQL Server 和 Azure SQL 之间的差异
Microsoft SQL Server 是一个广为人知且广泛使用的 SQL 数据库服务器,已经获得了很高的流行度,并且被认为是许多项目的默认选择,这些项目从非常简单的网站到处理高负载并对业务至关重要的企业级服务都有。随着云技术的日益流行,随着这种情况的自然发展,人们期望通过将应用程序迁移到 Azure,数据库也能够随之迁移。为了满足这种需求,微软开发了 Azure SQL Service —— 一种微软 SQL Server 的 PaaS 版本,由他们的团队进行管理和升级;你需要负责的只有配置和数据管理。Azure 还提供了另一种选项,即 SQL Server VMs,这是在云中使用该数据库的另一种方式。在本节中,我们将重点讨论这两种服务的区别,并尝试识别它们的不同使用场景。
Azure SQL 基础知识
通过使用云中的 PaaS 服务,你正在稍微转移一些责任:
-
你不再是基础设施的维护者
-
当软件被考虑时,你不再需要负责不同的更新
-
通过与提供商签署 SLA,提供商负责确保服务正常运行
相反,你应该专注于以下几点:
-
正确配置服务,以满足你的性能目标和法律要求
-
集成不同的服务和应用程序,以便它们在与服务通信时能够体现最佳实践
-
实现 HA/DR 场景,以确保某一地区的故障或灾难不会影响你的系统
使用 Microsoft SQL Server 时,你完全在你自己的机器或租赁机器上,这些机器需要你自己维护和监控。虽然在许多场景下这种情况是合理的(因为可能有一些法律要求禁止你将数据存储在自己的数据中心以外,或者由于某些原因 Azure 无法提供你期望的性能),但在许多情况下,使用 PaaS 实例的 SQL 数据库是一个巨大的改进。事实上,使用此服务时,你有几个不同的选择:
-
具有隔离资源的单一数据库
-
弹性池中的池数据库
-
托管实例,这是与本地 SQL Server 最相似的模型
需要了解的重要一点是,所有新功能和更新都会部署到托管在 Azure 中的 SQL 数据库。这相比传统的 Microsoft SQL Server 给你带来了优势,因为你始终保持最新:你不需要自行安排服务器上的更新。
你拥有的服务器和数据库越多,更新它们的过程就变得越复杂和困难。比较这两种产品时,请考虑这一点。
另一个谈论 Azure SQL 时至关重要的方面是其购买模型。目前,你有两个选项:
-
基于 DTU:DTU 是计算、内存和 I/O 资源的混合,这些资源分配给你的数据库
-
基于 vCore:这个选项允许你自己选择所有内容(包括 vCore 数量、内存大小和存储性能)。
你可能会想知道 DTU 如何反映实际的硬件;有一篇很好的文章试图在本章的进一步阅读部分解释这些度量标准。
在大多数情况下,使用 DTU 作为度量标准是更好的选择——因为很难预测应用程序的确切硬件需求。当你是高级 SQL Server 用户并且知道自己究竟需要多少核心或内存时,可以使用基于 vCore 的模型。
你可能会想,Azure SQL Service 的扩展能力是什么?当然,你可以为你的数据库分配更多(或更少)资源,但有些场景下这会使事情变得更加复杂(或者你的应用程序在数据库性能方面有不同的需求,这种模型根本行不通)。为了应对这些情况,你可以选择使用弹性池。这个概念相当简单——通常,你为单个数据库分配资源:

使用弹性池时,你稍微改变了模型,池中的资源被分配,具体如下:

这有什么变化呢?这为你提供了更多的灵活性;你不再需要托管一个庞大的单一数据库(在资源分配方面),你可以轻松地进行扩展,使其与其他实例共享负载。而且,它在成本控制方面也更具优势;你可以同时扩展多个数据库,而不需要控制它们各自的需求。
扩展 Azure SQL 是一个重要的课题,需要最初的很多关注——我们将在本章末尾回到这个话题。
除了性能和不同的扩展能力外,Azure SQL 还提供了许多在考虑将其作为数据存储时非常重要的附加功能。因为没有存储在数据库中的信息,通常情况下一个应用程序是没有意义的,因此可用性也是非常重要的。幸运的是,Azure SQL 实现了许多出色的功能,使其成为一个成熟的存储选项:
-
自动备份:在本地环境中,配置和管理备份要复杂得多,因为你需要了解服务器配置并找到存储它们的地方。而在 Azure 中,事情变得非常简单,因为 Azure Storage 集成了自动备份,确保性能和可靠性。
-
地理复制:即使单个区域出现故障,你仍然可以为客户提供数据。通过 Azure SQL,你可以配置一个次级读取区域,确保在故障解决之前你能保持在线。
-
故障转移组:你不需要自己实现故障转移能力和逻辑,可以依赖 Azure SQL 当前提供的功能。这使得创建全球分布的应用程序变得更加容易,因为你只需要关注配置,而不需要担心基础设施。
要准确了解 Azure SQL 与 Microsoft SQL Server 的区别,可以参考以下链接:docs.microsoft.com/en-us/azure/sql-database/sql-database-features。
高级 Azure SQL 特性
除了一些确保 Azure SQL 是一个完整的关系型数据库并且可以依赖构建系统的基本功能外,还有许多附加功能,使得使用这个服务变得非常有趣:
-
自动监控和调优:你有没有遇到过这样的情况,在使用数据库几个月后,数据库里充满了过时的索引、存储过程和函数?Azure SQL 通过主动监控你如何使用和维护数据库,并在有改进的空间时给出建议,使这一过程变得更加简单。我发现这个功能非常有帮助——如今,开发速度特别快,且重点在于为市场提供新价值,很容易迷失方向,忽视应该从数据库中移除的内容。借助服务推荐来删除索引、改进模式和查询参数化,我发现大多数时候我的存储状态更好了。
-
自适应查询处理:虽然 SQL Server 也有这个功能,但在 Azure SQL 中使用它是对其他性能推荐的一个重要补充。基本上,当启用此功能时,服务器引擎会尝试找到查询的最佳执行计划。
-
安全性和合规性功能:确保你存储的数据是安全的,并且所有漏洞能够尽早被发现是非常重要的。在 Azure SQL 中,提供了很多额外功能,旨在从敏感性和合规性的角度分析你的数据。有内建工具会搜索任何可能影响数据完整性或导致数据泄露的异常和威胁。此外,Azure SQL 与 Azure Active Directory (AD) 集成,并支持 多因素认证 (MFA)——这使得审计和授权等操作变得更加简单,无需额外努力。
Azure SQL 的安全功能将在本章后续部分进行描述,这样你可以全面了解该服务的功能。
SQL Server 在虚拟机上
如果你不想完全使用 PaaS,你可以创建一个已经内置 SQL Server 镜像的虚拟机。在这种选项下,服务的性能将依赖于虚拟机的性能——如果你发现数据库的 CPU 或内存资源不足,唯一需要做的就是扩展虚拟机。要在门户中创建虚拟机,你需要搜索运行在你选择操作系统上的 SQL Server,如下所示:

如你所见,提供了免费的 SQL Server 许可证,而且更重要的是,更新的版本可以在 Linux 机器上运行。一旦你选择了感兴趣的镜像,你可以点击“创建”按钮开始配置虚拟机。
如下截图所示,有许多字段需要填写,才能实际使用该服务:

当然,这与 SQL 服务器在 Azure 中的 IaaS 模型相关。配置向导将为你提供关于虚拟机默认大小和其他所需参数的建议。一旦配置完成,你将能够通过 RDP 或安全的 SSH 隧道连接到它。
如果你想远程连接到 SQL 服务器,记得打开 1433 端口。
创建和配置 Azure SQL
在阅读本章开头后,你应该能够感知到 Azure SQL 如何工作以及它为你提供了什么。尽管一些理论始终是有益的,但实践才能勾画出完整的图景,并让你完全理解这个话题。在本节中,我们将专注于在门户中创建和配置 Azure SQL,并尝试识别所有前述功能。你还将看到,管理这个 PaaS 服务与本地版本的区别,特别是在使用其功能时。
创建 Azure SQL 实例
在 Azure 门户中,当你搜索 Azure SQL 时,你将看到许多不同的选项,如 SQL 数据库、SQL 服务器(逻辑服务器)或 SQL 弹性数据库池。虽然它们都允许你创建一个数据库,但开始使用该服务的最简单方法是使用 SQL 数据库 ——这仍然需要创建一个服务器。在下面的截图中,你可以看到我的服务器配置:

以下是我的数据库配置:

这里重要的是选择源——你有三个选项:
-
空白数据库:在大多数情况下,这是你最感兴趣的第一个选项
-
示例:我已经使用它,因此我的数据库中已经有数据
-
备份:如果你想从现有的备份中恢复一个数据库,这是一个很好的选项
对于空白数据库选项,你还可以选择排序规则;在下拉菜单中,选择适合你数据的选项。我们还将稍微关注一下定价配置:

如你所见,使用 DTU 模型时,你可以选择三个不同的层级:
-
基本:适用于较小的工作负载
-
标准:在成本和性能之间提供最佳平衡
-
高级:适用于需要大规模性能能力的所有工作负载
根据层级,你将获得固定的 DTU 数量(对于基本层),或者你需要选择你感兴趣的数量:

这里重要的是,大部分数据库成本都是基于资源分配的——记得选择你能选择的最大数据库大小(例如,在标准层并选择 400 DTU 时,100 MB 和 250 GB 之间的定价没有差异)。
当然,你还可以在基于 DTU 的模型和 vCores 选择之间切换:

使用 vCore 时,选择“最大数据大小”会影响定价。此外,这里你可以选择两个不同的定价层:
-
一般用途:适用于大多数常见场景,无需特别的弹性和流量需求
-
业务关键:此级别提供更好的性能和更低的延迟(且价格显著更高)
当你对配置满意后,可以点击“创建”按钮,开始资源配置过程。配置完成后,你可以通过访问“概览”面板来查看基本信息:

现在我们将尽量介绍大部分功能,以便你更好地理解如何使用此服务。
Azure SQL 功能在门户中
我们将从“配置”面板开始——当你点击它时,你将看到它允许你设置数据库的等级和定价模型。当你想提高数据库的性能时,这个选项特别有用;你可以轻松更改为其分配的 DTU 或 vCore 数量,使其可以更快速地处理查询。
正如我之前提到的,配置单一数据库适用于较简单的场景,在这些场景中,你可以轻松监控数据库,且性能需求不会快速变化。在所有其他情况下,更好的选择是使用弹性池。
当你进入“地理复制”面板时,你将看到一个类似于测试时在 Azure Cosmos DB 中看到的地图:

在此页面上,你可以快速创建一个次级区域,以便在需要时执行故障转移。为此,点击你感兴趣的区域,接着将显示“创建次级”屏幕:

你将需要再次创建一个服务器(如果该区域内还没有现有的服务器)以及定价层(当然,次级数据库的定价层可以不同)。此外,在此面板中,你可以启用数据库的故障转移组——为此,请点击以下面板:

通过实现故障转移组,你可以为你的数据库引入自动故障转移:

然而,如果你想执行强制故障转移,你可以点击次级数据库,这将显示一个允许执行该操作的屏幕:

请记住,执行故障转移可能需要一些时间才能完全传播——确保你已做好准备。
当你进入连接字符串面板时,你将看到一个适用于不同环境的连接字符串模板:

你还可以下载不同的驱动程序,如 ADO.NET、JDBC、ODBC 或 PHP。
请记住,该服务仅提供连接字符串的模板——您必须设置用户名和密码才能使其工作。
目前,我们正在探索 Azure 中的 SQL 数据库——让我们看看当前 SQL 服务器的具体情况。您可以通过点击“概览”面板上的服务器名称找到它。最初,屏幕看起来一样,但您很快会意识到它提供了许多不同的功能:

不幸的是,我们无法覆盖所有功能,但我会尽量为您描述其中的大部分。当我们查看“设置”部分时,我们可以看到以下面板:
-
故障转移组:如前所述,要引入自动故障转移,您必须创建一个故障转移组。一个组由主服务器和备用服务器组成,具有定义好的故障转移策略和宽限期——这个设置定义了从故障检测到实际故障转移之间的时间。
-
管理备份:要为您的服务器配置备份(例如,启用长期保留(LTR)),您可以访问此面板。它还显示所有可用的备份。
-
活动目录管理员:可以使用定义在活动目录用户中的用户设置服务器管理员。当然,您可以为此设置多个用户——窍门是使用组而不是单个帐户。
-
SQL 数据库:要快速访问由此特定服务器提供的数据库,请使用此面板。
-
SQL 弹性池:与 SQL 数据库类似,此面板显示可用的弹性池。要创建新的池,请转到“概览”面板并点击“+ 新建池”按钮。
-
删除的数据库:即使数据库从服务中删除,您仍然有机会恢复它。在这种情况下,请查看该面板以查看所有可恢复的数据库。
-
导入/导出历史:所有对数据库进行的导入和导出操作将在此处显示。这是一个很好的审计工具,因此您不会错过任何未经通知的数据库导出情况。
-
DTU 配额:如果您有兴趣查看服务器的 DTU/vCore 配额,可以访问此面板。
安全
关于 Azure SQL 功能,有多种不同的选项可以用来确保您的解决方案安全。诸如防火墙、完整的操作审计和数据加密等功能是此服务的常见功能,甚至在基础层也可以使用。在本节中,我们将重点学习上述功能,以确保您的实例得到保护并免受大多数威胁。
防火墙
浏览 SQL 数据库时,您可能注意到在“概览”面板上有一个“设置服务器防火墙”按钮:

这是设置允许流量进入 Azure SQL 防火墙规则的最简单方法。
在 Azure SQL 中,所有流量默认被拒绝——您必须将所有应允许与服务器通信的计算机的 IP 列入白名单。
在我们开始配置防火墙之前,您必须了解我们为什么真的需要它。如果我尝试使用 Microsoft SQL Server Management Studio 连接到我的服务器,这里会发生什么:

如您所见,它自动检测到我的 IP 地址没有被列入白名单,因此服务器拒绝与我通信。我们需要在此添加一个特定的 IP 地址,这样通信就会被允许。
实际上,默认情况下,只有托管在 Azure 内的机器被允许与 Azure SQL 通信。如果您希望从本地计算机连接到服务器,您必须设置防火墙规则。
在门户中,您可以通过点击设置服务器防火墙按钮来添加规则——这将显示一个屏幕,您可以在其中明确设置一个应能够与服务器通信的 IP 地址:

在此屏幕上,您还可以防止 Azure 服务与您的 Azure SQL 实例通信。此外,您可以在此处添加虚拟网络——借助此功能,您可以创建包含应用程序和数据库的整个生态系统,从而通过一套非常严格的规则保护它免受访问。
高级威胁保护
高级威胁保护(ATP)是 Azure SQL 的一个高级功能,默认情况下在服务中未启用。目前,它提供 60 天的免费试用期,您可以在此期间测试此功能是否适合您。您可以通过高级威胁保护面板启用它:

如您所见,它由三个独立的功能组成:
-
数据发现与分类:用于从敏感性和法律分类(例如—GDPR 要求)角度分析数据
-
漏洞评估:这使用 SQL Server 的最佳实践检查您的数据库是否存在可能的漏洞
-
威胁检测:一个主动监控数据库中可疑活动并为您记录的功能
在下文中,您可以看到 Azure SQL 如何对示例数据进行分类:

结果是,它自动检测了信息类型并声明了其敏感性标签。然后,它显示了一个摘要,向您提供存储在数据库中的数据形态的整体概览。
这个功能是分析大型数据库是否符合新规定的一个极好的工具——如果您不确定自己是否存储了敏感数据,可以使用它。
在漏洞评估方面,以下是我数据库示例扫描的结果:

扫描完成后,系统会给出安全检查结果——它检查诸如数据分类、是否启用审计或谁可以访问数据等事项。例如,如果你有许多用户并分配了广泛的权限,则会对此发出警报。在扫描过程中将检查近 50 条规则,这是对其他所有安全功能的一个重要补充。
更多的安全指南可以在进一步阅读部分的相关链接中找到。
审计
如果你想确切知道服务器内部发生了什么,你必须启用审计:

这将记录所选存储中的所有操作(当然,如果你选择了存储选项的话)。目前,有三种不同的存储审计日志选项:
-
存储
-
日志分析(预览版)
-
事件中心(预览版)
虽然存储选项有点静态,但你可以使用剩下的两个选项进行更动态的集成(尤其是在使用 Azure 事件中心时)。一旦启用审计,你就可以在点击查看审计日志按钮时看到所有已记录的操作:

动态数据掩码
有时你可能希望允许某人读取数据库中的数据,但同时又不希望他或她读取更敏感的数据(例如出生日期、地址或姓氏)。在 Azure SQL 中,有一个名为动态数据掩码(Dynamic Data Masking)的功能可以实现这一点:

有两种方法可以添加掩码——要么使用推荐方法,要么点击+ 添加掩码按钮手动添加:

你需要做的是选择模式、表格、列以及掩码字段格式——一旦配置这些并保存规则,非管理员用户将看到掩码后的值。以下显示的是管理员的值:

以下显示的是没有管理员权限的用户的值:

如你所见,LastName和Email对于非管理员用户是被掩码的,正如计划所示。
扩展 Azure SQL
数据库的性能需求可能会根据应用程序的时间和当前状态而有所不同。这时,扩展功能就显得至关重要——你可以根据服务的需求调整成本和可用资源。在 Azure SQL 中,有多个不同的场景需要考虑:你是使用单一数据库还是弹性池,是否需要扩展读取,或者是否需要在任何地方都能使用所有功能。在这一小节中,我将向你展示如何快速做出决策以及在哪里可以找到扩展工具。
单一数据库
正如我们之前提到的,单一数据库的扩展非常简单——只需进入 Configure blade 并选择您感兴趣的新层级。您可以通过观察数据库性能轻松决定是否需要扩展数据库:

如果您看到数据库的使用率不断激增,或者数据库的利用率已经接近最大值,增加一些 DTU 或其他资源总是一个好主意。
请记住,您可以在使用率达到上限时设置警报,因此有一些方法可以自动化这一过程。
弹性池
启用弹性池后,情况会有所不同——例如,您不再是单个数据库操作一个 DTU,而是可以选择一个弹性池,这引入了一个稍微不同的弹性 DTU 模型:

在这种模型中,您将使用弹性池配置来扩展数据库。对于单一数据库,您只能更改可用的最大数据大小(该大小也受到池所设置值的限制)。
读取扩展
有时您只需要扩展数据库的读取能力。这种情况发生在您更倾向于提供内容而不是修改它时(例如,您有一个非常受欢迎的门户,虽然管理单一位置,但内容却是全球提供的)。在 Azure SQL 中,您可以只扩展服务的某一部分——即负责为您管理读取操作的部分。
请注意,您需要选择 Premium/Business Critical 层级才能使此功能生效。
要启用数据库的读取扩展,您可以使用 REST API:
HTTP PUT https://management.azure.com/subscriptions/{SubscriptionId}/resourceGroups/{GroupName}/providers/Microsoft.Sql/servers/{ServerName}/databases/{DatabaseName}?api-version= 2014-04-01-preview
Body:
{
"properties":
{
"readScale":"Enabled"
}
}
或者,您可以使用 PowerShell:
Set-AzureRmSqlDatabase -ResourceGroupName <my-resource-group> -ServerName <my-server> -DatabaseName <my-database> -ReadScale Disabled
分片
扩展数据库的另一种方式是使用分片。与弹性池不同,通过使用分片,您为每个数据库分配独立的资源。它也是一种横向扩展模型(即,您会更倾向于提供另一个数据库,而不是扩展现有的数据库)。
请注意,您还可以通过使用弹性数据库拆分合并工具来为弹性池使用分片:docs.microsoft.com/en-us/azure/sql-database/sql-database-elastic-scale-overview-split-and-merge。
通常情况下,如果您:
-
数据量过大,无法通过单个实例处理
-
想要进行负载均衡请求
-
想要实现数据的地理分布
这里的关键是每个分片的数据结构必须相同。您可以在本章的 进一步阅读部分找到有关分片的完整文档。
监控与调优
本章我们讨论的最后一项内容是 Azure SQL 的监控与调优。由于数据库通常是许多应用程序的核心,因此快速诊断性能或使用方面的问题并在需要时轻松调整至关重要。Azure SQL 提供了多种不同的功能,您可以利用它们从您的实例中获取洞察。
监控
要监控您的 SQL 数据库,您可以使用警报(假设您已经阅读了上一章,第十五章,使用应用程序洞察监控您的应用程序)。您可以通过点击警报面板访问此功能:

这里有两种可用的警报类型:
-
添加指标警报(经典)
-
添加活动日志警报
它们的工作方式稍有不同——指标警报是基于 CPU 百分比、死锁或数据库总大小等值触发的,而活动日志警报则是在发生事件时触发。您可以同时使用它们来覆盖以下内容:
-
性能不足(指标)
-
无效查询(指标)
-
配置问题(指标)
-
整体服务健康状况(指标)
-
即将进行的维护活动(活动日志)
-
实际服务问题(活动日志)
-
服务健康建议(活动日志)
调优
有一个叫做智能性能的功能组,它允许您监控和调优 SQL 数据库的性能:

现在让我们查看性能建议:

虽然最初此功能为空,但在使用 Azure SQL 时,它会显示不同的建议。这里的重点是我们可以自动化操作——只需点击自动化按钮,显示另一个屏幕,您可以在其中选择感兴趣的内容:

这个屏幕实际上是之前展示的自动调优面板。您可以使用它来自动执行诸如管理索引或强制查询计划之类的操作。
总结
Azure SQL 是一个非常复杂且扩展的服务,工作方式与其本地版本 Microsoft SQL Server 类似。虽然它是一个完整的 PaaS Azure 组件,但仍然支持许多高级操作,如分片、多租户、AD 集成、故障转移和地理复制。除了托管在云中,您仍然可以像使用独立版本的 SQL Server 一样使用它。在下一章,我们将讨论本书中提到的最后一个 PaaS 服务,即 Azure Data Lake Storage。
问题
-
在更新策略方面,Azure SQL 与 Microsoft SQL Server 有何不同?
-
什么是分片?
-
您在 Azure SQL 中创建了一个新的 SQL 数据库,但服务器拒绝连接到它。可能是什么问题呢?
-
Azure SQL 的两种购买模式是什么?
-
什么是弹性池?
-
DTU 和 eDTU 之间有什么区别?
-
如何在 Azure SQL 中屏蔽特定字段?
-
可用的审计日志目标有哪些?
进一步阅读
-
如何理解 DTU:
sqlperformance.com/2017/03/azure/what-the-heck-is-a-dtu -
性能建议:
docs.microsoft.com/en-us/azure/sql-database/sql-database-advisor -
SQL 数据库安全性:
docs.microsoft.com/en-us/azure/sql-database/sql-database-security-overview -
读取扩展:
docs.microsoft.com/en-us/azure/sql-database/sql-database-read-scale-out -
分片:
docs.microsoft.com/en-us/azure/sql-database/sql-database-elastic-scale-introduction
第十七章:大数据存储 - Azure Data Lake
有时,我们需要存储无限量的数据。这种情况涵盖了大多数大数据平台,其中即便是一个软限制最大容量也可能会在应用的积极开发和维护中引发问题。得益于 Azure Data Lake,我们在存储结构化和非结构化数据方面拥有无限的可能性,所有这些都拥有高效的安全模型和出色的性能。
本章将涵盖以下主题:
-
Azure Data Lake Store 基础
-
存储数据到 Azure Data Lake Store
-
安全特性与关注点
-
使用 Azure Data Lake Store 的最佳实践
技术要求
要完成本章的练习,你将需要:
- 访问 Azure 订阅
了解 Azure Data Lake Store
在考虑存储解决方案时,你必须考虑你希望存储的数据量。根据你的需求,你可能会选择 Azure 中不同的服务选项——Azure Storage、Azure SQL 或 Azure Cosmos DB。也有各种数据库可以作为虚拟机的镜像(如 Cassandra 或 MongoDB);这个生态系统相当丰富,所以每个人都能找到所需的东西。当你没有存储数据量的上限,或者在考虑到当今应用的特点时,数据量增长得如此迅速,以至于没有可能声明一个永远不会达到的安全上限时,就会出现问题。对于这种情况,有一种专门的存储方式,叫做 Data Lakes。它们允许你以数据的自然格式存储数据,因此不需要对存储的信息进行任何结构化。在 Azure 中,针对这种问题的解决方案叫做 Azure Data Lake Store;在本章中,你将学习该服务的基础知识,从而能够深入了解并根据自己的需求进行调整。
Azure Data Lake Store 基础
Azure Data Lake Store 被称为超大规模数据存储库并非没有原因——在存储文件时没有限制。它可以支持任何格式、任何大小,并存储以不同方式结构化的信息。这也是大数据分析的一个理想模型,因为你可以根据自己的处理服务需求来存储文件(有些人更喜欢少量的大文件,有些人则喜欢很多小文件——选择最适合你的方式)。对于关系型、NoSQL 或图形数据库等其他存储解决方案来说,这是不可行的,因为它们在保存非结构化数据时总是存在某些限制。让我们看一个关于 Azure Data Lake Store 与 Azure Storage 的比较示例:
| AZDS | Azure Storage | |
|---|---|---|
| 限制 | 无文件大小/文件数量限制 | 最大账户容量为 500 TB,文件的最大大小 |
| 冗余 | LRS | LRS/ZRS/GRS/RA-GRS |
| API | WebHDFS | Azure Blob Storage API |
这里最重要的是冗余——目前,Azure Data Lake Store 支持的唯一模型是 LRS。这意味着在发生灾难时,可能会丢失存储在单个数据中心的数据。为了避免这种情况,你必须实施自己的策略将数据复制到副本中。实际上,你有两种模型可以选择——同步复制,如下所示:

或者你可以选择异步复制,如下所示:

这两种解决方案有一些明显的优缺点:
-
同步:确保数据已保存到副本中,考虑到重复数据时更难处理,且性能较低。
-
异步:数据可能会丢失(因为在灾难发生之前,你不会将数据移到副本),但性能更好(因为你只需保存数据而不等待复制),且更容易处理。
虽然复制看起来对 Azure Storage 更有利,但请记住,Azure Data Lake Store 文件系统基于 HDFS——这使得与许多开源工具的无缝集成成为可能,例如:
-
Apache Hive
-
Apache Storm
-
Apache Spark
-
MapReduce
-
Apache Pig
-
还有更多...!
这为你提供了一个更好的生态系统和工具。如果你想将数据存储在 Azure Data Lake Store 中,并希望使用 HDInsights 来对你的文件进行分析和转换,而不是使用其他 Azure 工具,你可以轻松连接到你的实例并开始工作。
请注意,目前 ADLS 支持 HDInsight 3.2、3.4、3.5 和 3.6 版本。
在访问存储在 Azure Data Lake Store 实例中的文件时,它采用了类似 POSIX 的权限模型;你基本上会操作三种不同的权限,这些权限可以应用于文件或文件夹:
-
读取 (R):用于读取数据
-
写入 (W):用于写入数据
-
执行 (E):适用于文件夹,用于授予文件夹上下文中的读写权限(例如创建子文件夹或列出文件)
我们将在安全部分讨论更多的安全概念。
创建一个 Azure Data Lake Store 实例
要创建一个 Azure Data Lake Store 实例,你需要在门户中搜索 Azure Data Lake,并填写以下表格:

然而,你需要考虑以下几点:
-
位置:目前有四个不同的可用位置——美国中部、美国东部 2、北欧和西欧。请记住,在数据中心之间传输数据会额外收费,所以如果计划使用此服务,请仔细规划您的架构。
-
定价包:有两种可用的定价模型——按使用付费和固定月度承诺。它们各有优缺点(固定定价通常更便宜,但当您的应用程序增长时,它不那么灵活,有时很难提前规划所需的容量),因此请尽量了解您的应用程序使用该服务的特性,以选择最适合您的那种。
-
加密设置:默认情况下,新帐户的数据加密已启用。虽然可以禁用它,在大多数情况下您将保持默认设置。此外,有两种加密模式——要么让服务为您管理加密密钥,要么提供您自己的密钥(存储在 Azure 密钥保管库中)。
由于轮换加密密钥是个好主意,您可能会遇到问题,即使由于故障,您的数据的冗余副本也无法访问。虽然可以从备份数据中恢复,但您需要一个旧密钥来解密它。因此,建议在意外中断时存储旧密钥的副本。
当您点击“创建”按钮时,您的服务将被配置好—您可以访问它以查看概览:

由于它是新创建的,我们看不到描述我们存储了多少数据的不同指标。而且,当前成本为 0 美元——这当然是我们预期的,因为没有文件上传到服务中。从用户界面的角度来看,目前我们无法做太多事情;一些额外功能,如“防火墙”,将在本章后面描述。除了门户,您还可以通过使用 Microsoft Azure 存储资源管理器轻松访问您的 Azure 数据湖存储实例:

当您有多个文件和文件夹并尝试浏览它们时,这将使事情变得更加容易。
在 Azure 数据湖存储中存储数据
因为 Azure 数据湖存储的全部关注点是存储数据,在本章的这一部分中,您将看到如何存储不同的文件,使用权限限制对其的访问,并组织您的实例。在这里需要记住的重要事情是,您不仅限于使用大数据工具来存储或访问存储在服务中的数据——如果您设法与 Azure 数据湖存储协议通信,您可以轻松地使用 C#、JavaScript 或任何其他类型的编程语言操作文件。
使用 Azure 门户进行导航
要开始在 Azure 门户中使用文件,您需要点击“数据浏览器”按钮:

一旦您点击它,您将看到一个新屏幕,在那里您可以选择许多不同选项来创建文件夹、上传文件或更改访问属性。虽然这个工具不是管理成千上万个文件的最佳方式,但它可以让您了解存储了什么以及如何存储:

门户中提供的用户界面的缺点是,它有时会卡顿,尤其是在你拥有数百个文件时。如果你存储了数 GB 的数据,一些操作(如删除文件夹)也可能会失败。在这种情况下,最好使用 PowerShell 或自定义流程来执行操作。
现在我们将讨论用户界面上可用的选项。
筛选器
当你点击“筛选器”按钮时,你将看到一个屏幕,显示你感兴趣的文件:

这是快速限制文件夹内显示文件的最简单方法,但当然也有一些限制——例如,你不能使用通配符只筛选特定的文件扩展名。
要移除筛选器,请点击筛选器屏幕上的重置按钮。
新文件夹
这个简单的选项让你有可能创建一个新文件夹:

请注意,默认情况下,只有你可以访问新文件夹——为了让其他人可见(并允许他们读取),你必须显式地为其分配特定的用户组。
上传
使用“上传”功能,你可以直接从本地计算机将文件上传到云端:

你选择的文件将被上传到你当前浏览的文件夹中。还可以选择允许覆盖现有文件;如果你决定不覆盖并上传重复文件,你将看到以下错误:

访问权限
Azure 数据湖存储的最重要特点之一是能够完全声明对存储在其中的特定资源的访问权限:

默认情况下,只有你可以访问文件或文件夹。要添加新的用户或组,可以点击“+ 添加”按钮:

我想在这里讨论一些重要的内容:
-
权限:请记住,要授予某人列出文件夹内文件的权限,你需要分配两个权限:读取和执行。创建文件夹内的子项时也适用相同的要求。
-
添加到:可以将特定权限集传播到不仅是单个文件夹,而是文件夹内的所有文件夹。当你希望快速允许某人列出某个父目录下的文件和文件夹时,这尤其有用。
-
添加为:你可以将一组权限添加为默认权限条目(默认分配给文件夹的所有其他用户)或访问权限条目(指定某人如何访问它)。你也可以结合使用这两种方式以加快操作。
文件和文件夹
在每个可见的文件和文件夹旁边,你可以看到一个图标,点击该图标会显示一个菜单,提供额外的选项:

事实上,这些选项与我们刚刚讨论的相同——它们只是应用于特定的文件夹和文件。
Microsoft Azure Storage Explorer
大多数前述选项都可以通过 Microsoft Azure Storage Explorer 来执行:

不幸的是,它不提供为文件和文件夹分配权限的功能。不过,它在浏览存储数据时是一个更好的选择——更重要的是,它会自动显示分配给项目的一组所需权限。
使用 SDK
管理文件和你的 Azure Data Lake Store 实例的最灵活(也是最先进)选项是使用适合你当前语言的 SDK。目前,官方支持三种不同的语言:
-
.NET 平台
-
Java
-
Python
还可以使用 REST API,基本上你可以使用任何语言连接它。以下代码片段可以让你连接到服务:
var client = AdlsClient.CreateClient("<account-name">, <credentials>);
在进行身份验证以连接到你的服务时,有两种可用的选项:
-
终端用户身份验证
-
服务到服务的身份验证
这两种场景在进一步阅读部分的文档中有详细描述。无论你选择哪种选项,最终都会使用生成的 OAuth 2.0 令牌。在这里,你可以找到一个简单的服务提供者,它利用了所描述的方法,并允许你轻松创建新文件夹并向文件中追加数据:
public class DataLakeProvider : IDisposable
{
private readonly DataLakeStoreFileSystemManagementClient _client;
public DataLakeProvider(string clientId, string clientSecret)
{
var clientCredential = new ClientCredential(clientId, clientSecret);
var creds = ApplicationTokenProvider.LoginSilentAsync("domainId", clientCredential).Result;
_client = new DataLakeStoreFileSystemManagementClient(creds);
}
public Task CreateDirectory(string path)
{
return _client.FileSystem.MkdirsAsync("datalakeaccount", path);
}
public async Task AppendToFile(string destinationPath, string content)
{
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(content)))
{
await _client.FileSystem.ConcurrentAppendAsync("datalakeaccount", destinationPath, stream, appendMode: AppendModeType.Autocreate);
}
}
public void Dispose()
{
_client.Dispose();
}
}
你可以在进一步阅读部分提到的博客文章中阅读更多关于如何编写这种提供者的内容。
使用 SDK 的一个重要优点是能够抽象化许多操作并自动化它们——你可以轻松地递归删除文件或动态创建文件。使用 UI 时,这些操作是无法实现的,而大多数严肃的项目开发人员宁愿编写代码,也不愿依赖手动文件管理。
安全性
Azure Data Lake Store 提供了一种与其他 Azure 存储选项不同的安全模型。实际上,它为你提供了一个复杂的解决方案,包含身份验证、授权、网络隔离、数据保护和审计。由于它被设计为数据驱动系统的基础,因此在保护谁(或什么)以及如何访问存储的信息时,必须扩展常见的功能。本节将介绍不同的安全功能并详细描述它们,让你熟悉它们并了解如何使用。
身份验证和授权
为了验证谁或什么可以访问存储的数据, Azure Data Lake Store 使用 Azure Active Directory 来识别当前访问数据的实体。为了授权它,Azure 通过 基于角色的访问控制(RBAC)来保护资源本身,并使用 POSIX ACL 来保护数据。
了解这两个术语之间的区别非常重要:
-
身份验证:这决定了谁或什么尝试访问特定的资源。
-
授权:通过限制仅将资源的访问权限分配给已被分配特定权限集的人员,从而保护资源。
重要的是要记住,如果你有多个订阅承载着不同的资源,并希望它们访问 Azure Data Lake Store,你必须将相同的 Azure AD 实例分配给它们所有——如果没有这样做,其中一些将无法访问数据,因为只有在分配给 ADLS 的目录中定义的用户和服务才能通过身份验证并获得访问权限。
让我们查看 RBAC 和 POSIX 模型之间的区别。
RBAC
RBAC 控制谁可以访问 Azure 资源。它是一个独立的角色和权限集合,与存储的数据无关。要检查此功能,请点击访问控制(IAM)刀片:

在前面的屏幕上,你可以看到我为资源分配了三个不同的应用程序(服务)和一个用户。他们还具有不同的角色:
-
所有者:完全访问权限,包括确定谁可以访问该资源
-
贡献者:完全访问权限,除非确定谁可以访问该资源
如果你点击角色按钮,你将看到 ADLS 可能角色的完整列表:

使用访问控制(IAM)刀片,你可以轻松控制谁能访问你的 Azure Data Lake Store 实例以及如何访问——每当你想更改权限或访问它的用户/服务集合时,使用它。
一个好的做法是管理组而不是单独的实体——这让你可以在一个地方(Azure AD)添加/移除用户或实体,而不必浏览资源及其 RBAC。
POSIX ACL
如前所述,你可以通过提供一组定义为读取、写入和执行的权限来管理对你实例中数据的访问。它们是 POSIX 访问控制列表(ACL)的一部分,而 ACL 是 Hadoop HDFS 的一部分,HDFS 是此 Azure 服务的引擎之一。如果你曾使用过 FTP 服务器,可能已经处理过文件系统权限;它们通常被描述为包含r、w、x和字符-的数字或字符串。以下是一个示例:
-
-rwx------等同于0700,并仅声明所有者具有读取、写入和执行权限。 -
-rwxrwxrwx等同于0777,并声明每个人都可以读取、写入和执行权限。 -
-rw-rw-rw-等同于0666,并声明所有人均可读取和写入权限。
你可以在进一步阅读部分中找到更多关于 POSIX ACL 模型的信息。
网络隔离
我之前提到了防火墙刀片,但我们跳过了它,目的是让你在熟悉该服务后能学到一些东西。当你点击防火墙刀片时,你会看到一个屏幕,允许你指定哪些 IP 地址可以访问你的实例:

这里最重要的是能够阻止其他 Azure 服务访问您的数据——如果您有要求必须禁止任何人读取存储在 ADLS 中的信息,这一点非常有用。您可以在 进一步阅读 部分的链接中了解更多关于安全功能的信息。
最佳实践
Azure Data Lake Store 在访问存储的数据以及执行读写操作时与其他服务有所不同。由于此服务旨在存储 PB 级别的数据,了解如何存储数据的最佳实践非常重要,这样可以避免像需要重新组织所有文件或读取/写入速度慢等问题。这还包括安全功能(如前所述),因为它是整个解决方案的重要组成部分。在本节中,我们将重点讨论多个关于 ADLS 的建议,帮助您在使用时更加得心应手,并能够利用最佳实践。
性能
许多存储解决方案的一个重要特性是它们的性能。通常,我们期望无论负载高低、单个记录大小如何,数据库都能正常工作。对于 ADLS,您需要考虑以下因素:
-
并行性:正如文档中所述,在执行读写操作时,确保提供一定程度的并行性非常重要。每个核心的理想线程数被定义为 8-12。
-
文件大小:虽然不同的数据分析解决方案可能会根据文件大小的不同而有不同的表现,但同样重要的是要知道,ADLS 也有一个最佳的文件大小。由于它基于 HDFS,并利用 POSIX 模型来处理权限,它更倾向于使用较大的文件(几百兆字节)而不是较小的文件,以避免在复制、连接和身份验证检查时出现问题。
-
I/O 限制:虽然 Azure Data Lake Store 在吞吐量方面没有启用硬性限制,但当您的作业在容量上非常苛刻时,仍然可能会遇到一些问题。重要的是要记住,即使在这个服务中,您仍然可能会遇到一些软限制,这些限制可以在联系 Azure 支持后解除。如果遇到 429 错误,可能是由于限流。
-
批处理:在许多面对高吞吐量的情况下,使用批处理来减少写入操作可能是有益的。在 ADLS 中,批处理的最佳大小被定义为 4 MB —— 通过执行这种大小的写入操作,您可以减少所需的 IOPS,并提升服务的整体性能。
安全性
我们之前稍微讨论过这个话题,在这里我们做个总结。当使用 ADLS 并考虑其安全功能(如身份验证、授权和文件访问)时,重要的是要记住以下几点:
-
优先使用组而不是用户/服务:虽然最初将单个用户分配到资源或文件夹可能更容易,但一旦对数据感兴趣的人数迅速增长,您将迅速面临问题。这就是为什么最好使用 Azure AD 组来确定对资源本身的 RBAC 访问以及对文件和文件夹的 POSIX ACL。它还提高了解决方案的性能,因为检查实体是否属于组比遍历长用户列表更快。
-
最小权限集合:与其他服务一样,始终从访问您的 Azure 数据湖存储实例所需的最小权限集合开始。不要为仅读取数据的用户分配写入权限,或者为仅在文件夹中读取单个文件的服务分配执行权限。
-
启用防火墙:通常情况下,您不希望允许任何人访问存储在 ADLS 中的数据。为了保护您的解决方案,使只有子集 IP 地址能够访问信息,启用防火墙,以便拒绝名单之外的任何人。
弹性
确保数据安全存储并且在 DC 内部出现问题时不会丢失是至关重要的。正如本章开头所提到的,ADLS 不支持地理冗余性——您必须自行实施。为此,您必须集成一个工具,允许您按照需要复制数据。文档中提到了三种不同的工具——Distcp、Azure 数据工厂和 AdlsCopy,当然,您也可以使用任何其他能够连接到 Azure 数据湖存储并与服务集成的工具。
在考虑 Azure 数据湖存储的灾难恢复(DR)或高可用(HA)时,考虑诸如 RPO、不一致性和执行故障切换时的复杂数据合并问题等因素。有时,等待服务恢复而不是切换到次要副本可能更好。
数据结构
您将根据不同的使用场景选择不同的数据结构——对于 IoT 数据,它将非常细粒度:
{Vector1}/{Vector2}/{Vector3}/{YYYY}/{MM}/{DD}/{HH}/{mm}
另一方面,用于存储用户数据的结构可能完全不同:
{AppName}/{UserId}/{YYYY}/{MM}/{DD}
这完全取决于您当前的需求。在计划对存储的文件执行分析时,数据结构至关重要——它直接影响文件的大小和数量,进而可能影响您的活动可能工具集。
这里另一个重要的事项是法律要求——如果您使用任何敏感数据作为文件夹或文件名,您必须能够在用户告诉您希望被遗忘或要求删除账户时有效地执行清理。
概要
在本章中,你已经学习了一些关于 Azure Data Lake Store 的知识,它是一个旨在存储几乎无限数据量的 Azure 服务,并且不影响数据的结构。我们已经讨论了数据结构、安全功能和最佳实践等内容,因此你应该能够开始构建自己的解决方案,基于这个特定的 Azure 组件。请记住,Azure Storage 可能很容易替代它——这完全取决于你的需求和期望。如果你需要更灵活的安全模型、更好的性能和更高的限制,ADLS 就是你的选择。这部分书籍到此结束,涵盖了存储数据的服务、监控服务以及它们之间的通信。下一章,你将学习更多关于 Azure 的扩展性、性能和可维护性方面的知识。
问题
-
哪种安全模型更好——管理安全组还是单独管理实体?为什么?
-
RBAC 和 POSIX ACL 之间有什么区别?
-
ADLS 中的文件最大可以有多大?
-
哪种数据结构更好——一个包含成千上万文件的单一文件夹,还是一个包含多个文件的文件夹层级结构?
-
Azure Data Lake Store 可以与任何编程语言一起使用吗?
-
ADLS 和 Azure Storage 有什么区别?
-
如何确保基于 ADLS 的解决方案具有地理冗余性?
进一步阅读
-
终端用户认证:
docs.microsoft.com/en-us/azure/data-lake-store/data-lake-store-end-user-authenticate-net-sdk -
服务到服务认证(authentication):
docs.microsoft.com/en-us/azure/data-lake-store/data-lake-store-service-to-service-authenticate-net-sdk -
编写 Data Lake Store 提供程序:
blog.codenova.pl/post/azure-functions-webjobs-and-data-lake-writing-a-custom-extension-2 -
Data Lake Store 操作.NET:
docs.microsoft.com/en-us/azure/data-lake-store/data-lake-store-data-operations-net-sdk -
安全概述:
docs.microsoft.com/en-us/azure/data-lake-store/data-lake-store-security-overview -
最佳实践:
docs.microsoft.com/en-us/azure/data-lake-store/data-lake-store-best-practices
第十八章:扩展 Azure 应用
在云中谈论可靠和稳定的应用程序时,扩展是必不可少的。虽然在基础设施即服务(IaaS)或本地部署的模型中,这个过程可能显得有些复杂且笨重,但 Azure 提供了多种不同的方式,可以快速扩展我们的应用程序,且没有停机时间。
本章将涵盖以下主题:
-
自动扩展、向上扩展、向外扩展
-
扩展 Azure 应用服务
-
扩展 Azure 函数
-
扩展 Azure 服务结构
技术要求
要执行本章的练习,您将需要以下内容:
- 访问 Azure 订阅
自动扩展、向上扩展、向外扩展
云计算的核心是扩展——这是此类设置相比本地部署设置的最重要优势之一。云能够快速适应新需求,尤其是流量的增加,而云所提供的灵活性使您能够创建更稳定的服务,减少意外负载峰值和硬件性能不足的风险。在本章中,我们将深入探讨扩展主题,帮助您深入理解 Azure 中不同服务的行为,并确保扩展功能是自动化的,并且尽可能少需要关注。
自动扩展
您可以按以下方式定义许多服务的自动扩展功能:
自动扩展是一项功能,允许服务、机器或应用程序根据预定义的参数(如 CPU 利用率、内存使用情况或人工因素,如吞吐量单元或工作者利用率)自动向上或向外扩展。
通常,您可以按以下方式描述自动扩展:

前面的图示可以按以下方式描述:
-
资源正常接受传入请求
-
同时,有一个实体监控资源——它会根据扩展规则检查资源,并决定是否需要执行扩展操作。
-
一个实体根据设置决定是否扩展资源,它可以将资源向上/向下扩展或向外扩展。
当然,除了优点,扩展也有其缺点:
-
它可能会使您的应用程序变得无响应。
-
它需要额外的资源来进行负载均衡(如果是向外扩展)。
-
这需要时间,具体取决于扩展特性。因此,必须在设计阶段规划此类操作。
-
在许多情况下,这会使您的解决方案变得更加昂贵。
服务如何扩展完全取决于服务本身。让我们看一些例子:
-
Azure 事件中心可以手动/自动扩展(使用自动膨胀功能)。您可以为实例分配更多的吞吐量单元(TUs),使其能够接收更多的消息。自动缩减扩展尚未实现。
-
Azure 应用服务可以手动和自动扩展(取决于您选择的层级)。您可以使用多个不同的参数,缩小扩展也会自动进行。
-
Azure Cosmos DB 依赖于分配给实例的请求单元(RU)。
-
Azure SQL 有不同的扩展模型——你可以使用数据库事务单元(DTUs)或虚拟核心(vCores)。
-
Azure Functions 通过内部的工作者和扩展控制器机制自动扩展。
-
Azure 存储不支持扩展。
如你所见,在 Azure 中没有单一的扩展解决方案——你必须为每个组件单独实现可行的解决方案。经验法则是,你对资源的控制越少,扩展将越自动化。而对于 IaaS 场景,你必须操作虚拟机的数量,而在PaaS中,你将最终得到虚拟核心或其他单元。这里你可以找到按扩展复杂度从左到右排列的不同云模型(其中IaaS具有最复杂的模型):

向上扩展与向外扩展
有两种不同类型的扩展(至少在 Azure 中是这样):
-
水平扩展:通过升级硬件/增加一个层级来进行扩展
-
水平扩展:添加服务的实例
向上扩展可以如下呈现:

而为了进行比较,水平扩展的描述如下:

因此,在第一个场景中(向上扩展),你将从单个实例中获得更好的性能,而水平扩展则允许你并行处理工作。两种选项的使用场景不同,基本上取决于你计划运行的工作负载。这些是一些例子:
-
如果你的代码是顺序执行且无法并行化,使用向上扩展。
-
如果你的代码在单位时间内需要大量的计算能力,而不是将其分配到多台机器上,使用向上扩展。
-
如果你有方法对负载进行负载均衡,使用水平扩展。
-
如果你能够在多台机器上执行相同的工作,并且没有碰撞的风险,使用水平扩展。
使用水平扩展可以比作多线程——但当然是在一个更大的规模上。事实上,问题非常相似。如果你的机器有多个核心,并且能够同时执行你的代码,那么你必须引入非常相似的约束。
水平扩展的常见问题通常是由访问状态引起的——无论它是通过任何类型的存储共享,还是在多台机器之间分布。在使用此功能之前,请确保了解这些问题。
在 Azure 中,多个服务的扩展方式不同。我们将重点讨论其中三个,以便更好地理解这一主题。
扩展 Azure 应用服务
我们通过学习一些基本的 Azure 应用服务 开始了在 Microsoft Azure 上的旅程。这是一个非常常见的 PaaS 组件,在许多 Azure 用户中被广泛使用,适用于非常简单的网站和要求高性能与高可靠性的复杂系统。为了确保您的 Web 应用 始终在线,或者检查它是否面临压力,您需要实施某种扩展规则。对于此服务,您有两个选择——要么使用手动扩展(并实现某种警报,以便知道何时执行此操作),要么使用自动扩展功能,这样在维护方面会更轻松。在本节中,我们将介绍并比较这两者。
手动扩展
手动扩展是从基础层开始提供的功能——它不适用于免费的或共享的服务。根据所选择的实际层级,您的应用服务可以使用不同数量的实例。
在这里,您可以查看 B2 层的情况:

在前面的配置中,最大可用实例数设置为三。 However,如果我将扩展到标准层,结果如下:

情况看起来有些不同——两个功能发生了变化:
-
我可以将实例数量设置为最多 10 个
-
可以启用自动扩展
请注意,将扩展到高级层将允许您为应用服务设置最多 20 个实例。
自动扩展
虽然手动扩展对于需求较低的网站和系统可能是合适的(因为它们在发生事件时不需要迅速采取行动),但如果您的应用程序是一个受欢迎的电子商务网站,您希望操作快速执行,包括扩展。例如,让我们尝试现在启用自动扩展——这将显示一个表单,允许您管理这些设置:

事实上,您在这里有两个选择:
-
基于指标扩展:允许您选择一个指标,该指标将作为自动扩展的触发条件
-
扩展到特定的实例数:默认情况下执行(因此应该与基于指标的扩展一起使用)
要根据指标配置自动扩展,您需要一个规则。您可以通过点击 + 添加规则链接来添加该规则。这样做将显示另一个表单(比当前的表单复杂得多),您可以在其中选择所有对您有意义的内容:

在前面的截图中,您可以看到一个规则,当 CPU 使用率在 10 分钟内超过 70% 时,会触发自动扩展。一旦满足所有条件,运行时将向应用服务添加另一个实例。更重要的是,如果条件在 5 分钟后仍然成立(冷却时间(分钟)期间),将再次触发扩展操作。这将一直持续,直到达到您设置的最大实例数为止。
请记住,你可以为应用程序设置多个规则。而且,似乎创建一个按规则减少实例数的规则是个好主意,这样当负载恢复正常时,它会移除多余的实例。
一旦添加了规则,你可以点击保存以确认更改——现在,每当规则被认为是激活状态时,你的应用程序就会自动扩展。在继续之前,我想向你展示另外两件事。你可能注意到在扩展页面上有两个额外的部分:JSON 和 Notify。它们在管理服务时提供了一些额外的选项:
-
JSON: 这会生成一个 JSON 模板,可以与 ARM 模板一起使用,以自动配置你的资源。当创建服务时,它会自动添加扩展规则。
-
Notify: 这使你能够在 Azure 中自动向资源管理员发送通知,当出现问题时通知他们。
这里你可以看到为我的规则生成的 JSON:
{
"location": "West Europe",
"tags": {
"$type": "Microsoft.WindowsAzure.Management.Common.Storage.CasePreservedDictionary, Microsoft.WindowsAzure.Management.Common.Storage"
},
"properties": {
"name": "handsonazure-euw-webapp-autoscale",
"enabled": true,
"targetResourceUri": "/subscriptions/1a2d5d1c-dee5-4deb-a93a-366cc83feb46/resourceGroups/handsonazure-euw-rg/providers/Microsoft.Web/serverfarms/handsonazure-euw-appserviceplan",
"profiles": [
{
"name": "Auto created scale condition",
"capacity": {
"minimum": "1",
"maximum": "10",
"default": "1"
},
"rules": [
{
"scaleAction": {
"direction": "Increase",
"type": "ChangeCount",
"value": "1",
"cooldown": "PT5M"
},
"metricTrigger": {
"metricName": "CpuPercentage",
"metricNamespace": "",
"metricResourceUri": "/subscriptions/1a2d5d1c-dee5-4deb-a93a-366cc83feb46/resourceGroups/handsonazure-euw-rg/providers/Microsoft.Web/serverFarms/handsonazure-euw-appserviceplan",
"operator": "GreaterThan",
"statistic": "Average",
"threshold": 70,
"timeAggregation": "Average",
"timeGrain": "PT1M",
"timeWindow": "PT10M"
}
}
]
}
],
"notifications": [],
"targetResourceLocation": "West Europe"
},
"id": "/subscriptions/1a2d5d1c-dee5-4deb-a93a-366cc83feb46/resourceGroups/handsonazure-euw-rg/providers/microsoft.insights/autoscalesettings/handsonazure-euw-webapp-autoscale",
"name": "handsonazure-euw-webapp-autoscale",
"type": "Microsoft.Insights/autoscaleSettings"
}
扩展 Azure Functions
使用 PaaS 服务时,你可以配置当 CPU 使用率达到最大允许值,或请求数超过阈值时,应用程序的行为。然而,Azure 还提供了其他模型的服务——其中最有趣的是无服务器架构,它进一步抽象了控制,旨在提供更简便的配置、最小的维护以及专注于提供业务价值的能力。
在本节中,你将看到 Azure 应用服务和 Azure Functions 在扩展方面的不同,从技术和概念的角度来看。
扩展无服务器应用程序
当使用无服务器服务(例如 Azure Functions、Azure Cosmos DB 或 Azure Event Grid)时,配置该功能的选项非常有限。例如:
-
在 Azure Functions 中,你依赖于定价模型(消费计划与应用服务计划)
-
在 Azure Cosmos DB 中,你修改 RU 的数量
-
在 Azure Event Grid 中,你无法定义服务如何扩展。
这一切的原因是你无法控制应用程序主机——底层服务引擎与应用程序完全分离,无法直接修改。你能做的是间接控制它,要么通过改变处理单元的数量,要么通过可用的配置选项,这些选项可以被解释并应用。
请注意,无服务器是一个模型,你与运行时(在某些情况下甚至与云服务提供商)是隔离的。如果这种缺乏控制不适合你,最好尝试 PaaS 或 IaaS 模型和服务。
扩展 Azure Functions
在 Azure Functions 中,至少在消费计划下,不可能进行扩展。当然,使用 App 服务计划时,您可以进行扩展并获得更好的硬件,但这不会影响服务本身。相反,它会创建更多的资源来消耗。另一方面,您不能手动扩展。唯一的可能性是让 Azure Functions 自动扩展。为此,该服务实现了扩展控制器的概念。这是一个内部功能,它持续监控承载函数运行时的特定工作进程的行为,如果其中一个似乎过载,则会向工作集添加另一台机器。
Azure Functions 的扩展行为非常复杂且仅部分描述,因为它包含了开源部分或未公开的部分。我将在本章中尽力详细描述,以便您了解做出扩展决策的准确算法。
在您的 Azure Functions 实例做出扩展决策之前,它会检查以下内容:
-
扩展间隔:扩展仅在经过特定时间间隔后发生。
-
当前工作进程数:如果工作进程的数量(运行函数宿主的工作进程)超过了配置的最大值,系统会决定从工作集移除一个工作进程。
-
负载因子:如果负载因子接近最大值,则会添加一个新的工作进程。相反,如果负载因子下降,则会移除一个工作进程。
-
繁忙工作进程比例:如果繁忙的工作进程数超过了配置的最大值,则会向工作集添加另一个工作进程。
-
空闲工作进程:如果空闲工作进程的数量大于定义的最大值,系统会从工作集移除其中一个工作进程。
上述操作的定义值可以如下找到:
public const int DefaultMaxWorkers = 100;
public const int DefaultBusyWorkerLoadFactor = 80;
public const double DefaultMaxBusyWorkerRatio = 0.8;
public const int DefaultFreeWorkerLoadFactor = 20;
public const double DefaultMaxFreeWorkerRatio = 0.3;
public static readonly TimeSpan DefaultWorkerUpdateInterval = TimeSpan.FromSeconds(10);
public static readonly TimeSpan DefaultWorkerPingInterval = TimeSpan.FromSeconds(300);
public static readonly TimeSpan DefaultScaleCheckInterval = TimeSpan.FromSeconds(10);
public static readonly TimeSpan DefaultManagerCheckInterval = TimeSpan.FromSeconds(60);
public static readonly TimeSpan DefaultStaleWorkerCheckInterval = TimeSpan.FromSeconds(120);
上述值来自 Azure Functions Host 的 GitHub 仓库。这些值可能会在一段时间后更改,但如果您有兴趣,可以查看以下项目:github.com/Azure/azure-functions-host
此外,您可以通过在应用程序设置中提供 WEBSITE_MAX_DYNAMIC_APPLICATION_SCALE_OUT 值来控制实例的最大数量:

更重要的是,如果您将 Function App 实例连接到 Azure Application Insights 实例,您将能够通过检查 Live Metrics Stream 功能查看它有多少个工作进程:

扩展 Azure Service Fabric
我们已经讨论了通过与两个不同的 Azure 服务合作来扩展的两种不同模型;Azure 应用服务和 Azure Functions。
在添加新实例或提升硬件性能方面,它们有很大的不同,因为它们引入了多个概念,并提供了不同的灵活性。在本章的最后部分,我们将介绍另一个服务——Azure Service Fabric。这个 Azure 产品在进行垂直或水平扩展时行为略有不同,因为它要求你管理虚拟机。此外,执行这一操作需要一组独特的技能,以确保操作顺利且正确。
手动扩展集群
在 Azure Service Fabric 中,集群可以通过两种方式进行缩放:
-
手动:通过在集群配置中选择适当的选项
-
程序化:通过使用 Azure SDK
实际上,集群的特性在一开始就已经选定,当你选择节点类型及其配置时,如下截图所示:

扩展 Azure Service Fabric 服务类似于扩展虚拟机,因为它基于包含不确定数量虚拟机的节点,这意味着你实际上依赖于扩展集。
最好在一开始就搭建一个能够处理计划负载的集群,而不是在压力下进行缩放,尤其是当你需要严格的事务保证时,这可能会影响缩放时间。请查看进一步阅读部分,那里有一篇文章描述了高效的集群规划。
在使用 Azure Service Fabric 扩展时,请记住,向扩展集添加机器总是需要时间。因此,请考虑提前规划此类操作,以便将对当前操作的影响降至最低。要实际扩展你的集群,你需要使用创建时就配置的扩展集的扩展功能,如下图所示:

执行此操作的另一个选项是使用 ARM 模板,代码片段如下:
"resources":[
{
"type":"Microsoft.Compute/virtualMachineScaleSets",
"apiVersion":"2017-03-30",
"name":"[parameters('<scale-set-name>')]",
"location":"[resourceGroup().location]",
"sku":{
"name":"[parameters('<sku>')]",
"tier":"<tier>",
"capacity":"[parameters('<capacity>')]"
}
}
]
通过提供<capacity>值,你可以轻松更改为你的 SF 集群提供计算的虚拟机数量。
使用 Azure SDK 来扩展你的集群
另一个缩放集群的选项是使用 Azure 计算 SDK。你可能会想,这个特性有哪些使用场景——毕竟,我们已经有了手动/自动缩放功能。然而,仍然有一些更高级的场景,可能适合使用自己的控制器进行缩放:
-
使用自定义度量进行扩展,而这并不适用于自动扩展。
-
在缩放操作之前可以执行额外的操作。
-
在关键工作负载的情况下,完全控制缩放操作。
要获取 Azure 计算 SDK,你需要下载以下 NuGet 包:Microsoft.Azure.Management.Fluent,网址为:www.nuget.org/packages/Microsoft.Azure.Management.Fluent/。其他语言(如 Java 或 Python)也有类似的库,你可以在进一步阅读部分的链接中找到它们。
要扩展集群的规模,你可以使用以下代码片段:
var vmScaleSet = AzureClient.VirtualMachineScaleSets.GetById(ScaleSetId);
var capacity = (int)Math.Min(MaximumNodeCount, vmScaleSet.Capacity + 1);
vmScaleSet.Update().WithCapacity(capacity).Apply();
同样的方式也适用于 Java:
vmScaleSet.update().withCapacity(capacity).apply();
如你所见,这段代码非常简单—你只需要获取当前的规模集 ID 来引用它,然后修改其容量。在这个示例中,我使用了值 1,但没有任何限制,阻止你使用其他数字。
通过前面的示例,你也可以缩减集群的规模。然而,请记住,你不应该将集群缩减到低于其可靠性层级。如果这样做,你将无法再依赖它,甚至可能导致集群不稳定。
如果你使用的是比铜更高的可靠性层级,那么你不需要担心未使用的机器,因为它们会被自动移除。否则,你需要手动处理。为此,你需要知道哪些虚拟机当前没有被使用。要移除不再需要的节点,你可以使用以下操作:
await client.ClusterManager.DeactivateNodeAsync(node.NodeName, NodeDeactivationIntent.RemoveNode);
scaleSet.Update().WithCapacity(capacity).Apply();
await client.ClusterManager.RemoveNodeStateAsync(node.NodeName);
它们基本上做三种不同的事情:
-
停用并从集群中移除节点
-
减少规模集容量
-
删除节点状态
要找到要移除的节点,你必须查询集群并查找最近添加的机器:
using (var client = new FabricClient())
{
var node = (await client.QueryManager.GetNodeListAsync())
.Where(n => n.NodeType.Equals(NodeTypeToScale, StringComparison.OrdinalIgnoreCase))
.Where(n => n.NodeStatus == System.Fabric.Query.NodeStatus.Up)
.OrderByDescending(n =>
{
var instanceIdIndex = n.NodeName.LastIndexOf("_");
var instanceIdString = n.NodeName.Substring(instanceIdIndex + 1);
return int.Parse(instanceIdString);
})
.FirstOrDefault();
}
你可能会想,为什么最近添加的机器会成为扩展操作的受害者?这是因为在集群利用率较高时,工作被委派给了它。它最初并不属于该规模集,完成任务后可以移除。
总结
在本章中,我们涵盖了三种完全不同的服务扩展—Azure App Service、Azure Functions 和 Azure Service Fabric。你看到这种操作如何适应不同的应用模型—有时你扩展服务实例、虚拟机,或者干脆不控制它,而是让运行时为你处理。实际上,在云中进行服务扩展比使用自有服务器要容易得多。你无需重新配置负载均衡器、防火墙、路由器和服务器。使用扩展功能时,始终尽量自动化这一过程—手动扩展仅适用于非常简单的场景,而且容易让服务器处于低效使用状态。
在接下来的两章中,我们将介绍另外两个 Azure 服务:Azure CDN 和 Azure Traffic Manager,它们帮助确保你的应用在高负载下仍然可用。
问题
-
扩展和扩展的区别是什么?
-
扩展的使用场景有哪些?
-
在无服务器服务中可以进行扩展吗?
-
在 Azure App Services 中,扩展是否会影响服务的定价?
-
为什么在 Azure Service Fabric 中,扩展操作可能是危险的?
-
手动扩展的缺点是什么?
-
如果你想在 CPU 使用率达到 80% 时自动扩展你的 Azure 应用服务,应该怎么做?
进一步阅读
-
Service Fabric 集群规划:
docs.microsoft.com/en-us/azure/service-fabric/service-fabric-cluster-capacity -
Service Fabric 集群扩展:
docs.microsoft.com/en-us/azure/service-fabric/service-fabric-cluster-scaling -
Azure SDK:
docs.microsoft.com/en-us/azure/index#pivot=sdkstools&panel=sdkstools-all
第十九章:使用 Azure CDN 提供静态内容
托管许多静态文件,尤其是在我们开发一个非常受欢迎的应用程序时,是一项严峻的任务,它影响着我们的 Web 服务性能和整体用户体验。如果我们加载图片、文件或文档的速度过慢,客户可能会选择我们的一位竞争对手,他们提供类似的功能,但表现更好。得益于像 Azure 内容交付网络(CDN)这样的云服务,我们能够快速处理大带宽的内容,这是因为它与 Azure 存储的集成,并使用 Azure 的本地组件。
本章将涵盖以下主题:
-
CDN
-
使用和配置 Azure CDN
-
使用 Azure CDN 优化静态内容的提供
-
使用 Azure CDN 开发应用程序
技术要求
要完成本章的练习,您将需要以下内容:
-
一个 Azure 订阅
-
Visual Studio 2017
Azure CDN 基础
如果您托管的是一个包含许多静态文件的热门网站,您可能会想知道如何优化这些文件的提供给用户。当寻找解决方案时,您必须考虑许多不同的因素;如 HTTP 协议规范、浏览器能力、服务器性能、网络延迟等。整个问题远非简单,并且需要大量资源才能以正确的方式实现。为了解决这些困难,CDN 的概念应运而生。CDN 代表内容分发网络,概括了一个复杂服务的概念,该服务负责将内容提供给所有浏览您网站的用户。在本章中,您将学习 Azure CDN,这是一个 Azure 组件,旨在为所有列举的问题提供快速且可靠的解决方案。
使用 CDN
当用户访问您的网站时,必须获取为特定页面提供的所有静态内容。这意味着以下操作:
-
浏览器必须请求网页所需的所有图片、文件和脚本
-
请求必须排队,因为浏览器对单个域的请求次数是有限制的
-
在大多数情况下,页面必须逐步呈现,因为内容是从服务器获取的
-
如果服务器当前负载过重,它可以限制请求
-
浏览器必须遵循所有已实现的缓存机制,当然,前提是您的网站告诉它如何执行
我们可以将整个过程描述如下:

在前面的场景中,用户的每个请求直接路由到网站,然后与服务器连接以获取数据。当然,我们可以想象文件由不同的服务器托管的情况,如下图所示:

这种替代方案可以稍微提高性能(因为服务器将使用不同的域名进行识别),但它增加了维护和配置的复杂性。而且,如果你面临网络延迟问题,这种设置并不是正确的解决方案(因为增加一个服务器不会产生太大差异)。在我们讨论不同架构时,你可能开始想象另一种可能产生区别的设置。
作为 网站 和 服务器 之间的代理,负责适当的缓存,它可以轻松地扩展,并且具有高可用性,如下图所示:

CDN 正是我们所说的代理。它们提供以下功能:
-
如果负载超过预期,它们可以轻松地进行扩展
-
它们可以缓存请求,从而减少最终服务器的负载
-
它们尊重缓存控制头,因此可以轻松提供资源的 生存时间 (TTL)
-
它们通过同时为多个用户提供内容,提高了你网站的响应能力
在本章后续,你将学习如何利用这些服务,通过使用 Azure CDN。
在门户中创建 Azure CDN
创建 Azure CDN 的过程与本书中其他服务的创建过程相似。首先,你需要点击 + 创建资源按钮,并搜索 Azure CDN。从搜索结果中选择 CDN。这将带你进入一个表单,你可以在其中输入有关新服务的所有必要信息:

这里有两点值得一提:
-
定价层:与其他 Azure 服务相比,定价层略有不同,因为你不再可以选择 Basic、Standard 和 Premium 选项。在这里,你需要决定将使用哪种 产品——你可以从包含 Verizon、Akamai 和 Microsoft 等提供商的列表中选择。它们提供不同的功能,如动态站点加速、视频流优化和资产预加载。完整的列表可以在本章的 进一步阅读 部分找到。
-
现在创建一个新的 CDN 端点:如果你知道你的源(一个缓存资源的端点)是什么,你可以立即为整个服务创建它。
要快速查看特定定价层中提供的内容,你可以点击 查看完整定价详情链接:

如你所见,你需要为每 GB 的出站数据传输付费,根据选择的提供商,价格可能差异近五倍。当你点击 创建按钮时,服务创建过程将开始。创建完成后,你可以访问你自己的 Azure CDN 实例:

如果你决定不与服务一起创建端点,你将看到一个与我的类似的页面, 端点 部分为空。让我们点击 + 端点 按钮来实际创建一个。如前所述,端点是 CDN 的元素,它缓存数据并用于特定目的。在接下来的部分,你可以看到我为第一个端点设置的示例:

如你所见,我选择了 Storage 作为源类型。为了能够这么做,你必须在与你的 CDN 位于同一资源组中实际拥有一个 Azure Storage 实例。你还可以选择其他可用类型,如 Cloud Service、Web App 或自定义源。一旦添加了端点,你将能够通过点击 概述 选项卡上的端点来管理它。
优化与缓存
CDN 的核心是优化内容并进行缓存。通过这种方式,它们提升了你网站的性能和用户体验。在上一节中,你了解了内容分发网络(CDN)的概念,并配置了你的 Azure CDN* 实例。现在我们将尝试学习一些更高级的功能,例如压缩、缓存规则和优化。
配置端点
要访问端点配置,你必须在 概述 选项卡上点击它:

这将显示一个新页面,在该页面中你可以找到关于特定 CDN 端点的所有信息,如其主机名、可用协议以及配置的内容优化规则。事实上,该页面看起来与前一个页面非常相似——它只提供了一些附加选项:

现在我们将讨论可用于端点的不同功能。
压缩
CDN 的基本功能之一是压缩——它们允许你动态压缩不同类型的文件,例如降低文件大小并减少网络延迟:

启用后,你可以选择你感兴趣的 MIME 类型。如果你计划支持其他类型,也可以添加新的类型。
记住,文件必须被 CDN 缓存,才能在动态过程中进行压缩。
缓存规则
默认情况下,CDN 根据你提供的 Cache-Control 头部缓存内容。然而,你可以明确地定义它应该如何行为,如果:
-
缺少头部信息
-
引入了查询字符串
-
特定的匹配条件匹配
在这里,你可以找到此功能的基本设置:

如你所见,它为你提供了对服务行为的很大控制,尤其是通过自定义缓存规则。
地理过滤
有时,你需要为特定国家/地区屏蔽特定的内容。没有 CDN,这样的功能可能会遇到问题——你必须通过编程控制谁可以根据地理位置访问特定的图像或文件。使用 Azure CDN,你可以在几秒钟内启用该功能:

在 Geo-filtering 面板上,你可以为特定国家/地区配置不同的规则,阻止或允许访问 CDN 中的文件夹或特定文件。
使用 Azure CDN 开发应用程序
Azure CDN 本身并没有提供什么特殊功能——它只是缓存内容,并负责无延迟地提供它。然而,重要的是要了解如何在你的应用程序中使用它。在 Azure 中,集成 Azure CDN 和例如 Azure App Services 是小菜一碟。只需几次鼠标点击,你就可以让你的 CDN 与现有的 Web 应用程序协同工作。在本章的最后一节,你将看到配置集成所需的步骤,并能够提升你网站的性能。
配置 Azure App Service 与 Azure CDN
要配置 Azure App Service 使其与 Azure CDN 实例一起工作,你需要找到 Networking 面板。它让你能够启用不同的 Web 应用功能,包括 CDN:

当你点击为应用程序配置 Azure CDN 时,你将看到另一个屏幕,在那里你可以配置 Azure App Service 和 Azure CDN 之间的链接。
Azure CDN 将自动开始缓存静态文件,这些文件可以在你的网站中找到。此时发布应用程序是一个好主意,这样你就不必等到过程结束之后再发布。
事实上,你现在有两个选项可以继续:
-
使用现有的 CDN 配置文件(如果你已经完成了本章前面部分的练习,你应该已经创建并准备好使用 CDN)
-
创建一个全新的配置文件
在接下来的部分,你可以找到我的配置(我选择了一个现有的端点来加速进程):

一旦你的端点创建完成,你可以检查它是否工作。你可以例如检查我的应用程序的资源,如我所做的那样:

为了本次练习,我使用了模板中的一个示例应用程序。如你所见,配置了 CDN 后,我的应用程序的源自动被修改——所有静态内容都通过我的 Azure CDN 端点提供,而不是我的服务器。
总结
在本章中,你了解了什么是 CDN,以及它们如何帮助你为 Web 应用程序实现更好的性能和用户体验。我们已经配置了 Azure CDN 实例,并看到了如何通过压缩内容来优化内容的提供。阅读完本章后,你应该能够为特定国家/地区过滤特定内容,并能够制定合适的缓存规则,从而定义你的实例将如何表现。
在本书的下一章中,这是描述 Azure 服务的最后一章,我们将介绍一个更高级的场景——使用 Azure Traffic Manager 分配负载并保护数据免受故障的影响。
问题
-
使用 Azure CDN 可以解决哪些问题?
-
Azure CDN可用的 CDN 提供商有哪些?
-
CDN 的起源是什么?
-
压缩在 Azure CDN 中是如何工作的?
-
存储在 Azure CDN 中的内容的默认 TTL 是多少?
进一步阅读
第二十章:使用 Azure 流量管理器分配负载
有时我们希望根据后端的性能来分配负载,或者在某些服务器正在维护时将用户路由到不同的服务器。如果没有一个能无缝、快速完成这项任务的服务,这可不是一件容易的事。感谢Azure 流量管理器,我们能够提高关键应用的可用性,在执行大型复杂部署时分配流量,或者在进行维护时避免停机。
本章将涵盖以下主题:
-
使用 Azure 流量管理器
-
不同的路由方法
-
端点监控
技术要求
要进行本章的练习,您需要以下内容:
- 访问 Azure 订阅
Azure 流量管理器基础
假设以下情况——您有一个需要全球服务的应用。为了保证全球所有客户的最佳性能,您在不同的区域(一个在北美,一个在欧洲,一个在非洲)提供了不同的服务实例。然而,有一个问题。您必须明确告诉客户访问应用的特定实例——即离他们位置最近的那个实例。
虽然这当然是可行的(只需提供正确的 URL),但这个解决方案并不理想。例如,如果您的客户去度假,并在接下来的两周里呆在欧洲而不是非洲怎么办?为了克服这种问题,您可以在 Azure 中利用一个名为Azure 流量管理器的服务,它会处理来访请求的正确路由,并允许您在应用中实现高可用性。
Azure 流量管理器的功能
您可以将Azure 流量管理器视为一个在 DNS 层级上工作的负载均衡器。为了理解这个概念,请看以下示例。默认情况下,如果没有像Azure 流量管理器这样的服务,您的客户将使用端点 URL 将请求从客户端应用发送到服务器应用:

如果您想要对来访请求进行负载均衡,您必须在架构中引入另一个元素,来负责将请求路由到正确的后端(并可能确保它们是健康的):

这种设置的缺点是可能会引入延迟。更重要的是,在这种情况下,您的客户直接通过负载均衡器连接,这并没有解决全球分配入口点的问题。
上面的示例是使用反向代理时常见的解决方案,反向代理充当您系统的网关。
上述场景定义了一种解决方案,其中负载均衡基于基于 TCP/UDP 分配流量,因此它比 DNS 层级要低得多。当使用Azure 流量管理器时,请求的流动方式完全不同:

流程可以描述如下:
-
向DNS 服务发送DNS 查询以获取服务器地址。
-
DNS 服务被配置为指向Azure 流量管理器,而不是直接指向某个服务。
-
Azure 流量管理器根据查询特征选择正确的端点,并返回一个包含正确服务器地址的DNS 响应。
-
客户端接收到DNS 响应并使用它连接到正确的服务器。
实际上,客户端需要执行两个请求:
-
获取服务器的 URL。
-
发送实际请求
虽然这看起来可能有些开销,但实际上,这种影响是不可察觉的。
请注意,采用这种解决方案的优势在于能够直接将请求发送到服务器,而没有参与通信的中介服务。
在 Azure 门户中创建 Azure 流量管理器。
要在门户中开始使用 Azure 流量管理器,您需要点击+ 创建资源按钮并搜索 traffic manager。然后在搜索结果中选择流量管理器配置文件。您将看到一个表单,您需要在其中填写所有必填字段才能创建服务:

虽然大多数选项应该不言自明,但有一个下拉框需要我们特别关注,即路由方法。这里有六种不同的可用方法:
-
性能
-
权重
-
优先级
-
地理位置
-
MultiValue
-
子网
在描述每个选项之前,您需要理解路由方法到底是什么。之前我提到过,Azure 流量管理器决定将用户路由到哪个端点。这个路由操作可能会根据选择的方法给出不同的结果。让我们考虑以下场景:
-
您的应用程序实例在全球分布,您希望将用户路由到离其最近的实例。
-
您的应用程序实例提供不同的性能,您希望将用户路由到提供最佳用户体验的实例。
-
您有一个处理所有流量的主要区域,并希望在发生故障或临时问题时将用户路由到次要区域。
-
您希望均匀分配流量,或者按照设定的权重分配流量。
-
您希望将用户的 IP 地址映射到特定实例。
根据选择的场景,应该选择不同的路由方法。接下来我将详细描述它们。
路由方法 – 性能
当使用性能路由方法时,用户将被路由到“最接近”的端点。需要记住的是,这个“最接近”的端点可能不是地理上最接近的,因为此方法考虑的是性能而非距离。假设内部的 Azure 流量管理器存储了关于配置的端点以下信息:
| 端点 | 区域 | 延迟 |
|---|---|---|
| 服务器 A | 西欧 | 12 毫秒 |
| 服务器 B | 东部美国 2 | 67 毫秒 |
在前面的场景中,表现更好的端点是 服务器 A。当选择性能路由方法时,用户将被路由到该服务器。
需要记住的是,在性能路由方法下,Azure 流量管理器会检查响应的延迟,并考虑发送请求的 DNS 服务器的 IP 地址,而不是客户端的 IP 地址。
路由方法 - 权重
当你想要均匀分配流量或基于预定义权重分配流量时,权重路由方法是你要寻找的解决方案。使用该方法时,你需要定义权重,在决定请求应该路由到哪里时,这些权重将被考虑在内。让我们来看一下以下表格:
| 端点 | 权重 | 状态 |
|---|---|---|
| 服务器 A | 100 | 在线 |
| 服务器 B | 100 | 降级 |
| 服务器 A - staging | 5 | 在线 |
在前面的例子中,我们有三个端点,其中一个报告了问题。尽管 服务器 A 和 服务器 B 的权重相同,但由于服务器 B 的状态报告为降级,它将不会被视为健康的端点,因此用户将不会被路由到它。剩下的两个服务器有不同的权重。在这种情况下,Azure 流量管理器将随机将用户分配到一个端点,其概率由该端点的权重决定。如果我们假设有 105 个请求,其中 100 个会被路由到 服务器 A,其余的路由到 服务器 A – staging。
权重路由方法是进行 A/B 测试的绝佳选择,你可以随机将用户路由到包含新特性的应用程序新实例。如果用户喜欢新特性,你可以调整权重并将其余流量路由到该实例。
路由方法 - 优先级
优先级路由方法是最简单的方法,适用于一个简单的场景,其中你有一个主区域托管你的应用程序,并且你希望确保在出现问题时可以轻松地切换到次要区域。让我们考虑以下场景:
| 服务器 | 优先级 | 状态 |
|---|---|---|
| 服务器 A | 1 | 在线 |
| 服务器 A - 辅助 | 2 | 在线 |
在前面的例子中,所有流量将被路由到服务器 A,原因如下:
-
它的优先级设置为
1 -
它的状态被认为是在线的
现在发生了一些事情,主副本宕机了:
| 服务器 | 优先级 | 状态 |
|---|---|---|
| 服务器 A | 1 | 降级 |
| 服务器 A - 次要 | 2 | 在线 |
由于服务器 A 被认为不健康,所有流量将被路由到辅助实例,直到主实例恢复正常。
请记住,客户端可能会缓存 DNS 响应,这会延长你的端点对它们不可用的时间。
路由方法 - 地理位置
有时候你需要将用户路由到特定区域,考虑到它的位置。这样做有多个原因,例如:
-
法律要求
-
内容本地化
-
从最接近的服务器提供应用程序,考虑到距离因素
请记住,最靠近用户的区域可能并不是网络延迟最小的区域。不要过度使用此路由方法来实现最佳的用户体验。
使用地理路由方法时,你将区域分配给已配置的端点:
| 服务器 | 区域 |
|---|---|
| 服务器 A | 法国 |
| 服务器 B | 亚洲 |
| 服务器 C | 全球 |
现在,为了将用户路由到正确的服务器,Azure 流量管理器尝试通过读取源 DNS 服务器的 IP 地址来确定其位置。它从州/省(如果不支持,则为国家/地区)开始,最终确定在全球值上。
使用地理路由方法时,Azure 流量管理器会返回一个端点,不论其是否健康。利用嵌套配置文件来扩展路由方法并实现高可用性是非常重要的。
路由方法 – 多值
多值路由方法与其他路由方法略有不同,因为它允许返回多个健康的端点,并让客户端选择使用哪个端点。这种场景适用于当你不知道如何将用户路由到服务端,但同时你希望确保用户被路由到健康端点的情况。
为确保该路由方法能够返回端点,必须将其设置为“外部”并分配 IPv4 或 IPv6 地址。
路由方法 – 子网
最后一种路由方法是最复杂的,因为它允许你将特定的 IP 地址(或一系列 IP 地址)映射到特定的端点。
该方法的使用案例可能有所不同,例如:
-
你想要阻止使用特定 ISP 的用户
-
你希望将来自公司网络的用户路由到应用程序的内部实例
-
你已经为应用程序设置了品牌,并希望将来自不同公司网络的用户路由到特定的品牌实例
使用子网路由方法时,确保覆盖所有可能的 IP 地址,因为如果未能做到这一点,将会返回 NODATA 响应,导致客户端返回错误。
一旦你对选择的路由方法满意,可以点击“创建”按钮,在 Azure 中创建资源。
在 Azure 门户中使用 Azure 流量管理器
当你访问你的 Azure 流量管理器实例时,你会看到一个包含服务概览的默认屏幕:

由于当前没有端点附加到这个特定配置文件,显示的端点列表为空。在添加新端点之前,我们先简单了解一下其他服务功能。
配置
当你访问配置面板时,你将看到 Azure 流量管理器实例的完整配置:

它包含诸如路由方法(默认显示您在创建服务时选择的那个)、端点监控设置和快速端点故障转移设置等内容。从此屏幕,您基本上可以控制 Azure Traffic Manager 的行为。例如,假设您的每个端点都有一个自定义的/status端点,专为与服务配合使用而设计。默认情况下,Azure Traffic Manager 检查默认端点 URL(在此设置为/),因此您需要更改路径字段,如下所示:

对于期望的状态码也是如此。如果您的端点可以返回一系列 HTTP 状态码,并且每一个都应该被视为成功,您可以在“期望状态码范围”字段中输入该范围:

您可以在此尝试不同的设置,以便它们能够反映您需要覆盖的实际场景。
真实用户测量
使用性能路由方法时,Azure Traffic Manager 会检查 DNS 请求的来源,并将结果转换为一个内部表格,反映不同终端用户网络的网络延迟。虽然此选项适用于大多数用例,但有时您可能希望能够告诉 Azure Traffic Manager 实际的延迟。通过“真实用户测量”功能,您可以将 JavaScript 代码注入到客户端端点,直接将延迟信息发送到此 Azure 服务。
为此,请转到“真实用户测量”选项卡,并点击生成密钥按钮:

您将看到两个字段:
-
密钥:存储生成的密钥
-
测量 JavaScript:保存应注入到客户端应用程序中的脚本。
一旦使用生成的脚本,它将开始向您的 Azure Traffic Manager 实例发送有关延迟和客户端网络的附加信息,从而提高服务做出决策的准确性。
准确性改进不是即时的——Azure Traffic Manager 需要从不同网络收集大量数据,以提高性能。
端点
Azure Traffic Manager 的主要功能是配置它所处理的端点。您可以通过 Endpoints 选项卡访问它:

要添加一个端点,您必须输入以下值:
-
类型:您可以选择 Azure 端点、外部端点和嵌套端点三种类型。不同的选择会影响整个表单——选择 Azure 端点时,您可以选择一个 Azure 服务;选择外部端点时,您需要提供一个完全合格的域名或 IP 地址;而选择嵌套端点时,您可以指向另一个 Traffic Manager 配置文件。
-
名称:端点的唯一名称。
-
目标资源类型/FQDN 或 IP/目标资源:根据 Type 值,您需要选择不同的值来配置端点。
-
Priority:因为我的路由方法是 Priority,我必须为这个特定的终端输入正确的值。如果选择了其他方法,您可能会在此处看到其他字段。
在以下示例中,我选择了一个 Azure 端点,并将配置指向我的一个 Azure 应用服务。我执行了两次操作,并向我的应用程序的两个实例添加了两个不同的端点:

记住,您不能将指向同一区域的服务域添加到单一的 Azure Traffic Manager 配置文件中。
如您所见,在添加终端后,它们的状态显示为“检查终端”。这意味着 Azure Traffic Manager 正在尝试收集关于它们健康状况的信息。如果有问题,您会看到“降级”状态:

就我而言,问题是由于配置无效导致的,因为我将 Configurationblade 中的Path字段设置为/status,结果发现这是一个无效值(在我的应用中,我将该端点实现为/api/status)。在主服务中修正配置后,其状态显示为在线:

最后需要配置的是在 DNS 服务器上设置 DNS 记录,指向您的 Azure Traffic Manager 实例(通过使用可以在 Overview blade 上找到的 DNS 名称)。
监控
除了将流量路由到不同的终端,Azure Traffic Manager 还提供了一些额外的监控功能。除了传统的 Metrics blade 外,还有一个额外的功能叫做流量视图,它使您能够进行监控。此外,您可以使用许多不同的内建机制(如 Windows 操作系统中的nslookup),来检查服务的当前配置。
Nslookup
要使用 nslookup,您必须使用管理员帐户在 Windows 中运行命令行。加载完成后,输入以下命令:
nslookup <Traffic-Manager-DNS-name>
稍等片刻,它应该返回一个结果,显示命令解析:
DNS request timed out.
timeout was 2 seconds.
DNS request timed out.
timeout was 2 seconds.
Non-authoritative answer:
Name: waws-prod-db3-119.cloudapp.net
Address: 40.85.74.227
Aliases: handsonazure.trafficmanager.net
handsonazure02-eun-appservice.azurewebsites.net
waws-prod-db3-119.sip.azurewebsites.windows.net
如您所见,它指向我的应用程序的第二个实例(handsonazure02,托管在北欧区域)。我得到这个响应的原因是,主端点被认为已经降级。一旦它重新上线,我再次运行了命令,得到了一个截然不同的响应:
Name: waws-prod-am2-229.cloudapp.net
Address: 104.40.250.100
Aliases: handsonazure.trafficmanager.net
handsonazure01-euw-appservice.azurewebsites.net
waws-prod-am2-229.sip.azurewebsites.windows.net
现在它返回了主服务器(如预期,使用的是 Priority 路由方法)。
记住,您必须等待固定时间,才能使所有 DNS 更改传播。这个时间值可以在 Configurationblade 中配置,方法是修改 DNS 生存时间字段(DNS time to live)。
流量视图
流量视图是一个附加的监控功能,允许您查看所选路由方法在 DNS 层面的具体工作情况。它提供了额外的有用信息,例如:
-
实际延迟级别
-
流量量级
-
用户位置
记住,此功能最多需要 24 小时才能传播并收集所有必要的信息。
默认情况下,该功能的屏幕如下所示:

一旦收集到信息,你可以利用图形化的数据展示,更好地理解所选路由方法的行为(并可能加以改进)。
总结
这是本书的最后一章,介绍了 Azure 服务之一——Azure Traffic Manager 的基础知识。你已经学习了流量分配的基本概念和多种路由方法,这些方法涵盖了许多实际的使用案例,可能会在你的日常工作中遇到。现在你应该理解这个特定的 Azure 服务是如何工作的,以及如何通过正确使用其功能(如配置、真实用户测量和监控)来实现预期目标。在下一章(也是最后一章)中,我将为你展示一些在 Azure 门户和不同云组件上工作的实用技巧,进一步提升你的技能。
问题
-
Azure Traffic Manager 支持哪些路由方法?
-
如何使用真实用户测量功能?
-
你可以链接不同的 Azure Traffic Manager 配置文件吗?
-
是否可以使用外部终端节点?
-
客户端是否直接连接到 Azure Traffic Manager 返回的终端节点?
-
网关和 Azure Traffic Manager 之间的主要区别是什么?
-
Azure Traffic Manager 可以用来实现高可用性吗?如果可以,如何实现?
进一步阅读
-
使用 Azure DNS 和 Traffic Manager 进行灾难恢复:
docs.microsoft.com/en-us/azure/networking/disaster-recovery-dns-traffic-manager -
它是如何工作的:
docs.microsoft.com/en-us/azure/traffic-manager/traffic-manager-how-it-works
第二十一章:Azure 提示和技巧
做事总有不止一种方式。这句话在 Azure 生态系统中尤为真实,我们在配置资源、管理服务和开发应用程序时,提供了多种工具和快捷方式。本章将向读者展示如何进一步提高生产力,并缩短交付可用解决方案所需的时间。
本章将涵盖以下主题:
-
Cloud Shell 和 Azure CLI
-
锁定资源
-
正确的命名规范
-
Azure 中的资源
技术要求
为了进行本章的练习,你将需要以下内容:
-
Azure 订阅
-
Azure CLI,访问
docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest
Azure CLI 和 Cloud Shell
使用 Azure 门户执行所有操作,如配置资源、修改配置或查找特定值,确实是管理订阅和已部署服务的最简单方式之一。然而,当你有数十个或数百个不同的订阅、资源组和实例时,这可能会变得繁琐。在这种情况下,能够访问脚本和命令来加速操作并实现自动化(如果需要)是更好的选择。在本节中,我们将介绍 Azure 提供的两种基本工具:Azure CLI 和 Cloud Shell,当仅使用门户不足以满足需求时,你可以使用这两种工具。
Azure CLI
Azure CLI 是一款跨平台的命令行工具,你可以将其安装在本地以管理 Azure 资源。通常,命令的格式如下:
$ az [resource] [command] -param1 "Foo" -param2 123
例如,你可以使用 Azure CLI 创建一个函数应用,如下所示:
$ az functionapp create --name "handsonazureapp" --storage-account "handsonazurestorage" --consumption-plan-location "westeurope" --resource-group "myResourceGroup"
安装 Azure CLI 的说明可以在技术要求部分找到。该部分指向一篇文章,描述了多个不同平台的安装过程,例如 Windows、macOS 和 Linux。安装完 Azure CLI 后,打开命令行终端并输入以下命令:
$ az login
片刻后,你应该会看到类似于我的结果,系统会要求你在本地验证 Azure CLI:
$ az login
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code DRXXXXXXX to authenticate.
要验证工具,请访问显示的网页并输入显示的代码。如果一切正确,命令将成功结束,结果会显示与你的帐户关联的所有订阅信息。使用 Azure CLI 时,你不需要记住所有命令——你可以使用以下命令来查找相关命令:
$ az find -q "query"
假设你现在想要使用 Azure Functions。要查询与该服务相关的所有命令,我可以使用以下查询:
$ az find -q function
`az functionapp create`
Create a function app.
The function app's name must be able to produce a unique FQDN as
AppName.azurewebsites.net.
`az functionapp list`
List function apps.
`az functionapp delete`
Delete a function app.
`az functionapp stop`
Stop a function app.
`az functionapp start`
Start a function app.
`az functionapp restart`
Restart a function app.
`az functionapp update`
Update a function app.
`az functionapp`
Manage function apps.
`az functionapp config`
Configure a function app.
`az functionapp show`
Get the details of a function app.
如你所见,这非常简单——你可以快速部署新资源,而无需触碰 Azure 门户。在这里,你可以找到创建函数应用的完整示例说明:
$ az storage account create --sku Standard_LRS --kind Storage --resource-group handsonazure-euw-rg --name handsonazurestorage
$ az functionapp create --name handsonazure-euw-functionapp --storage-account handsonazurestorage --resource-group handsonazure-euw-rg --consumption-plan-location westeurope
在上面的示例中,我跳过了创建资源组的过程。如果你想创建一个新的资源组,只需使用az group create命令。
Cloud Shell
使用 Azure CLI 的替代方法是一个名为 Cloud Shell 的工具。你可以通过点击 Azure 门户中的 Cloud Shell 按钮直接访问它:

当你打开 Cloud Shell 时,门户底部将显示一个欢迎屏幕,询问你选择感兴趣的 shell:

选择并不重要,因为你可以随时更改所选选项。由于我个人更喜欢 PowerShell 而非 Bash,因此我的默认选项是前者。
Bash 和 PowerShell 脚本在功能上是对等的。你应该选择一个你更喜欢使用的 shell。
如果这是你第一次使用 Cloud Shell,你还需要挂载一个存储账户,该账户可以与此功能一起使用。Cloud Shell 用它在会话之间保持文件。这里有两个选择;你可以让它为你创建一个存储账户,或者点击“显示高级设置”按钮选择特定选项:

一旦一切配置正确,Azure 将尝试初始化你的 Cloud Shell 账户:
Your cloud drive has been created in:
Subscription Id: <subscription-id>
Resource group: cloudshell-euw-rg
Storage account: cloudshelleuwstorage
File share: cloudshelleuwfileshare
Initializing your account for Cloud Shell...\
Requesting a Cloud Shell.Succeeded.
Connecting terminal...
Welcome to Azure Cloud Shell
Type "dir" to see your Azure resources
Type "help" to learn about Cloud Shell
MOTD: Switch to PowerShell from Bash: pwsh
VERBOSE: Authenticating to Azure ...
VERBOSE: Building your Azure drive ...
Azure:/
PS Azure:\>
使用 Cloud Shell 类似于浏览文件系统。你的 Azure 资源以目录的形式呈现,可以通过常见的命令行命令如dir或cd访问。你可以通过输入以下命令选择你希望使用的订阅:
PS Azure:\> cd <subscription-name>
然后,你可以通过以下命令轻松浏览其中的所有资源:
PS Azure:\> cd AllResources
PS Azure:\> dir
请注意,你可以通过 Cloud Shell 访问的资源是有限制的——目前你可以使用它操作以下服务:
-
资源组
-
Web 应用
-
存储账户
-
虚拟机
例如,要获取 Azure Files 的连接字符串,你可以使用类似这样的命令:
PS Azure:\> cd StorageAccounts\<storage-account-name>\files
PS Azure:\> dir
当然,在 Cloud Shell 中,你可以同时使用 Azure PowerShell 命令和 Azure CLI。如果你在命令行中输入az命令,你将看到以下结果:

基本上,你在上一节中学到的所有内容都可以在这里应用。这是一个很棒的工具,一旦你习惯使用命令代替浏览 Azure 门户,它将大大提高你的工作效率。
锁
当利用可用的各种命令时,创建和管理 Azure 资源变得更加简单,这些命令可以让您工作更快,同时还能够自动化流程。然而,当您部署数百个资源时,可能会出现错误 —— 您可能会意外移动、重命名甚至删除不应该被触碰的资源。为了防止这种情况发生,可以使用锁定功能 —— 一种简单的功能,可以阻止您执行禁止的操作。在本节中,您将学习如何创建它们,并根据自己的需求使用它们。
创建和管理锁定
锁定几乎所有资源在门户上是可用的。您只需点击锁定选项卡即可访问它们:

在上述示例中,假设我想要保护我的资源组并禁止删除它。为此,我必须单击“+ 添加”按钮和相应的锁定类型:

现在,如果我尝试删除一个资源组,我将收到以下错误:

正如您可能注意到的,有两种类型的锁定;删除和只读。
只读锁定会阻止我对资源进行更改 —— 对于资源组来说,例如,我无法添加新服务:

当然,只读锁定对不同的资源起作用方式不同。如果我向我的 Azure 存储帐户引入一个只读锁定,它将阻止我对服务配置进行任何更改:

锁定也是 Azure 资源,这意味着您可以通过 Azure Powershell 命令(例如Get-AzureRmResourceLock)或 ARM 模板来管理它们。
命名约定
如果您不引入简单、直观且易于遵循的命名约定,那么在 Azure 中治理和管理资源可能会变得具有挑战性。在软件开发领域,服务的适当命名尤其困难,因为您必须考虑不同的区域、环境和实例。在本节中,我们将尝试探讨命名约定的不同概念,您可以根据需要应用或调整。
查找最佳命名约定
在 Azure 中,您必须考虑资源的以下方面:
-
部署资源的区域
-
资源类型
-
资源名称
-
资源实例类型/环境
我们将从资源组开始。默认情况下,您可以按以下方式命名它:
MyNewService
newPortal
oldplatform
一个经验法则是选择一个自解释的名称。例如选择一个名为MyNewService的名称是可以的,但它并没有提供以下信息:
-
资源组所在的位置
-
它代表什么环境(测试/生产/暂存等等)
更重要的是,如果你例如列出你订阅中的资源,你将无法知道MyNewService是什么资源类型,除非选择其类型。当然,像az group list这样的命令会为你提供资源的完整信息,如果你只想导出资源名称,你需要额外添加一个字段。在这种情况下,值得在资源组名称中注解资源类型,格式如下:
MyNewServiceResourceGroup
newPortal-resourceGroup
oldplatform-rg
到目前为止,情况不错——资源的名称现在看起来好多了。接下来,让我们考虑添加位置:
MyNewResourceResourceGroupEastUS2
newPortal-westEurope-resourceGroup
oldplatform-eun-rg
现在情况好多了——我们立刻知道我们正在考虑什么资源类型以及它位于哪里。有了这些信息,浏览不同服务变得更加容易。最后可以添加的内容是环境:
MyNewResourceResourceGroupEastUS2Test
newPortal-westEurope-prod-resourceGroup
oldplatform-staging-eun-rg
现在信息已完整。当然,一切取决于你的个人设置,因为你可能决定将所有环境存储在一个单一的资源组中。即便如此,还是值得包含其余的数据,以便为所有已提供的资源创建一致的名称。
记住,不同的 Azure 资源在命名时有不同的限制。虽然 Azure App Services 对此可能较为宽松,Azure Storage 则不允许使用字母和数字以外的字符。
实际上,你对命名约定的要求将决定资源名称中需要包含的内容,例如:
-
是否在不同地区部署资源
-
是否使用多个环境来开发你的应用
-
是否为多个环境使用单一资源组
一般规则是使用你喜欢的,并且足够灵活的命名约定,以便几年后仍然能涵盖部署到 Azure 的服务。这里最糟糕的情况是,过一段时间不得不更改它,因为它无法反映你业务的变化。
Azure 中的资源
Microsoft Azure 的核心就是资源——你直接或间接地管理它们,但无论如何,你接触的大多数内容都是某种形式的资源。无论它是某个特定服务(如 Azure Functions 或 Azure Traffic Manager),它的某个部分(如 Azure App Services 的应用设置),还是某个独立功能(如本章讨论的锁定功能),你都可以通过 Azure 资源管理器(通常简称为 Azure RM)来管理它们。在本章的最后部分,我们将讨论如何访问 Azure 资源的属性,以便你可以利用这些属性来调查配置并自动化流程,例如部署或监控。
Azure 资源浏览器
访问 Azure 资源的最简单方法是使用Azure 资源浏览器。你可以通过访问resources.azure.com/来使用它。
你的默认界面将类似于我的界面:

要浏览您的资源,您必须展开左侧可用的节点。最初,您可以访问两种不同的节点类型:
-
提供者:这些与特定的 Azure 服务相关,例如 Azure Cosmos DB 或 Azure Storage。
-
订阅:由于订阅本身也是一个 Azure 资源,因此您可以使用 Azure 资源浏览器来浏览它。
这两种节点类型使您能够进行不同类型的操作;提供者是特定 Azure 服务的高级表示,而订阅包含有关其中配置资源的信息。更重要的是,它使您可以直接查看资源的参数:

收集到的信息可以用于在 ARM 模板中输入所需的信息。您可以随时根据 Azure 资源浏览器中的可见结果进行参考。此工具还允许您直接编辑资源参数(通过点击“编辑”按钮),并生成一个 PowerShell/Ansible 脚本,该脚本可以用于管理资源。以下是为我的 Azure 存储账户生成的 PowerShell 命令示例:
# PowerShell equivalent script
# GET handsonazureeuwstorage
Get-AzureRmResource -ResourceGroupName handsonazure-euw-rg -ResourceType Microsoft.Storage/storageAccounts -ResourceName "handsonazureeuwstorage" -ApiVersion 2017-10-01
# SET handsonazureeuwstorage
$PropertiesObject = @{
#Property = value;
}
Set-AzureRmResource -PropertyObject $PropertiesObject -ResourceGroupName handsonazure-euw-rg -ResourceType Microsoft.Storage/storageAccounts -ResourceName "handsonazureeuwstorage" -ApiVersion 2017-10-01 -Force
# DELETE handsonazureeuwstorage
Remove-AzureRmResource -ResourceGroupName handsonazure-euw-rg -ResourceType Microsoft.Storage/storageAccounts -ResourceName "handsonazureeuwstorage" -ApiVersion 2017-10-01 -Force
# Action ListAccountSas
$ParametersObject = @{
signedServices = "(String)"
signedResourceTypes = "(String)"
signedPermission = "(String)"
signedIp = "(String)"
signedProtocol = "(String)"
signedStart = "(String)"
signedExpiry = "(String)"
keyToSign = "(String)"
}
Invoke-AzureRmResourceAction -ResourceGroupName handsonazure-euw-rg -ResourceType Microsoft.Storage/storageAccounts -ResourceName handsonazureeuwstorage -Action ListAccountSas -Parameters $ParametersObject -ApiVersion 2017-10-01 -Force
# Action ListServiceSas
$ParametersObject = @{
canonicalizedResource = "(String)"
signedResource = "(String)"
signedPermission = "(String)"
signedIp = "(String)"
signedProtocol = "(String)"
signedStart = "(String)"
signedExpiry = "(String)"
signedIdentifier = "(String)"
startPk = "(String)"
endPk = "(String)"
startRk = "(String)"
endRk = "(String)"
keyToSign = "(String)"
rscc = "(String)"
rscd = "(String)"
rsce = "(String)"
rscl = "(String)"
rsct = "(String)"
}
Invoke-AzureRmResourceAction -ResourceGroupName handsonazure-euw-rg -ResourceType Microsoft.Storage/storageAccounts -ResourceName handsonazureeuwstorage -Action ListServiceSas -Parameters $ParametersObject -ApiVersion 2017-10-01 -Force
# Action listKeys
Invoke-AzureRmResourceAction -ResourceGroupName handsonazure-euw-rg -ResourceType Microsoft.Storage/storageAccounts -ResourceName handsonazureeuwstorage -Action listKeys -ApiVersion 2017-10-01 -Force
# Action regenerateKey
$ParametersObject = @{
keyName = "(String)"
}
Invoke-AzureRmResourceAction -ResourceGroupName handsonazure-euw-rg -ResourceType Microsoft.Storage/storageAccounts -ResourceName handsonazureeuwstorage -Action regenerateKey -Parameters $ParametersObject -ApiVersion 2017-10-01 -Force
正如您所见,您无需自己编写这样的脚本,您可以直接使用 Azure 资源管理器,复制它们,并根据需要进行调整。
总结
在本书的最后一章中,我们讨论了与特定服务无关的主题,而是扩展了您当前的知识,并使您成为 Microsoft Azure 平台上更好的用户、开发人员和架构师。您已经学会了如何使用 Azure CLI 和 Cloud Shell 来简化管理操作,如何利用锁来保护所有脆弱的资源,以及如何读取 Azure 服务配置。我们还讨论了采用适当命名约定的好处以及它们如何影响您在 Azure 上部署的应用程序。这是一次激动人心的 Azure 云之旅,您发现了来自该平台的许多不同 PaaS 服务。Azure 是一个极好的生态系统,允许您构建小型网页和复杂的企业级平台。而且,它也非常动态——这就是为什么我强烈建议您查看本书中详细列出的进一步阅读部分,以便您能获得更多经验,熟悉更高级的概念。这里重要的一点是,您要不断更新自己的知识,无论是通过阅读博客、参加聚会或会议,还是阅读文章和书籍。由于云计算是近年来软件开发中的一个主要话题,熟悉它并建立自己的技能集是至关重要的,这将帮助您在日常工作中得心应手。
问题
-
在 Azure 中,资源的两种不同锁类型是什么?它们是如何工作的?
-
您可以在 Cloud Shell 中使用 Azure CLI 吗?
-
您可以从哪里获得有关您在 Azure 中配置资源的详细信息?
-
合适的命名规范能带给你哪些好处?
-
为什么 Cloud Shell 需要配置存储账户?
深入阅读
- 请参考 Azure 博客:
azure.microsoft.com/en-us/blog/。
第二十二章:评估
第一章:Azure App Service
-
是的,App Service 和 Web App 可以互换使用。
-
目前,我们有三类:开发/测试、生产和隔离。
-
一旦超出计算限制,Free 和 Shared 层将被阻止,导致应用程序对用户不可用。
-
目前,我们有五个不同的提供者:Azure AD、Facebook、Google、Twitter 和 Microsoft Account。
-
是的,Premium 层基于新的 Dv2 机器,这些机器使用更强大的 CPU 和更新的架构。
-
你必须启用应用程序日志记录功能。
-
不幸的是,你不能这样做;你至少需要 Shared 层。
-
是的,你可以。App Service Plan 可以支持不止一个 App Service。
-
有三种操作系统可用:Windows、Linux 和 Docker。
-
不,你不能—你必须重新创建 App Service 来更改底层操作系统。
-
你可以在哪里找到正确的位置地址?你可以使用 FTP 或 FTPS。你可以在概览面板上找到位置地址。
-
用户级别的凭据将在你访问的所有订阅中的所有 App Services 中设置。应用程序级别的凭据在创建 App Service 时创建,并与之关联。
-
向上扩展意味着设置更好的服务器。向外扩展意味着设置更多的服务器。
-
你每个实例将支付 50 美元,总共 500 美元。
-
它的目的是实现更好的可靠性和 App Service 的隔离性。它还具有更高的限制,涉及工作实例的最大数量。
-
是的,它是—使用 Docker 作为操作系统。
第二章:Azure WebJobs
-
是的,可以使用 Free 或 Shared 层运行 WebJobs。然而,不建议这样做,因为一些作业需要启用 Always On 功能,而 Free 和 Shared 层无法启用该功能。
-
我们可以以持续运行或手动/按计划触发的方式运行作业。
-
是的,你可以。一般来说,WebJobs 支持与 App Services 相同的编程语言。
-
你可以部署
run.{extension}文件。 -
如果是这样,如何操作?是的,你可以;需要将所有文件归档为 ZIP 包并部署。
-
它必须作为一个单一实例创建。
-
是的,WebJobs 共享与其托管的 App Services 相同的应用程序设置。
第三章:作为容器部署 Web 应用
-
Azure 容器注册表是一个 Azure 服务,充当托管在云中的 Docker 私有注册表。它与其他 Azure 容器服务无缝集成。
-
管理员登录是一个功能,允许你使用注册表名称作为用户名和密钥作为密码,在 ACR 中进行身份验证。
-
要在 App Service 中运行容器,你必须选择 Docker 作为操作系统。
-
是的,你可以—实际上,两种可能性(公共/私有)都可以。
-
是的,你可以—有一个专门的面板可用于扩展你的实例。
-
要在 AKS 集群中更新应用程序并限制停机时间,你必须部署多个具有相同应用程序的 Pod。
-
你必须创建一个服务主体,它将在身份验证过程中使用。
第四章:使用 Service Fabric 的分布式应用程序与微服务
-
可靠的演员实现了虚拟演员模式,旨在将负载分配到成千上万个有状态服务中。可靠的服务是 Service Fabric 中用于实现有状态服务的模式,这些服务可以是多线程的,且不需要扩展到那个级别。
-
有状态服务在本地存储状态。无状态服务则不处理状态,或者使用外部存储。
-
一个名为
ICommunicationListener的接口。 -
SF 中的节点类型是专门的虚拟机规模集,旨在处理特定的工作负载。
-
是的,因为它们是独立的 VMSS。
-
是的,您可以。
-
节点到节点的安全性和客户端到节点的安全性。
-
群集是 Service Fabric 的整体实例——它是所有其他概念的容器。应用程序是多个服务的逻辑容器,这些服务具有相同的目的。服务是应用程序中的单个工作单元,可以根据需要进行扩展。分区是一个标识符,用于正确地将负载分配到各个服务中。副本是用于复制状态或实现服务更高持久性的虚拟机实例。
-
为了实现所需的群集和托管应用程序的韧性和耐久性,必须配置五个节点。
-
可靠性层级用于设置节点内虚拟机的数量。
第五章:使用 Azure 搜索
-
索引是包含文档的元数据表,这些文档由 Azure 搜索使用。
-
推送模型用于具有低延迟要求的应用程序,在这种情况下,您的文档一旦推送到服务,就会立即被索引。
-
是的,您可以为索引器定义自定义调度。
-
默认情况下,Azure 搜索使用 Lucene 分析器。
-
是的,可以使用自定义分析器。
-
副本是托管索引的服务的物理实例,而分区是它的一部分,提供特定的资源。
-
您必须使用
api-key头。
第六章:使用通知中心发送移动通知
-
PNS 代表推送通知服务。
-
是的,每个供应商都有自己的 PNS。
-
主要区别在于补丁功能——安装可以在创建后更新,而注册则不能。
-
否,设备的可用数量根据不同层级有所不同。
-
根据您的环境,可以使用 Visual Studio 或通过编程方式查询通知中心实例。
-
有多种可能性:可以使用 Azure 门户或 Visual Studio,或者启用测试发送并通过编程方式发送通知。
-
丰富内容通知是包含不止文本的通知(例如,包含图片的通知)。
第七章:无服务器和 Azure 函数
-
在按消耗计费定价模型中,您为每次函数执行单独付费。
-
GB-s 是 Azure Functions 中的消耗单位;它表示每秒的千兆字节数,并定义了在单位时间内使用的内存量。
-
是的,您可以,然而这并不是建议的服务模型。
-
它被称为 Function App。
-
在 Azure Functions 的 V1 版本中曾实验性支持此语言。目前(在 V2 中),不再支持。
-
是的——这就是 Azure Storage 绑定的工作方式。
-
当你的 Function App 自动部署时,它会被设置为只读,以避免在过程中手动更改。
-
不完全是——在 Azure Function 中,所有设置都可以通过环境变量进行配置。
第八章:通过 Logic Apps 集成不同组件
-
Azure Logic Apps 利用无服务器定价模型,你为每次执行单独付费,考虑到为这个特定应用使用的所有模块。
-
是的,这是可能的。
-
你需要一个额外的扩展,叫做 Azure Logic Apps for Visual Studio。
-
执行完成后,你可以访问它并查看在执行流程期间使用的评估值。
-
是的,确实有一个特殊的连接器来与这些服务合作。
-
Azure Logic Apps 可以导出为 JSON 文件,并存储在版本控制系统中。
第九章:瑞士军刀——CosmosDB
-
Azure Cosmos DB 支持 SQL、MongoDB、Cassandra、Azure Table 和 Gremlin。
-
是的——在 Azure Cosmos DB Table API 中,你几乎可以实现无限扩展能力、故障切换功能以及更好的性能。
-
严格一致性、边界过时、一致会话、一致前缀和最终一致性。
-
在这两者中,边界过时一致性更为严格。
-
是的,方法是配置防火墙规则。
-
不,Azure Cosmos DB 的 SQL API 与 SQL Server 有很大不同。
-
Azure Cosmos DB 中的存储过程用于在查询过程中执行额外的逻辑。
-
是的,这是可能的。
第十章:使用 Event Grid 进行响应式架构
-
目前,可以使用 Event Grid 模式或 Cloud Events 模式。
-
你必须使用
aeg-sas-key或aeg-sas-token头。 -
你必须返回一个包含验证代码的正确 JSON。
-
如果是 Azure 服务,端点不必经过验证。
-
当事件无法传递时,传递将在预定间隔后重试。
-
在订阅创建期间,你可以使用
Subject字段定义过滤器。 -
本地 Azure Functions 运行时实现了 Event Grid 端点,可以用来测试你的应用程序。
第十一章:使用 Azure Storage——表格、队列、文件和 Blob
-
热存储和冷存储——账户创建后,可以在 blob 级别选择归档。
-
你必须同时包含分区键和行键。
-
可用的模型有:LRS、ZRS、GRS 和 RA-GRS。
-
文件存储更像是一个文件共享,而 Blob 存储则作为应用程序和用户数据的存储。它们还具有不同的定价模型。
-
是的,你可以。
-
一条消息最多可保留 7 天。
-
消息的最大大小为 64 KB。
-
分区键的最大大小是 1 KB。
-
在 Table Storage 中,我们使用乐观并发模型。
-
使用 Azure 文件,你可以通过添加额外的帐户轻松扩展共享。当使用本地共享时,你需要购买、安装并配置硬件才能使用它。
第十二章:大数据管道 - Azure Event Hub
-
消费者组用于允许使用不同的处理器并行处理事件,这些处理器之间相互独立。
-
1TU 允许你每秒处理 1,000 条消息。
-
这取决于你的需求—唯一需要记住的是,一旦集线器创建完成,你无法更改分区的数量。
-
TUs 被分配给命名空间并由集线器共享。
-
发送、监听和管理。
-
是的,可以。
-
一些消费者可能会失去租约并变得空闲。
第十三章:实时数据分析 - Azure Stream Analytics
-
在 Azure Stream Analytics 中,你需要为 流计算单元 (SUs) 付费。
-
流输入让你可以直接从流中处理数据。通过引用输入,你可以选择一个服务,例如 Azure 存储,它将按时间间隔进行处理。
-
应用时间是事件生成的时间。到达时间告诉你事件被服务接收的时间。
-
SELECT Id INTO [output-alias] FROM [input-alias]。 -
是的,可以与多个输入值一起工作。
-
如果你基于到达时间应用事件排序,则事件会出现乱序,这会扰乱事件的实际顺序。
-
是的,你可以—为此,你必须使用
SUBSTRING函数。
第十四章:企业集成 - Azure 服务总线
-
在队列中,一旦消息被读取,它就不会再发送给其他读取者。而主题则允许你实现多个读取者,专注于相同的一组消息。
-
不,你必须至少使用标准层才能使用主题。
-
死信队列用于避免由于无效消息导致队列被阻塞。
-
在 Azure 服务总线中,使用会话来确保消息按 FIFO(先进先出)顺序处理。
-
分区队列的最大大小为 80 GB。
-
在主动复制中,你会同时向两个命名空间发送消息。在被动模型中,你只会向次要命名空间发送消息,但前提是它能够传递给主要命名空间。
-
你必须定义主要和次要区域,将它们配对,并定义故障转移触发器。
第十五章:使用应用程序洞察监控你的应用程序
-
你需要一个仪表键。
-
是的—Azure 应用程序洞察支持多种不同的编程语言。
-
智能分析是一组功能,结合了机器学习和数据分析,以扩展应用程序洞察的能力。
-
你可以使用分析模块对收集到的日志执行查询。
-
你可以使用 ARM 模板在资源配置过程中创建警报。
-
是的,这是可用的选项之一。
第十六章:Azure 中的 SQL - Azure SQL
-
Azure SQL 会不断更新,并且比 Microsoft SQL Server 更快获得更新。
-
分片是将你的数据和数据库划分成更小的块,这些块可以均匀分布,并且只处理它们自己的负载部分。
-
默认情况下,防火墙会阻止连接——你需要将你的 IP 地址添加到白名单中。
-
基于 DTU 和基于 vCore。
-
弹性池是一种扩展模型,在这种模型中,你不是拥有一个带有预配置吞吐量的单一数据库,而是拥有一个池,你可以动态地将其分配给一组数据库。
-
eDTU 是用于弹性池的弹性 DTU。
-
你可以使用称为动态数据屏蔽的功能。
-
你有三个可选项:Azure 存储、Azure 日志分析和 Azure 事件中心。
第十七章:大数据存储 – Azure 数据湖
-
通常情况下,管理安全组更容易——在这种情况下,你不需要在每次给新用户授予访问权限时都添加一个单独的实体。
-
RBAC 基于角色,而 POSIX ACL 基于根据分配给用户或组的操作来计算权限集。
-
在 ADLS 中没有文件大小限制。
-
这取决于你的需求——虽然特定的结构可能不影响性能,但文件大小可能会影响。
-
是的,ADLS 将与任何能够连接到它的语言兼容。
-
Azure 存储引入了文件大小限制和容量限制。它还提供了比 Azure 数据湖存储更简单的安全模型。
-
你必须实现复制到次要区域。
第十八章:扩展 Azure 应用程序
-
扩展是水平扩展应用程序(通过增加附加实例),而升级是垂直扩展应用程序(即配置更好的硬件)。
-
在大多数情况下,你会选择扩展而不是升级。这是因为硬件的性能是有限的,增加另一台机器比配置新的 CPU 和内存更容易。
-
其实并不完全如此——虽然从概念上讲可能可行,但无服务器服务避免了扩展。
-
是的——如果你扩展 Azure 应用服务,服务的价格将乘以当前正在运行的实例数量。
-
在 SF 中,扩展是完全没问题的。危险的操作是扩展,因为这要求你将工作负载从一个地方迁移到另一个地方。
-
当你的应用程序处理的流量是动态的时,手动扩展可能会有问题——在这种情况下,你需要不断监控,并在需要时进行扩展。
-
你可以设置自动扩展功能,并声明 CPU 触发器来扩展你的应用程序。
第十九章:使用 Azure CDN 提供静态内容
-
Azure CDN 旨在缓存以提高应用程序的性能,同时为多个用户提供静态内容。
-
目前,Azure CDN 支持 Verizon、Akamai 和 Microsoft。
-
源是缓存资源的端点。
-
在 Azure CDN 中,压缩功能允许你即时压缩文件,减小它们的大小并减少网络延迟。
-
文件的默认 TTL 设置为七天。
第二十章:使用 Azure 流量管理器分配负载
-
支持的路由方法有:性能、加权、地理位置、多值和子网。
-
要使用真实用户测量功能,你必须将自定义脚本注入到你的网站中,该脚本将发送关于网络调用延迟的更新值。
-
当然——在这种情况下,你将获得一个嵌套的配置文件。
-
是的,使用外部端点是可能的。
-
一旦 DNS 名称被解析(以确保 Azure 流量管理器指向正确的端点),客户端将直接与该端点连接。
-
Azure 流量管理器在 DNS 层面工作,而网关是一种模式,允许你对请求进行负载均衡,但它作为系统的单一入口。
-
是的,你可以使用 Azure 流量管理器实现高可用性——在这种情况下,你需要实现一个配置文件,允许你在某个区域无法响应时切换到其他区域。
第二十一章:Azure 提示与技巧
-
订阅级别锁和资源级别锁均可用。
-
当然——Azure CLI 可以在 Cloud Shell 内使用,作为服务的一部分。
-
若要获取详细信息,你可以访问资源浏览器应用。
-
一般来说:更好的资源管理、更快的过滤和更简便的开发。
-
它使用存储账户在不同会话之间持久化文件。


浙公网安备 33010602011771号