自 2017 年 8 月
卷 32 编号 8
此文章由机器翻译。
Azure - 使用无服务器体系结构进行批处理
通过 Joseph Fultz |自 2017 年 8 月
对于 Azure 企业客户中管理其云需求量, 关键的难题是能够以控制它们的支出,并且要将返回到使用者这些成本计费。幸运的是,有多个供应商都提供工具,如 Cloud Cruiser、 Cloudyn 和 Cloudability,可帮助收集使用情况数据和生成一组丰富的报表。此外,还可以找到很好的如何以编程方式,如从以前的同事,我,Ed Mondek,他会在其中将显示如何将数据拉入 Excel 并查看它的 post 请求数据的许多示例 (bit.ly/2rzDOPI)。但是,如果你想要定期请求该数据并启用历史记录,存在的趋势和预测的视图,你需要存储大量更多数据。对于拥有数千个每个订阅的资源的大型企业,该数量的数据可能会极其困难,并可以肯定不是你将希望以提取和在本地计算机上保留。
幸运的是,还有一种方法。在本文中我将引导你完成设置以提取此类数据,提供一些扩展并将数据存储到一个位置,其中进一步无服务器提取-转换-加载 (ETL) 过程 (分析,映射-化简,依此类推) 的工作,可以进行。我将 touch 上的总体设计、 关键决策点和在采用无服务器的方法要考虑的重要事项。
确定消耗
第一个决定企业协议 (EA) 计费 API 和 Azure 计费 API 后,为中心解决特定订阅其请求之间进行选择。我原型以具有 EA 中的多个注册的企业客户为目标。在与其工作的方案中,订阅正在使用的管理边界的一部分的两个特定产品组和来分隔非生产资源中的生产。这可能导致非常高易失性概念证明 (PoC) 的工作类型的创建为新组和新产品行在 Azure 中开始由于变化不定的订阅数。因此,我选择了使用 EA API,因为它简化了工作范围中,我不需要创建用于订阅的发现机制。这会让我使用不具有在适用于企业注册外部创建的订阅的任何数据设定的质询。虽然这是为处理独特的重要区域,它附带了许多具有组织解决其他进程和管理挑战,我想要完成的工作的作用域之外。
要求和逻辑流
在任何体系结构,则需要在设计中的大多数详细审查的系统和测试之间的交集。无服务器体系结构不会更改需要考虑的移动 supersystem,以及必须考虑到离散的子系统了特定约束的数据量。主体中的更改构建此类 supersystem 是更深度或范围中定义的系统,如调整队列的吞吐量,但不是大小调整硬件大小承载时它。你仍必须考虑延迟、 连接、 卷、 可用性、 成本,以及任意数量的其他因素,但大小调整和定义的服务端细节,一旦您已经定义的容量以及要满足的标识的要求所需的功能的成本的工作。没有定义主机环境和其所需的所有项目,因为您可能在过去执行的任何额外工作。
将导入设计到系统的信息的总体流程将如下所示之前,让我们请注意的一些关于源系统和最终状态系统的某些要求:
- 所有 EA 下每个订阅的数据将返回的所有资源可用的指定月份中每一天。这可能导致大量数据,月随着线性增长。
- 可能在这个月中更新所有记录。规定的结算计时为 72 小时。为安全的点,我将为给定月份直到 72 个小时之后的后续月份的开始考虑变化不定的所有记录。
- 使用情况数据未使用的 ID 对于注册,请返回,以便需要将其添加。
- 确定成本是单独的活动,并且需要检索费率卡以及进一步处理。
- 将为不在指定 EA 的订阅中不接收任何信息。
此外,有几个原型必须包含的技术的业务要求:
- 能够创建只读和地理上分散的数据集必须包含。
- 处理性能应该调整与性能成本。
- 应在设计保护订阅级别的访问权限的能力。
总体流程本身的相当简单,在于我只要检索的数据、 添加少量的信息并将其保留到目标存储。
如下图中所示图 1,用于获取向其目标的数据的路径是相当简单,因为没有与任何外部系统以外 EA 计费 API 集成。我知道,我工作时通过数据,我将需要做一些量初始处理和扩展 (例如,添加的注册 ID),并在持久性端需要处理来自前一天的提取的现有记录。我可能需要查看分隔这些两个进程。
图 1 逻辑流
因此,你会看到表示检索、 扩充和持久性,由某些排队机制而分隔的三个主要块。在我品牌某些技术选取和启动,查看与这些组件实现,并将并行运行的处理管道的详细信息后,将启动复杂性。
技术映射
此时在过程中,超过了要求整个系统的两个因素可能会派上用场: 企业标准和个人首选项。如果这些正在发生冲突时,将会导致几乎无限的争论。幸运的是,在这种情况,我不需要担心这。我已约束,以及那些我注意的初始要求我自己组合。在这种情况下,我想要确保要进行命中这些标记:
- 最简单的计算资源调配和快速测试周期中的编辑/更新
- 简单的自动缩放
- 对于地理分布的数据轻松设置
- 用于计划和触发工作的简单机制
在这里,我想重点介绍在工作而不在系统设置。我将其保留操作后我有一个工作原型,如成本分析,以及各种实现遵守直到公司标准。我未考虑某些替代方法,例如 Azure SQL 数据库,而不是 Azure Cosmos DB,但我将重点介绍我选择和每个这些选择的主要动机。
- 计算:Azure 函数将提供我这里。它符合以实现缩放和简单起见我需要同时还通过绑定提供轻松配置的计划和触发作业和轻松集成。
- 队列:保留操作简单,我将使用 Azure 存储 Blob 并单独的由容器文件。每个初始的输入文件的未知但预期大型大小使非选项,以进行初始检索的存储队列,并可能超出处理单独的订阅数据拆分为运行中获取它们。除此之外,我想要保留机制统一和实际上不需要任何高级的功能,如优先级消息、 路由、 特定于消息的安全和病毒的消息处理。
- 存储:Azure Cosmos DB 的确是此处我友元。使用订阅 ID 作为分区键使我按来限制访问订阅,如有必要。此外,添加和删除的易用性读取和读写地理上分散的副本和本机 Power BI 中的支持使此事的我的系统。最后,我不得不承认少量个人偏差:我想要支持我已使用多年太多要放弃的 SQL 语法正确的文档存储机制。
图 2表示逻辑体系结构,以及向其添加一些处理流程技术的应用程序。
图 2 技术映射和数据流
我已采用 liberty 我使用的名称,其中包括在此图中,但你可能没有在此阶段设计的名称。使用的形状指示在 play; 中的技术在行上的数字的进程的执行的序列,并箭头指示哪些组件将启动在出站调用。请注意,我标识出了我将我的实现中的工作部分作为采用的四个 Azure 函数、 四个 Azure 存储 Blob 容器和三个 Azure Cosmos DB 集合。
将数据分成三个集合可用于说明,但有 grander 用途。我不会为每个类型的文档需要相同的安全,并分离成为易于理解和管理。更重要,由集合定义的性能特征并分离允许我更轻松地通过来优化,在与其他两个保持最小的同时,专门为 DetailedUsageData,具有大型的高吞吐量集合。
检索数据
我想要运行类似于使用 Cron 作业干什么从开始数据迁移前两个分支。而 WebJobs SDK 本身则要支持此类型的实现,它会使大量的配置运行时 eAzure 函数基于 WebJobs SDK 和自然支持计时器触发器的工作,它是一种简单的选择。我无法使用过 Azure 数据工厂因为它是专门为移动数据所做的一个工具,它支持将 Web 数据检索和使用 Blob。但是,这意味着需要找出与引用数据和当我没有行 id。 更新 Azure Cosmos DB 中的重复记录的某些事项熟悉开发和调试使用 Azure 函数和我可以获得与 Application Insights 的 Azure 函数集成的信息此实例中,使 Azure 函数我首选的选择。
计时器触发器具有一个明显的函数,但它可以使 DailyEABatchControl 地知道要处理内容,来检索注册集合具有以下架构中从配置信息:
{
"enrollmentNumber": "<enrollment number>",
"description": "",
"accessKey": "<access key>",
"detailedEnabled": "true",
"summaryEnabled": "false",
}
现在,具有注册编号、 访问密钥和一个标志,用于处理 ("detailedEnabled") 打开足以满足我来完成的工作。但是,应启动添加功能和需要其他运行的配置信息,Azure Cosmos DB 将允许我轻松地将元素添加到文档架构而无需完成一组重新处理和数据迁移。一旦触发 DailyEABatchControl 时,它将循环访问的所有文档,为具有"detailedEnabled"每个注册调用 RetrieveUsage 设置为 true,将分离的逻辑来检索源数据的逻辑从启动作业。我使用 JobLog 集合来确定是否作业已运行的一天中, 所示图 3。
图 3 作业控制逻辑
// Get list of enrollments for daily processing
List<Enrollment> enrollments =
inputDocument.CreateDocumentQuery<Enrollment>(
UriFactory.CreateDocumentCollectionUri(dbName, enrollmentCollection),
new SqlQuerySpec("SELECT * FROM c WHERE c.detailedEnabled = 'true'"),
queryOptions).ToList<Enrollment>();
// Get yesterday's date to make sure there are logs for today
int comparisonEpoch =
(int)(DateTime.UtcNow.AddDays(-1) - new DateTime(1970, 1, 1)).TotalSeconds;
string logQuery =
"SELECT * FROM c WHERE c.epoch > '" + comparisonEpoch.ToString() + "'";
List<JobLog> logs = inputDocument.CreateDocumentQuery<JobLog>(
UriFactory.CreateDocumentCollectionUri(dbName, jobLogCollection),
new SqlQuerySpec(logQuery), queryOptions).ToList<JobLog>();
// Get list of enrollments for which there is no match
var jobList = enrollments.Where(x =>
!logs.Any (l => l.enrollmentNumber == x.enrollmentNumber));
最后一个 lamba 会导致问题的那一天尚未为其检索数据的注册的筛选列表。接下来,我将调用 RetrieveUsage (第 3 步图 2) 从在通过调用它具有 HTTPClient 与它的 post 正文中有足够的数据的 DailyEABatchControl 须知注册,它提取数据和为其它,提取的月份中所示图 4。
图 4 检索使用情况数据
foreach(var doc in jobList)
{
HttpClient httpClient = new HttpClient();
string retrieveUsageUri = @"https://" +
System.Environment.GetEnvironmentVariable("retrieveUsageUri");
string postBody = "{\"enrollment\":\"" + doc.enrollmentNumber + "\"," +
"\"month\":\"" + DateTime.Now.ToString("yyyy-MM") + "\"}";
httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
var content = new StringContent(postBody, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(theUri, content);
response.EnsureSuccessStatusCode();
string fetchResult = await response.Content.ReadAsStringAsync();
}
值得指出的,这并不旨在是打开的系统。因此,我不想执行 RetrieveUsage 函数只是任何调用方,我将创建已关闭的处理循环。因此,我已受它需中不会显示代码图 4,但从 GetEnvironmentVariable("retrieveUsageUri") 返回是 URI 的一部分。在企业中实现、 服务主体和 Azure Active Directory 集成将是安全的一个更现实可行的选择,以实现更高。
我的数据之旅在第一个路线的最后一步是在 RetrieveUsage 函数中将在其中具有保留到 Azure Blob 存储的 newdailyusage 容器。但是,为了将该数据我必须构造调用并且包括 accessKey 作为标头中的持有者令牌:
HttpClient httpClient = new HttpClient();
string retrieveUsageUri = usageQB.FullEAReportUrl();
httpClient.DefaultRequestHeaders.Add("authorization", bearerTokenHeader);
httpClient.DefaultRequestHeaders.Add("api-version", "1.0");
var response = await httpClient.GetAsync(retrieveUsageUri);
response.EnsureSuccessStatusCode();
string responseText = await response.Content.ReadAsStringAsync();
由于篇幅所限,我已剪切超出此代码块一些日期操作和尚未包含用于生成 bearerTokenHeader 或 UsageReportQueryBuilder 帮助程序类。但是,这应该是足够用于说明它们正在使用和排序。AccessKey 传递给 FromJwt,将返回 BearerToken 类型,从中我只需抓住标头,然后将其添加到从通过调用 usageQB.FullEAReportUrl 构造的 URL 创建请求的静态方法。最后,我更新输出绑定到的路径和文件名对于 Blob 目标,我想:
path = "newdailyusage/" + workingDate.ToString("yyyyMMdd")
+ "-" + data.enrollment + "-usage.json";
var attributes = new Attribute[]
{
new BlobAttribute(path),
new StorageAccountAttribute("eabillingstorage_STORAGE")
};
using (var writer = await binder.BindAsync<TextWriter>(attributes))
{
writer.Write(responseText);
}
这将导致类似如下所示的 Azure 存储空间中的结构:
newdailyusage/
20170508-1234-usage.json
20170508-456-usage.json
20170507-123-usage.json
这使我可以将数据存储多个注册和每个注册的多个文件,以防处理也不会发生出于某种原因。此外,由于数据可能更改前天月进行时,很重要准备进行研究和对帐可用的文件,以防出现异常情况显示在报表数据。
将数据拆分为并行处理
使用传入的数据量和以某种方式处理每一天的给定月份更新记录的工作,务必处理此数据以并行方式。通常情况下,至少现今,这是 C# 中为中断出的并行库、 编写几行代码并正在在并行处理天才个赞自己。但是,在此实例中,我想只是依赖于要执行该操作为我并且允许我的平台的功能,以专注于每个离散任务。
序列中下一步的 Azure 函数具有已配置与 blob 触发器,因此它将选取驻留在入站的处理存储容器的文件。在此步骤的作业是将入站的文件拆分为文件每天每个注册。总之,这是一个非常简单的步骤,但它需要进行反序列化 JSON 文件,到 RAM。务必请注意,由于即可选择要用于原型方法只需调用反序列化方法:
JsonConvert.DeserializeObject<List<EAUsageDetail>>(myBlob);
我知道这足以应对我目的,但 Azure 函数主机的存在 RAM 分配为 1.5 GB。有可能,对于使用大量的资源设置大注册,文件将变得太大,在某一时刻加载到 RAM,用例分析和将该文件拆分的替代方法将具有用于在一个月中。此外,如果你创建一个 Azure 函数,需要多个五分钟的时间运行,它将需要修改,因为当前的默认值为 5 分钟,但这可以调整到通过主机配置 JSON 的 10 分钟的最大值。在早期已经提到,知道数据的卷将是整个系统中的密钥在每个点以及的集成。一旦已反序列化数据,我将抓住退出的最大日期并设置从第一天到一天最大值,以启动选择出每个这些天的数据中所示循环图 5。
图 5 中选择每一天的数据
// Loop through collection filtering by day
for(int dayToProcess = 1; dayToProcess <= maxDayOfMonth; dayToProcess++)
{
// Get documents for current processing day
var docsForCurrentDay = results.Where (d => d.Day==dayToProcess);
// Serialize to string
string jsonForCurrentDay =
JsonConvert.SerializeObject(docsForCurrentDay);
log.Info($"***** Docs for day {dayToProcess} *****");
// Get date for one of the records for today
string processDateString = (from docs in results where docs.Day ==
dayToProcess select docs.Date).First();
path = "newdailysplit/" + DateTime.Parse(processDateString).ToString("yyyyMMdd")
+ "-" + enrollment + "-dailysplit.json";
// Write out each day's data to file in container "\newdailysplit"
var attributes = new Attribute[]
{
new BlobAttribute(path),
new StorageAccountAttribute("eabillingstorage_STORAGE")
};
using (var writer = await binder.BindAsync<TextWriter>(attributes))
{
writer.Write(jsonForCurrentDay);
}
}
一旦已拆分为单独的文件并写出所有日期 (请参阅中的步骤 7图 2),我只需将文件移动到 processedusage 容器。切记关系图图 2轻松地分析,我已省略某些容器-具体而言,错误文件容器是缺少关系图中。这是保存期间处理,导致异常的任何文件,该文件是否整个使用文件或只是其中之一的每日拆分的容器。我不会花费的时间或更正丢失或误码天的数据,因为用于给定的月份和注册或单个的每日拆分以更正此问题后发现问题时,可以触发该进程的努力。此外明确缺少于该原型警报和补偿机制时出现错误,但这是要通过与 Operations Management Suite 的 Application Insights 集成向上冒泡。
将数据保存到 Azure Cosmos DB
文件拆分和已准备好拾取 ProcessDailyUsage 函数,是时候,请考虑某些问题需要解决的问题,即对该目标以及如何处理更新的吞吐量。通常使用时通过在企业中的某些解决方案体系结构,则你会遇到较旧系统能力较小或其中实时加载和高吞吐量方案需要管理。我不自然具备这种体系结构,我云本机安装程序中的任何硬吞吐量约束,但我可以创建我自己的问题,如果我不需要考虑的卷和速度我要将传送到我正在使用的云服务的数据的时间较长。
我的数据集,每个每日拆分大约 2.4 mb,并包含有关 1200 各个文档。请记住,每个文档表示一个计量器读取一个在 Azure 中设置的资源。因此,为每个 EA 的每日拆分中的文档数可能会有很大根据资源使用情况整个企业内。ProcessDailyUsage 函数已配置为触发 newdailysplit 容器中根据接收的新 blob。这意味着将能操作数据的多达 31 并发函数执行。用于帮助我估计我进行 Azure Cosmos DB 设置需要我在 documentdb.com/capacityplanner 使用计算器。一些经验性测试的情况下必须进行的初始设置几个猜测。我知道将 31 并发执行,但它是有点难钉子下每秒创建将不会进行重复运行的并发请求数。此原型的最终结果将帮助你通知的最终的体系结构和设置,要求,但是因为我正在前滚此时间线上,我将在它使用以下路径作为我规则用于估计需要尝试:
- 1200 记录
- 31 并发执行数 (适用于具有单一的 EA)
- 每个请求 (来自测量少量的各个请求的实验证据) 0.124 秒
我将舍入到因此估计过高负载更保守的估计,0.1 秒。此网 310 每秒请求数 EA,反过来到达大约 7800 请求单元 (Ru) 基于计算器结果中,如下所示在图 6。
图 6 Azure Cosmos DB 定价计算器
因为可以设置而不会调用支持最大 RUs 为 10000,这看起来可能有点高。但是,我将运行不受阻止的并行过程,并且,增加了吞吐量重要的是,这反过来将提高成本。设计结构,因为它是适合我用这用于某些测试,但我需要的限制机制来降低处理,以便我可以设置更少 (ru),并保存自己少量 money 实际解决方案时,这是一个主要考虑因素。我不需要尽快捕获的数据,只需在足够合理时间内该人无法查看和每日使用它。好消息是 Azure 函数团队最终将得到解决的问题的积压工作中有并发控制机制 (bit.ly/2tcpAbI),并将提供一次实现的控制的好方法。其他一些选项是引入人工 (让我们所有同意这是错误) 的任意延迟或重新处理并处理在 C# 代码中显式并行执行。此外,正如技术专家 Fabio Cavalcante 指出对话中的另一个不错的选择是通过添加 Azure 存储队列和使用功能,例如可见性超时和计划的传递充当限制机制有点修改体系结构。这会将添加几个部分移动到系统,当我将具有来计算出的激活时将数据保存在存储中,使用队列的交互或队列的 64 KB 块中的数据切片。限制 Azure 函数中可用后,我将能够将其保存在我正在使用此种简化形式。此处突出的一点是,使用无服务器体系结构时你必须熟悉的在其你正在生成,以及每个决策的成本的平台的约束。
设置时超过 2500 (ru),系统将要求指定分区键。这适用于我,因为我想要在任何情况下分区数据,以在将来帮助具有缩放性和安全的不同而不同。
在中可以看到图 7,我已指定 8000 (ru),即略高于计算指示,且我已指定作为分区键的 SubscriptionId。
图 7 预配新的集合
此外,设置 ProcessDailyUsage newdailysplit 容器的 blob 触发器和输入和输出 Azure Cosmos DB 的绑定。输入的绑定可用来查找存在的某一天和注册的记录,并以处理重复项。我将确保我 FeedOptions 设置跨分区查询标志,如中所示图 8。
图 8 使用 FeedOptions 设置跨分区查询标志
string docsToDeleteQuery = String.Format(@"SELECT * FROM c where c.Enrollment =
""{0}"" AND c.Date = ""{1}""", enrollment, incomingDataDate);
FeedOptions queryOptions = new FeedOptions { MaxItemCount = -1,
EnableCrossPartitionQuery = true };
IQueryable<Document> deleteQuery = docDBClient.CreateDocumentQuery<Document>(
UriFactory.CreateDocumentCollectionUri(dbName, collectionName),
new SqlQuerySpec(docsToDeleteQuery), queryOptions);
log.Info("Delete documents");
int deletedDocumentCount = 0;
foreach (Document doc in deleteQuery)
{
await docDBClient.DeleteDocumentAsync(((dynamic)doc)._self,
new RequestOptions { PartitionKey =
new PartitionKey(((dynamic)doc).SubscriptionId) });
deletedDocumentCount++;
}
创建查询上获取注册的所有记录的日期,然后循环访问并将其删除。这是其中 SQL Azure 可能已使操作更容易通过发出删除查询或通过使用与已知的主键的 upsert 的一个实例。但是,在 Azure Cosmos DB,若要执行 upsert 我需要行 ID,这意味着我必须进行往返行程并执行针对我知道唯一标识该文档,然后使用该行的 id 或 selflink 的字段的比较。对于此示例,我只需删除所有记录,然后又添加了新 — 和可能更新 — 后的文档。若要执行此操作我需要在分区键将传递给 DeleteDocumentAsync 方法。是一种优化取回文档和执行本地比较、 更新任何更改的文档和添加 net 新文档。它是少量负担,因为每个文档中的元素的所有必须进行比较。由于没有定义的计费文档没有主键,可能可以查找匹配的文档使用 SubscriptionId、 MeterId、 InstanceId 和日期并比较的元素的从那里的其余部分。这将减轻从 Azure Cosmos DB 工作,并可以减少总体流量。
清除将文档添加回集合的方式,我只需循环访问文档并作为 Azure 函数的输出绑定定义 AddAsync documentCollector 上的调用:
// Update the enrollment field in the incomming collection
incomingDailyUsage.ForEach (usage => usage.Enrollment = enrollment);
int processedRecordCount=0;
foreach (EnrollmentUsageDetail usageDoc in incomingDailyUsage)
{
await documentCollector.AddAsync(usageDoc);
processedRecordCount++;
}
尽管这不是很多更改,我还已通过将注册编号添加到集合中每个文档完成很少的扩展。运行一个每日拆分文件生成中显示的日志信息图 9。
图 9 中每天的日志信息将文件拆分
2017-06-10T01:16:55.291 Function started (Id=bfb220aa-97ab-4d36-9c1e-602763b93ff0)
2017-06-10T01:16:56.041 First 15 chars: [{"AccountOwner
2017-06-10T01:16:56.181 get date
2017-06-10T01:16:56.181 getting enrollment
2017-06-10T01:16:56.181 Incoming date: 11/01/2016 for Enrollment: 4944727
2017-06-10T01:16:56.181 Collection: partitionedusage
2017-06-10T01:16:56.181 query: SELECT * FROM c where c.Enrollment = "4944727" AND c.Date = "11/01/2016"
2017-06-10T01:16:56.181 Create delete query
2017-06-10T01:16:56.197 Delete documents
2017-06-10T01:17:23.189 2142 docs deleted while processing 20161101-4944727-dailysplit.json
2017-06-10T01:17:23.189 Import documents
2017-06-10T01:17:44.628 2142 records imported from file 20161101-4944727-dailysplit.json
2017-06-10T01:17:44.628 Moving file 20161101-4944727-dailysplit.json to /processedusage container
2017-06-10T01:17:44.674 Deleting 20161101-4944727-dailysplit.json
2017-06-10T01:17:44.690 Completed!
2017-06-10T01:17:44.690 Function completed (Success, Id=bfb220aa-97ab-4d36-9c1e-602763b93ff0, Duration=49397ms)
最后的注释
唯一的操作左侧,以执行操作是使用不同的输入运行良好很多次迭代,然后测量以便我可以正确大小我正在使用的服务。这包括测试地理复制功能和需要订阅数据访问; 解决实现安全某些进一步原型制作这些是已两个选择 Azure Cosmos DB 的主要原因。可以收集 net 课程是一些我们似乎在领域中继续学习那些 IT:
- 没有幻项目符号,即使不使用无服务器体系结构。
- 执行任何操作将替换彻底的测试。
- 大小依赖服务并将该处理严重时调整你的硬件大小在过去。
- 请密切注意成本,尤其是在高吞吐量的情况下。
使用与 Azure 函数类似的无服务器计算的优势是仅为什么使用付费。对于此正则但很少发生的处理,这可能是在成本节约更大的好处。最后,配置功能是更好的体验并允许更快地产品比配置主机服务器。
Joseph Fultz是 microsoft 的云解决方案架构师。他负责与 Microsoft 客户合作,共同开发体系结构,从而利用 Microsoft Azure 解决业务问题。以前,Fultz 是用于开发和体系结构的 GM 的汽车共享程序 (mavendrive.com) 导致的。可以通过 Twitter (@JosephRFultz) 或电子邮件 (jofultz@microsoft.com) 与他联系。
衷心感谢以下 Microsoft 技术专家对本文的审阅:Fabio Calvacante