ASP.NET Core 中的緩存

緩存的基本概念

緩存是分佈式系統中的重要組件,主要解決高併發,大數據場景下,熱點數據訪問的性能問題。提供高性能的數據快速訪問。git

緩存原理

  • 將數據寫入到讀取速度更快的存儲設備;
  • 將數據緩存到離應用最近的位置;
  • 將數據緩存到離用戶最近的位置。

緩存設計

  • 緩存內容 熱點數據,靜態資源
  • 緩存位置 CDN,反向代理,分佈式緩存服務器,本機(內存,硬盤)
    • CDN:存放HTML、CSS、JS等靜態資源;
    • 反向代理:動靜分離,只緩存用戶請求的靜態資源;
    • 分佈式緩存:緩存數據庫中的熱點數據;
    • 本地緩存:緩存應用字典等經常使用數據。
  • 過時策略 固定時間,相對時間
  • 同步機制 實時寫入,異步刷新

分佈式緩存 Memcache 與 Redis 的比較

  • 數據結構:Memcache只支持key value存儲方式,Redis支持更多的數據類型,好比Key value、hash、list、set、zset;
  • 多線程:Memcache支持多線程,Redis支持單線程;CPU利用方面Memcache優於Redis;
  • 持久化:Memcache不支持持久化,Redis支持持久化(快照和AOF日誌兩種持久化方式);
  • 內存利用率:Memcache高,Redis低(採用壓縮的狀況下比Memcache高)。使用簡單的key-value存儲的話,Memcached的內存利用率更高,而若是Redis採用hash結構來作key-value存儲,因爲其組合式的壓縮,其內存利用率會高於Memcache。
  • 過時策略:Memcache過時後,不刪除緩存,會致使下次取數據數據的問題,Redis有專門線程,清除緩存數據;

緩存穿透,緩存擊穿,緩存雪崩解決方案

  • 緩存穿透
    緩存穿透是指查詢一個必定不存在的數據。因爲會頻繁的請求數據庫,對數據庫形成訪問壓力。
    解決方法:
    • 對結果爲空的數據也進行緩存,不過設置它的過時時間會很短,最長不超過五分鐘。
    • 必定不存在的key,採用布隆過濾器,創建一個大的Bitmap中,查詢時經過該bitmap過濾。github

      布隆過濾器(Bloom Filter)是1970年由布隆提出的。它其實是一個很長的二進制向量和一系列隨機映射函數。布隆過濾器能夠用於檢索一> 個元素是否在一個集合中。它的優勢是空間效率和查詢時間都遠遠超過通常的算法,缺點是有必定的誤識別率和刪除困難
      若是想要判斷一個元素是否是在一個集合裏,通常想到的是將全部元素保存起來,而後經過比較肯定。鏈表,樹等等數據結構都是這種思路. > 可是隨着集合中元素的增長,咱們須要的存儲空間愈來愈大,檢索速度也愈來愈慢(O(n),O(logn))。不過世界上還有一種叫做散列表(又叫哈> 希表,Hash table)的數據結構。它能夠經過一個Hash函數將一個元素映射成一個位陣列(Bit array)中的一個點。這樣一來,咱們只要看看這個點是否是1就能夠知道集合中有沒有它了。這就是布隆過濾器的基本思想。redis

  • 緩存雪崩
    緩存雪崩是指在咱們設置緩存時採用了相同的過時時間,致使緩存在某一時刻同時失效,請求所有轉發到DB,DB瞬時壓力太重雪崩。
    解決方法:
    • 經過加鎖或者隊列來控制讀數據庫寫緩存的線程數量。好比對某個key只容許一個線程查詢數據和寫緩存,其餘線程等待。
    • 分散緩存失效時間,好比在設置過時時間的時候增長一個隨機數儘量的保證緩存不會大面積的同時失效。
  • 緩存擊穿
    緩存擊穿是指對於一些設置了過時時間的key,若是這些key可能會在過時後的某個時間點被超高併發地訪問。這個和緩存雪崩的區別在於這裏針對某一key緩存,前者則是不少key。
    解決方法:
    • 使用互斥鎖來解決問題,通俗的描述就是,一萬個用戶訪問了,可是隻有一個用戶能夠拿到訪問數據庫的權限,當這個用戶拿到這個權限以後從新建立緩存,這個時候剩下的訪問者由於沒有拿到權限,就原地等待着去訪問緩存。

數據一致性

數據不一致的幾種狀況:算法

  • 數據庫有數據,緩存沒有數據;
  • 數據庫有數據,緩存也有數據,數據不相等;
  • 數據庫沒有數據,緩存有數據。

目前比較經常使用的數據緩存策略的是Cache Aside Pattern,更新緩存是先把數據存到數據庫中,成功後,再讓緩存失效。
這種策略下不一致產生的緣由只有更新數據庫成功,可是刪除緩存失敗。
解決方案:shell

  1. 對刪除緩存進行重試.
  2. 按期全量更新緩存。
  3. 合理設置緩存過時時間。

使用內置 MemoryCache

ASP.NET Core 支持多種不一樣的緩存。包括內存緩存,分佈式緩存(Redis 和 SQL Server)。Github 開源地址 Libraries for in-memory caching and distributed caching.數據庫

IMemoryCache是把數據存儲在Web服務器的內存中。api

  1. 在 ConfigureServices 中調用 AddMemoryCache 經過依賴關係注入引用服務。緩存

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMemoryCache();
    
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }
  2. 在控制器類中用構造器注入的方式建立 IMemoryCache 的對象。服務器

    using Microsoft.Extensions.Caching.Memory;
    
    public class ValuesController : ControllerBase
    {
        private IMemoryCache _cache;
    
        public ValuesController(IMemoryCache cache)
        {
            _cache = cache;
        }
    }
  3. 一些經常使用操做
    • 建立緩存

      Set()

      DateTime cacheEntry1 = DateTime.Now;
      
      var cacheEntryOptions = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(3));
      
      _cache.Set("cache1", cacheEntry1, cacheEntryOptions);

      GetOrCreate() GetOrCreateAsync

      var cacheEntry = _cache.GetOrCreate("cache1", entry => 
      {
          entry.SetAbsoluteExpiration(TimeSpan.FromSeconds(3));
          return DateTime.Now;
      });
    • 獲取緩存

      Get()

      var cacheEntry = this._cache.Get<DateTime?>("cache1");

      TryGetValue()

      DateTime cacheEntry;
      
      if (!_cache.TryGetValue("cache1", out cacheEntry))
      {
          // Key not in cache, so get data.
          cacheEntry = DateTime.Now;
      
          var cacheEntryOptions = new MemoryCacheEntryOptions()
              .SetAbsoluteExpiration(TimeSpan.FromSeconds(3));
      
          _cache.Set("cache1", cacheEntry, cacheEntryOptions);
      }
    • 刪除緩存

      Remove()

      _cache.Remove("cache1");
  4. 其餘知識點

    ICacheEntry成員:
    • Key 緩存key
    • Value 緩存值
    • AbsoluteExpiration 絕對過時時間,爲null則條件無效
    • AbsoluteExpirationRelativeToNow 相對當前時間的絕對過時時間(使用TimeSpan),爲null條件無效
    • SlidingExpiration 滑動過時時間
    • ExpirationTokens 提供用來自定義緩存過時
    • PostEvictionCallbacks 緩存失效回調
    • Priority 緩存項優先級(在緩存滿載的時候絕對清除的順序)
    • Size 表明緩存數據的大小,在內存緩存中通常爲null
    緩存過時的方式
    • 絕對到期(指定在一個固定的時間點到期)
    • 滑動到期(在一個時間長度內沒有被命中則過時,若是命中則順延)
    • 到期Token(自定義過時)

使用分佈式緩存 Redis

  1. Nuget 安裝 Microsoft.Extensions.Caching.Redis
  2. ConfigureServices 方法裏面添加服務 AddDistributedRedisCache

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDistributedRedisCache(options => {
            options.Configuration = "localhost";
            options.InstanceName = "Instance1";
        });
    
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }
  3. 經常使用操做
    RedisCache 實現了 IDistributedCache 接口,提供了經常使用的添加、檢索和刪除操做。
    • GetGetAsync 採用字符串鍵並以byte[]形式檢索緩存項(若是在緩存中找到)
    • SetSetAsync 使用字符串鍵向緩存添加項byte[]形式
    • RefreshRefreshAsync 根據鍵刷新緩存中的項,並重置其可調過時超時值(若是有)
    • RemoveRemoveAsync 根據鍵刪除緩存項
    var now = DateTime.Now;
    var cacheValue = System.Text.Encoding.UTF8.GetBytes(now.ToString());
    var options = new DistributedCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(3));
    _cache.Set("cache1", cacheValue, options);
    
    _cache.Refresh("cache1");
    
    var value = _cache.Get("cache1");
    var nowString = System.Text.Encoding.UTF8.GetString(value);
    
    _cache.Remove("cache1");

    因爲自帶的 RedisCache 繼承 IDistributedCache 接口並無提供 Redis的一些高級特性好比Hash, List, Set等。

使用 Stackexchange.Redis 本身封裝一個 RedisHelper 類

基於Stackexchange.Redis封裝一個簡單RedisHelper類:

RedisHelper 類

public class RedisHelper
{
    private readonly RedisOptions _options;

    private readonly Lazy<ConnectionMultiplexer> _connectionMultiplexer;

    public RedisHelper(IOptions<RedisOptions> optionsAccessor)
    {
        if (optionsAccessor == null)
        {
            throw new ArgumentNullException(nameof(optionsAccessor));
        }

        _options = optionsAccessor.Value;
        _connectionMultiplexer = new Lazy<ConnectionMultiplexer>(CreateConnectionMultiplexer);
    }

    public IDatabase GetDatabase()
    {
        return _connectionMultiplexer.Value.GetDatabase();
    }

    private ConnectionMultiplexer CreateConnectionMultiplexer()
    {
        if (_options.ConfigurationOptions != null)
        {
            return ConnectionMultiplexer.Connect(_options.ConfigurationOptions);
        }
        else
        {
            return ConnectionMultiplexer.Connect(_options.Configuration);
        }
    }
}

RedisOptions 配置類

public class RedisOptions : IOptions<RedisOptions>
{
    /// <summary>
    /// The configuration used to connect to Redis.
    /// </summary>
    public string Configuration { get; set; }

    /// <summary>
    /// The configuration used to connect to Redis.
    /// This is preferred over Configuration.
    /// </summary>
    public ConfigurationOptions ConfigurationOptions { get; set; }

    /// <summary>
    /// The Redis instance name.
    /// </summary>
    public string InstanceName { get; set; }

    RedisOptions IOptions<RedisOptions>.Value
    {
        get { return this; }
    }
}

RedisHelperServiceCollectionExtensions 擴展類

public static class RedisHelperServiceCollectionExtensions
{
    public static IServiceCollection AddRedisHelper(this IServiceCollection services, Action<RedisOptions> setupAction)
    {
        if (services == null)
        {
            throw new ArgumentNullException(nameof(services));
        }

        if (setupAction == null)
        {
            throw new ArgumentNullException(nameof(setupAction));
        }

        services.AddOptions();
        services.Configure(setupAction);
        services.AddSingleton<RedisHelper>();

        return services;
    }
}

在 ConfigureServices 裏面添加服務引用

public void ConfigureServices(IServiceCollection services)
{
    var redisOptions = Configuration.GetSection("RedisOptions").Get<RedisOptions>();
    services.AddRedisHelper(options => {
        options.Configuration = redisOptions.Configuration;
        options.InstanceName = redisOptions.InstanceName;
    });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}

在 Controller 裏面使用

public class ValuesController : ControllerBase
{
    private readonly RedisHelper _redisHelper;

    public ValuesController(RedisHelper redisHelper)
    {
        _redisHelper = redisHelper;
    }

    // GET api/values
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        _redisHelper.GetDatabase().StringSet("test_key_2", "test_value_2", TimeSpan.FromSeconds(60));
        return new string[] { "value1", "value2" };
    }
}

網上一些開源的Redis擴展:

參考

相關文章
相關標籤/搜索