深圳嘉华_架构师进阶之路_20分布式缓存

一、什么是缓存

缓存简单理解就是保存的一个数据副本,以便于后续能够进行快速的访问。
缓存的使用是为了解决快速访问数据(读数据)的场景。在现有的互联网应用中,缓存的使用是一种能够提升服务快速响应的关键技术,需要在设计技术方案时对业务场景,具有一定的前瞻性评估后,决定在技术架构中是否需要引入缓存解决这种这种非功能需求。

1、缓存响应的四种方式

1.1、内存缓存

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

1.2、响应缓存

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

1.3、响应缓存中间件

Microsoft.AspNetCore.ResponseCaching 包中的ResponseCaching

1.4、分布式缓存

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

2、内存缓存

内存缓存即我们常用的System.Runtime.Caching/MemoryCache.

2.1、MemoryCache

MemoryCache是.Net Framework 4.0开始提供的内存缓存类,使用该类型可以方便的在程序内部缓存数据并对于数据的有效性进行方便的管理,借助该类型可以实现ASP.NET中常用的Cache类的相似功能,并且可以适应更加丰富的使用场景。

2.1.1 MemoryCache内部数据结构

在MemoryCache类内部,数据的组织方式跟MemoryCacheStore、MemoryCacheKey和MemoryCacheEntry这三个类有关,它们的作用分别是:

  • MemoryCacheStore:承载数据
  • MemoryCacheKey:构造检索项
  • MemoryCacheEntry:缓存内部数据的真实表现形式

2.1.2 MemoryCahe中查询数据

private void btnGet_Click(object sender, EventArgs e)  
{  
    ObjectCache cache = MemoryCache.Default;  
    string fileContents = cache["filecontents"] as string;    
    if (fileContents == null)  
    {  
        CacheItemPolicy policy = new CacheItemPolicy();    
        List<string> filePaths = new List<string>();  
        filePaths.Add("c:\\cache\\example.txt");    
        policy.ChangeMonitors.Add(new   
        HostFileChangeMonitor(filePaths));    
        // Fetch the file contents.  
        fileContents = File.ReadAllText("c:\\cache\\example.txt");    
        cache.Set("filecontents", fileContents, policy);  
    }  
    Label1.Text = fileContents;  
}

3、响应缓存

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

4、响应缓存中间件

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

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

在startUp.cs的 Configure中

app.UseResponseCaching();
   app.Use(async (context, next) => {
    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)
  • VaryByQueryKeys: 在使用 MVC/Web API 控制器或 Razor页面页模型ResponseCache属性指定设置适当的标头为响应缓存所需的参数。

4、分布式缓存

分布式的缓存是由多个应用程序服务器,通常作为对其进行访问的应用程序服务器的外部服务维护共享缓存。 分布式的缓存可以提高性能和可伸缩性的 ASP.NET Core 应用,尤其是当应用程序托管的云服务或服务器场。
分布式的缓存具有几大优势,其中缓存的数据存储在单个应用程序服务器其他缓存方案。当分布式缓存的数据,则数据:

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

4.1、分布式缓存实现的几种方式

  • NCache 分布式内存缓存
  • SQL Server 分布式缓存
  • Redis 分布式缓存

4.2、NCache分布式内存缓存

分布式内存缓存 (AddDistributedMemoryCache) 是框架提供的实现IDistributedCache,在内存中存储项。 分布式内存缓存不是实际的分布式的缓存。 缓存的项存储在运行该应用程序的服务器上的应用程序实例。
NCache,它是一个开源的支持.NET的分布式缓存,它的优势在于完全基于内存,所以你可以在业务增长时组建内存服务器的集群来实现线性扩展,相比于数据库能节省近八成的成本,并且在读写应用数据上得到更快的体验。NCache很适合存储session会话,在多Web服务器负载时也解决了会话保持的需求。
IDistributedCache接口使用示例:

private byte[] LoadCustomer(string custId) {
    string key = "Customers:CustomerID:" + custId;
    // is the customer in the cache?
    byte[] customer = _cache.Get(key);
    if(customer == null) {
       // the cache doesn't have it. so load from DB
       customer = LoadFromDB(key);
       // And, cache it for next time
       _cache.Set(key, customer);
    }
    return customer;
}

NCache是IDistributedCache的一种实现,所以在ASP.NET Cor应用中使用NCache不需要特殊的配置。
以下是IDistributedCache接口的定义(注意每个方法都有对应的Async版本的重载)。

namespace Microsoft.Extensions.Caching.Distributed
{
    public interface IDistributedCache
    {
        // Each of these methods also has an “Async” overload
        byte[] Get(string key);
        void Refresh(string key);
        void Remove(string key); 
        // Specify absolute & sliding expiration thru options
        void Set(string key, byte[] value, DistributedCacheEntryOptions options);
    }
}

接下来在ASP.NET Core的Startup类中配置NCacheIDistributedCache

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddNCacheDistributedCache();
    }
}

如果对缓存需求不高, 使用IDistributedCache保留了更改分布式缓存灵活性。但是要权衡这种放弃高级缓存特性的成本。为什么不考虑一下NCache?NCache API在ASP.NET Core应用内可以直接使用,和旧的ASP.NET Cache API非常相似,它包含了大量零成本新特性,可以获得企业级的分布式缓存收益。记住一点,看在性能提升和扩展能力的份上,早用早享受。没有这些高级特性,那只能可怜地缓存一些简单数据。这份NCache caching features了解更多差别。

4.2.1、NCache实现ASP.NET Core会话存储到分布式缓存

旧的ASP.NET的会话状态由框架提供,允许第三方组件以插件形式接入。在ASP.NET Core中的会话也是相似的,采用链式插件调用,有两种方式使用NCache作为ASP.NET Core的会话存储:

1.NCache作为ASP.NET Core的IDistributedCache Provider存储配置
如果把NCache作为ASP.NET Core的IDistributedCache provider进行配置,NCache能自动的成为会话的默认存储方式,不用再有多余的操作。但是相比于旧的ASP.NET会话状态,这种方式能使用到的特性有限。

以下是ASP.NET Core默认会话缺失的地方:

  • ASP.NET Core无会话锁提供。 自定义对象的byte数组支持: ASP.NET
    Core强制你在把自定义对象存储到会话前先转换成byte数组。
  • NCache作为ASP.NET Core的Sessions Provider配置
    比起前一个方式,NCache已经实现了ASP.NET Core的Sessions Provider。这种方式拥有更多的特性可以利用。

以下是在Startup类进行配置的示例。

public class Startup
{
    public void Configure(IApplicationBuilder app,  IHostingEnvironment env)
    {
        app.UseNCacheSession();
    } 
}

你可以在appsettings.json文件中做如下ASP.NET Core会话配置。

{
    "NCacheSessions": {
        "CacheName": "demoCache",
        "EnableLogs": "True",
        "RequestTimeout": "90",
        "EnableDetailLogs": "False",
        "ExceptionsEnabled": "True",
        "WriteExceptionsToEventLog": "False"
    }
}

4.3、SQL Server分布式缓存

分布式 SQL Server 缓存实现(AddDistributedSqlServerCache)允许分布式缓存使用 SQL Server 数据库作为其后备存储。 若要在 SQL Server 实例中创建 SQL Server 缓存的项表,可以使用 sql-cache 工具。 该工具将创建一个表,其中包含指定的名称和架构。
通过运行 sql-cache create 命令在 SQL Server 中创建一个表。 提供 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.

在Startup.ConfigureServices中

services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = 
        _config["DistCache_ConnectionString"];
    options.SchemaName = "dbo";
    options.TableName = "TestCache";
});

4.4、分布式 Redis 缓存

Redis是内存中数据存储的开源数据存储,通常用作分布式缓存。 可以在本地使用 Redis,也可以为 Azure 托管的 ASP.NET Core 应用配置Azure Redis 缓存。

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

4.5、分布式缓存的使用

若要使用 IDistributedCache 接口,请从应用程序中的任何构造函数请求 IDistributedCache 的实例。 实例通过依赖关系注入(DI)来提供。
示例应用启动时,IDistributedCache 被注入到 Startup.Configure中。 当前时间使用 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(20));
        cache.Set("cachedTimeUTC", encodedCurrentTimeUTC, options);
    });

示例应用程序注入IDistributedCache到IndexModel以供索引页。
  每次加载索引页时,缓存时间检查缓存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(20));
        await _cache.SetAsync("cachedTimeUTC", encodedCurrentTimeUTC, options);
        return RedirectToPage();
    }
}

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(0);
        }
        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最适合于您的应用程序,请考虑以下:

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

猜你喜欢

转载自blog.csdn.net/huan13479195089/article/details/105235058