日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

Util应用框架基础(七) - 缓存

發布時間:2023/11/23 windows 58 coder
生活随笔 收集整理的這篇文章主要介紹了 Util应用框架基础(七) - 缓存 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本節介紹Util應用框架如何操作緩存.

概述

緩存是提升性能的關鍵手段之一.

除了提升性能,緩存對系統健壯性和安全性也有影響.

不同類型的系統對緩存的依賴程度不同.

對于后臺管理系統,由于是給管理人員使用的,用戶有限,而且操作基本都需要身份認證和授權,甚至可能部署在局域網內,一般僅對耗時操作使用緩存即可.

但是商城,門戶網站這類系統, 它們部署在互聯網上,并且允許匿名用戶訪問,僅緩存耗時操作是不夠的.

除了訪問量可能比較大,另外需要防范網絡流氓的惡意攻擊,他們會發送大量請求來試探你的系統.

如果某個讀取操作直接到達數據庫,哪怕僅執行非常簡單的SQL,由于請求非常密集,服務器的CPU將很快到達100%從而拒絕服務.

對于這類系統,需要對暴露到互聯網上的所有頁面和讀取數據的API進行緩存.

當然也可以使用更多的只讀數據庫和其它高性能數據庫分攤讀取壓力,本文介紹基于內存和Redis的緩存操作.

緩存框架

要緩存數據,需要選擇一種緩存框架.

.Net 緩存

  • 本地緩存 IMemoryCache

    .Net 提供了 Microsoft.Extensions.Caching.Memory.IMemoryCache 進行本地緩存操作.

    IMemoryCache 可以將數據對象緩存到Web服務器進程的內存中.

    本地緩存的主要優勢是性能非常高,而且不需要序列化對象.

    本地緩存的主要問題是內存容量受限和更新同步困難.

    本地緩存可使用的內存容量受Web服務器內存的限制.

    可以在單體項目中使用本地緩存.

    如果單體項目僅部署一個Web服務器實例,緩存只有一個副本,不存在更新同步的問題.

    但是如果將單體項目部署到Web集群,由于Web服務器實例不止一個,每個Web服務器都會產生一個緩存副本.

    想要同時更新多個Web服務器的本地緩存非常困難,這可能導致緩存的數據不一致.

    可以使用負載均衡器的會話粘滯特性將用戶每次請求都定位到同一臺Web服務器,從而避免多次請求看到不一致的數據.

    微服務項目情況則更為復雜,由于包含多個Web Api項目,每個Web Api項目都會部署到一個或多個Web服務器.

    不同 Web Api 項目可能需要使用相同的緩存數據,無法使用負載均衡器的會話粘滯特性解決該問題.

    我們需要使用分布式緩存來解決內存容量和更新同步的問題.

  • 分布式緩存 IDistributedCache

    .Net 提供了 Microsoft.Extensions.Caching.Distributed.IDistributedCache 進行分布式緩存操作.

    IDistributedCache 可以將數據對象序列化后保存到 Redis 等緩存服務器中.

    相比基于內存的本地緩存, 分布式緩存的性能要低得多, 不僅要序列化對象,還需要跨進程網絡調用.

    但是由于不使用 Web 服務器的內存,所以可以輕松的增加緩存容量.

    把緩存抽出來放到專門的服務器后,多個Web Api項目就可以共享緩存.

    由于緩存只有一份,也就不存在同步更新.

  • 直接使用.Net 緩存的問題

    毫無疑問,你可以直接使用 IMemoryCacheIDistributedCache 接口進行緩存操作.

    但會面臨以下問題:

    • Api生硬且不統一.

      IDistributedCache 直接操作 byte[] ,如果不進一步封裝很難使用.

      你需要明確指定是本地緩存還是分布式緩存,無法使用統一的API,不能通過配置進行切換.

    • 需要自行處理緩存過期引起的性能問題.

      如果同一時間,緩存正好大面積過期,大量請求到達數據庫,從而導致系統可能崩潰,這稱為緩存雪崩.

      簡單的處理辦法是給每個緩存項設置不同的緩存時間,如果統一配置緩存時間,則添加一個隨機間隔,讓緩存過期的時間錯開即可.

      另一個棘手的問題,如果很多請求并發訪問某個熱點緩存項,當緩存過期,這些并發請求將到達數據庫,這稱為緩存擊穿.

      雖然只有一個緩存項過期,但還是會損害系統性能.

      可以對并發請求加鎖,只允許第一個進入的請求到達數據庫并更新緩存,后續請求將從更新的緩存讀取.

      為進一步提升性能,可以在緩存過期前的某個時間更新緩存,從而避免鎖定請求造成的等待.

    • 缺失前綴移除等關鍵特性.

      有些緩存項具有相關性,比如為當前用戶設置權限,菜單,個人偏好等緩存項,當他退出登錄時,需要清除跟他相關的所有緩存項.

      你可以一個個的移除,但相當費力.

      可以為具有相關性的緩存項設置相同的緩存前綴,并通過緩存前綴找出所有相關緩存項,從而一次性移除它們.

      遺憾的是, .Net 緩存并不支持這些特性,需要自行實現.

緩存框架 EasyCaching

EasyCaching 是一個專業而易用的緩存框架,提供統一的API接口,并解決了上述問題.

EasyCaching 支持多種緩存提供程序,可以將緩存寫入內存,Redis,Memcached等.

EasyCaching 支持多種序列化方式,可在使用分布式緩存時指定.

EasyCaching 支持前綴移除,模式移除等高級用法.

除此之外,EasyCaching 還支持2級緩存.

2級緩存可以讓你的項目從本地緩存中獲取數據,這樣可以獲得很高的讀取性能.

當本地緩存過期,本地緩存會請求Redis分布式緩存,Redis緩存從數據庫讀取最新數據,并更新本地緩存.

Redis還充當事件總線的角色,每當數據更新,通過Redis總線發布事件,同步更新所有本地緩存副本,解決了本地緩存更新困難的難題.

與 IMemoryCache 相比, EasyCaching 的本地緩存性能稍低,畢竟實現了更多功能.

Util應用框架使用 EasyCaching 緩存框架,并進行簡單包裝.

Util 僅引入了 EasyCaching 的本地緩存Redis緩存兩種提供程序, 以及 SystemTextJson 序列化方式.

如果需要使用其它提供程序和序列化方式,請自行引入相關 Nuget 包.

基礎用法

配置緩存

配置本地緩存

  • 引用Nuget包

    Nuget包名: Util.Caching.EasyCaching

  • AddMemoryCache

    使用 AddMemoryCache 擴展方法啟用本地緩存.

    • 默認配置不帶參數,設置以下默認值:

      • MaxRdSecond 設置為 1200秒.

      • CacheNulls 設置為 true.

      var builder = WebApplication.CreateBuilder( args );
      builder.AsBuild().AddMemoryCache();
      
    • 使用 IConfiguration 進行配置.

      可以使用 appsettings.json 文件進行配置.

      var builder = WebApplication.CreateBuilder( args );
      builder.AsBuild().AddMemoryCache( builder.Configuration );
      

      默認配置節: EasyCaching:Memory

      appsettings.json 配置文件示例.

      {
        "EasyCaching": {
          "Memory": {
            "MaxRdSecond": 1200,
            "CacheNulls": true
          }
        }
      }
      
    • 使用委托進行配置.

      var builder = WebApplication.CreateBuilder( args );
      builder.AsBuild().AddMemoryCache( options => {
          options.MaxRdSecond = 1200;
          options.CacheNulls = true;
      } );
      

配置Redis緩存

  • 引用Nuget包

    Nuget包名: Util.Caching.EasyCaching

  • AddRedisCache

    使用 AddRedisCache 擴展方法啟用Redis緩存.

    • 最簡單的配置方法只需傳入Redis服務地址,并設置以下默認值.

      • MaxRdSecond 設置為 1200秒.

      • CacheNulls 設置為 true.

      • AllowAdmin 設置為 true.

      • 端口設置為 6379.

      • SerializerName 設置為 "SystemTextJson".

      范例:

      var builder = WebApplication.CreateBuilder( args );
      builder.AsBuild().AddRedisCache( "127.0.0.1" );
      

      如果要修改端口為 6666,如下所示.

      builder.AsBuild().AddRedisCache( "127.0.0.1",6666 );
      

      還可以統一設置緩存鍵前綴,下面的示例將緩存鍵前綴設置為 "test:".

      builder.AsBuild().AddRedisCache( "127.0.0.1",6666,"test:" );
      
    • 使用 IConfiguration 進行配置.

      可以使用 appsettings.json 文件進行配置.

      builder.AsBuild().AddRedisCache( builder.Configuration );
      

      默認配置節: EasyCaching:Redis

      appsettings.json 配置文件示例.

      {
        "EasyCaching": {
          "Redis": {
            "MaxRdSecond": 1200,
            "CacheNulls": true,
            "DbConfig": {
              "AllowAdmin": true,
              "Endpoints": [
                {
                  "Host": "localhost",
                  "Port": 6739
                }
              ],
              "Database": 0
            }
          }
        }
      }
      
    • 使用委托進行配置.

      builder.AsBuild().AddRedisCache( options => {
          options.MaxRdSecond = 1200;
          options.CacheNulls = true;        
          options.DBConfig.AllowAdmin = true;
          options.DBConfig.KeyPrefix = "test:";
          options.DBConfig.Endpoints.Add( new ServerEndPoint( "127.0.0.1", 6379 ) );
      } );
      

配置二級緩存

  • 引用Nuget包

    Nuget包名: Util.Caching.EasyCaching

  • AddHybridCache

    使用 AddHybridCache 擴展方法啟用2級緩存.

    • 最簡單的配置方法不帶參數,設置以下默認值.

      • TopicName 設置為 EasyCachingHybridCache.

        TopicName 是Redis總線發布事件的主題名稱.

      啟用2級緩存之前,應先配置本地緩存和Redis緩存.

      范例:

      var builder = WebApplication.CreateBuilder( args );
      builder.AsBuild()
        .AddMemoryCache()
        .AddRedisCache( "127.0.0.1" )
        .AddHybridCache();
      

      如果要修改 TopicName,傳入主題參數,如下所示.

      builder.AsBuild()
        .AddMemoryCache()
        .AddRedisCache( "127.0.0.1" )
        .AddHybridCache( "topic" );
      
    • 使用 IConfiguration 進行配置.

      可以使用 appsettings.json 文件進行配置.

      builder.AsBuild()
        .AddMemoryCache()
        .AddRedisCache( "127.0.0.1" )
        .AddHybridCache( builder.Configuration );
      

      除了需要配置2級緩存提供程序,還需要配置 Redis 總線.

      默認配置節名稱:

      • 2級緩存默認配置節名稱: EasyCaching:Hybrid

      • Redis總線配置節名稱: EasyCaching:RedisBus

      appsettings.json 配置文件示例.

      {
        "EasyCaching": {
          "Hybrid": {
            "LocalCacheProviderName": "DefaultInMemory",
            "DistributedCacheProviderName": "DefaultRedis",
            "TopicName": "EasyCachingHybridCache"
          },
          "RedisBus": {
            "Endpoints": [
              {
                "Host": "localhost",
                "Port": 6739
              }
            ],
            "SerializerName": "SystemTextJson"
          }
        }
      }
      
    • 使用委托進行配置.

      builder.AsBuild()
        .AddMemoryCache()
        .AddRedisCache( "127.0.0.1" )
        .AddHybridCache( hybridOptions => {
          hybridOptions.LocalCacheProviderName = "DefaultInMemory";
          hybridOptions.DistributedCacheProviderName = "DefaultRedis";
          hybridOptions.TopicName = "topic";
        }, redisBusOptions => {
            redisBusOptions.Endpoints.Add( new ServerEndPoint( "127.0.0.1", 6379 ) );
            redisBusOptions.SerializerName = "SystemTextJson";
        } )
      
  • 配置參數

    EasyCaching 緩存提供了多個配置參數,具體請參考 EasyCaching 文檔.

    下面介紹幾個比較重要的參數.

    • MaxRdSecond

      MaxRdSecond 是額外添加的緩存間隔最大隨機秒數.

      MaxRdSecond 用于防止緩存雪崩,在緩存時間基礎上增加隨機秒數,以防止同一時間所有緩存項失效.

      MaxRdSecond 的默認值為 120, 增加的隨機間隔是120秒以內的某個隨機值.

      你可以增大 MaxRdSecond ,以更大的范圍錯開各緩存項的失效時間.

      對于集成測試,你如果要測試緩存失效時間,需要將該值設置為 0.

    • CacheNulls

      CacheNulls 用于解決緩存穿透問題.

      當使用 Get( key, ()=> value ) 方法獲取緩存時,如果返回的value為null,是否應該創建緩存項.

      CacheNulls 的值為 true 時,創建緩存項.

      如果返回值為null不創建緩存項,使用相同緩存鍵的每次請求都會到達數據庫.

      CacheNulls設置為 true 可防范正常業務的緩存穿透.

      但惡意攻擊每次傳遞的參數可能不同,請求依然會到達數據庫,且浪費緩存空間.

      可以通過緩存全部有效參數的方式精確判斷輸入參數是否在有效業務范圍,不過會占用過多內存.

      要減少內存占用,可使用布隆過濾器.

      EasyCaching尚未內置布隆過濾器,請自行實現.

緩存鍵

每個緩存項有一個唯一標識的鍵名,通過緩存鍵來獲取緩存項.

緩存鍵通常是一個字符串.

可以以任意方式構造緩存鍵,只要保證唯一即可.

但是根據緩存項的功能進行構造更容易識別緩存項的用途.

范例1:

是否管理員緩存鍵

IsAdmin-1

IsAdmin 代表是否管理員, 1是用戶的Id,需要把用戶Id的參數拼接到緩存鍵,以識別特定的緩存項

范例2:

菜單緩存鍵.

Menu-1

Menu 代表菜單, 1是用戶的Id.

緩存鍵前綴

如果用戶退出了,我們需要清除他的全部緩存項.

EasyCaching支持通過緩存鍵前綴批量移除緩存項.

修改前面的范例.

是否管理員緩存鍵: User-1-IsAdmin

菜單緩存鍵: User-1-Menu

User代表用戶,1是用戶Id, User-1 前綴可以標識Id為1的用戶.

使用 User-1 前綴就可以移除用戶1的所有緩存項.

CacheKey

你可以直接創建緩存鍵字符串,不過有些緩存鍵可能比較復雜,由很多參數構成.

另外可能需要在多個地方使用同一個緩存鍵進行操作.

用一個對象來封裝緩存鍵的構造,不僅可以降低緩存鍵的復雜性,而且也方便多處使用.

Util應用框架提供了一個緩存鍵對象 Util.Caching.CacheKey.

CacheKey 包含兩個屬性, Prefix 和 Key.

Prefix 是緩存鍵前綴,Key是緩存鍵.

通常不直接使用 CacheKey,而是從它派生具體的緩存鍵,這樣可以更清晰的表示緩存項的用途,以及更好的接收參數.

范例:

  • 定義 AclCacheKey 緩存鍵.

    AclCacheKey 表示訪問控制緩存鍵,接收用戶Id和資源Id參數.

    public class AclCacheKey : CacheKey {
        public AclCacheKey( string userId, string resourceId ) {
            Prefix = $"User-{userId}:";
            Key = $"Acl-{resourceId}";
        }
    }
    
  • 使用 AclCacheKey 緩存鍵.

    實例化 AclCacheKey ,傳入參數, 通過 Key 屬性獲取緩存鍵.

    var cacheKey = new AclCacheKey("1","2");
    var key = cacheKey.Key;
    

    Key 屬性返回 Prefix 與 Key 連接后的結果: User-1:Acl-2

    也可以使用 ToString 方法獲取緩存鍵.

    var cacheKey = new AclCacheKey("1","2");
    var key = cacheKey.ToString();
    

緩存操作

Util應用框架緩存操作提供了三個接口: ICache, ILocalCache, IRedisCache.

Util.Caching.ICache 是緩存操作的主要接口.

根據緩存配置,ICache可以在本地緩存,Redis緩存,2級緩存切換.

  • 如果僅配置本地緩存, ICache實例為本地緩存操作.

  • 如果僅配置 Redis 緩存,ICache實例為Redis緩存操作.

  • 如果同時配置本地緩存和 Redis 緩存,ICache 實例為后配置的緩存操作.

  • 如果配置了2級緩存,ICache 實例為2級緩存操作.

注意事項

如果使用2級緩存,有些操作不可用,調用會拋出異常.

示例上下文

  • 通過依賴注入獲取 ICache 實例.

    public class Service : IService {
      private ICache _cache;
      private IUserResourceRepository _repository;
    
      public Service( ICache cache,IUserResourceRepository repository ) {
          _cache = cache;
          _repository = repository;
      }
    }
    
  • 用戶資源示例

    public class UserResource {
        public string UserId { get; set; }
        public string UserName { get; set; }
        public string ResourceId { get; set; }
        public string ResourceName { get; set; }
    }
    
  • 用戶資源緩存鍵示例

    public class UserResourceCacheKey : CacheKey {
        public UserResourceCacheKey( string userId,string resourceId ) {
            Prefix = $"User-{userId}:";
            Key = $"Resource-{resourceId}";
        }
    }
    
  • 用戶資源倉儲示例

    public interface IUserResourceRepository {
      UserResource GetUserResource( string userId, string resourceId );
      Task<UserResource> GetUserResourceAsync( string userId, string resourceId );
    }
    

API

  • Exists

    功能: 判斷緩存是否存在

    • bool Exists( CacheKey key )

      范例:

      var cacheKey = new UserResourceCacheKey( "1", "2" );
      bool exists = _cache.Exists( cacheKey );
      
    • bool Exists( string key )

      范例:

      bool exists = _cache.Exists( "User-1:Resource-2" );
      
  • ExistsAsync

    功能: 判斷緩存是否存在

    • Task<bool> ExistsAsync( CacheKey key, CancellationToken cancellationToken = default )

      范例:

      var cacheKey = new UserResourceCacheKey( "1", "2" );
      bool exists = await _cache.ExistsAsync( cacheKey );
      
    • Task<bool> ExistsAsync( string key, CancellationToken cancellationToken = default )

      范例:

      bool exists = await _cache.ExistsAsync( "User-1:Resource-2" );
      
  • Get

    功能: 從緩存中獲取數據

    • T Get<T>( CacheKey key )

      范例:

      var cacheKey = new UserResourceCacheKey( "1", "2" );
      var result = _cache.Get<UserResource>( cacheKey );
      
    • T Get<T>( string key )

      范例:

      var result = _cache.Get<UserResource>( "User-1:Resource-2" );
      
    • List<T> Get<T>( IEnumerable<CacheKey> keys )

      通過緩存鍵集合獲取結果集合.

      范例:

      var keys = new List<UserResourceCacheKey> { new ( "1", "2" ), new( "3", "4" ) };
      var result = _cache.Get<UserResource>( keys );
      
    • List<T> Get<T>( IEnumerable<string> keys )

      通過緩存鍵集合獲取結果集合.

      范例:

      var keys = new List<string> { "User-1:Resource-2", "User-3:Resource-4" };
      var result = _cache.Get<UserResource>( keys );
      
    • T Get<T>( CacheKey key, Func<T> action, CacheOptions options = null )

      從緩存中獲取數據,如果數據不存在,則執行獲取數據操作并添加到緩存中.

      范例:

      var cacheKey = new UserResourceCacheKey( "1", "2" );
      var result = _cache.Get( cacheKey,()=> _repository.GetUserResource( "1", "2" ) );
      

      CacheOptions 配置包含 Expiration 屬性,用于設置緩存過期時間間隔,默認值: 8小時.

      設置1小時過期,如下所示.

      var cacheKey = new UserResourceCacheKey( "1", "2" );
      var result = _cache.Get( cacheKey, () => _repository.GetUserResource( "1", "2" ), new CacheOptions { Expiration = TimeSpan.FromHours( 1 ) } );
      
    • T Get<T>( string key, Func<T> action, CacheOptions options = null )

      從緩存中獲取數據,如果數據不存在,則執行獲取數據操作并添加到緩存中.

      范例:

      var result = _cache.Get( "User-1:Resource-2",()=> _repository.GetUserResource( "1", "2" ) );
      
  • GetAsync

    功能: 從緩存中獲取數據

    • Task<object> GetAsync( string key, Type type, CancellationToken cancellationToken = default )

      無法傳入泛型返回類型參數可使用該重載方法.

      范例:

      object result = await _cache.GetAsync( "User-1:Resource-2", typeof( UserResource ) );
      
    • Task<T> GetAsync<T>( CacheKey key, CancellationToken cancellationToken = default )

      范例:

      var cacheKey = new UserResourceCacheKey( "1", "2" );
      var result = await _cache.GetAsync<UserResource>( cacheKey );
      
    • Task<T> GetAsync<T>( string key, CancellationToken cancellationToken = default )

      范例:

      var result = await _cache.GetAsync<UserResource>( "User-1:Resource-2" );
      
    • Task<List> GetAsync<T>( IEnumerable<CacheKey> keys, CancellationToken cancellationToken = default )

      通過緩存鍵集合獲取結果集合.

      范例:

      var keys = new List<UserResourceCacheKey> { new ( "1", "2" ), new( "3", "4" ) };
      var result = await _cache.GetAsync<UserResource>( keys );
      
    • Task<List> GetAsync<T>( IEnumerable<string> keys, CancellationToken cancellationToken = default )

      通過緩存鍵集合獲取結果集合.

      范例:

      var keys = new List<string> { "User-1:Resource-2", "User-3:Resource-4" };
      var result = await _cache.GetAsync<UserResource>( keys );
      
    • Task<T> GetAsync<T>( CacheKey key, Func<Task<T>> action, CacheOptions options = null, CancellationToken cancellationToken = default )

      從緩存中獲取數據,如果數據不存在,則執行獲取數據操作并添加到緩存中.

      范例:

      var cacheKey = new UserResourceCacheKey( "1", "2" );
      var result = await _cache.GetAsync( cacheKey,async ()=> await _repository.GetUserResourceAsync( "1", "2" ) );
      

      CacheOptions 配置包含 Expiration 屬性,用于設置緩存過期時間間隔,默認值: 8小時.

      設置1小時過期,如下所示.

      var cacheKey = new UserResourceCacheKey( "1", "2" );
      var result = await _cache.GetAsync( cacheKey,async ()=> await _repository.GetUserResourceAsync( "1", "2" ), new CacheOptions { Expiration = TimeSpan.FromHours( 1 ) } );
      
    • Task<T> GetAsync<T>( string key, Func<Task<T>> action, CacheOptions options = null, CancellationToken cancellationToken = default )

      從緩存中獲取數據,如果數據不存在,則執行獲取數據操作并添加到緩存中.

      范例:

      var result = await _cache.GetAsync( "User-1:Resource-2",async ()=> await _repository.GetUserResourceAsync( "1", "2" ) );
      
  • GetByPrefix

    功能: 通過緩存鍵前綴獲取數據

    • List<T> GetByPrefix<T>( string prefix )

      范例:

      var result = _cache.GetByPrefix<UserResource>( "User-1" );
      
  • GetByPrefixAsync

    功能: 通過緩存鍵前綴獲取數據

    • Task<List<T>> GetByPrefixAsync<T>( string prefix, CancellationToken cancellationToken = default )

      范例:

      var result = await _cache.GetByPrefixAsync<UserResource>( "User-1" );
      
  • TrySet

    功能: 設置緩存,當緩存已存在則忽略,設置成功返回true

    • bool TrySet<T>( CacheKey key, T value, CacheOptions options = null )

      范例:

      var cacheKey = new UserResourceCacheKey( "1", "2" );
      var value = _repository.GetUserResource( "1", "2" );
      var result = _cache.TrySet( cacheKey, value );
      

      CacheOptions 配置包含 Expiration 屬性,用于設置緩存過期時間間隔,默認值: 8小時.

      設置1小時過期,如下所示.

      var cacheKey = new UserResourceCacheKey( "1", "2" );
      var value = _repository.GetUserResource( "1", "2" );
      var result = _cache.TrySet( cacheKey, value, new CacheOptions { Expiration = TimeSpan.FromHours( 1 ) } );
      
    • bool TrySet<T>( string key, T value, CacheOptions options = null )

      范例:

      var value = _repository.GetUserResource( "1", "2" );
      var result = _cache.TrySet( "User-1:Resource-2", value );
      
  • TrySetAsync

    功能: 設置緩存,當緩存已存在則忽略,設置成功返回true

    • Task<bool> TrySetAsync<T>( CacheKey key, T value, CacheOptions options = null, CancellationToken cancellationToken = default )

      范例:

      var cacheKey = new UserResourceCacheKey( "1", "2" );
      var value = await _repository.GetUserResourceAsync( "1", "2" );
      var result = await _cache.TrySetAsync( cacheKey, value );
      

      CacheOptions 配置包含 Expiration 屬性,用于設置緩存過期時間間隔,默認值: 8小時.

      設置1小時過期,如下所示.

      var cacheKey = new UserResourceCacheKey( "1", "2" );
      var value = await _repository.GetUserResourceAsync( "1", "2" );
      var result = await _cache.TrySetAsync( cacheKey, value, new CacheOptions { Expiration = TimeSpan.FromHours( 1 ) } );
      
    • Task<bool> TrySetAsync<T>( string key, T value, CacheOptions options = null, CancellationToken cancellationToken = default )

      范例:

      var value = await _repository.GetUserResourceAsync( "1", "2" );
      var result = await _cache.TrySetAsync( "User-1:Resource-2", value );
      
  • Set

    功能: 設置緩存,當緩存已存在則覆蓋

    • void Set<T>( CacheKey key, T value, CacheOptions options = null )

      范例:

      var cacheKey = new UserResourceCacheKey( "1", "2" );
      var value = _repository.GetUserResource( "1", "2" );
      _cache.Set( cacheKey, value );
      

      CacheOptions 配置包含 Expiration 屬性,用于設置緩存過期時間間隔,默認值: 8小時.

      設置1小時過期,如下所示.

      var cacheKey = new UserResourceCacheKey( "1", "2" );
      var value = _repository.GetUserResource( "1", "2" );
      _cache.Set( cacheKey, value, new CacheOptions { Expiration = TimeSpan.FromHours( 1 ) } );
      
    • void Set<T>( string key, T value, CacheOptions options = null )

      范例:

      var value = _repository.GetUserResource( "1", "2" );
      _cache.Set( "User-1:Resource-2", value );
      
    • void Set<T>( IDictionary<CacheKey,T> items, CacheOptions options = null )

      范例:

      var items = new Dictionary<CacheKey, UserResource> {
          { new UserResourceCacheKey( "1", "2" ), _repository.GetUserResource( "1", "2" ) },
          { new UserResourceCacheKey( "3", "4" ), _repository.GetUserResource( "3", "4" ) }
      };
      _cache.Set( items );
      
    • void Set<T>( IDictionary<string,T> items, CacheOptions options = null )

      范例:

      var items = new Dictionary<string, UserResource> {
          { "User-1:Resource-2", _repository.GetUserResource( "1", "2" ) },
          { "User-3:Resource-4", _repository.GetUserResource( "3", "4" ) }
      };
      _cache.Set( items );
      
  • SetAsync

    功能: 設置緩存,當緩存已存在則覆蓋

    • Task SetAsync<T>( CacheKey key, T value, CacheOptions options = null, CancellationToken cancellationToken = default )

      范例:

      var cacheKey = new UserResourceCacheKey( "1", "2" );
      var value = await _repository.GetUserResourceAsync( "1", "2" );
      await _cache.SetAsync( cacheKey, value );
      

      CacheOptions 配置包含 Expiration 屬性,用于設置緩存過期時間間隔,默認值: 8小時.

      設置1小時過期,如下所示.

      var cacheKey = new UserResourceCacheKey( "1", "2" );
      var value = await _repository.GetUserResourceAsync( "1", "2" );
      await _cache.SetAsync( cacheKey, value, new CacheOptions { Expiration = TimeSpan.FromHours( 1 ) } );
      
    • Task SetAsync<T>( string key, T value, CacheOptions options = null, CancellationToken cancellationToken = default )

      范例:

      var value = await _repository.GetUserResourceAsync( "1", "2" );
      await _cache.SetAsync( "User-1:Resource-2", value );
      
    • Task SetAsync<T>( IDictionary<CacheKey, T> items, CacheOptions options = null, CancellationToken cancellationToken = default )

      范例:

      var items = new Dictionary<CacheKey, UserResource> {
          { new UserResourceCacheKey( "1", "2" ), await _repository.GetUserResourceAsync( "1", "2" ) },
          { new UserResourceCacheKey( "3", "4" ), await _repository.GetUserResourceAsync( "3", "4" ) }
      };
      await _cache.SetAsync( items );
      
    • Task SetAsync<T>( IDictionary<string, T> items, CacheOptions options = null, CancellationToken cancellationToken = default )

      范例:

      var items = new Dictionary<string, UserResource> {
          { "User-1:Resource-2", await _repository.GetUserResourceAsync( "1", "2" ) },
          { "User-3:Resource-4", await _repository.GetUserResourceAsync( "3", "4" ) }
      };
      await _cache.SetAsync( items );
      
  • Remove

    功能: 移除緩存

    • void Remove( CacheKey key )

      范例:

      var cacheKey = new UserResourceCacheKey( "1", "2" );
      _cache.Remove( cacheKey );
      
    • void Remove( string key )

      范例:

      _cache.Remove( "User-1:Resource-2" );
      
    • void Remove( IEnumerable<CacheKey> keys )

      范例:

      var keys = new List<UserResourceCacheKey> { new ( "1", "2" ), new( "3", "4" ) };
      _cache.Remove( keys );
      
    • void Remove( IEnumerable<string> keys )

      范例:

      var keys = new List<string> { "User-1:Resource-2", "User-3:Resource-4" };
      _cache.Remove( keys );
      
  • RemoveAsync

    功能: 移除緩存

    • Task RemoveAsync( CacheKey key, CancellationToken cancellationToken = default )

      范例:

      var cacheKey = new UserResourceCacheKey( "1", "2" );
      await _cache.RemoveAsync( cacheKey );
      
    • Task RemoveAsync( string key, CancellationToken cancellationToken = default )

      范例:

      await _cache.RemoveAsync( "User-1:Resource-2" );
      
    • Task RemoveAsync( IEnumerable<CacheKey> keys, CancellationToken cancellationToken = default )

      范例:

      var keys = new List<UserResourceCacheKey> { new( "1", "2" ), new( "3", "4" ) };
      await _cache.RemoveAsync( keys );
      
    • Task RemoveAsync( IEnumerable<string> keys, CancellationToken cancellationToken = default )

      范例:

      var keys = new List<string> { "User-1:Resource-2", "User-3:Resource-4" };
      await _cache.RemoveAsync( keys );
      
  • RemoveByPrefix

    功能: 通過緩存鍵前綴移除緩存

    • void RemoveByPrefix( string prefix )

      范例:

      _cache.RemoveByPrefix( "User-1" );
      
    • Task RemoveByPrefixAsync( string prefix, CancellationToken cancellationToken = default )

      范例:

      await _cache.RemoveByPrefixAsync( "User-1" );
      
  • RemoveByPattern

    功能: 通過模式移除緩存

    • void RemoveByPattern( string pattern )

      范例:

      移除 User 開頭的緩存.

      _cache.RemoveByPattern( "User*" );
      
  • RemoveByPatternAsync

    功能: 通過模式移除緩存

    • Task RemoveByPatternAsync( string pattern, CancellationToken cancellationToken = default )

      范例:

      移除 User 開頭的緩存.

      await _cache.RemoveByPatternAsync( "User*" );
      
  • Clear

    功能: 清空緩存

    • void Clear()

      范例:

      _cache.Clear();
      
  • ClearAsync

    功能: 清空緩存

    • Task ClearAsync( CancellationToken cancellationToken = default )

      范例:

      await _cache.ClearAsync();
      

ILocalCache

Util.Caching.ILocalCache 從 ICache 派生,表示本地緩存.

當同時配置本地緩存和Redis緩存, 如果你想明確使用本地緩存, 請使用 ILocalCache.

Api 參考 ICache.

IRedisCache

Util.Caching.IRedisCache 從 ICache 派生,表示 Redis 分布式緩存.

當同時配置本地緩存和Redis緩存, 如果你想明確使用 Redis 緩存, 請使用 IRedisCache.

IRedisCache 除了繼承基礎緩存操作外,還將添加 Redis 專用緩存操作.

目前 IRedisCache 尚未添加 Redis 專用操作,后續根據需要進行添加.

Api 參考 ICache.

更新緩存

  • 設置緩存到期時間

    創建緩存項時可以設置一個過期時間間隔,超過到期時間,緩存將失效.

    EasyCaching 目前尚不支持滑動過期.

    下面的示例設置1小時的過期時間間隔,當超過1小時,緩存過期后,將重新加載最新數據.

    var cacheKey = new UserResourceCacheKey( "1", "2" );
    var result = _cache.Get( cacheKey, () => _repository.GetUserResource( "1", "2" ), new CacheOptions { Expiration = TimeSpan.FromHours( 1 ) } );
    
  • 通過本地事件總線更新緩存

    基于過期時間被動更新,適合實時性要求低的場景.

    當數據庫的值已更新,從緩存中讀取舊值,對業務基本沒有影響或影響很小.

    但有些數據具有更高的實時性,在數據庫更新時,需要同步更新緩存中的副本.

    可以通過發布訂閱本地事件總線實時更新特定緩存.

緩存攔截器

  • CacheAttribute 緩存攔截器

    [Cache] 是一個緩存攔截器,使用 ICache 接口操作緩存.

    它會根據參數自動創建緩存鍵,并調用攔截的方法獲取數據并緩存起來.

    如果你不關心緩存鍵的長相,可以使用 [Cache] 攔截器快速添加緩存.

    范例:

    public interface ITestService {
        [Cache]
        UserResource Get( string userId, string resourceId );
    }
    
    • 設置緩存鍵前綴 Prefix.

      緩存鍵前綴支持占位符, {0} 代表第一個參數.

      范例:

      public interface ITestService {
          [Cache( Prefix = "User-{0}" )]
          UserResource Get( string userId, string resourceId );
      }
      

      下面的示例調用 ITestService 的 Get 方法,傳入參數 userId = "1" , resourceId = "2" .

      創建的緩存鍵為: "User-1:1:2".

      緩存鍵前綴 User-{0} 中的 {0} 替換為第一個參數 userId ,即 User-1.

      使用 : 按順序連接所有參數值.

      var result = _service.Get( "1", "2" );
      
    • 設置緩存過期間隔 Expiration ,單位: 秒,默認值: 36000

      范例:

      設置 120 秒過期.

      public interface ITestService {
          [Cache( Expiration = 120 )]
          UserResource Get( string userId, string resourceId );
      }
      
  • LocalCacheAttribute 本地緩存攔截器

    [LocalCache] 與 [Cache] 類似,但它使用 ILocalCache 接口操作緩存.

    如果你的某個操作需要使用本地緩存,可以用 [LocalCache].

    具體操作請參考 [Cache].

  • RedisCacheAttribute Redis緩存攔截器

    [RedisCache] 與 [Cache] 類似,但它使用 IRedisCache 接口操作緩存.

    如果你的某個操作需要使用Redis緩存,可以用 [RedisCache].

    具體操作請參考 [Cache].

緩存內存釋放

當緩存占據大量內存空間,調用 Clear 清理緩存并不會釋放內存,等待一段時間仍然不會釋放.

對于 IMemoryCache 同樣如此.

某些測試環境,你可以調用 GC.Collect() 強制回收內存空間.

生產環境,不應手工回收.

源碼解析

ICache 緩存操作

Util.Caching.ICache 是緩存操作接口.

CacheManager 將緩存操作委托給 EasyCaching 的 IEasyCachingProvider 接口.

IEasyCachingProvider 根據配置的提供程序切換為本地緩存或Redis緩存.

當配置了2級緩存, 緩存操作委托給 IHybridCachingProvider 2級緩存提供程序接口.

/// <summary>
/// 緩存
/// </summary>
public interface ICache {
    /// <summary>
    /// 緩存是否已存在
    /// </summary>
    /// <param name="key">緩存鍵</param>
    bool Exists( CacheKey key );
    /// <summary>
    /// 緩存是否已存在
    /// </summary>
    /// <param name="key">緩存鍵</param>
    bool Exists( string key );
    /// <summary>
    /// 緩存是否已存在
    /// </summary>
    /// <param name="key">緩存鍵</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task<bool> ExistsAsync( CacheKey key, CancellationToken cancellationToken = default );
    /// <summary>
    /// 緩存是否已存在
    /// </summary>
    /// <param name="key">緩存鍵</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task<bool> ExistsAsync( string key, CancellationToken cancellationToken = default );
    /// <summary>
    /// 從緩存中獲取數據
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="key">緩存鍵</param>
    T Get<T>( CacheKey key );
    /// <summary>
    /// 從緩存中獲取數據
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="key">緩存鍵</param>
    T Get<T>( string key );
    /// <summary>
    /// 從緩存中獲取數據
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="keys">緩存鍵集合</param>
    List<T> Get<T>( IEnumerable<CacheKey> keys );
    /// <summary>
    /// 從緩存中獲取數據
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="keys">緩存鍵集合</param>
    List<T> Get<T>( IEnumerable<string> keys );
    /// <summary>
    /// 從緩存中獲取數據,如果不存在,則執行獲取數據操作并添加到緩存中
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="key">緩存鍵</param>
    /// <param name="action">獲取數據操作</param>
    /// <param name="options">緩存配置</param>
    T Get<T>( CacheKey key, Func<T> action, CacheOptions options = null );
    /// <summary>
    /// 從緩存中獲取數據,如果不存在,則執行獲取數據操作并添加到緩存中
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="key">緩存鍵</param>
    /// <param name="action">獲取數據操作</param>
    /// <param name="options">緩存配置</param>
    T Get<T>( string key, Func<T> action, CacheOptions options = null );
    /// <summary>
    /// 從緩存中獲取數據
    /// </summary>
    /// <param name="key">緩存鍵</param>
    /// <param name="type">緩存數據類型</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task<object> GetAsync( string key, Type type, CancellationToken cancellationToken = default );
    /// <summary>
    /// 從緩存中獲取數據
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="key">緩存鍵</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task<T> GetAsync<T>( CacheKey key, CancellationToken cancellationToken = default );
    /// <summary>
    /// 從緩存中獲取數據
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="key">緩存鍵</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task<T> GetAsync<T>( string key, CancellationToken cancellationToken = default );
    /// <summary>
    /// 從緩存中獲取數據
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="keys">緩存鍵集合</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task<List<T>> GetAsync<T>( IEnumerable<CacheKey> keys, CancellationToken cancellationToken = default );
    /// <summary>
    /// 從緩存中獲取數據
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="keys">緩存鍵集合</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task<List<T>> GetAsync<T>( IEnumerable<string> keys, CancellationToken cancellationToken = default );
    /// <summary>
    /// 從緩存中獲取數據,如果不存在,則執行獲取數據操作并添加到緩存中
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="key">緩存鍵</param>
    /// <param name="action">獲取數據操作</param>
    /// <param name="options">緩存配置</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task<T> GetAsync<T>( CacheKey key, Func<Task<T>> action, CacheOptions options = null, CancellationToken cancellationToken = default );
    /// <summary>
    /// 從緩存中獲取數據,如果不存在,則執行獲取數據操作并添加到緩存中
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="key">緩存鍵</param>
    /// <param name="action">獲取數據操作</param>
    /// <param name="options">緩存配置</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task<T> GetAsync<T>( string key, Func<Task<T>> action, CacheOptions options = null, CancellationToken cancellationToken = default );
    /// <summary>
    /// 通過緩存鍵前綴獲取數據
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="prefix">緩存鍵前綴</param>
    List<T> GetByPrefix<T>( string prefix );
    /// <summary>
    /// 通過緩存鍵前綴獲取數據
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="prefix">緩存鍵前綴</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task<List<T>> GetByPrefixAsync<T>( string prefix, CancellationToken cancellationToken = default );
    /// <summary>
    /// 設置緩存,當緩存已存在則忽略,設置成功返回true
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="key">緩存鍵</param>
    /// <param name="value">值</param>
    /// <param name="options">緩存配置</param>
    bool TrySet<T>( CacheKey key, T value, CacheOptions options = null );
    /// <summary>
    /// 設置緩存,當緩存已存在則忽略,設置成功返回true
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="key">緩存鍵</param>
    /// <param name="value">值</param>
    /// <param name="options">緩存配置</param>
    bool TrySet<T>( string key, T value, CacheOptions options = null );
    /// <summary>
    /// 設置緩存,當緩存已存在則忽略,設置成功返回true
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="key">緩存鍵</param>
    /// <param name="value">值</param>
    /// <param name="options">緩存配置</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task<bool> TrySetAsync<T>( CacheKey key, T value, CacheOptions options = null, CancellationToken cancellationToken = default );
    /// <summary>
    /// 設置緩存,當緩存已存在則忽略,設置成功返回true
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="key">緩存鍵</param>
    /// <param name="value">值</param>
    /// <param name="options">緩存配置</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task<bool> TrySetAsync<T>( string key, T value, CacheOptions options = null, CancellationToken cancellationToken = default );
    /// <summary>
    /// 設置緩存,當緩存已存在則覆蓋
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="key">緩存鍵</param>
    /// <param name="value">值</param>
    /// <param name="options">緩存配置</param>
    void Set<T>( CacheKey key, T value, CacheOptions options = null );
    /// <summary>
    /// 設置緩存,當緩存已存在則覆蓋
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="key">緩存鍵</param>
    /// <param name="value">值</param>
    /// <param name="options">緩存配置</param>
    void Set<T>( string key, T value, CacheOptions options = null );
    /// <summary>
    /// 設置緩存集合
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="items">緩存項集合</param>
    /// <param name="options">緩存配置</param>
    void Set<T>( IDictionary<CacheKey,T> items, CacheOptions options = null );
    /// <summary>
    /// 設置緩存集合
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="items">緩存項集合</param>
    /// <param name="options">緩存配置</param>
    void Set<T>( IDictionary<string, T> items, CacheOptions options = null );
    /// <summary>
    /// 設置緩存,當緩存已存在則覆蓋
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="key">緩存鍵</param>
    /// <param name="value">值</param>
    /// <param name="options">緩存配置</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task SetAsync<T>( CacheKey key, T value, CacheOptions options = null, CancellationToken cancellationToken = default );
    /// <summary>
    /// 設置緩存,當緩存已存在則覆蓋
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="key">緩存鍵</param>
    /// <param name="value">值</param>
    /// <param name="options">緩存配置</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task SetAsync<T>( string key, T value, CacheOptions options = null, CancellationToken cancellationToken = default );
    /// <summary>
    /// 設置緩存集合
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="items">緩存項集合</param>
    /// <param name="options">緩存配置</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task SetAsync<T>( IDictionary<CacheKey, T> items, CacheOptions options = null, CancellationToken cancellationToken = default );
    /// <summary>
    /// 設置緩存集合
    /// </summary>
    /// <typeparam name="T">緩存數據類型</typeparam>
    /// <param name="items">緩存項集合</param>
    /// <param name="options">緩存配置</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task SetAsync<T>( IDictionary<string, T> items, CacheOptions options = null, CancellationToken cancellationToken = default );
    /// <summary>
    /// 移除緩存
    /// </summary>
    /// <param name="key">緩存鍵</param>
    void Remove( CacheKey key );
    /// <summary>
    /// 移除緩存
    /// </summary>
    /// <param name="key">緩存鍵</param>
    void Remove( string key );
    /// <summary>
    /// 移除緩存集合
    /// </summary>
    /// <param name="keys">緩存鍵集合</param>
    void Remove( IEnumerable<CacheKey> keys );
    /// <summary>
    /// 移除緩存集合
    /// </summary>
    /// <param name="keys">緩存鍵集合</param>
    void Remove( IEnumerable<string> keys );
    /// <summary>
    /// 移除緩存
    /// </summary>
    /// <param name="key">緩存鍵</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task RemoveAsync( CacheKey key, CancellationToken cancellationToken = default );
    /// <summary>
    /// 移除緩存
    /// </summary>
    /// <param name="key">緩存鍵</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task RemoveAsync( string key, CancellationToken cancellationToken = default );
    /// <summary>
    /// 移除緩存集合
    /// </summary>
    /// <param name="keys">緩存鍵集合</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task RemoveAsync( IEnumerable<CacheKey> keys, CancellationToken cancellationToken = default );
    /// <summary>
    /// 移除緩存集合
    /// </summary>
    /// <param name="keys">緩存鍵集合</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task RemoveAsync( IEnumerable<string> keys, CancellationToken cancellationToken = default );
    /// <summary>
    /// 通過緩存鍵前綴移除緩存
    /// </summary>
    /// <param name="prefix">緩存鍵前綴</param>
    void RemoveByPrefix( string prefix );
    /// <summary>
    /// 通過緩存鍵前綴移除緩存
    /// </summary>
    /// <param name="prefix">緩存鍵前綴</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task RemoveByPrefixAsync( string prefix, CancellationToken cancellationToken = default );
    /// <summary>
    /// 通過緩存鍵模式移除緩存
    /// </summary>
    /// <param name="pattern">緩存鍵模式,范例: test*</param>
    void RemoveByPattern( string pattern );
    /// <summary>
    /// 通過緩存鍵模式移除緩存
    /// </summary>
    /// <param name="pattern">緩存鍵模式,范例: test*</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task RemoveByPatternAsync( string pattern, CancellationToken cancellationToken = default );
    /// <summary>
    /// 清空緩存
    /// </summary>
    void Clear();
    /// <summary>
    /// 清空緩存
    /// </summary>
    /// <param name="cancellationToken">取消令牌</param>
    Task ClearAsync( CancellationToken cancellationToken = default );
}

/// <summary>
/// EasyCaching緩存服務
/// </summary>
public class CacheManager : ICache {

    #region 字段

    /// <summary>
    /// 緩存提供器
    /// </summary>
    private readonly IEasyCachingProviderBase _provider;
    /// <summary>
    /// 緩存提供器
    /// </summary>
    private readonly IEasyCachingProvider _cachingProvider;

    #endregion

    #region 構造方法

    /// <summary>
    /// 初始化EasyCaching緩存服務
    /// </summary>
    /// <param name="provider">EasyCaching緩存提供器</param>
    /// <param name="hybridProvider">EasyCaching 2級緩存提供器</param>
    public CacheManager( IEasyCachingProvider provider, IHybridCachingProvider hybridProvider = null ) {
        CachingOptions.Clear();
        if ( provider != null ) {
            _provider = provider;
            _cachingProvider = provider;
        }
        if( hybridProvider != null )
            _provider = hybridProvider;
        _provider.CheckNull( nameof( provider ) );
    }

    #endregion

    #region Exists

    /// <inheritdoc />
    public bool Exists( CacheKey key ) {
        key.Validate();
        return Exists( key.Key );
    }

    /// <inheritdoc />
    public bool Exists( string key ) {
        return _provider.Exists( key );
    }

    #endregion

    #region ExistsAsync

    /// <inheritdoc />
    public async Task<bool> ExistsAsync( CacheKey key, CancellationToken cancellationToken = default ) {
        key.Validate();
        return await ExistsAsync( key.Key, cancellationToken );
    }

    /// <inheritdoc />
    public async Task<bool> ExistsAsync( string key, CancellationToken cancellationToken = default ) {
        return await _provider.ExistsAsync( key, cancellationToken );
    }

    #endregion

    #region Get

    /// <inheritdoc />
    public T Get<T>( CacheKey key ) {
        key.Validate();
        return Get<T>( key.Key );
    }

    /// <inheritdoc />
    public T Get<T>( string key ) {
        var result = _provider.Get<T>( key );
        return result.Value;
    }

    /// <inheritdoc />
    public List<T> Get<T>( IEnumerable<CacheKey> keys ) {
        return Get<T>( ToKeys( keys ) );
    }

    /// <summary>
    /// 轉換為緩存鍵字符串集合
    /// </summary>
    private IEnumerable<string> ToKeys( IEnumerable<CacheKey> keys ) {
        keys.CheckNull( nameof( keys ) );
        var cacheKeys = keys.ToList();
        cacheKeys.ForEach( t => t.Validate() );
        return cacheKeys.Select( t => t.Key );
    }

    /// <inheritdoc />
    public List<T> Get<T>( IEnumerable<string> keys ) {
        Validate();
        var result = _cachingProvider.GetAll<T>( keys );
        return result.Values.Select( t => t.Value ).ToList();
    }

    /// <summary>
    /// 驗證
    /// </summary>
    private void Validate() {
        if ( _cachingProvider == null )
            throw new NotSupportedException( "2級緩存不支持該操作" );
    }

    /// <inheritdoc />
    public T Get<T>( CacheKey key, Func<T> action, CacheOptions options = null ) {
        key.Validate();
        return Get( key.Key, action, options );
    }

    /// <inheritdoc />
    public T Get<T>( string key, Func<T> action, CacheOptions options = null ) {
        var result = _provider.Get( key, action, GetExpiration( options ) );
        return result.Value;
    }

    /// <summary>
    /// 獲取過期時間間隔
    /// </summary>
    private TimeSpan GetExpiration( CacheOptions options ) {
        var result = options?.Expiration;
        result ??= TimeSpan.FromHours( 8 );
        return result.SafeValue();
    }

    #endregion

    #region GetAsync

    /// <inheritdoc />
    public async Task<object> GetAsync( string key, Type type, CancellationToken cancellationToken = default ) {
        return await _provider.GetAsync( key, type, cancellationToken );
    }

    /// <inheritdoc />
    public async Task<T> GetAsync<T>( CacheKey key, CancellationToken cancellationToken = default ) {
        key.Validate();
        return await GetAsync<T>( key.Key, cancellationToken );
    }

    /// <inheritdoc />
    public async Task<T> GetAsync<T>( string key, CancellationToken cancellationToken = default ) {
        var result = await _provider.GetAsync<T>( key, cancellationToken );
        return result.Value;
    }

    /// <inheritdoc />
    public async Task<List<T>> GetAsync<T>( IEnumerable<CacheKey> keys, CancellationToken cancellationToken = default ) {
        return await GetAsync<T>( ToKeys( keys ), cancellationToken );
    }

    /// <inheritdoc />
    public async Task<List<T>> GetAsync<T>( IEnumerable<string> keys, CancellationToken cancellationToken = default ) {
        Validate();
        var result = await _cachingProvider.GetAllAsync<T>( keys, cancellationToken );
        return result.Values.Select( t => t.Value ).ToList();
    }

    /// <inheritdoc />
    public async Task<T> GetAsync<T>( CacheKey key, Func<Task<T>> action, CacheOptions options = null, CancellationToken cancellationToken = default ) {
        key.Validate();
        return await GetAsync( key.Key, action, options, cancellationToken );
    }

    /// <inheritdoc />
    public async Task<T> GetAsync<T>( string key, Func<Task<T>> action, CacheOptions options = null, CancellationToken cancellationToken = default ) {
        var result = await _provider.GetAsync( key, action, GetExpiration( options ), cancellationToken );
        return result.Value;
    }

    #endregion

    #region GetByPrefix

    /// <inheritdoc />
    public List<T> GetByPrefix<T>( string prefix ) {
        if( prefix.IsEmpty() )
            return new List<T>();
        Validate();
        return _cachingProvider.GetByPrefix<T>( prefix ).Where( t => t.Value.HasValue ).Select( t => t.Value.Value ).ToList();
    }

    #endregion

    #region GetByPrefixAsync

    /// <inheritdoc />
    public async Task<List<T>> GetByPrefixAsync<T>( string prefix, CancellationToken cancellationToken = default ) {
        if( prefix.IsEmpty() )
            return new List<T>();
        Validate();
        var result = await _cachingProvider.GetByPrefixAsync<T>( prefix, cancellationToken );
        return result.Where( t => t.Value.HasValue ).Select( t => t.Value.Value ).ToList();
    }

    #endregion

    #region TrySet

    /// <inheritdoc />
    public bool TrySet<T>( CacheKey key, T value, CacheOptions options = null ) {
        key.Validate();
        return TrySet( key.Key, value, options );
    }

    /// <inheritdoc />
    public bool TrySet<T>( string key, T value, CacheOptions options = null ) {
        return _provider.TrySet( key, value, GetExpiration( options ) );
    }

    #endregion

    #region TrySetAsync

    /// <inheritdoc />
    public async Task<bool> TrySetAsync<T>( CacheKey key, T value, CacheOptions options = null, CancellationToken cancellationToken = default ) {
        key.Validate();
        return await TrySetAsync( key.Key, value, options, cancellationToken );
    }

    /// <inheritdoc />
    public async Task<bool> TrySetAsync<T>( string key, T value, CacheOptions options = null, CancellationToken cancellationToken = default ) {
        return await _provider.TrySetAsync( key, value, GetExpiration( options ), cancellationToken );
    }

    #endregion

    #region Set

    /// <inheritdoc />
    public void Set<T>( CacheKey key, T value, CacheOptions options = null ) {
        key.Validate();
        Set( key.Key, value, options );
    }

    /// <inheritdoc />
    public void Set<T>( string key, T value, CacheOptions options = null ) {
        _provider.Set( key, value, GetExpiration( options ) );
    }

    /// <inheritdoc />
    public void Set<T>( IDictionary<CacheKey, T> items, CacheOptions options = null ) {
        Set( ToItems( items ), options );
    }

    /// <summary>
    /// 轉換為緩存項集合
    /// </summary>
    private IDictionary<string, T> ToItems<T>( IDictionary<CacheKey, T> items ) {
        items.CheckNull( nameof( items ) );
        return items.Select( item => {
            item.Key.Validate();
            return new KeyValuePair<string, T>( item.Key.Key, item.Value );
        } ).ToDictionary( t => t.Key, t => t.Value );
    }

    /// <inheritdoc />
    public void Set<T>( IDictionary<string, T> items, CacheOptions options = null ) {
        _provider.SetAll( items, GetExpiration( options ) );
    }

    #endregion

    #region SetAsync

    /// <inheritdoc />
    public async Task SetAsync<T>( CacheKey key, T value, CacheOptions options = null, CancellationToken cancellationToken = default ) {
        key.Validate();
        await SetAsync( key.Key, value, options, cancellationToken );
    }

    /// <inheritdoc />
    public async Task SetAsync<T>( string key, T value, CacheOptions options = null, CancellationToken cancellationToken = default ) {
        await _provider.SetAsync( key, value, GetExpiration( options ), cancellationToken );
    }

    /// <inheritdoc />
    public async Task SetAsync<T>( IDictionary<CacheKey, T> items, CacheOptions options = null, CancellationToken cancellationToken = default ) {
        await SetAsync( ToItems( items ), options, cancellationToken );
    }

    /// <inheritdoc />
    public async Task SetAsync<T>( IDictionary<string, T> items, CacheOptions options = null, CancellationToken cancellationToken = default ) {
        await _provider.SetAllAsync( items, GetExpiration( options ), cancellationToken );
    }

    #endregion

    #region Remove

    /// <inheritdoc />
    public void Remove( CacheKey key ) {
        key.Validate();
        Remove( key.Key );
    }

    /// <inheritdoc />
    public void Remove( string key ) {
        _provider.Remove( key );
    }

    /// <inheritdoc />
    public void Remove( IEnumerable<CacheKey> keys ) {
        Remove( ToKeys( keys ) );
    }

    /// <inheritdoc />
    public void Remove( IEnumerable<string> keys ) {
        _provider.RemoveAll( keys );
    }

    #endregion

    #region RemoveAsync

    /// <inheritdoc />
    public async Task RemoveAsync( CacheKey key, CancellationToken cancellationToken = default ) {
        key.Validate();
        await RemoveAsync( key.Key, cancellationToken );
    }

    /// <inheritdoc />
    public async Task RemoveAsync( string key, CancellationToken cancellationToken = default ) {
        await _provider.RemoveAsync( key, cancellationToken );
    }

    /// <inheritdoc />
    public async Task RemoveAsync( IEnumerable<CacheKey> keys, CancellationToken cancellationToken = default ) {
        await RemoveAsync( ToKeys( keys ), cancellationToken );
    }

    /// <inheritdoc />
    public async Task RemoveAsync( IEnumerable<string> keys, CancellationToken cancellationToken = default ) {
        await _provider.RemoveAllAsync( keys, cancellationToken );
    }

    #endregion

    #region RemoveByPrefix

    /// <summary>
    /// 通過緩存鍵前綴移除緩存
    /// </summary>
    /// <param name="prefix">緩存鍵前綴</param>
    public void RemoveByPrefix( string prefix ) {
        if( prefix.IsEmpty() )
            return;
        _provider.RemoveByPrefix( prefix );
    }

    #endregion

    #region RemoveByPrefixAsync

    /// <inheritdoc />
    public async Task RemoveByPrefixAsync( string prefix, CancellationToken cancellationToken = default ) {
        if( prefix.IsEmpty() )
            return;
        await _provider.RemoveByPrefixAsync( prefix, cancellationToken );
    }

    #endregion

    #region RemoveByPattern

    /// <inheritdoc />
    public void RemoveByPattern( string pattern ) {
        if( pattern.IsEmpty() )
            return;
        _provider.RemoveByPattern( pattern );
    }

    #endregion

    #region RemoveByPatternAsync

    /// <inheritdoc />
    public async Task RemoveByPatternAsync( string pattern, CancellationToken cancellationToken = default ) {
        if( pattern.IsEmpty() )
            return;
        await _provider.RemoveByPatternAsync( pattern, cancellationToken );
    }

    #endregion

    #region Clear

    /// <inheritdoc />
    public void Clear() {
        Validate();
        _cachingProvider.Flush();
    }

    #endregion

    #region ClearAsync

    /// <inheritdoc />
    public async Task ClearAsync( CancellationToken cancellationToken = default ) {
        Validate();
        await _cachingProvider.FlushAsync( cancellationToken );
    }

    #endregion
}

CacheKey 緩存鍵

通過繼承 CacheKey 創建自定義緩存鍵對象,可以封裝緩存鍵的構造細節.

/// <summary>
/// 緩存鍵
/// </summary>
public class CacheKey {
    /// <summary>
    /// 緩存鍵
    /// </summary>
    private string _key;

    /// <summary>
    /// 初始化緩存鍵
    /// </summary>
    public CacheKey() {
    }

    /// <summary>
    /// 初始化緩存鍵
    /// </summary>
    /// <param name="key">緩存鍵</param>
    /// <param name="parameters">緩存鍵參數</param>
    public CacheKey( string key,params object[] parameters) {
        _key = string.Format( key, parameters );
    }

    /// <summary>
    /// 緩存鍵
    /// </summary>
    public string Key {
        get => ToString();
        set => _key = value;
    }

    /// <summary>
    /// 緩存鍵前綴
    /// </summary>
    public string Prefix { get; set; }

    /// <summary>
    /// 獲取緩存鍵
    /// </summary>
    public override string ToString() {
        return $"{Prefix}{_key}";
    }
}

CacheAttribute 緩存攔截器

[Cache] 緩存攔截器提供了緩存操作的快捷方式.

/// <summary>
/// 緩存攔截器
/// </summary>
public class CacheAttribute : InterceptorBase {
    /// <summary>
    /// 緩存鍵前綴,可使用占位符, {0} 表示第一個參數值,范例: User-{0}
    /// </summary>
    public string Prefix { get; set; }
    /// <summary>
    /// 緩存過期間隔,單位:秒,默認值:36000
    /// </summary>
    public int Expiration { get; set; } = 36000;

    /// <summary>
    /// 執行
    /// </summary>
    public override async Task Invoke( AspectContext context, AspectDelegate next ) {
        var cache = GetCache( context );
        var returnType = GetReturnType( context );
        var key = CreateCacheKey( context );
        var value = await GetCacheValue( cache, returnType, key );
        if( value != null ) {
            SetReturnValue( context, returnType, value );
            return;
        }
        await next( context );
        await SetCache( context, cache, key );
    }

    /// <summary>
    /// 獲取緩存服務
    /// </summary>
    protected virtual ICache GetCache( AspectContext context ) {
        return context.ServiceProvider.GetService<ICache>();
    }

    /// <summary>
    /// 獲取返回類型
    /// </summary>
    private Type GetReturnType( AspectContext context ) {
        return context.IsAsync() ? context.ServiceMethod.ReturnType.GetGenericArguments().First() : context.ServiceMethod.ReturnType;
    }

    /// <summary>
    /// 創建緩存鍵
    /// </summary>
    private string CreateCacheKey( AspectContext context ) {
        var keyGenerator = context.ServiceProvider.GetService<ICacheKeyGenerator>();
        return keyGenerator.CreateCacheKey( context.ServiceMethod, context.Parameters, GetPrefix( context ) );
    }

    /// <summary>
    /// 獲取緩存鍵前綴
    /// </summary>
    private string GetPrefix( AspectContext context ) {
        try {
            return string.Format( Prefix, context.Parameters.ToArray() );
        }
        catch {
            return Prefix;
        }
    }

    /// <summary>
    /// 獲取緩存值
    /// </summary>
    private async Task<object> GetCacheValue( ICache cache, Type returnType, string key ) {
        return await cache.GetAsync( key, returnType );
    }

    /// <summary>
    /// 設置返回值
    /// </summary>
    private void SetReturnValue( AspectContext context, Type returnType, object value ) {
        if( context.IsAsync() ) {
            context.ReturnValue = typeof( Task ).GetMethods()
                .First( p => p.Name == "FromResult" && p.ContainsGenericParameters )
                .MakeGenericMethod( returnType ).Invoke( null, new[] { value } );
            return;
        }
        context.ReturnValue = value;
    }

    /// <summary>
    /// 設置緩存
    /// </summary>
    private async Task SetCache( AspectContext context, ICache cache, string key ) {
        var options = new CacheOptions { Expiration = TimeSpan.FromSeconds( Expiration ) };
        var returnValue = context.IsAsync() ? await context.UnwrapAsyncReturnValue() : context.ReturnValue;
        await cache.SetAsync( key, returnValue, options );
    }
}

LocalCacheAttribute 本地緩存攔截器

/// <summary>
/// 本地緩存攔截器
/// </summary>
public class LocalCacheAttribute : CacheAttribute {
    /// <summary>
    /// 獲取緩存服務
    /// </summary>
    protected override ICache GetCache( AspectContext context ) {
        return context.ServiceProvider.GetService<ILocalCache>();
    }
}

RedisCacheAttribute Redis緩存攔截器

/// <summary>
/// Redis緩存攔截器
/// </summary>
public class RedisCacheAttribute : CacheAttribute {
    /// <summary>
    /// 獲取緩存服務
    /// </summary>
    protected override ICache GetCache( AspectContext context ) {
        return context.ServiceProvider.GetService<IRedisCache>();
    }
}

總結

以上是生活随笔為你收集整理的Util应用框架基础(七) - 缓存的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

久久久久久久av | 欧美日韩亚洲第一页 | 麻豆国产露脸在线观看 | 亚洲自拍自偷 | 国产精品av久久久久久无 | 日韩网站在线免费观看 | 日本中文字幕视频 | 久久久亚洲网站 | 日韩精品中字 | 国产精品成人国产乱一区 | 456免费视频| 免费日韩视 | 日韩av成人免费看 | 天天鲁一鲁摸一摸爽一爽 | 久久综合久久综合这里只有精品 | 精品国产一区二区三区久久久 | 日产乱码一二三区别在线 | 免费高清看电视网站 | 日本福利视频在线 | 日韩av影视在线观看 | 亚洲视频综合 | 国产美女视频网站 | 成人在线免费av | 99久久夜色精品国产亚洲96 | 国产精品va视频 | 国产又粗又猛又黄又爽的视频 | 久久免费播放 | 色鬼综合网 | 久久9视频 | av中文字幕不卡 | 在线观看日本高清mv视频 | 看片黄网站 | 久久久久激情视频 | 国产美腿白丝袜足在线av | 国产午夜在线观看 | a天堂一码二码专区 | 欧美日本在线观看视频 | 九色91福利 | 久久精品一区二区三区四区 | 久久国产精品一国产精品 | 一区久久久 | 亚洲一区 av | 中文字幕4 | 狠狠操影视| 在线精品视频免费播放 | 亚洲狠狠丁香婷婷综合久久久 | 国产91在线免费视频 | 亚洲特级毛片 | 在线日韩精品视频 | 天天操比 | 在线观看日韩专区 | 婷婷激情5月天 | 日韩一区二区三区高清免费看看 | 欧美日韩精品免费观看 | 亚洲精品国精品久久99热一 | 九九热只有精品 | 一区二区视频在线免费观看 | 精品国产一区二区三区免费 | 日韩欧美在线国产 | 国产在线一卡 | 精品国产乱码久久 | 99视频一区| 中文字幕在线播放日韩 | 亚洲欧美视频在线播放 | 欧美国产视频在线 | 日韩免费三级 | 91av影视| 在线 视频 一区二区 | 深爱五月网| 国产成人一区二区三区久久精品 | 色香天天 | 久久草网| 最近日韩免费视频 | 国产一区二区在线观看免费 | 久久99精品久久久久久久久久久久 | 伊人一级 | 国产精品久久久久久影院 | 精品国产亚洲在线 | 欧美在线视频一区二区 | 国产亚洲视频在线免费观看 | 丝袜美腿av| 亚洲国产午夜 | 国产色网| 一区二区三区四区五区六区 | 2019免费中文字幕 | 国产精品av在线 | 亚洲国产免费看 | 国产综合香蕉五月婷在线 | 国产99自拍| 国产精品久久久久999 | 久久高清 | 精品网站999www| 亚洲视频 中文字幕 | 国产亚洲精品电影 | 亚洲综合小说 | 97超碰人人网 | 国产日产亚洲精华av | 在线观看免费av网 | 九九九九色 | 91精品久久久久久综合五月天 | 国产一区二区三区免费观看视频 | 精品久久久久久国产91 | 91人人人| 四虎成人精品永久免费av | 午夜电影一区 | 国产黄色成人 | 黄色av网站在线观看免费 | 在线观看理论 | 国产精品1区2区3区 久久免费视频7 | 亚洲国产资源 | 奇米四色影狠狠爱7777 | 中文字幕资源网在线观看 | 久久视频这里有久久精品视频11 | 最近中文字幕免费观看 | 永久免费精品视频网站 | 狠狠干天天操 | 国产精品视频 | 久久99精品国产99久久6尤 | 色偷偷网站视频 | 婷婷综合网 | 91视频国产高清 | 婷婷色资源 | 99视频国产精品 | 国产精品久久久久久久99 | 亚洲精品人人 | 国产成人高清 | av在线播放一区二区三区 | 欧美日韩大片在线观看 | 狠狠色伊人亚洲综合网站色 | 高清av免费看 | 成人免费一区二区三区在线观看 | 国产中文字幕av | 国产午夜不卡 | 中文日韩在线 | 国产一区二区日本 | 欧美性视频网站 | 中文字幕在线精品 | 国产男女爽爽爽免费视频 | 色偷偷男人的天堂av | 国产免费资源 | 亚洲精品午夜国产va久久成人 | 久久久麻豆精品一区二区 | 色在线亚洲 | 国产成人久久av免费高清密臂 | 欧美日韩一区二区三区在线观看视频 | 日韩中文幕 | 色婷婷激情电影 | 成人av在线看| 狠狠干2018 | 国产精品av久久久久久无 | 亚洲精品视频第一页 | 婷婷激情欧美 | 能在线观看的日韩av | a级国产乱理伦片在线播放 久久久久国产精品一区 | 久久成人资源 | 天天操夜操 | 日韩黄色在线观看 | 日韩精品视频网站 | 中中文字幕av | 成年人黄色大片在线 | 91高清不卡 | 国产国产人免费人成免费视频 | 国产成人免费网站 | 五月婷婷av| 久久99精品久久久久久清纯直播 | 国产一级片免费视频 | 欧美国产一区二区 | 日韩视频欧美视频 | 精一区二区| www天天干 | 日韩免费高清 | 日韩电影中文字幕在线 | 久久字幕 | 精品成人免费 | 国产精品亚洲片在线播放 | 久草在线一免费新视频 | 伊人天堂网 | 九九视频这里只有精品 | 中文字幕av在线 | 狠色狠色综合久久 | 久久国产精品免费视频 | 国产一区二区在线免费观看 | 西西大胆免费视频 | 欧美污污视频 | 免费99精品国产自在在线 | av免费在线观看网站 | 精品久久久久亚洲 | 久久精品美女视频 | 亚洲激情六月 | 国产精品videossex国产高清 | 在线观看国产区 | 日韩在线精品 | 伊人久久一区 | 激情婷婷av | 999久久久久久久久6666 | 国产成人一区二区精品非洲 | 久久狠狠干| 国产成人精品日本亚洲999 | 国产韩国日本高清视频 | 在线观看视频国产一区 | 成人久久影院 | 免费a级大片 | 婷婷丁香色综合狠狠色 | 国产午夜精品久久久久久久久久 | 97超碰精品| 亚洲专区中文字幕 | 欧美激情第一区 | 欧美成人999 | 中文字幕亚洲国产 | 久久国产亚洲 | 人人舔人人插 | 亚洲人成网站精品片在线观看 | 中文字幕色播 | 丁香婷婷色| 综合国产视频 | 美女国内精品自产拍在线播放 | 国产精品成人国产乱一区 | 深爱激情五月婷婷 | 婷婷伊人综合亚洲综合网 | 天天艹天天 | 免费看成人a | 久久99精品久久久久久秒播蜜臀 | 成年免费在线视频 | 欧美a√在线 | 久久任你操 | 日日爽 | 天天爽天天爽夜夜爽 | 午夜精品一区二区三区免费 | 成人禁用看黄a在线 | 亚洲精品美女免费 | 久久这里只有精品视频首页 | 色婷婷精品大在线视频 | 久久久精品网站 | 中文字幕在线影院 | 中文字幕人成不卡一区 | 精品欧美在线视频 | 麻豆传媒视频在线免费观看 | 97成人资源| 人九九精品 | 久久久久电影网站 | 久久在线观看 | 黄色影院在线免费观看 | 高清一区二区三区 | 中文字幕在线观看的网站 | 日韩簧片在线观看 | 亚洲人人射 | 国产视频在线播放 | 免费久久片 | 国产资源在线观看 | av大片网址 | 天天射网站 | 日韩av高清 | 黄色视屏在线免费观看 | 亚洲男女精品 | 中文字幕在线观看第一页 | 国产精品一区二区久久久 | 日韩久久久久 | 国产精品一区二区三区在线播放 | 欧美91视频 | 丰满少妇麻豆av | 日韩字幕在线 | 久久综合给合久久狠狠色 | 婷婷色在线视频 | 天堂av网址 | 99九九热只有国产精品 | 欧美ⅹxxxxxx | 激情小说网站亚洲综合网 | 国产精品原创av片国产免费 | 久久精品这里都是精品 | 日本h在线播放 | 久久99国产综合精品 | 丝袜美女在线 | 久久成人精品电影 | 国产精品免费视频一区二区 | 999成人 | 久久久久久久看片 | 日韩小视频 | 一级黄色大片在线观看 | 国产美女免费观看 | 国产主播99 | 久久亚洲欧美日韩精品专区 | 国产精品24小时在线观看 | 日韩高清精品免费观看 | 免费看成年人 | 国产精品久久久电影 | 久久免费视频4 | 久久新视频 | 欧洲精品在线视频 | 日本视频不卡 | 成人永久免费 | 日韩视频一区二区 | 日日干日日操 | 国产一区二区视频在线播放 | 美女网站视频久久 | 麻豆视频免费播放 | 色狠狠综合天天综合综合 | 日韩,精品电影 | 在线观看久草 | 成人黄色大片在线观看 | 夜夜操网| 久9在线 | 精品国产乱码一区二区三区在线 | 中文字幕在线色 | 九九天堂 | 中文字幕在线不卡国产视频 | 精品国产理论片 | 国产精品免费久久久久久 | 国产又粗又硬又长又爽的视频 | 九九热久久免费视频 | 国产精品12 | 中文字幕黄色网 | 亚洲自拍av在线 | 亚洲黑丝少妇 | 麻豆视频一区 | 91精品国产自产91精品 | 午夜免费视频网站 | 欧美最新另类人妖 | 国产亚洲精品久久久久动 | 久草在线视频在线观看 | 国产又粗又猛又色又黄视频 | 一区 二区电影免费在线观看 | www亚洲一区 | 99这里精品 | 亚洲全部视频 | 国产伦理剧 | 中文字幕国内精品 | 午夜精品一区二区三区可下载 | 99精品国产亚洲 | 亚洲 中文 在线 精品 | 亚洲v精品 | 国产精品久久久久久久久久尿 | 午夜美女福利 | 久久精品国产一区 | 九九久久在线看 | 久久在现 | 亚洲黄色成人 | 欧美极品少妇xxxxⅹ欧美极品少妇xxxx亚洲精品 | 草久久精品 | 福利视频区 | 人人爱爱人人 | 激情五月六月婷婷 | 色亚洲激情 | 国产一区二区久久久久 | 国产精品18久久久久久久久 | 在线播放日韩 | 婷婷六月激情 | 精品无人国产偷自产在线 | 国产最新在线 | 国内免费久久久久久久久久久 | 特级片免费看 | 日韩精品极品视频 | 不卡中文字幕在线 | 中文日韩在线 | 日本护士撒尿xxxx18 | 国产一区二区成人 | 午夜国产在线 | 欧美怡红院视频 | 免费a网| 天天干天天干天天干天天干天天干天天干 | 国产91成人在在线播放 | 在线视频黄 | 欧美韩日视频 | 亚洲一区黄色 | 四虎永久国产精品 | 亚洲精品午夜一区人人爽 | 婷婷六月天综合 | 亚洲一区在线看 | 麻豆传媒视频在线 | 97香蕉久久国产在线观看 | 四川bbb搡bbb爽爽视频 | 国产在线观看你懂得 | 国产一线天在线观看 | 91自拍视频在线 | 亚洲九九影院 | 国产成人精品电影久久久 | 热久久最新地址 | 欧美精品久久久久久久免费 | 国产亚洲视频在线 | 国产区精品在线 | 亚洲精品黄网站 | 欧美日韩免费一区二区三区 | 黄色软件大全网站 | 成人动漫精品一区二区 | 女人18毛片90分钟 | 99爱这里只有精品 | 99久久网站 | 国产一级精品绿帽视频 | 国产精品视频 | 色婷婷色 | 国产在线 一区二区三区 | 精品视频97 | 一级黄色av | 狠狠色免费 | 久久午夜电影网 | 91污视频在线观看 | 狠狠狠色 | 国产一区二区三区网站 | 在线视频久 | 日韩在线免费观看视频 | 久草视频在线观 | 天天干天天做 | 国产91学生粉嫩喷水 | 蜜桃视频精品 | 久久五月天婷婷 | av免费线看| 久久激情视频免费观看 | 亚洲国产精品va在线看黑人 | 国产午夜精品免费一区二区三区视频 | 国产黄色网 | 国产99精品在线观看 | 国产精品第一视频 | 福利久久| 96超碰在线 | a在线观看视频 | 国产成人免费高清 | 国产成人三级在线播放 | 成人网大片| 亚洲理论片在线观看 | 四虎永久网站 | 麻豆 free xxxx movies hd| 欧美成人精品三级在线观看播放 | 五月天综合色激情 | 精品久久久免费视频 | 不卡中文字幕在线 | 天天色棕合合合合合合 | 国产免费又爽又刺激在线观看 | 波多野结衣在线中文字幕 | 成人av中文字幕 | 久久久.com| 久久成人高清视频 | 欧美成人xxxx | 激情视频免费在线观看 | 欧美综合色在线图区 | 国产一区二区在线免费视频 | 午夜av网站 | 日日夜色 | 成人免费影院 | 91九色蝌蚪在线 | 国产高清区 | a天堂最新版中文在线地址 久久99久久精品国产 | 国产成人99久久亚洲综合精品 | 中文字幕一区二区三 | 97免费在线观看视频 | 久久成人在线视频 | 91桃色国产在线播放 | 国产成人精品一区二区在线 | 91精品91 | 超碰在线日本 | 久久精品国产精品 | 精品久久久久久久久久国产 | 中文字幕影视 | 中文字幕五区 | 一区免费观看 | 日韩免费一区 | 日本中文字幕在线看 | 99热超碰在线 | 夜夜躁日日躁狠狠躁 | 91成人观看| 97在线资源| av成人动漫在线观看 | 爱av在线网 | 欧美精品久久人人躁人人爽 | 91精品国产一区二区在线观看 | 亚洲精品日韩一区二区电影 | 九九久久视频 | 日韩av影片在线观看 | 国产一区二区久久精品 | 日韩1级片 | 激情黄色一级片 | .国产精品成人自产拍在线观看6 | 亚洲电影图片小说 | 国产成人精品综合久久久 | 亚洲高清在线观看视频 | 狠狠色噜噜狠狠狠 | 成人免费一区二区三区在线观看 | 精品久久久久久国产91 | 免费在线电影网址大全 | 亚洲片在线 | 肉色欧美久久久久久久免费看 | 国产成人久久精品亚洲 | 91伊人久久大香线蕉蜜芽人口 | 久青草电影 | 波多野结衣电影一区二区三区 | 国产高清av | 夜夜爽88888免费视频4848 | 懂色av懂色av粉嫩av分享吧 | 91精品视频在线 | 欧美日韩精品在线观看视频 | 久久综合之合合综合久久 | 成人h动漫精品一区二 | 激情图片久久 | 国产亚洲欧美日韩高清 | 韩国精品一区二区三区六区色诱 | 欧美激情精品久久久 | 国产精品美女视频 | 色婷婷国产精品一区在线观看 | 免费亚洲黄色 | av一级片网站 | 亚洲激情中文 | 精品国产一区二区久久 | wwwwww黄 | 三级黄色网络 | 中文字幕av免费 | 久久久久久高潮国产精品视 | 国产精品婷婷午夜在线观看 | 亚洲色图激情文学 | 久草精品视频在线播放 | 亚洲永久精品在线观看 | 91精品视频在线免费观看 | 最近中文字幕免费 | 日韩激情免费视频 | 一级国产视频 | 精品a视频 | 精品久久1| 国产美女精品 | 中文字幕中文字幕 | 日韩精品综合在线 | 日韩免费观看一区二区三区 | 在线观看av麻豆 | 91欧美日韩国产 | 五月婷婷在线视频观看 | 国产91精品久久久久 | 亚洲免费在线观看视频 | 2022国产精品视频 | 国产精品不卡在线观看 | 天天草天天操 | 在线91网 | 国产精品系列在线观看 | 久久综合婷婷综合 | 国产一级黄色免费看 | 久久激情视频 久久 | 久久久久久久久影视 | 欧美伦理电影一区二区 | 午夜精品久久久久久久久久久久久久 | 亚洲国产午夜 | 91九色视频国产 | 综合伊人av| 中文字幕在线视频一区二区三区 | 中文字幕第一 | 天天操天天干天天综合网 | 亚洲激情在线播放 | 伊人色综合久久天天 | 日本韩国中文字幕 | 久久综合九色综合久99 | 国产亚洲小视频 | 麻豆高清免费国产一区 | 伊人久久在线观看 | 亚洲欧美久久 | 激情综合色综合久久综合 | 亚洲国产精品激情在线观看 | 999热线在线观看 | 国产精品普通话 | 青草视频在线 | 色无五月 | 国产成年人av | 精品一区二三区 | 久草手机视频 | 婷婷丁香花五月天 | 亚洲精品小区久久久久久 | 久久在线 | 五月婷婷黄色网 | 亚洲不卡av一区二区三区 | 免费在线成人av电影 | 亚洲乱码精品久久久久 | 成人免费在线观看入口 | 99视频精品免费视频 | 日本99精品 | 中文字幕成人 | 欧美在线aa | 在线观看香蕉视频 | 在线观看视频在线观看 | japanese黑人亚洲人4k | 免费午夜在线视频 | 久久区二区 | 亚洲综合小说电影qvod | 国内精品久久久久久久久久 | 国产精品毛片久久 | av网站在线观看免费 | 国产视频一区在线 | 国产高清在线看 | 久草在线资源免费 | 狠狠色狠狠色合久久伊人 | 国产黄色精品在线 | 黄色三级网站在线观看 | 又黄又刺激的网站 | 狠狠干婷婷色 | 香蕉视频在线免费看 | 欧美日韩久久不卡 | 久久久综合精品 | 中文字幕亚洲在线观看 | 射久久 | 亚洲在线观看av | 久久久久久久久久久高潮一区二区 | 欧美激情精品久久久久久免费印度 | 五月开心综合 | 在线观看视频在线观看 | 中文字幕免费中文 | 日韩欧美精选 | 久久天天躁狠狠躁亚洲综合公司 | 国产精品女人久久久 | 免费日韩 精品中文字幕视频在线 | 天天插日日插 | 欧美国产日韩激情 | 久久久.com| 男女全黄一级一级高潮免费看 | 久草精品视频在线看网站免费 | 又黄又爽又无遮挡的视频 | 国产中文字幕一区二区三区 | 欧美成人亚洲成人 | 久草精品视频 | 国产女人免费看a级丨片 | 国产91在线看 | 黄网站色欧美视频 | 九九热在线免费观看 | 亚洲精品国产综合99久久夜夜嗨 | 天天干天天操天天射 | 狠狠躁夜夜躁人人爽超碰97香蕉 | 免费观看91视频大全 | 久久激情视频免费观看 | 成人黄色资源 | 91精品在线播放 | 亚洲精品麻豆视频 | 日韩久久视频 | 久草在线资源观看 | 国产短视频在线播放 | 91在线免费观看国产 | av网站大全免费 | 天天艹| 精品一区二区影视 | 日韩av一区二区在线影视 | 99人久久精品视频最新地址 | 精品一区二区在线播放 | 亚洲精品在线观看不卡 | 国产特级毛片aaaaaa毛片 | 天天夜夜亚洲 | 国产在线专区 | 亚洲日韩中文字幕在线播放 | 国产一区二区三区高清播放 | 天天干天天干天天操 | 亚洲午夜精品电影 | 精品国产色 | 中国一级片在线观看 | 国产精品一区二区av日韩在线 | 亚洲 欧美 日韩 综合 | 婷婷中文字幕在线观看 | 中文字幕国语官网在线视频 | 免费观看黄 | 久久久午夜视频 | 久久免费视频这里只有精品 | 国产精品久久三 | 99久久夜色精品国产亚洲 | 免费网站在线观看成人 | 国产精品久久久久av | 天天干天天做天天爱 | 久久精品精品电影网 | 久久热首页 | 欧美片一区二区三区 | 精品一区二区免费在线观看 | 97精品超碰一区二区三区 | 成人精品久久久 | 久久午夜免费视频 | 国产精品成人在线观看 | 在线电影a| 亚州日韩中文字幕 | а天堂中文最新一区二区三区 | 色哟哟国产精品 | 中文字幕在线观看2018 | 天天干,天天射,天天操,天天摸 | 成人精品影视 | 婷婷精品国产一区二区三区日韩 | 色综合久久久久久久久五月 | 国产一区在线免费 | 九九热在线视频免费观看 | 日韩欧美一区二区不卡 | 国产婷婷在线观看 | 天堂av最新网址 | 日韩高清网站 | 美女久久99 | 国产麻豆精品传媒av国产下载 | 视频在线99 | 午夜精品婷婷 | 久久草草热国产精品直播 | 精品久久久免费 | 91人人人 | 亚州性色 | 麻豆免费看片 | 国产一级免费av | 亚洲国产精品传媒在线观看 | 99精彩视频在线观看免费 | 国产中文字幕在线观看 | 天天天在线综合网 | 中文字幕精品一区 | 亚洲高清视频在线观看 | 蜜臀久久99精品久久久久久网站 | 日本三级香港三级人妇99 | 人人草在线观看 | 久久av中文字幕片 | 久久亚洲视频 | 国产麻豆视频免费观看 | 久久久国产影视 | 蜜臀av一区二区 | 性日韩欧美在线视频 | 国产成人在线免费观看 | 超碰在线97国产 | 激情网婷婷 | 亚洲成人av在线电影 | 国产成人福利片 | 日韩国产欧美在线播放 | 亚洲精品国偷自产在线99热 | 国产精品久久久久久久久蜜臀 | 久久久久99999 | 亚洲国产欧美在线看片xxoo | 国产精品久久在线观看 | 国产精品免费成人 | 久久精品91视频 | 91久草视频 | 黄色高清视频在线观看 | 免费观看日韩 | 日韩欧美在线第一页 | 综合久久精品 | 日韩电影中文 | 国产精品久久久久一区二区 | 久久综合狠狠综合久久狠狠色综合 | 久香蕉| 99re8这里有精品热视频免费 | av导航福利 | 国产精品69av | 久久久久麻豆v国产 | 中文字幕在线国产精品 | 免费黄在线观看 | av手机在线播放 | 免费一级特黄录像 | 婷婷久久久 | 91成人网在线观看 | 欧美福利精品 | 日韩电影在线观看一区二区 | a国产精品 | 一级一片免费观看 | 日韩欧美亚州 | 麻豆视频91| 爱爱av在线 | 国产欧美日韩精品一区二区免费 | 99精品国产成人一区二区 | 黄色片毛片 | 日色在线视频 | 久久国产午夜精品理论片最新版本 | 久久久九九 | 欧美成人在线免费观看 | 国产成人福利 | 999视频网站 | 天堂在线一区二区 | 五月婷婷激情五月 | 日韩免费小视频 | 欧美精品乱码久久久久久按摩 | 九九涩涩av台湾日本热热 | www五月天| 黄色电影网站在线观看 | 麻豆影视网 | 永久免费看av | 欧美一级日韩三级 | 国产手机在线视频 | 麻豆影视在线免费观看 | 久久国产精品偷 | 久久国产精品一区二区三区 | 视频成人免费 | 国产美女被啪进深处喷白浆视频 | 亚洲国产操 | av片中文字幕 | 亚洲国产中文字幕 | 美女网站色在线观看 | 国产色黄网站 | 久久久蜜桃一区二区 | 日产乱码一二三区别在线 | 一级黄色视屏 | 开心激情婷婷 | 在线观看精品国产 | 国产精品一区在线观看 | 天天色视频 | 一区二区精品久久 | 91影视成人| 久久久久高清 | 开心激情婷婷 | 777奇米四色 | 一级免费片 | av片中文| 国产在线91精品 | 欧美日韩在线观看一区 | 丁香激情综合国产 | 午夜精品久久久 | 成人av中文字幕 | 亚州国产精品视频 | 美女视频网站久久 | www.精选视频.com | 国产人成在线观看 | 亚洲欧美经典 | 亚洲精品字幕在线观看 | 欧美日韩亚洲国产一区 | 欧美日韩一区二区在线观看 | 国产精品视频免费在线观看 | 精品成人久久 | 一区二区三区精品久久久 | 国产美女视频 | 91免费观看国产 | 亚洲国产精品电影 | 91大神精品视频在线观看 | 婷婷av资源| 2020天天干夜夜爽 | 天天插狠狠插 | 国产视频综合在线 | 爱爱一区 | 成人国产精品入口 | 天天射天天色天天干 | 中文字幕av网站 | 九色91福利 | 91av精品 | 久免费视频| 在线观看a视频 | 天天干天天操天天射 | 色综合久久久 | 久久精品视频免费播放 | 久久视频二区 | 亚洲精品白浆高清久久久久久 | 久久综合在线 | www.天天射.com | 日韩在线视频国产 | 97色在线观看免费视频 | 在线观看视频福利 | av手机版| 在线黄网站 | 黄色一级免费 | 手机av网站 | 国产成本人视频在线观看 | 成人午夜电影在线 | 五月综合网 | 国产午夜精品av一区二区 | 欧美老女人xx | 国产精品99久久免费观看 | 天天射一射 | 国产免费大片 | 国产字幕在线观看 | 欧美久久久久久久久久久 | 激情五月婷婷综合 | 69中文字幕 | 久久国产手机看片 | av手机版| 天天干人人干 | 超碰在线中文字幕 | 国产麻豆电影在线观看 | av大全在线看 | 免费a级毛片在线看 | 激情五月***国产精品 | 深夜男人影院 | 日韩videos | 亚洲少妇久久 | 中文字幕在线观看完整版 | 在线 国产 亚洲 欧美 | 黄色三级免费片 | 在线观看视频中文字幕 | 涩涩色亚洲一区 | 亚洲激情视频在线观看 | 最新极品jizzhd欧美 | 婷婷激情五月 | 四虎影视8848dvd| 综合久久婷婷 | 亚州av网站 | 91在线porny国产在线看 | 日韩va在线观看 | 三级在线视频播放 | 日韩视频在线观看视频 | 日日夜夜噜噜噜 | 在线观看精品一区 | 在线视频专区 | 四虎在线永久免费观看 | 福利av在线| 国产美女在线免费观看 | 国产不卡视频在线播放 | av黄色av | 免费精品人在线二线三线 | av电影亚洲 | 亚洲一级电影视频 | 麻豆手机在线 | 国产一区免费观看 | 91精品国自产在线偷拍蜜桃 | 国产在线精品一区二区 | 国产精品久久久久久影院 | 欧美大香线蕉线伊人久久 | 亚洲欧洲视频 | 久久观看 | 亚洲在线日韩 | 99久久免费看 | 中文字幕中文中文字幕 | 国产精品久久久久亚洲影视 | 久久精品电影网 | 9久久精品 | 久草在线综合 | 国产一级在线观看 | 欧美人交a欧美精品 | 九九色视频 | 国产丝袜高跟 | 美国av片在线观看 | 九九热精品视频在线观看 | 日韩在线网址 | 日韩专区在线播放 | 日日夜夜操操操操 | 91成人在线网站 | 超碰人人在线观看 | 国产一区在线视频 | 日日夜夜精品网站 | 91精品在线免费 | 午夜999| 免费99视频 | 91av蜜桃 | 4438全国亚洲精品观看视频 | 色鬼综合网 | 国产福利一区二区在线 | 99爱爱| 日韩精品免费在线观看 | 毛片二区 | 在线欧美小视频 | 国产一区在线免费观看 | 国产精品不卡在线观看 | 国产精品久久久999 国产91九色视频 | 午夜美女网站 | 在线成人免费 | 国产麻豆视频免费观看 | 成人羞羞视频在线观看免费 | 日韩久久视频 | 久久成人视屏 | 日韩免费 | 毛片无卡免费无播放器 | 久久久久久久久久久久国产精品 | 欧美国产91 | 992tv又爽又黄的免费视频 | 欧美在线观看小视频 | 热精品| 波多野结衣在线中文字幕 | 97在线免费观看视频 | 人人澡人人添人人爽一区二区 | 日韩va在线观看 | 黄色小说免费在线观看 | 手机在线免费av | 黄网站免费久久 | www.在线观看av| 色爽网站 | 国产精品成人免费精品自在线观看 | 天天干com | 日韩欧美在线免费 | 韩国一区二区在线观看 | 中文字幕av电影下载 | 日韩精品免费一线在线观看 | 五月天久久激情 | 国内精品视频一区二区三区八戒 | 久久综合九色综合欧美就去吻 | 日韩一级黄色大片 | 国产小视频福利在线 | 天天综合操| 免费成人在线网站 | 成人黄色国产 | 久久久久日本精品一区二区三区 | 中文国产在线观看 | 综合久久综合久久 | 久久久 激情 | 中文字幕 欧美性 | 国产精品精品久久久久久 | 网站免费黄色 | 日韩免费久久 | 97在线资源 | 亚洲v精品| 五月激情久久 | 在线激情网 | 久久久综合九色合综国产精品 | 91成版人在线观看入口 | 中文字幕一区二区三区在线观看 | 国产1区在线观看 | 国产精品网在线观看 | 中文字幕日韩无 | 片网址| 人人看人人 | 伊人午夜 | 欧美视频一区二 | 久久99久久99精品免费看小说 | 国产视频精品久久 | 成人影片免费 | 丝袜精品视频 | 欧美在线视频免费 | 久久伦理电影网 | 久久欧美综合 | 久射网| 日本公妇色中文字幕 | 精品国产伦一区二区三区观看方式 | 97视频在线观看免费 | 最新av在线播放 | 日日夜夜精品视频 | 人人揉人人揉人人揉人人揉97 | 久热免费 | 国产中文字幕视频在线观看 | 色婷婷色 | 人人干人人超 |