如何在 .NET Core Web API 应用程序中实现缓存

作者 : 慕源网 本文共4993个字,预计阅读时间需要13分钟 发布时间: 2022-03-10 共358人阅读

介绍

性能是应用程序的重要因素之一。影响应用程序性能的因素有很多(如数据库调用(查询)、Web 服务负载等)。我们必须更加专注于性能方面,以交付高质量的应用程序。为此,其中一个选项称为缓存。在本文中,我们将探讨什么是缓存以及如何在 .NET Core Web API 应用程序中实现缓存。

什么是缓存?

缓存是存储经常使用的数据的技术。通过消除对外部数据源的不必要请求,可以更快地为任何未来或后续请求提供这些数据。

为什么需要缓存机制?

有时我们的应用程序经常调用相同的方法并从数据库中获取数据。这些请求的输出始终相同。它不会在数据库中更改或更新。在这种情况下,我们可以使用缓存来减少数据库调用并直接从缓存中检索数据。

有 3 种类型的缓存可用,

  1. In-Memory Cache – 数据缓存在服务器的内存中。
  2. Persistent in-process Cache – 数据缓存在某个文件或数据库中。
  3. 分布式缓存– 数据缓存在共享缓存和多个进程中。示例: Redis 缓存

在本文中,我们主要关注 In-Memory Cache 机制。

内存缓存

In-Memory 缓存是指将缓存数据存储在服务器的内存中

优点

  • 它比其他缓存机制更容易和更快
  • 减少 Web 服务/数据库的负载
  • 提高性能
  • 高度可靠
  • 它适用于中小型应用。

缺点

  • 如果缓存配置不正确,它可能会消耗服务器的资源。
  • 增加维护。
  • 可扩展性问题。适用于单台服务器。如果我们有很多服务器,那么不能将缓存共享给所有服务器。

如何在 ASP.NET Core Web API 应用程序中实现内存缓存

前提条件

  • Visual Studio 2019 或 Visual Studio 2022

按照以下步骤使用 Visual Studio 2019 创建 ASP.NET Web API。

第1步

打开 Visual Studio 2019,单击Create a new project

第2步

选择ASP.NET Core Web 应用程序项目模板,然后单击下一步。

第 3 步

输入项目名称为Sample_Cache,然后单击 Next。

第四步

选择.NET Core 3.1项目,然后单击创建。

第 5 步

安装Microsoft.Extensions.Caching.Memory NuGet 包以实现内存缓存。

第 6 步

创建EmployeeController类并将内存缓存服务注入到构造函数中。

public class EmployeeController: ControllerBase {
    private ICacheProvider _cacheProvider;
    public EmployeeController(ICacheProvider cacheProvider) {
        _cacheProvider = cacheProvider;
    }
}

第 7 步

将services.AddMemoryCache()注册到Startup类 中的ConfigureServices()方法。

public void ConfigureServices(IServiceCollection services)
{
	services.AddMemoryCache();
}

简单的缓存实现

让我们在EmployeeController类中添加以下代码。

EmployeeController.cs

[Route("getAllEmployee")]
public IActionResult GetAllEmployee() {
    if (!_cache.TryGetValue(CacheKeys.Employees, out List < Employee > employees)) {
        employees = GetEmployeesDeatilsFromDB(); // Get the data from database
        var cacheEntryOptions = new MemoryCacheEntryOptions {
            AbsoluteExpiration = DateTime.Now.AddMinutes(5),
                SlidingExpiration = TimeSpan.FromMinutes(2),
                Size = 1024,
        };
        _cache.Set(CacheKeys.Employees, employees, cacheEntryOptions);
    }
    return Ok(employees);
}

Employee.cs

public class Employee
{
	public int Id { get; set; }
	[Required]
	public string FirstName { get; set; }
	[Required]
	public string LastName { get; set; }
	[Required]
	public string EmailId { get; set; }
}

CacheKeys.cs

public static class CacheKeys
{
	public static string Employees => "_Employees";
}

在上面的代码中,每当请求到来时,首先检查缓存是否有值(员工详细信息)。如果没有,则从数据库中检索员工详细信息并将其存储在缓存中。由于缓存有员工详细信息,因此对于下一个请求,员工详细信息是从缓存而不是数据库中获取的。

内存缓存参数

  • Size – 这允许您设置此特定缓存条目的大小,以便它不会开始消耗服务器资源。
  • SlidingExpiration -缓存将处于非活动状态的时间。如果在此特定时间段内没有任何人使用缓存条目,它将过期。在我们的例子中,我们将 SlidingExpiration 设置为 2 分钟。如果 2 分钟内没有对该缓存条目的请求,则缓存将被删除。
  • AbsoluteExpiration -缓存值的实际过期时间。一旦达到时间,缓存条目将被删除。在我们的例子中,我们将 AbsoluteExpiration 设置为 5 分钟。一旦时间达到 5 分钟,缓存将过期。绝对到期不应小于滑动到期。

输出

第一次请求 

第二个请求

问题

如果同一项目同时有多个请求,则请求不会等待第一个完成。由于缓存中没有值,因此在设置缓存之前,所有请求都会发生数据库调用。

例如,

  1. 用户“A”发送对员工详细信息的请求。这里的缓存没有任何值。因此,此请求调用数据库以检索员工详细信息。假设从数据库中获取员工详细信息需要 5 秒。
  2. 同时(在完成用户“A”请求之前),用户“B”发送相同员工详细信息的请求。这里的缓存没有任何价值。所以,这个请求也有一个数据库调用。

使用锁实现缓存

我们将使用锁来解决上述问题。

例如,

  1. 用户“A”发送对员工详细信息的请求。这里的缓存没有任何值。因此,它进入锁定状态并进行数据库调用以检索员工详细信息。
  2. 同时,用户“B”发送对员工详细信息的请求。这个请求会一直等到锁被释放。这将减少对相同数据的数据库调用。
  3. 一旦从数据库中检索到员工详细信息,它就会存储在缓存中。现在,锁被释放并向用户“A”发送响应。
  4. 现在,从缓存中获取值并向用户“B”发送响应。

我们将使用锁重构缓存机制。让我们考虑下面的代码。

EmployeeController.cs

[Route("api/[controller]")]
public class EmployeeController: ControllerBase {
    private ICacheProvider _cacheProvider;
    public EmployeeController(ICacheProvider cacheProvider) {
            _cacheProvider = cacheProvider;
        }
        [Route("getAllEmployee")]
    public IActionResult GetAllEmployee() {
        try {
            var employees = _cacheProvider.GetCachedResponse().Result;
            return Ok(employees);
        } catch (Exception ex) {
            return new ContentResult() {
                StatusCode = 500,
                    Content = "{ \n error : " + ex.Message + "}",
                    ContentType = "application/json"
            };
        }
    }
}

CacheProvider.cs

public class CacheProvider: ICacheProvider {
    private static readonly SemaphoreSlim GetUsersSemaphore = new SemaphoreSlim(1, 1);
    private readonly IMemoryCache _cache;
    public CacheProvider(IMemoryCache memoryCache) {
        _cache = memoryCache;
    }
    public async Task < IEnumerable < Employee >> GetCachedResponse() {
        try {
            return await GetCachedResponse(CacheKeys.Employees, GetUsersSemaphore);
        } catch {
            throw;
        }
    }
    private async Task < IEnumerable < Employee >> GetCachedResponse(string cacheKey, SemaphoreSlim semaphore) {
        bool isAvaiable = _cache.TryGetValue(cacheKey, out List < Employee > employees);
        if (isAvaiable) return employees;
        try {
            await semaphore.WaitAsync();
            isAvaiable = _cache.TryGetValue(cacheKey, out employees);
            if (isAvaiable) return employees;
            employees = EmployeeService.GetEmployeesDeatilsFromDB();
            var cacheEntryOptions = new MemoryCacheEntryOptions {
                AbsoluteExpiration = DateTime.Now.AddMinutes(5),
                    SlidingExpiration = TimeSpan.FromMinutes(2),
                    Size = 1024,
            };
            _cache.Set(cacheKey, employees, cacheEntryOptions);
        } catch {
            throw;
        } finally {
            semaphore.Release();
        }
        return employees;
    }
}

代码说明

首先,我们正在检查缓存是否有价值。

bool isAvailable = _cache.TryGetValue(cacheKey, out List<Employee> employees);

if (isAvailable)
	return employees;

如果缓存中的值可用,则从缓存中获取值并向用户发送响应,否则异步等待进入信号量。

await semaphore.WaitAsync();

一旦一个线程被授予访问信号量的权限,为了安全起见,重新检查之前是否已经填充了该值(避免并发线程访问)。

isAvailable = _cache.TryGetValue(cacheKey, out employees);

if (isAvailable)
	return employees;

然后在缓存中仍然没有值,调用数据库并将值存储在缓存中。

employees = EmployeeService.GetEmployeesDeatilsFromDB();

var cacheEntryOptions = new MemoryCacheEntryOptions
{
	AbsoluteExpiration = DateTime.Now.AddMinutes(5),
	SlidingExpiration = TimeSpan.FromMinutes(2),
	Size = 1024,
};

_cache.Set(cacheKey, employees, cacheEntryOptions);

向用户发送响应。

概括

在本文中,我们使用 .NET Core Web API 学习了以下功能。 

  • 什么是缓存, 
  • 缓存机制的类型
  • 关于内存缓存的详细信息
  • 在 ASP.NET Core Web API 中实现内存中缓存。 

慕源网 » 如何在 .NET Core Web API 应用程序中实现缓存

常见问题FAQ

程序仅供学习研究,请勿用于非法用途,不得违反国家法律,否则后果自负,一切法律责任与本站无关。
请仔细阅读以上条款再购买,拍下即代表同意条款并遵守约定,谢谢大家支持理解!

发表评论

开通VIP 享更多特权,建议使用QQ登录