Microsoft Forms 是一款用于创建调查和测验的产品。它广泛应用于 Microsoft365 商业版订阅者。许多 Microsoft365 教育订阅者使用测验来完成课堂测试和家庭作业。
Forms 后端服务有多个微服务,可处理各种工作负载(例如,提供静态和动态 Web 内容、为 Forms Web 客户端和集成方使用提供 REST API 等)。这些微服务构建在 .NET 上(主要是 .NET Framework 4.7.2 上的 ASP.NET WebForm/WebAPI)。
2022 年,我们将前端 REST API 服务迁移到 .NET 6,CPU 效率提高了近 200%,更重要的是,刷新了团队成员的技能(例如 SDK 样式项目文件和多目标、ASP.NET Core 应用程序开发) ,特别是中间件和过滤器管道)。今年早些时候,我们完成了后端服务的 .NET 6 迁移,该服务处理 CRUD REST API 来访问 SQL Azure 数据库中的数据。
我们的方法
我们花了几年时间分两个阶段为迁移做好了准备。我们首先针对&进行定位netstandard2.0
或多定位。net472
net6
- 第一阶段:删除了大多数对 HttpContext(和 System.Web 命名空间)的引用。
- 第二阶段:大多数依赖项都已升级或替换,以允许 .NET Core 应用程序使用,并且大多数项目都针对
netstandard2.0
或多目标net472
&net6
.
当我们最终开始迁移 Web 应用程序时,我们发现由于在第 1 阶段和第 2 阶段花费了工作,因此只需付出很少的努力即可消除不兼容性。
然而,我们确实需要花时间研究网络应用程序中的配置代码。以前,配置数据是通过临时方式访问的。无论何时需要一条配置信息,都会编写代码以某种方式检索它。我们将 .NET 6 迁移作为应用选项模式的机会。现在,所有代码结构都通过注入的 IOptions<> 或 IOptionsMonitor<> 访问配置数据。我们发现,通过这种方式,代码的可读性和可维护性得到了提高,并且组件消耗哪些配置数据变得非常明显。
netstandard2.0
随着时间的推移,我们发现目标或多目标变得越来越有问题。
- 越来越多的依赖项被删除
net472
,甚至是 .NET Framework 构建,因此我们必须坚持使用旧版本或在项目文件中到处添加 Condition 属性。- 而Condition属性只适用于多目标,所以我们需要越来越多地转向
netstandard2.0
多目标。 - 坚持使用旧版本并不总是有效,因为旧版本开始出现仅在 .NET Core 工作负载中出现的错误。新版本甚至开始与旧版本不兼容(在编译时)。
- 而Condition属性只适用于多目标,所以我们需要越来越多地转向
- 定位
netstandard2.0
意味着无法使用许多仅限 .NET Core 的依赖项,通常是那些具有更好性能和成本的依赖项。 - 即使对于多目标项目,也越来越需要条件编译,以适应两个运行时的不同性能/成本特征。例如,使用插值字符串作为日志内容对于 来说是一个不好的模式
net472
,因为这会无条件地创建字符串,但对于 .NET Core,只要 log 方法具有接受插值字符串处理程序的重载,则使用插值字符串作为日志内容是最有效、最具表现力的方式。 - 多目标会增加编译时间。由于我们仍然需要
net472
构建来运行遗留应用程序,.NET 团队建议避免定位netstandard2.0
并更频繁地进行多定位。 - 渐进式迁移还给我们带来了另一个问题——身份验证的不兼容。为了保持现有 ASP.NET 应用程序不受影响,我们选择迁移大量 OWIN 和 ASP.NET 代码来构建兼容的身份验证堆栈。
结果
我们将多个 ASP.NET 应用程序迁移到 ASP.NET Core,我们发现 CPU 效率(我们使用 RPS/CPU,即每个 CPU 的 RPS 百分比作为指标)在迁移后提高最多。我们认为大部分改进是由于将托管模型从 IIS 更新为 Kestrel。我们认为这就是为什么应用程序越简单,CPU 效率提高得越多。例如,2 个非常简单的 Web 应用程序的迁移都使 CPU 效率提高了 400% 以上,而访问数据库的相对复杂的后端应用程序则获得了 100% 以上的提高(当然,仍然非常突出)。
另一个显着的变化是线程数量。我们将两个进程的最小工作线程数设置为 400。以前,ASP.NET Web 应用程序在启动后几个小时内(有时甚至更快)将其推至 400+。现在,ASP.NET Core 应用程序在整个生命周期内使用 60-80 个线程。
前端服务CPU使用率
CPU 使用率的改进非常显着。在下图中,红线表示升级到 .NET 6 的服务。蓝线表示保留在 .NET Framework 上的服务。您可以看到 .NET 6 升级发生的时间点以及我们提供的改进。
前端服务关键API延迟P75
后端net472/net6服务CPU效率对比
在这里,您可以清楚地看到从 IIS+ASP.NET(浅蓝线表示 RPS,绿线表示进程 CPU)切换到 .NET 6 进程(蓝线表示 RPS,橙色线表示进程)带来的 CPU 效率显着提升中央处理器)。
总而言之,您可以看到前端和后端的 CPU 效率都有显着的提升,请求延迟和工作集的提升虽然不那么显着,但也令人兴奋。通过这些改进,我们的云服务计算成本降低了 30% 以上。
最重要的是,该团队在现代 .NET 技术堆栈上构建了新技能。每个团队成员都对此感到高兴和兴奋,并热切地寻求利用新平台的机会,编写更高效的代码,以及设计和构建更有吸引力的功能。现在,我们开始考虑 .NET 8!
文章作者 | Ray Yao(姚锐)