MasaFramework — 快取入門與規則配置

概念

什麼是快取,在項目中,為了提高數據的讀取速度,我們會對不經常變更但訪問頻繁的數據做快取處理,我們常用的快取有:

功能

目前,MasaFramework為我們提供了以下能力

入門

分散式快取

  1. 新建ASP.NET Core 空項目Assignment.DistributedCache,並安裝Masa.Contrib.Caching.Distributed.StackExchangeRedis
dotnet new web -o Assignment.DistributedCache
cd Assignment.DistributedCache

dotnet add package Masa.Contrib.Caching.Distributed.StackExchangeRedis --version 0.6.0-rc.5
  1. 配置Redis配置資訊
{
    "RedisConfig":{
        "Servers":[
            {
                "Host":"localhost",
                "Port":6379
            }
        ],
        "DefaultDatabase":3,
        "ConnectionPoolSize":10
    }
}
  1. 註冊分散式快取,並使用Redis快取,修改Program.cs
var builder = WebApplication.CreateBuilder(args);

//註冊分散式快取
builder.Services.AddDistributedCache(distributedCacheOptions =>
{
    distributedCacheOptions.UseStackExchangeRedisCache();//使用分散式Redis快取, 默認使用本地`RedisConfig`下的配置
});

使用分散式快取的數據來源默認為 IOptionsMonitor<RedisConfigurationOptions>,如果本地未正確在RedisConfig節點配置快取資訊,且項目中也沒有通過其它方式配置使其支援選項模式,則默認使用的Redis配置為: 地址: localhost、埠:6379,密碼:空,資料庫:db0

  1. 新建User類,用於接收用戶資訊
public class User
{
    public string Name { get; set; }

    public int Age { get; set; }
}
  1. 如何使用IDistributedCacheClient,修改Program.cs
// 設置快取
app.MapPost("/set/{id}", async (IDistributedCacheClient distributedCacheClient, [FromRoute] string id, [FromBody] User user) =>
{
    await distributedCacheClient.SetAsync(id, user);
    return Results.Accepted();
});

// 獲取快取
app.MapGet("/get/{id}", async (IDistributedCacheClient distributedCacheClient, [FromRoute] string id) =>
{
    var value = await distributedCacheClient.GetAsync<User>(id);
    return Results.Ok(value);
});

多級快取

  1. 新建ASP.NET Core 空項目Assignment.DistributedCache,並安裝Masa.Contrib.Caching.MultilevelCacheMasa.Contrib.Caching.Distributed.StackExchangeRedis
dotnet new web -o Assignment.MultilevelCache
cd Assignment.MultilevelCache

dotnet add package Masa.Contrib.Caching.MultilevelCache --version 0.6.0-rc.5
dotnet add package Masa.Contrib.Caching.Distributed.StackExchangeRedis --version 0.6.0-rc.5
  1. 註冊多級快取,並使用分散式Redis快取,修改Program.cs
var builder = WebApplication.CreateBuilder(args);

//註冊多級快取
builder.Services.AddMultilevelCache(distributedCacheOptions =>
{
    distributedCacheOptions.UseStackExchangeRedisCache();//使用分散式Redis快取
});
  1. 新建User類,用於接收用戶資訊
public class User
{
    public string Name { get; set; }

    public int Age { get; set; }
}
  1. 如何使用IMultilevelCacheClient,修改Program.cs
// 設置快取
app.MapPost("/set/{id}", async (IMultilevelCacheClient multilevelCacheClient, [FromRoute] string id, [FromBody] User user) =>
{
    await multilevelCacheClient.SetAsync(id, user);
    return Results.Accepted();
});

// 獲取快取
app.MapGet("/get/{id}", async (IMultilevelCacheClient multilevelCacheClient, [FromRoute] string id) =>
{
    var value = await multilevelCacheClient.GetAsync<User>(id);
    return Results.Ok(value);
});

測試

藉助Postman或者Swagger或者使用其它API測試工具,分別測試設置快取與獲取快取,以驗證分散式快取以及多級快取是可以正常使用的。

友情提示:檢查Redis快取,找到剛剛你配置的快取,確定下它的存儲結果是否與你想像的一致!!

規則

經過測試,我們的分散式快取與多級快取是可以正常使用的,但查看Redis的存儲結果後,發現它們實際的存儲與我們心目中的結果好像是有點出入,它們分別是:

  1. 快取Key不同 (與我們設置的Key不完全一致)
  2. 結構不同 (實際存儲的為Hash類型)
  3. 內容不同 (內容經過壓縮)

image.png

快取Key的生成規則

快取Key支援三種規則:

枚舉 描述
None 1 不做處理,傳入的Key即為實際的快取Key
TypeName 2 實際的快取Key = $”{GetTypeName(T)}.{傳入快取Key}” (默認)
TypeAlias 3 根據TypeName得到對應的別名與Key的組合,Format: ${TypeAliasName}{:}{key}

詳細規則可查看

存儲結構與規則

Masa.Contrib.Caching.Distributed.StackExchangeRedis使用的是Hash存儲,通過使用Hash存儲,支援快取的絕對過期以及相對過期,其中:

描述 詳細 特殊
absexp 絕對過期時間的Ticks 自公曆 0001-01-01 00:00:00:000 到絕對過期時間的計時周期數 (1周期 = 100ns 即 1/10000 ms) -1 為永不過期
sldexp 滑動過期時間的Ticks 自公曆 0001-01-01 00:00:00:000 到滑動過期時間的計時周期數 (1周期 = 100ns 即 1/10000 ms,每次獲取數據時會刷新滑動過期時間) -1 為永不過期
data 數據 存儲用戶設置的快取數據

內容壓縮規則

  1. 當存儲值類型為以下類型時,不對數據進行壓縮:
  • Byte
  • SByte
  • UInt16
  • UInt32
  • UInt64
  • Int16
  • Int32
  • Int64
  • Double
  • Single
  • Decimal
  1. 當存儲值類型為字元串時,對數據進行壓縮
  2. 當存儲值類型不滿足以上條件時,對數據進行序列化並進行壓縮

分散式Redis快取示例

分散式快取註冊

方案一. 通過本地配置文件註冊

  1. 修改appsettings.json文件
{
    "RedisConfig":{
        "Servers":[
            {
                "Host":"localhost",
                "Port":6379
            }
        ],
        "DefaultDatabase":3,
        "ConnectionPoolSize":10
    }
}
  1. 註冊分散式Redis快取
builder.Services.AddDistributedCache(distributedCacheOptions =>
{
    distributedCacheOptions.UseStackExchangeRedisCache();
});

方案二. 手動指定Redis配置註冊

builder.Services.AddDistributedCache(distributedCacheOptions =>
{
    distributedCacheOptions.UseStackExchangeRedisCache(options =>
    {
        options.Servers = new List<RedisServerOptions>()
        {
            new("localhost", 6379)
        };
        options.DefaultDatabase = 3;
        options.ConnectionPoolSize = 10;
        options.GlobalCacheOptions = new CacheOptions()
        {
            CacheKeyType = CacheKeyType.None //全局禁用快取Key格式化處理
        };
    });
});

方案三. 通過選項模式註冊

  1. 通過Configure方法使其支援選項模式
builder.Services.Configure<RedisConfigurationOptions>(redisConfigurationOptions =>
{
    redisConfigurationOptions.Servers = new List<RedisServerOptions>()
    {
        new("localhost", 6379)
    };
    redisConfigurationOptions.DefaultDatabase = 3;
    redisConfigurationOptions.ConnectionPoolSize = 10;
    redisConfigurationOptions.GlobalCacheOptions = new CacheOptions()
    {
        CacheKeyType = CacheKeyType.None
    };
});
  1. 註冊分散式Redis快取
builder.Services.AddDistributedCache(distributedCacheOptions =>
{
    distributedCacheOptions.UseStackExchangeRedisCache();
});

方案四. 通過指定Configuration註冊

  1. 在Redis快取的配置存儲到本地appsettings.json文件
{
    "RedisConfig":{
        "Servers":[
            {
                "Host": "localhost",
                "Port": 6379
            }
        ],
        "DefaultDatabase": 3,
        "ConnectionPoolSize": 10
    }
}
  1. 指定Configuration註冊分散式Redis快取
var builder = WebApplication.CreateBuilder(args);

//註冊分散式快取
builder.Services.AddDistributedCache(distributedCacheOptions =>
{
    // 使用存儲Redis配置的Configuration
    distributedCacheOptions.UseStackExchangeRedisCache(builder.Configuration.GetSection("RedisConfig"));
});

方案五. 將配置存儲到Dcc上,並通過Configuration提供的手動映射功能,實現選項模式

  1. 使用Dcc,並手動指定映射
builder.AddMasaConfiguration(configurationBuilder =>
{
    configurationBuilder.UseDcc();//使用Dcc 擴展Configuration能力,支援遠程配置

    configurationBuilder.UseMasaOptions(options =>
    {
        //通過手動映射RedisConfigurationOptions的配置,實現選項模式
        options.MappingConfigurationApi<RedisConfigurationOptions>("{替換為Dcc中配置所屬的AppId}", "{替換為Redis配置的對象名稱}");
    });
});
  1. 註冊分散式Redis快取
builder.Services.AddDistributedCache(distributedCacheOptions =>
{
    distributedCacheOptions.UseStackExchangeRedisCache();
});

方案三、四、五的本質都是通過支援選項模式來註冊分散式Redis快取

修改快取Key映射規則

修改快取Key映射規則十分簡單,我們在配置時更改CacheKeyType為對應的規則即可,但當 CacheKeyType = 3 需要注意,它需要額外提供類型名與別名的對應關係,完整例子如下:

  1. 修改appsettings.json, 將CacheKeyType的值改為 3
{
    "RedisConfig":{
        "Servers":[
            {
                "Host":"localhost",
                "Port":6379
            }
        ],
        "DefaultDatabase":3,
        "ConnectionPoolSize":10,
        "GlobalCacheOptions": {
          "CacheKeyType": 3 //CacheKeyType為3時啟用別名格式化快取Key,可節省快取Key的鍵長度
        }
    }
}
  1. 註冊分散式快取並配置類型名與別名的對應關係
builder.Services.AddDistributedCache(distributedCacheOptions =>
{
    distributedCacheOptions.UseStackExchangeRedisCache();
}, typeAliasOptions =>
{
    typeAliasOptions.GetAllTypeAliasFunc = () => new Dictionary<string, string>()
    {
        { "String", "s" }//當類型為String時,格式化後的Key為 s:key
    };
});

通過指定類型與別名的對應關係,從而使得最終形成較短的快取Key,以達到節省存儲空間的目的,快取Key生成規則可查看

多級快取示例

多級快取註冊

方案一. 通過本地配置文件註冊

  1. 修改appsettings.json文件,分別配置多級快取配置以及Redis快取配置
{
  // 多級快取全局配置,非必填
  "MultilevelCache": {
    "SubscribeKeyPrefix": "masa",//默認訂閱方key前綴,用於拼接channel
    "SubscribeKeyType": 3, //默認訂閱方key的類型,默認ValueTypeFullNameAndKey,用於拼接channel
    "CacheEntryOptions": {
      "AbsoluteExpirationRelativeToNow": "00:00:30",//絕對過期時長(距當前時間)
      "SlidingExpiration": "00:00:50"//滑動過期時長(距當前時間)
    }
  },

  // Redis分散式快取配置
  "RedisConfig": {
    "Servers": [
      {
        "Host": "localhost",
        "Port": 6379
      }
    ],
    "DefaultDatabase": 3
  }
}
  1. 添加多級快取並使用分散式Redis快取
builder.Services.AddMultilevelCache(distributedCacheOptions =>
{
    distributedCacheOptions.UseStackExchangeRedisCache();
});

方案二. 通過手動指定配置

builder.Services.AddMultilevelCache(distributedCacheOptions =>
{
    distributedCacheOptions.UseStackExchangeRedisCache(RedisConfigurationOptions);
});

未配置記憶體快取時,默認記憶體快取永久有效

除了上述兩種方式以外,多級快取的記憶體快取配置也同樣支援選項模式,我們可以通過Dcc或者利用 builder.Services.Configure<MultilevelCacheOptions>(builder.Configuration)來支援選項模式

修改快取Key映射規則

源碼解讀

IDistributedCacheClient (分散式快取客戶端)

IDistributedCacheClient介面提供以下方法來處理分散式快取

以下方法會根據全局快取Key的規則配置以及傳入快取Key的規則配置,檢測是否需要格式化快取Key,對需要格式化Key的操作按照快取Key格式化規則進行處理,詳細查看:

  • Get<T>GetAsync<T>: 根據快取Key返回類型為T的結果 (如果快取不存在,則返回Null)
  • GetList<T>GetListAsync<T>: 根據快取Key集合返回對應的快取值的集合 (針對不存在的快取key,其值返回Null)
  • GetOrSet<T>GetOrSetAsync<T>: 如果在快取中找到,則返回類型為T的結果,如果快取未找到,則執行Setter,並返回Setter的結果
  • Set<T>SetAsync<T>: 將指定的快取Key以及快取值添加到快取
  • SetList<T>SetListAsync<T>: 將指定的快取Key、Value集合添加快取
  • Remove<T>RemoveAsync<T>: 將指定的快取Key (快取Key集合) 從快取中移除
  • Refresh<T>RefreshAsync<T>: 刷新指定的快取Key (快取Key集合) 的生命周期
    • 適用於未被刪除、絕對過期時間沒有到,但相對過期時間快到的快取 (延長滑動過期時間)
  • Exists<T>ExistsAsync<T>: 如果在快取中找到,則返回true,否則返回false
  • GetKeys<T>GetKeysAsync<T>: 根據key pattern 得到符合規則的所有快取Key
  • GetByKeyPattern<T>GetByKeyPatternAsync<T>: 根據key pattern 得到符合規則的所有快取Key、Value集合
  • HashIncrementAsync: 將指定的快取Key的值增加Value,並返回增長後的結果
  • HashDecrementAsync: 將指定的快取Key的值減少Value,並返回減少後的結果
    • 支援設置最小的Value,避免減少後的值低於設置的最小值,執行失敗則返回: -1
  • KeyExpire<T>KeyExpireAsync<T>: 設置快取Key的生命周期

以下方法不執行快取Key格式化, 應傳入快取完整Key:

  • RemoveRemoveAsync: 將指定的快取Key (快取Key集合) 從快取中移除
  • RefreshRefreshAsync: 刷新指定的快取Key (快取Key集合) 的生命周期
    • 適用於未被刪除、絕對過期時間沒有到,但相對過期時間快到的快取
  • ExistsExistsAsync: 如果在快取中找到,則返回true,否則返回false
  • GetKeysGetKeysAsync: 根據key pattern 得到符合規則的所有快取Key
    • 例: 傳入User*,可得到快取中以User開頭的所有快取Key
  • KeyExpireKeyExpireAsync: 設置快取Key的生命周期

IMultilevelCacheClient (多級快取客戶端)

  • Get<T>GetAsync<T>: 根據快取Key返回類型為T的結果 (如果快取不存在,則返回Null) (支援監控快取變更)
  • GetList<T>GetListAsync<T>: 根據快取Key集合返回對應的快取值的集合 (針對不存在的快取key,其值返回Null)
  • GetOrSet<T>GetOrSetAsync<T>: 如果在快取中找到,則返回類型為T的結果,如果快取未找到,則執行Setter,並返回Setter的結果
  • Set<T>SetAsync<T>: 將指定的快取Key以及快取值添加到快取
  • SetList<T>SetListAsync<T>: 將指定的快取Key、Value集合添加快取
  • Remove<T>RemoveAsync<T>: 將指定的快取Key (快取Key集合) 從快取中移除
  • Refresh<T>RefreshAsync<T>: 刷新指定的快取Key (快取Key集合) 的生命周期
    • 適用於未被刪除、絕對過期時間沒有到,但相對過期時間快到的快取 (延長滑動過期時間)

IDistributedCacheClientFactory (分散式快取工廠)

  • Create: 返回指定Name的分散式快取客戶端

IMultilevelCacheClientFactory (多級快取工廠)

  • Create: 返回指定Name的多級快取客戶端

如果Name為空字元串時,可直接使用IDistributedCacheClientIMultilevelCacheClient, 默認註冊不指定Name時,則其Name為空字元串,可不通過Factory創建

總結

Masa Framework提供了分散式快取以及多級快取的實現,其中有幾個優秀的功能:

  • 多級快取提供了快取更新後同步更新記憶體快取功能
    • 當我們的服務是多副本時,不必擔心會快取更新後其它副本由於記憶體快取未過期,導致獲取到過期的快取數據,大大提升我們的用戶體驗
  • 支援滑動過期以及絕對過期混合使用
    • 避免無用的快取長時間被持久化,但對於熱點數據又可以避免打到Redis或者資料庫
  • 配置支援熱更新,配置更新後同步生效,無需重啟項目
  • 快取Key支援格式化,可根據當前快取值類型與傳入快取Key結合形成新的快取Key,提高了開發效率以及程式碼可讀性
    • 比如獲取用戶id為1的數據,可通過Client.Get<User>("1"),而無需:Client.Get<User>("User.1")

本章源碼

Assignment16

//github.com/zhenlei520/MasaFramework.Practice

開源地址

MASA.Framework://github.com/masastack/MASA.Framework

MASA.EShop://github.com/masalabs/MASA.EShop

MASA.Blazor://github.com/BlazorComponent/MASA.Blazor

如果你對我們的 MASA Framework 感興趣,無論是程式碼貢獻、使用、提 Issue,歡迎聯繫我們

16373211753064.png