在 ASP.NET Core 中编写高性能 Web API 的4个小技巧

Web API 通常用来与外部模块进行通信、发送和接收数据,作为后端开发人员,应该把写出高性能的应用作为目标。

下面 4 个技巧是我在编写 Web API 的小技巧。

1 、大量数据使用分页查询

接口传输大量数据可能会导致严重的性能问题、过多的内存消耗和速度减慢。为了缓解这些可能的瓶颈,强烈建议使用分页查询。

分页查询允许请求者选择特定范围的值。

下面是在 ASP. NET Core 中实现分页的方法之一:

[HttpGet("error-logs/{skip}/{take}")]
public async Task<ActionResult<List<Log>>> GetErrorLogsPaginated([FromRoute] int skip = 0, [FromRoute] int take = 10)
{
    var logErrors = await _context.Logs
      .Where(l => l.LogLevel == InternalLogLevel.Error)
      .Skip(skip)
      .Take(take)
      .OrderBy(c => c.Id)
      .ToListAsync();

  return Ok(logErrors);
}

2、减少网络往返次数

避免网络往返意味着应该尽可能在单个调用中查询必要数据,而不是进行多个调用,然后将它们放在一起。

[HttpGet("services-ids")]
public async Task<ActionResult<List<int>>> GetServicesIds()
{
  var servicesIds = await _context.Services.AsNoTracking().Select(x => x.Id).ToListAsync();
  return Ok(servicesIds);
}

[HttpGet("logs-with-service")]
public async Task<ActionResult<List<Log>>> GetLogsByServiceIds([FromQuery] List<int> serviceIds)
{
  var logs = await _context.Logs
    .AsNoTracking()
    .Where(s => serviceIds.Contains(s.Id))
    .Select(s => new Log
    {
      Id = s.Id,
      Service = s.Service,
    })
    .ToListAsync();
  return Ok(logs);
}

这里有两个接口,第一个接口返回了所有的服务 id,第二个接口返回包含具有这些 id 的记录的日志。在上面的示例中,需要进行两次调用,这是不建议的。

应该按照以下方式进行:

[HttpGet("logs-with-service")]
public async Task<ActionResult<List<Log>>> GetAllServicesWithLogs()
{
  var services = await _context.Services
    .AsNoTracking()
    .ToListAsync();

  var serviceIds = services.Select(s => s.Id).ToList();
  
  var logs = await _context.Logs
    .AsNoTracking()
    .Where(l => serviceIds.Contains(l.ServiceId))
    .ToListAsync();

  return Ok(logs);
}

3、使用 Task 等待任务完成

使用 async Task 意味着请求管道可以等到异步方法完成。

ASP. NET Core 希望操作方法返回 Task,以便它可以正确继承到异步请求管道中。如果使用 async void,将会导致异步操作完成之前出现响应。

不要使用:async void

[HttpDelete("/{id}")]
public async void DeleteService([FromRoute] int id)
{
  await _context.Services.FirstOrDefaultAsync(s => s.Id == id);
  await Response.WriteAsync("Successfully deleted record");
}

应该用: async Task

[HttpDelete("/{id}")]
public async Task DeleteService([FromRoute] int id)
{
  await _context.Services.FirstOrDefaultAsync(s => s.Id == id);
  await Response.WriteAsync("Successfully deleted record");
}

4、对经常访问的数据使用缓存

缓存很有用,它是一种通过减少数据库资源消耗来显著提高应用程序性能的技术。

在第一次请求中,从数据库中检索数据并写入缓存。在后续的请求中,如果数据存在于缓存中,则会立即返回给请求者,而不需要查询数据库。

缓存最适合数据不经常更改的场景,ASP.NET Core 支持两种主要类型的缓存:内存缓存和分布式缓存。

内存缓存非常简单,通过 IMemoryCache 接口提供。IMemoryCache 表示存储在 Web 服务器内存中的缓存。

下面时一个使用内存缓存的示例:

[HttpGet("log-errors-cache-in-memory")]
public async Task<ActionResult<List<Log>>> GetErrorLogsWithCacheInMemory()
{
  const string CacheKey = "logs_with_error";

  if (!_memoryCache.TryGetValue(CacheKey, out List<Log>? logErrors))
  {
    logErrors = await _context.Logs
      .Where(l => l.LogLevel == InternalLogLevel.Error)
      .ToListAsync();

    var cacheEntryOptions = new MemoryCacheEntryOptions()
      .SetAbsoluteExpiration(TimeSpan.FromMinutes(30));

    _memoryCache.Set(CacheKey, logErrors, cacheEntryOptions);
  }

  return Ok(logErrors);
}

实现缓存的另一种方式是使用分布式缓存,它可以在多个服务器之间共享,并且通常作为外部服务部署维护。

ASP. NET Core 通过 IDistributedCache 接口提供分布式缓存处理方法,有 Redis、内存、SQL Server、NCache、Azure CosmosDB 多种实现。

要使用分布式缓存,你需要有一个运行的缓存服务实例。

下面的代码展示了一个使用分布式缓存的终端节点:

[HttpGet("log-errors-cache-distributed")]
public async Task<ActionResult<List<Log>>> GetErrorLogsWithCacheDistributed()
{
    const string CacheKey = "logs_with_error";
    List<Log>? logErrors = await _memcachedClient.GetValueAsync<List<Log>>(CacheKey);

    if (logErrors == null)
    {
      logErrors = await _context.Logs
        .Where(l => l.LogLevel == InternalLogLevel.Error)
        .ToListAsync();

      await _memcachedClient.SetAsync(CacheKey, logErrors, TimeSpan.FromMinutes(30));
    }

  return Ok(logErrors);
}

两端代码看起来非常相似,最大的区别在于缓存存储的位置。

posted @   邓磊1024  阅读(34)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示