.net core 2.x - 缓存的四种方式

时间:2021-07-30 16:47:14

其实这些微软docs都有现成的,但是现在的人想对浮躁些,去看的不会太多,所以这里就再记录下 ,大家一起懒一起浮躁,呵呵.

0.基础知识

通过减少生成内容所需的工作,缓存可以显著提高应用的性能和可伸缩性。 缓存对不经常更改的数据效果最佳。 缓存生成的数据副本的返回速度可以比从原始源返回更快。 在编写并测试应用时,应避免依赖缓存的数据。ASP.NET Core 支持多种不同的缓存。 最简单的缓存基于 IMemoryCache,它表示存储在 Web 服务器内存中的缓存。 在包含多个服务器的服务器场上运行的应用应确保在使用内存中缓存时,会话是粘性的。 粘性会话可确保来自客户端的后续请求都转到同一台服务器。 例如,Azure Web 应用使用应用程序请求路由(ARR) 将所有的后续请求路由到同一台服务器。Web 场中的非粘性会话需要分布式缓存以避免缓存一致性问题。 对于某些应用,分布式的缓存可以支持更高版本向外缩放比内存中缓存。 使用分布式缓存可将缓存内存卸载到外部进程。内存中缓存可以存储任何对象;分布式缓存接口仅限于byte[]

这里只说有这么几种方式,但是不去深入.不喜拉到.

1.常见缓存响应的四种方式

1.1.内存缓存

  顾名思义,缓存在内存中,生命周期默认伴随应用程序

1.2.响应缓存

  响应缓存可减少客户端或代理到 web 服务器发出的请求数。 响应缓存还减少了工作的 web 服务器执行以生成响应。 响应缓存控制标头,指定要如何客户端、 代理和响应缓存中间件。

1.3.响应缓存中间件

  Microsoft.AspNetCore.ResponseCaching 包中的ResponseCaching

1.4.分布式缓存

  就是你对象,她会霸道的和你说和别人说,你是他的....但是到最后到底谁会不是是谁的谁 谁也不知道,但是她有对你的使用权,逛街包要挂在你身上,取东西要从你身上的包里拿出来.

其中的1-3点都是对core本身开刀的手术,4点是不仅对自己动刀子,还要对别人家(服务)动刀子...嗯,比较坏.

1.1内存缓存

  内存缓存即我们常用的System.Runtime.Caching/MemoryCache.有关缓存方法的说明,请参阅IMemoryCache 方法CacheExtensions 方法 .

  对象    : IMemoryCache

  可选配置项: MemoryCacheEntryOptions

      • 设置绝对到期时间。 这是条目可以被缓存的最长时间,防止可调过期持续更新时该条目过时太多。
      • 设置可调过期时间。 访问此缓存项的请求将重置可调过期时钟。
      • 缓存优先级设置为CacheItemPriority.NeverRemove
      • 设置一个PostEvictionDelegate它将在条目从缓存中清除后调用。 在代码中运行该回调的线程不同于从缓存中移除条目的线程。

  在core中我们使用内存缓存只需要注入 IMemoryCache 即可,然后就是一股脑地 get,getString, set....一顿操作猛如虎;具体案例见官方提供的参考.

1.2响应缓存

  a)  HTTP 1.1 缓存的缓存控制标头的规范需要接受是有效的缓存Cache-Control客户端发送的标头。 客户端可以发出请求的no-cache标头值和强制服务器生成的每个请求的新响应。始终遵循客户端Cache-Control请求标头是有意义,如果您考虑 HTTP 缓存的目标。 在正式规范,缓存旨在减少的满足请求的客户端、 代理和服务器的网络延迟和网络开销。 它不一定是一种方法来控制源服务器上的负载。没有任何当前开发人员可以控制此缓存的行为使用时响应缓存中间件因为中间件遵循正式缓存规范。 未来的增强功能到中间件将允许配置中间件,若要忽略的请求Cache-Control标头决定用于缓存的响应时。 使用中间件时,这将为您提供更好地控制负载在服务器上的机会。

  b) ResponseCacheAttribute指定缓存响应中设置相应的标头所需的参数。ResponseCache特性可应用于操作 (方法) 和控制器 (类)。 方法级属性重写在类级别特性中指定的设置。

1.3响应缓存中间件

  引用Microsoft.AspNetCore.App 元包或添加到的包引用Microsoft.AspNetCore.ResponseCaching包。

  配置:

  在Startup.ConfigureServices,将中间件添加到服务集合。

  services.AddResponseCaching();

  将应用配置为使用与中间件UseResponseCaching扩展方法,它将中间件添加到请求处理管道。 该示例应用将添加Cache-Control缓存最多 10 秒的可缓存响应的响应标头。 该示例将发送Vary标头用于配置中间件来提供缓存的响应才Accept-Encoding后续请求标头匹配的原始请求。 中的代码示例中, CacheControlHeaderValueHeaderNames需要using语句Microsoft.Net.Http.Headers命名空间。

  在startUp.cs的 Configure中

  app.UseResponseCaching();

   app.Use(async (context, next) => {

      // For GetTypedHeaders, add: using Microsoft.AspNetCore.Http;

    context.Response.GetTypedHeaders().CacheControl = new Microsoft.Net.Http.Headers.CacheControlHeaderValue() {Public = true, MaxAge = TimeSpan.FromSeconds(10) };

    context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.Vary] = new string[] { "Accept-Encoding" };

    await next();

    });

  app.UseMvc();

 选项:

  中间件提供了三个选项用于控制响应缓存。

选项 描述
UseCaseSensitivePaths 确定是否在区分大小写的路径上会缓存响应。 默认值为 false
MaximumBodySize 以字节为单位的响应正文最大缓存大小。 默认值是64 * 1024 * 1024(64 MB)。
大小限制 以字节为单位的响应缓存中间件大小限制。 默认值是100 * 1024 * 1024(100 MB)。

下面的示例配置到中间件:

    • 小于或等于 1024 字节的缓存响应。
    • 将响应存储通过区分大小写的路径 (例如,/page1/Page1单独存储)。
  services.AddResponseCaching(options =>
  {
  options.UseCaseSensitivePaths = true;
  options.MaximumBodySize = 1024;
  });

VaryByQueryKeys:

在使用 MVC/Web API 控制器或 Razor 页面页模型ResponseCache属性指定设置适当的标头为响应缓存所需的参数。 唯一参数ResponseCache严格需要中间件的属性是VaryByQueryKeys,这并不对应于实际的 HTTP 标头。 有关详细信息,请参阅ResponseCache 属性

不使用时ResponseCache属性中,响应缓存可以与变化VaryByQueryKeys功能。 使用ResponseCachingFeature直接从IFeatureCollectionHttpContext:

 var responseCachingFeature = context.HttpContext.Features.Get<IResponseCachingFeature>();
if (responseCachingFeature != null){
      responseCachingFeature.VaryByQueryKeys = new[] { "MyKey" };
  }

使用单个值等于*VaryByQueryKeys随缓存所有请求查询参数而都变化。

   响应缓存中间件所使用的 HTTP 标头

  用 HTTP 标头配置中间件的响应缓存。

  .net core 2.x - 缓存的四种方式

  .net core 2.x - 缓存的四种方式

1.4分布式缓存  

  上面的不是什么重点,小项目用用玩不是问题,所以没太大意思,重点是这里的分布式缓存实现.

  分布式的缓存是由多个应用程序服务器,通常作为对其进行访问的应用程序服务器的外部服务维护共享缓存。 分布式的缓存可以提高性能和可伸缩性的 ASP.NET Core 应用,尤其是当应用程序托管的云服务或服务器场。

  分布式的缓存具有几大优势,其中缓存的数据存储在单个应用程序服务器其他缓存方案。

  当分布式缓存的数据,则数据:

  • 是连贯(一致) 跨多个服务器的请求。
  • 服务器重新启动和应用部署仍然有效。
  • 不使用本地内存。

    分布式的缓存配置是特定于实现的。 本文介绍如何配置 SQL Server 和 Redis 分布式的缓存。 第三方实现也是可用,如NCache(GitHub 上的 NCache)。 无论选择哪一种实现,该应用程序与使用缓存进行交互IDistributedCache接口。

  系统必备

  若要使用的 SQL Server 分布式缓存,引用Microsoft.AspNetCore.App 元包或添加到的包引用Microsoft.Extensions.Caching.SqlServer包。

  若要使用 Redis 分布式缓存,引用Microsoft.AspNetCore.App 元包并添加到的包引用Microsoft.Extensions.Caching.Redis包。 Redis 包不包括在Microsoft.AspNetCore.App包,因此您必须在项目文件中分别引用 Redis 包。

   IDistributedCache 接口

IDistributedCache接口提供了以下方法操作的分布式的缓存实现中的项:

  • GetGetAsync –接受字符串键和检索缓存的项作为byte[]数组如果在缓存中找到。
  • SetSetAsync –中添加项 (作为byte[]数组) 到使用字符串键的缓存。
  • RefreshRefreshAsync –刷新缓存基于其密钥,重置其滑动到期超时值 (如果有) 中的项。
  • RemoveRemoveAsync –移除缓存项根据其字符串键值。

  建立分布式缓存服务

  注册的实现IDistributedCacheStartup.ConfigureServices。 本主题中所述的框架提供实现包括:

  分布式的内存缓存

  分布式内存缓存 (AddDistributedMemoryCache) 是框架提供的实现IDistributedCache,在内存中存储项。 分布式内存缓存不是实际的分布式的缓存。 缓存的项存储在运行该应用程序的服务器上的应用程序实例。

  分布式内存缓存是一个有用的实现:

  • 在开发和测试方案。
  • 生产和内存消耗情况中使用一台服务器时不会产生问题。 实现分布式内存缓存摘要缓存数据存储。 它允许实现真正的分布式缓存解决方案在将来如果多个节点或容错能力变得非常必要。

  示例应用将在开发环境中运行应用时使用的分布式内存缓存:

public void ConfigureServices(IServiceCollection services)
{ if (_hostContext.IsDevelopment()){ services.AddDistributedMemoryCache();}
   else
   {
      services.AddDistributedSqlServerCache(options =>
      {
        options.ConnectionString = _config["DistCache_ConnectionString"];
        options.SchemaName = "dbo";
        options.TableName = "TestCache";
       });
     }
   }
}

  1.4.1.分布式的 SQL 服务器缓存

  SQL Server 的分布式缓存实现 (AddDistributedSqlServerCache) 允许使用 SQL Server 数据库作为其后备存储分布式的缓存。 若要在 SQL Server 实例中创建的 SQL Server 缓存的项表中,可以使用sql-cache工具。 该工具使用名称和指定的架构创建一个表。通过运行 SQL Server 中创建一个表sql-cache create命令。 提供 SQL Server 实例 (Data Source),数据库 (Initial Catalog),架构 (例如, dbo),以及表名 (例如, TestCache):

  dotnet sql-cache create "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=DistCache;Integrated Security=True;" dbo TestCache

  记录一条消息以指示该工具已成功:

   Table and index were created successfully.

   创建的表sql-cache工具具有以下架构:

.net core 2.x - 缓存的四种方式

 备注

  应用应操作使用的实例的缓存值IDistributedCache,而不SqlServerCache

  本示例应用实现SqlServerCache非开发环境中:

public void ConfigureServices(IServiceCollection services)
{
if (_hostContext.IsDevelopment())
{
services.AddDistributedMemoryCache();
}
else
{
services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString =
_config["DistCache_ConnectionString"];
options.SchemaName = "dbo";
options.TableName = "TestCache";
});
}

备注

  一个ConnectionString(和 (可选)SchemaNameTableName) 通常存储在源代码管理之外 (例如,通过存储机密管理器中或在appsettings.json /appsettings。{Environment}.json文件)。 连接字符串可能包含应从源代码管理系统的凭据。

  1.4.2.分布式的 Redis 缓存

Redis是一种开源的内存中数据存储,通常用作分布式缓存。 您可以使用 Redis 本地,并且您可以配置Azure Redis 缓存Azure 托管 ASP.NET Core 应用。 应用配置缓存实现使用RedisCache实例 (AddDistributedRedisCache):

services.AddDistributedRedisCache(options =>
{
options.Configuration = "localhost";
options.InstanceName = "SampleInstance";
});

  若要在本地计算机上安装 Redis:

  使用分布式的缓存

  若要使用IDistributedCache接口,请求的实例IDistributedCache从任何应用程序中的构造函数。 实例由提供依赖关系注入 (DI)。当应用启动时IDistributedCache注入到Startup.Configure。 使用缓存的当前时间IApplicationLifetime(有关详细信息,请参阅Web 主机: IApplicationLifetime 接口):

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime lifetime, IDistributedCache cache)
{
lifetime.ApplicationStarted.Register(() =>
{
var currentTimeUTC = DateTime.UtcNow.ToString();
byte[] encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
var options = new DistributedCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds());
cache.Set("cachedTimeUTC", encodedCurrentTimeUTC, options);
});

  示例应用程序注入IDistributedCacheIndexModel以供索引页。

  每次加载索引页时,缓存时间检查缓存OnGetAsync。 如果尚未过期的缓存的时间,将显示时间。 如果自上一次访问缓存的时间 (已加载此页的最后一个时间) 已过去 20 秒,该页将显示缓存时间已过

  通过选择,立即更新为当前时间的缓存的时间重置缓存时间按钮。 按钮触发器OnPostResetCachedTime处理程序方法。

public class IndexModel : PageModel
{
private readonly IDistributedCache _cache; public IndexModel(IDistributedCache cache)
{
_cache = cache;
} public string CachedTimeUTC { get; set; } public async Task OnGetAsync()
{
CachedTimeUTC = "Cached Time Expired";
var encodedCachedTimeUTC = await _cache.GetAsync("cachedTimeUTC"); if (encodedCachedTimeUTC != null)
{
CachedTimeUTC = Encoding.UTF8.GetString(encodedCachedTimeUTC);
}
} public async Task<IActionResult> OnPostResetCachedTime()
{
var currentTimeUTC = DateTime.UtcNow.ToString();
byte[] encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
var options = new DistributedCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds());
await _cache.SetAsync("cachedTimeUTC", encodedCurrentTimeUTC, options); return RedirectToPage();
}
}

    备注

  无需为IDistributedCache实例使用 Singleton 或 Scoped 生命周期(至少对内置实现来说是这样的)。

  此外可以创建IDistributedCache实例可能需要某一个而不是使用 DI,但在代码中创建实例会使代码难以测试和违反显式依赖关系原则

  建议

  确定哪一种实现的时IDistributedCache最适合于您的应用程序,请考虑以下:

  • 现有的基础结构
  • 性能要求
  • 成本
  • 团队体验

  缓存解决方案通常依赖于内存中存储提供快速检索的缓存数据,但内存是有限的资源,而且成本展开。 仅存储通常用于缓存中的数据。通常情况下,Redis 缓存提供更高的吞吐量和延迟低于 SQL Server 缓存。 但是,进行基准测试时通常需要确定的缓存策略的性能特征。当 SQL Server 用作分布式的缓存后备存储时,使用的同一个数据库缓存与应用程序的普通数据存储和检索可以对这两者的性能产生负面影响。 我们建议使用专用的 SQL Server 实例为分布式缓存后备存储。

2.IDistributedCache简单封装

/// <summary>
/// <see cref="IDistributedCache"/>扩展方法
/// </summary>
public static class DistributedCacheExtensions
{
/// <summary>
/// 将对象存入缓存中
/// </summary>
public static void SetCache(this IDistributedCache cache, string key, object value, DistributedCacheEntryOptions options = null)
{
string json = JsonConvert.SerializeObject(value);
if (options == null)
{
cache.SetString(key, json);
}
else
{
cache.SetString(key, json, options);
}
} /// <summary>
/// 异步将对象存入缓存中
/// </summary>
public static async Task SetCacheAsync(this IDistributedCache cache, string key, object value, DistributedCacheEntryOptions options = null)
{
string json = JsonConvert.SerializeObject(value);
if (options == null)
{
await cache.SetStringAsync(key, json);
}
else
{
await cache.SetStringAsync(key, json, options);
}
} /// <summary>
/// 将对象存入缓存中,使用指定时长
/// </summary>
public static void Set(this IDistributedCache cache, string key, object value, int cacheSeconds)
{
DistributedCacheEntryOptions options = new DistributedCacheEntryOptions();
options.SetAbsoluteExpiration(TimeSpan.FromSeconds(cacheSeconds));
cache.SetCache(key, value, options);
} /// <summary>
/// 异步将对象存入缓存中,使用指定时长
/// </summary>
public static Task SetAsync(this IDistributedCache cache, string key, object value, int cacheSeconds)
{
DistributedCacheEntryOptions options = new DistributedCacheEntryOptions();
options.SetAbsoluteExpiration(TimeSpan.FromSeconds(cacheSeconds));
return cache.SetCacheAsync(key, value, options);
} /// <summary>
/// 将对象存入缓存中,使用功能配置
/// </summary>
public static void Set(this IDistributedCache cache, string key, object value, DistributedCacheEntryOptions cacheOptions)
{
if (cacheOptions == null)
{
return;
}
cache.SetCache(key, value, cacheOptions);
} /// <summary>
/// 异步将对象存入缓存中,使用功能配置
/// </summary>
public static Task SetAsync(this IDistributedCache cache, string key, object value, DistributedCacheEntryOptions cacheOptions)
{
if (cacheOptions == null)
{
return Task.FromResult();
}
return cache.SetCacheAsync(key, value, cacheOptions);
} /// <summary>
/// 获取指定键的缓存项
/// </summary>
public static TResult Get<TResult>(this IDistributedCache cache, string key)
{
string json = cache.GetString(key);
if (json == null)
{
return default(TResult);
}
return JsonConvert.DeserializeObject<TResult>(json);
} /// <summary>
/// 异步获取指定键的缓存项
/// </summary>
public static async Task<TResult> GetAsync<TResult>(this IDistributedCache cache, string key)
{
string json = await cache.GetStringAsync(key);
if (json == null)
{
return default(TResult);
}
return JsonConvert.DeserializeObject<TResult>(json);
} /// <summary>
/// 获取指定键的缓存项,不存在则从指定委托获取,并回存到缓存中再返回
/// </summary>
public static TResult GetCache<TResult>(this IDistributedCache cache, string key, Func<TResult> getFunc, DistributedCacheEntryOptions options = null)
{
TResult result = cache.Get<TResult>(key);
if (!Equals(result, default(TResult)))
{
return result;
}
result = getFunc();
if (Equals(result, default(TResult)))
{
return default(TResult);
}
cache.SetCache(key, result, options);
return result;
} /// <summary>
/// 异步获取指定键的缓存项,不存在则从指定委托获取,并回存到缓存中再返回
/// </summary>
public static async Task<TResult> GetCacheAsync<TResult>(this IDistributedCache cache, string key, Func<Task<TResult>> getAsyncFunc, DistributedCacheEntryOptions options = null)
{
TResult result = await cache.GetAsync<TResult>(key);
if (!Equals(result, default(TResult)))
{
return result;
}
result = await getAsyncFunc();
if (Equals(result, default(TResult)))
{
return default(TResult);
}
await cache.SetCacheAsync(key, result, options);
return result;
} /// <summary>
/// 获取指定键的缓存项,不存在则从指定委托获取,并回存到缓存中再返回
/// </summary>
public static TResult Get<TResult>(this IDistributedCache cache, string key, Func<TResult> getFunc, int cacheSeconds)
{
DistributedCacheEntryOptions options = new DistributedCacheEntryOptions();
options.SetAbsoluteExpiration(TimeSpan.FromSeconds(cacheSeconds));
return cache.GetCache<TResult>(key, getFunc, options);
} /// <summary>
/// 异步获取指定键的缓存项,不存在则从指定委托获取,并回存到缓存中再返回
/// </summary>
public static Task<TResult> GetAsync<TResult>(this IDistributedCache cache, string key, Func<Task<TResult>> getAsyncFunc, int cacheSeconds)
{
DistributedCacheEntryOptions options = new DistributedCacheEntryOptions();
options.SetAbsoluteExpiration(TimeSpan.FromSeconds(cacheSeconds));
return cache.GetAsync<TResult>(key, getAsyncFunc, options);
} /// <summary>
/// 获取指定键的缓存项,不存在则从指定委托获取,并回存到缓存中再返回
/// </summary>
public static TResult Get<TResult>(this IDistributedCache cache, string key, Func<TResult> getFunc, DistributedCacheEntryOptions cacheOptions)
{
if (cacheOptions == null)
{
return getFunc();
}
return cache.Get<TResult>(key, getFunc, cacheOptions);
} /// <summary>
/// 获取指定键的缓存项,不存在则从指定委托获取,并回存到缓存中再返回
/// </summary>
public static Task<TResult> GetAsync<TResult>(this IDistributedCache cache, string key, Func<Task<TResult>> getAsyncFunc, DistributedCacheEntryOptions cacheOptions)
{
if (cacheOptions == null)
{
return getAsyncFunc();
}
return cache.GetAsync<TResult>(key, getAsyncFunc, cacheOptions);
}
}

使用时候注入IDistributedCache即可,实现的效果和 内存缓存效果是一样的,.....还没看其实现源码,所以,,吹不起来.

这时候再上一篇中我们通过扫码登录之后获取到token,保存到 idistributedCache对象即可