6) 遥测(日志、指标、追踪)
以下代码添加了 OpenTelemetry 来收集 .NET 应用中的日志、指标和追踪。
builder.Services.AddOpenTelemetry()
.UseOtlpExporter()
.WithMetrics(m => m.AddAspNetCoreInstrumentation().AddHttpClientInstrumentation())
.WithTracing(t => t.AddAspNetCoreInstrumentation().AddHttpClientInstrumentation());
UseOtlpExporter()告诉它将遥测数据发送到哪里。通常是一个 OTLP 收集器(如 Grafana、Jaeger、Tempo、Azure Monitor)。这样你就可以在仪表板中可视化指标和追踪。WithMetrics()意味着它将收集指标。这些指标包括请求率(RPS)、请求持续时间(延迟)、GC 暂停、异常、HTTP 客户端计时。.WithTracing(...)意味着它将收集分布式追踪。当你的应用调用其他 API 或微服务时,这很有用。你可以看到一个请求从一项服务到另一项服务的完整路径,包括时间和瓶颈。
.NET 诊断工具
当你的应用上线运行时,你应该了解以下工具。你知道飞机上有一个黑匣子记录仪,用于了解飞机失事的原因。对于 .NET 来说,以下就是我们的黑匣子记录仪。它们在不附加调试器的情况下捕获发生的情况。
| 工具 | 功能 | 使用时机 |
|---|---|---|
dotnet-counters |
CPU、GC、请求率等实时指标 | 监控运行中的应用 |
dotnet-trace |
CPU 采样与性能追踪 | 查找运行缓慢的代码 |
dotnet-gcdump |
GC 堆转储(分配情况) | 诊断内存问题 |
dotnet-dump |
完整进程转储 | 调查崩溃或挂起 |
dotnet-monitor |
暴露上述所有功能的 HTTP 服务 | 通过 API 收集遥测数据 |
7) 以正确的方式在 Docker 中构建和运行 .NET 应用
多阶段构建是一种 Docker 技术,你使用一个镜像来构建应用,使用另一个更小的镜像来运行它。我们之所以进行多阶段构建,是因为 .NET SDK 镜像很大,但包含了所有构建工具。而 .NET 运行时镜像则很小,并为生产环境优化。你只将构建阶段发布的输出复制到运行阶段。
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
COPY . .
RUN dotnet restore
RUN dotnet publish -c Release -o /app/out -p:PublishTrimmed=true -p:PublishSingleFile=true -p:ReadyToRun=true
FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
COPY --from=build /app/out .
ENTRYPOINT ["./YourApp"] # 或者 ["dotnet","YourApp.dll"]
我来解释一下这些 Dockerfile 命令:
阶段1:构建
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build使用包含编译器和工具的 .NET SDK 镜像。AS build命名允许你稍后引用这个阶段。WORKDIR /src设置容器内的工作目录。COPY . .将你的源代码复制到容器中。RUN dotnet restore还原 NuGet 包。RUN dotnet publish ...以 Release 模式构建项目,为生产环境优化,并输出到/app/out。 相关标志:PublishTrimmed=true-> 移除未使用的代码PublishSingleFile=true-> 将所有内容捆绑到一个文件中ReadyToRun=true-> 预编译代码以实现更快的启动
阶段2:运行
FROM mcr.microsoft.com/dotnet/aspnet:9.0使用更轻量级的运行时镜像,没有编译器,只有运行时。WORKDIR /app你的应用将在容器内存放的位置。ENV ASPNETCORE_URLS=http://+:8080让应用监听 8080 端口(以及所有网络接口)。EXPOSE 8080记录你的容器使用的端口(用于 Docker/K8s 网络)。COPY --from=build /app/out .从 构建阶段 复制发布的输出到这个最终镜像。ENTRYPOINT ["./YourApp"]定义容器启动时运行的命令。如果你发布为单文件,则是./YourApp。否则,使用dotnet YourApp.dll。
8) 安全性
无处不在的 HTTPS,即使在代理后面
即使你的应用运行在像 Nginx、Cloudflare 或负载均衡器这样的反向代理后面,也要始终强制使用 HTTPS。为什么?因为如果不使用 SSL,内部流量仍然可能被截获,而且 Cookie、HSTS、浏览器 API 都需要 HTTPS。在 .NET 中,你可以像这样轻松地强制使用 HTTPS:
app.UseHttpsRedirection();
在生产环境中使用 HSTS
HSTS(HTTP 严格传输安全)告诉浏览器:
始终对此域名使用 HTTPS——甚至不要再尝试 HTTP!
一旦设置,浏览器会缓存此规则,因此用户不会意外访问不安全版本。你可以像下面这样轻松地强制执行:
if (!app.Environment.IsDevelopment())
{
app.UseHsts();
}
当你使用 HSTS 时,它会向浏览器发送这个 HTTP 头部: Strict-Transport-Security: max-age=31536000; includeSubDomains。浏览器将在 1 年(31,536,000 秒)内记住此设置,即该站点必须仅使用 HTTPS。并且 includeSubDomains 选项将此规则也应用于所有子域名(例如:api.abp.io, cdn.abp.io, account.abp.io 等)。
将密钥存储在环境变量或密钥存储中
切勿将密码、连接字符串或 API 密钥存储在代码或 Git 中。那么我们应该把它们保存在哪里呢?
最好/最实用的方式是使用环境变量。你可以在类 Unix 系统中轻松设置环境变量,如下所示:
export ConnectionStrings__Default="Server=...;User Id=...;Password=..."你可以像这样轻松地从 .NET 应用中访问这些环境变量:
var conn = builder.Configuration.GetConnectionString("Default");
或者使用密钥存储,例如:Azure Key Vault、AWS Secrets Manager、HashiCorp Vault。
为公共端点添加速率限制
别忘了,使用你应用的可能不只是"天真的人"!我们过去在面向公众的网站上多次遇到这个问题。所以,要保护你的公共 API 免受滥用、机器人和 DDoS 攻击。使用速率限制!!!阻止暴力破解攻击,防止你的资源耗尽……
在 .NET 中,有一个内置的速率限制功能(System.Threading.RateLimiting):
builder.Services.AddRateLimiter(_ => _
.AddFixedWindowLimiter("default", options =>
{
options.PermitLimit = 100;
options.Window = TimeSpan.FromMinutes(1);
}));
app.UseRateLimiter();
- 还有一个开源的速率限制库 -> github.com/stefanprodan/AspNetCoreRateLimit
- 另一个 -> nuget.org/packages/Polly.RateLimiting
安全的 Cookie
Cookie 通常是攻击的良好目标。你必须正确保护它们,否则可能面临 Cookie 窃取或 CSRF 攻击。
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Strict; // 或者 Lax
SecurePolicy = Always-> 仅通过 HTTPS 发送 CookieSameSite=Lax/Strict-> 防止 CSRF(跨站请求伪造)Strict= 最安全Lax= 对于登录会话来说是良好的平衡
9) 启动/冷启动
保持分层 JIT 开启
JIT(即时)编译器 在代码运行时将你应用的中间语言(IL)转换为本机 CPU 指令。分层 JIT 意味着运行时使用 2 个阶段的编译。实际上,这个设置在现代 .NET 中默认是启用的。所以只需保持开启。
- 第 0 层(快速 JIT): 快速、低优化的编译 -> 让你的应用尽快运行。 (在启动时使用。)
- 第 1 层(优化 JIT): 随后,运行时会以更深入的优化重新编译热点方法(经常使用的方法),以提高速度。
使用 PGO(配置文件引导优化)
PGO 让 .NET 从你应用的实际使用中学习。它分析哪些函数最常被使用,然后根据该模式重新优化构建。你可以把它想象成运行时说:
我已经看到了你的应用实际在做什么……我会相应地重新安排和优化代码路径。
在 .NET 8+ 中,你不必手动启用 PGO(配置文件引导优化)。JIT 收集运行时分析数据(例如哪些类型是常见的,分支预测)并随后使用它来生成更优化的代码。在 .NET 9 中,PGO 得到了改进:JIT 将 PGO 数据用于更多模式(如类型检查/转换)并做出更好的决策。
10) 优雅关闭
当我们和爱人分手时,常常会争吵并在事后后悔。当应用程序与操作系统"分手"时,应该处理得好一些 😘 ...
当你的应用停止时,可能是因为你部署了新版本,或者 Kubernetes 重启了 Pod…… 操作系统会发送一个名为 SIGTERM(终止)的信号。
优雅关闭 意味着妥善处理该信号,完成正在运行的任务,清理资源,然后干净地退出(像个成年人一样)!
var app = builder.Build();
var lifetime = app.Services.GetRequiredService<IHostApplicationLifetime>();
lifetime.ApplicationStopping.Register(() =>
{
// 停止接受新请求,完成进行中的请求,刷新遥测数据
});
app.Run();
在 K8s 上,设置 terminationGracePeriodSeconds 并配置就绪/启动探针。
11) 负载测试
有时候和爱人争吵是件好事。我们可以在结婚前看清他/她的真面目 😀 使用 k6 或 bombardier,并使用真实的负载和类似生产的限制进行测试。别等到你的应用在生产环境运行时才感到惊讶!这些主题都应该被测试:CPU %、GC 中的时间、LOH 分配、ThreadPool 队列长度 和 Socket 耗尽。
关于 K6
- 一个使用 Go 和 JavaScript 的现代负载测试工具。
- 在 GitHub 上有 29K 星
- GitHub 地址:https://github.com/grafana/k6
关于 Bombardier
- 用 Go 编写的快速跨平台 HTTP 基准测试工具。
- 在 GitHub 上有 7K 星
- GitHub 地址:https://github.com/codesenberg/bombardier
[
总结
总而言之,我列出了 11 项用于优化生产环境 .NET 应用的内容;涵盖了构建配置、托管设置、运行时行为、数据访问、遥测、容器化、安全性、启动性能以及在负载下的可靠性。通过应用本系列第一和第二部分中的清单,利用诸如裁剪发布、服务器 GC、最小化负载、池化 DbContext、OpenTelemetry、多阶段 Docker 构建、HTTPS 强制实施以及适当的关闭处理等技术——你将提升应用在真实流量和生产约束下的耐久性、可扩展性和可维护性。每一项都是一个检查点,你将能够交付一个健壮、高性能的 .NET 应用,为真实用户做好准备。
译自:https://abp.io/community/articles/optimize-your-dotnet-app-for-production-for-any-.net-app-2-78xgncpi

Comments