发布于 2026-01-06 6 阅读
0

如何提高 .NET DEV 全球展示挑战赛中 Web API 的性能(由 Mux 呈现):展示你的项目!

如何提高 .NET 中 Web API 的性能

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

在现代软件开发中,创建高性能 API 对于提供快速、稳定且响应迅速的用户体验至关重要。
无论您是构建新的 Web 应用程序还是优化现有应用程序,本文都将为您介绍 .NET 中提升性能的成熟策略。

这篇博文将带您了解 12 种可用于加快 API 速度、增强可扩展性和提高可维护性的基本技术。

我的网站antondevtips.com分享 .NET 和架构方面的最佳实践。
订阅即可成为更优秀的开发者。

1. 使用 async/await 进行异步编程

异步编程可以防止应用程序在等待 I/O 操作完成时阻塞线程。
这些操作包括:读取文件、等待数据库结果、等待 API 调用结果等等。

通过使用 async/await,.NET 可以并发处理更多请求而不会阻塞线程,从而提高可扩展性。
建议优先使用异步方法而非同步方法。

[HttpGet("items")]
public async Task<IActionResult> GetItemsAsync()
{
    var items = await _itemService.GetItemsAsync();
    return Ok(items);
}
Enter fullscreen mode Exit fullscreen mode

2. 改进读取查询的数据访问模式(使用 EF Core 或 Dapper)

数据访问模式对API性能影响巨大。目前常用的数据库访问库有两个:EF CoreDapper

为了改进数据访问模式,请考虑优化查询
添加数据库索引,优化查询,在 EF Core 中,考虑使用AsNoTracking只读查询。

// Read-only query with AsNoTracking
public async Task<List<User>> GetUsersAsync()
{
    return await _dbContext.Users
        .AsNoTracking()
        .Where(u => u.IsActive)
        .ToListAsync();
}
Enter fullscreen mode Exit fullscreen mode

如果您使用 Dapper,请确保使用参数化查询、建立正确的索引,并尽量减少与数据库的往返次数。

3. 利用投影、分页和筛选减少数据传输

传输大型数据集会降低 API 的运行速度。
因​​此,请仅返回客户端所需的数据。

从数据库读取数据时,请考虑使用:

  • 投影
  • 筛选、排序
  • 大型数据集的分页

投影功能允许您仅选择所需的字段,而不是检索整行数据:

var book = await context.Books
    .Include(b => b.Author)
    .Where(b => b.Id == id)
    .Select(b => new BooksPreviewResponse
    {
        Title = b.Title, Author = b.Author.Name, Year = b.Year
    })
    .FirstOrDefaultAsync(cancellationToken);
Enter fullscreen mode Exit fullscreen mode

为了提高读取查询的性能,可以考虑调整索引、过滤和排序条件。

对于大型数据集,请考虑使用分页来防止一次性向客户端返回过多数据:

[HttpGet("users")]
public async Task<IActionResult> GetUsersAsync(int pageNumber = 1,
    int pageSize = 10)
{
    var query = _dbContext.Users.AsNoTracking().Where(u => u.IsActive);

    var users = await query
        .Skip((pageNumber - 1) * pageSize)
        .Take(pageSize)
        .Select(u => new UserDto
        {
            Id = u.Id,
            Name = u.Name
        })
        .ToListAsync();

    return Ok(users);
}
Enter fullscreen mode Exit fullscreen mode

4. 最大程度减少 JSON 序列化开销

返回 JSON 响应时,默认System.Text.Json库通常比其他库更快Newtonsoft.Json
配置序列化选项可以去除不必要的字段,从而加快处理速度。

builder.Services
    .AddControllers()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;

        options.JsonSerializerOptions.IgnoreNullValues = true;
    });
Enter fullscreen mode Exit fullscreen mode
  • PropertyNamingPolicy = JsonNamingPolicy.CamelCase确保 JSON 中命名的一致性。
  • IgnoreNullValues = true:从输出中排除空属性,从而减小有效负载大小。

这虽然只是一个小小的优化,但却非常实用。

5. 添加缓存

缓存是您可以应用的最重要优化措施之一。
将频繁访问的数据存储在内存中或使用分布式缓存(例如 Redis)可以减少数据库往返次数。

我建议使用两种缓存方式:

  • 输出缓存
  • IDistributedCache / HybridCache (.NET 9) / Fusion Cache(存在多年的第三方库)

输出缓存会将整个 HTTP 响应存储一段时间,并在不再次执行控制器或 Minimal API 方法的情况下直接返回该响应:

builder.Services.AddOutputCache();

var app = builder.Build();
app.UseOutputCache();

[HttpGet("products")]
[OutputCache(Duration = 60)] // Cache response for 60 seconds
public async Task<IActionResult> GetProductsAsync()
{
    var products = await _productService.GetProductsAsync();
    return Ok(products);
}
Enter fullscreen mode Exit fullscreen mode

这是最容易添加到应用程序中的缓存类型,因为它几乎不需要任何代码。
您可以使用标签使缓存失效(清除)。

OutputCache 也支持 Redis。

如果您需要更精细的缓存控制,可以考虑使用缓存库。
最近我一直在研究.NET 9 中的混合缓存,并推荐使用它。
这是一种两级缓存:内存缓存 + 分布式缓存,可以解决缓存冲突问题(即多个请求同时遇到缓存未命中,并按照缓存旁路模式调用数据库)。

以下是如何使用HybridCache

builder.Services.AddHybridCache();

[HttpGet("orders/{id}")]
public async Task<IActionResult> GetOrderAsync(int id,
    [FromServices] IHybridCache cache)
{
    string cacheKey = $"Order_{id}";
    var order = await cache.GetOrCreateAsync(cacheKey, async entry =>
    {
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
        using var context = new AppDbContext();
        return await context.Orders.FindAsync(id);
    });

    if (order is null)
    {
        return NotFound();
    }

    return Ok(order);
}
Enter fullscreen mode Exit fullscreen mode

当多个应用程序需要连接到缓存,或者需要在应用程序重启后保留缓存时,请使用像 Redis 这样的分布式缓存。

6. 启用响应压缩

使用 Brotli 或 GZIP 压缩响应可以显著减小有效载荷的大小。
更小的响应意味着更快的数据传输速度和更好的用户体验。

Brotli 和 Gzip 可以减小输出的 JSON、HTML 或静态文件数据的大小。
在流程早期添加它们可以确保较小的有效负载。

builder.Services.AddResponseCompression(options =>
{
    options.EnableForHttps = true;
    options.Providers.Add<BrotliCompressionProvider>();
    options.Providers.Add<GzipCompressionProvider>();
});

builder.Services.Configure<BrotliCompressionProviderOptions>(options =>
{
    options.Level = System.IO.Compression.CompressionLevel.Fastest;
});

builder.Services.Configure<GzipCompressionProviderOptions>(options =>
{
    options.Level = System.IO.Compression.CompressionLevel.Fastest;
});

var app = builder.Build();
app.UseResponseCompression();
Enter fullscreen mode Exit fullscreen mode

7. 优化中间件管道

中间件的执行顺序会影响性能。
某些中间件应该尽早运行,而其他中间件(例如路由、身份验证和授权)则应该遵循逻辑顺序。

如果您使用响应压缩,请确保在提供静态文件之前添加压缩,以获得更小的有效负载:

var app = builder.Build();

// Correct middleware ordering
app.UseResponseCompression(); // Early in the pipeline
app.UseStaticFiles();         // Serve static files

app.UseRouting();

app.UseAuthentication(); // Authentication before authorization
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});
Enter fullscreen mode Exit fullscreen mode

8. 启用 HTTP/2 和 HTTP/3 协议

与 HTTP/1 相比,HTTP/2 和 HTTP/3 提供了显著的性能提升,包括多路复用和更低的延迟。
配置 Kestrel 以支持多种协议非常简单:

// In Program.cs
builder.WebHost.ConfigureKestrel(options =>
{
    // HTTP/1.1
    options.ListenAnyIP(5000);

    // HTTPS with HTTP/1.1, HTTP/2 and HTTP/3
    options.ListenAnyIP(5001, listenOptions =>
    {
        listenOptions.UseHttps();
        listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
    });
});

var app = builder.Build();
// Rest of the configuration
Enter fullscreen mode Exit fullscreen mode

支持较新协议的客户端将自动使用这些协议,而较旧的客户端可以回退到 HTTP/1.1。

9. 使用内容分发网络(CDN)

内容分发网络(CDN)是由地理位置分散的服务器组成的网络,它将内容缓存在靠近最终用户的服务器上。CDN
可以快速传输加载互联网内容所需的资源,包括 HTML 页面、JavaScript 文件、样式表、图像和视频。

当欧洲的客户端访问位于纽约的服务器所指向的网站时,可能会出现延迟问题。
当网站使用内容分发网络(CDN,例如 Cloudflare)时,距离客户端(在欧洲)最近的服务器会缓存网站内容,并将其返回给客户端,而无需向原始服务器发送请求。

通过将静态内容分发卸载到 CDN,您可以减轻自身服务器的负载并提高整体性能。

CDN 为您带来以下好处:

  • 减少往返次数:CDN 从更靠近客户端的边缘节点提供内容,从而加快加载速度。
  • 降低服务器负载:您的应用程序服务器专注于核心 API 功能。
  • 此外,许多 CDN 提供安全防护,可抵御垃圾邮件、DDoS 攻击和机器人攻击。

10.实施速率限制和节流

速率限制可以控制资源使用并防止滥用行为。
通过限制特定时间段内的请求数量,您可以保护 API 免受过载或 DDoS 攻击。

此外,还可以实施速率限制,根据用户的订阅情况处理一定数量的请求,并限制额外的请求。
例如,ChatGPT 就是利用这种方式工作的。

以下是如何在 ASP.NET Core 中使用内置功能实现速率限制的方法:

// In Program.cs
builder.Services.AddRateLimiter(options =>
{
    options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, IPAddress>(context =>
    {
        var ipAddress = context.Connection.RemoteIpAddress;
        return RateLimitPartition.GetFixedWindowLimiter(ipAddress,
            _ => new FixedWindowRateLimiterOptions
        {
            PermitLimit = 100, // Allow 100 requests
            Window = TimeSpan.FromMinutes(1), // Per 1 minute window
            QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
            QueueLimit = 0
        });
    });
});

var app = builder.Build();
app.UseRateLimiter();
Enter fullscreen mode Exit fullscreen mode

本示例使用 .NET 速率限制 API 将每个 IP 地址的请求数限制为每分钟 100 次。

11. 用极简 API 取代控制器

极简 API 可以减少传统 MVC 流程的一些开销。

.NET 9 中的 Minimal API 性能得到了极大的提升,每秒可处理的请求数比 .NET 8 多15%。
此外,Minimal API 的内存消耗量比以前的版本减少了93% 。

它们简洁、快速,并支持依赖注入、过滤器和其他常见的 ASP.NET Core 功能:

var app = builder.Build();

// Minimal API endpoint
app.MapGet("/customers/{id}", async (int id) =>
{
    var customer = await _dbContext.Customers.FindAsync(id);

    return customer is not null
        ? Results.Ok(customer)
        : Results.NotFound();
});

app.Run();
Enter fullscreen mode Exit fullscreen mode

与控制器相比,使用最小 API 几乎可以构建任何东西。
而且,微软在每个 .NET 版本中都主要关注最小 API 而不是控制器。

12. 使用 GraphQL

GraphQL 允许客户端在单个请求中从多个资源中精确查询所需字段,从而提升性能。
这与 REST API 不同,REST API 需要为每个资源发送一个新请求。

在 .NET 中实现 GraphQL 最流行且功能最丰富的库是Hot Chocolate

请考虑以下模型:

public record Product(
    int Id,
    string Name,
    decimal Price,
    bool IsAvailable
);

public record Review(
    int Id,
    int ProductId,
    string Content,
    int Rating
);

public record Recommendation(
    int Id,
    string Name,
    decimal Price
);
Enter fullscreen mode Exit fullscreen mode

以下是 GraphQL 查询:

public record Product(int Id, string Name, decimal Price);

public class Query
{
    [UseOffsetPaging(MaxPageSize = 100, IncludeTotalCount = true)]
    [UseProjection]
    [UseFiltering]
    [UseSorting]
    public IQueryable<Product> GetProducts(AppDbContext context)
    {
        return context.Products;
    }

    public Task<Product?> GetProductByIdAsync(AppDbContext context, int id)
    {
        return await context.Products.FirstOrDefaultAsync(p => p.Id == id);
    }

    public async Task<Recommendation> GetProductRecommendationsByIdAsync(
        AppDbContext context,
        IRecommendationService service,
        int productId,
        int recommendationsCount)
    {
        var product = await context.Products.FindAsync(productId);
        if (product is null)
        {
            return [];
        }

        var recommendations = await service.GetRecommendationsForProduct(product.Name, recommendationsCount)
            .ToListAsync();

        return recommendations;
    }

    public async Task<Review> GetReviewsByProductIdAsync(
        AppDbContext context, int productId)
    {
        var reviews = await context.Reviews
            .AsNoTracking()
            .Where(r => r.ProductId == productId)
            .ToListAsync();

        return reviews;
    }
}
Enter fullscreen mode Exit fullscreen mode

IQueryable<Product> GetProducts- 返回一个与 GraphQL 引擎连接的 IQueryable 对象,该对象使用以下中间件:

  • 过滤
  • 排序
  • 分页

其他方法是您在 Web API 端点中使用控制器或最小 API 时常用的典型方法。

以下是如何在客户端 Web 应用程序中使用 GraphQL 的方法:

query {
  getProducts(
    where: { name: { contains: "Phone" }, price: { eq: 1000 } }
    order: [{ name: ASC }
    skip: 10
    take: 20
  ) {
    id
    name
    price
    isAvailable
  }
}
Enter fullscreen mode Exit fullscreen mode

我们完全支持筛选、排序和分页功能,几乎无需编写任何代码;所有功能都由 Hot Chocolate 处理。

而这正是 GraphQL 相较于 REST API 的优势所在:

query {
  getProductById(id: 1) {
    id
    name
    price
    isAvailable

    getReviewsByProductId(productId: 1) {
      id
      content
      rating
    }
    getProductRecommendationsById(productId: 1) {
      id
      name
      price
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

在这里,我们只需向服务器发送一个 HTTP 请求,即可通过产品标识符获取产品信息、产品评价和推荐内容。
而使用 REST API 时,则需要发送三个单独的请求,或者自行实现 API 聚合器。

概括

考虑使用以下技术来显著提高 API 吞吐量和用户体验:

  • 利用 async/await
  • 优化数据库查询
  • 利用投影、分页和过滤
  • 最小化 JSON 序列化开销
  • 实现缓存(输出缓存、内存缓存、Redis 等)
  • 使用响应压缩
  • 谨慎订购中间件
  • 启用 HTTP/2 和 HTTP/3
  • 通过 CDN 提供静态资源
  • 增加速率限制和节流
  • 在 .NET 9 中切换到最小 API
  • 集成 GraphQL

逐步采用这些技巧,使用 OpenTelemetry、Benchmarks 等工具或 Application Insights 等基于云的工具来衡量改进情况,并不断改进您的 .NET Web 应用程序,以获得最佳结果。

我的网站antondevtips.com分享 .NET 和架构方面的最佳实践。
订阅即可成为更优秀的开发者。

文章来源:https://dev.to/antonmartyniuk/how-to-increase-performance-of-web-apis-in-net-4nnf