ASP.NET Core 中的快取

qq_42606051發表於2018-08-15

快取的基本概念

快取是分散式系統中的重要元件,主要解決高併發,大資料場景下,熱點資料訪問的效能問題。提供高效能的資料快速訪問。

快取原理

  • 將資料寫入到讀取速度更快的儲存裝置;
  • 將資料快取到離應用最近的位置;
  • 將資料快取到離使用者最近的位置。

快取設計

  • 快取內容 熱點資料,靜態資源
  • 快取位置 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過濾。
  • 快取雪崩
    快取雪崩是指在我們設定快取時採用了相同的過期時間,導致快取在某一時刻同時失效,請求全部轉發到DB,DB瞬時壓力過重雪崩。
    解決方法:
    • 通過加鎖或者佇列來控制讀資料庫寫快取的執行緒數量。比如對某個key只允許一個執行緒查詢資料和寫快取,其他執行緒等待。
    • 分散快取失效時間,比如在設定過期時間的時候增加一個隨機數儘可能的保證快取不會大面積的同時失效。
  • 快取擊穿
    快取擊穿是指對於一些設定了過期時間的key,如果這些key可能會在過期後的某個時間點被超高併發地訪問。這個和快取雪崩的區別在於這裡針對某一key快取,前者則是很多key。
    解決方法:
    • 使用互斥鎖來解決問題,通俗的描述就是,一萬個使用者訪問了,但是隻有一個使用者可以拿到訪問資料庫的許可權,當這個使用者拿到這個許可權之後重新建立快取,這個時候剩下的訪問者因為沒有拿到許可權,就原地等待著去訪問快取。

資料一致性

資料不一致的幾種情況:

  • 資料庫有資料,快取沒有資料;
  • 資料庫有資料,快取也有資料,資料不相等;
  • 資料庫沒有資料,快取有資料。

目前比較常用的資料快取策略的是Cache Aside Pattern,更新快取是先把資料存到資料庫中,成功後,再讓快取失效。 
這種策略下不一致產生的原因只有更新資料庫成功,但是刪除快取失敗。
解決方案:

  1. 對刪除快取進行重試.
  2. 定期全量更新快取。
  3. 合理設定快取過期時間。

使用內建 MemoryCache

ASP.NET Core 支援多種不同的快取。包括記憶體快取,分散式快取(Redis 和 SQL Server)。Github 開源地址 Libraries for in-memory caching and distributed caching.

IMemoryCache是把資料儲存在Web伺服器的記憶體中。

  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支援:

參考

 

鄭州專業婦科醫院

鄭州看不孕不育

鄭州不孕不育正規醫院

鄭州醫院哪家看男科好

相關文章