MasaFramework — 快取入門與規則配置
- 2022 年 10 月 21 日
- 筆記
- .NET, MASA Framework
概念
什麼是快取,在項目中,為了提高數據的讀取速度,我們會對不經常變更但訪問頻繁的數據做快取處理,我們常用的快取有:
- 本地快取
- 記憶體快取:IMemoryCache
- 分散式快取
- Redis: StackExchange.Redis
功能
目前,MasaFramework
為我們提供了以下能力
- IDistributedCacheClient: 分散式快取
- IMultilevelCacheClient: 多級快取
- Masa.Contrib.Caching.MultilevelCache: 基於記憶體快取以及分散式快取實現的多級快取,支援監控快取變更,分散式快取更新後相應的記憶體快取也會同步更新,避免命中過時的記憶體快取導致獲取錯誤的數據,同時也儘可能的將多個副本的記憶體快取保持同步
入門
- 前提條件:安裝.NET 6.0
分散式快取
- 新建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
- 配置
Redis
配置資訊
{
"RedisConfig":{
"Servers":[
{
"Host":"localhost",
"Port":6379
}
],
"DefaultDatabase":3,
"ConnectionPoolSize":10
}
}
- 註冊分散式快取,並使用
Redis
快取,修改Program.cs
var builder = WebApplication.CreateBuilder(args);
//註冊分散式快取
builder.Services.AddDistributedCache(distributedCacheOptions =>
{
distributedCacheOptions.UseStackExchangeRedisCache();//使用分散式Redis快取, 默認使用本地`RedisConfig`下的配置
});
使用分散式快取的數據來源默認為
IOptionsMonitor<RedisConfigurationOptions>
,如果本地未正確在RedisConfig
節點配置快取資訊,且項目中也沒有通過其它方式配置使其支援選項模式,則默認使用的Redis配置為: 地址: localhost、埠:6379,密碼:空,資料庫:db0
- 新建
User
類,用於接收用戶資訊
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
- 如何使用
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);
});
多級快取
- 新建ASP.NET Core 空項目
Assignment.DistributedCache
,並安裝Masa.Contrib.Caching.MultilevelCache
、Masa.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
- 註冊多級快取,並使用分散式
Redis
快取,修改Program.cs
var builder = WebApplication.CreateBuilder(args);
//註冊多級快取
builder.Services.AddMultilevelCache(distributedCacheOptions =>
{
distributedCacheOptions.UseStackExchangeRedisCache();//使用分散式Redis快取
});
- 新建
User
類,用於接收用戶資訊
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
- 如何使用
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的存儲結果後,發現它們實際的存儲與我們心目中的結果好像是有點出入,它們分別是:
- 快取Key不同 (與我們設置的Key不完全一致)
- 結構不同 (實際存儲的為Hash類型)
- 內容不同 (內容經過壓縮)
快取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 | 數據 | 存儲用戶設置的快取數據 |
內容壓縮規則
- 當存儲值類型為以下類型時,不對數據進行壓縮:
- Byte
- SByte
- UInt16
- UInt32
- UInt64
- Int16
- Int32
- Int64
- Double
- Single
- Decimal
- 當存儲值類型為字元串時,對數據進行壓縮
- 當存儲值類型不滿足以上條件時,對數據進行序列化並進行壓縮
分散式Redis快取示例
分散式快取註冊
方案一. 通過本地配置文件註冊
- 修改
appsettings.json
文件
{
"RedisConfig":{
"Servers":[
{
"Host":"localhost",
"Port":6379
}
],
"DefaultDatabase":3,
"ConnectionPoolSize":10
}
}
- 註冊分散式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格式化處理
};
});
});
方案三. 通過選項模式註冊
- 通過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
};
});
- 註冊分散式Redis快取
builder.Services.AddDistributedCache(distributedCacheOptions =>
{
distributedCacheOptions.UseStackExchangeRedisCache();
});
方案四. 通過指定Configuration
註冊
- 在Redis快取的配置存儲到本地
appsettings.json
文件
{
"RedisConfig":{
"Servers":[
{
"Host": "localhost",
"Port": 6379
}
],
"DefaultDatabase": 3,
"ConnectionPoolSize": 10
}
}
- 指定
Configuration
註冊分散式Redis快取
var builder = WebApplication.CreateBuilder(args);
//註冊分散式快取
builder.Services.AddDistributedCache(distributedCacheOptions =>
{
// 使用存儲Redis配置的Configuration
distributedCacheOptions.UseStackExchangeRedisCache(builder.Configuration.GetSection("RedisConfig"));
});
方案五. 將配置存儲到Dcc上,並通過Configuration提供的手動映射功能,實現選項模式
- 使用Dcc,並手動指定映射
builder.AddMasaConfiguration(configurationBuilder =>
{
configurationBuilder.UseDcc();//使用Dcc 擴展Configuration能力,支援遠程配置
configurationBuilder.UseMasaOptions(options =>
{
//通過手動映射RedisConfigurationOptions的配置,實現選項模式
options.MappingConfigurationApi<RedisConfigurationOptions>("{替換為Dcc中配置所屬的AppId}", "{替換為Redis配置的對象名稱}");
});
});
- 註冊分散式Redis快取
builder.Services.AddDistributedCache(distributedCacheOptions =>
{
distributedCacheOptions.UseStackExchangeRedisCache();
});
方案三、四、五的本質都是通過支援選項模式來註冊分散式Redis快取
修改快取Key映射規則
修改快取Key映射規則十分簡單,我們在配置時更改CacheKeyType為對應的規則即可,但當 CacheKeyType = 3 需要注意,它需要額外提供類型名與別名的對應關係,完整例子如下:
- 修改
appsettings.json
, 將CacheKeyType的值改為 3
{
"RedisConfig":{
"Servers":[
{
"Host":"localhost",
"Port":6379
}
],
"DefaultDatabase":3,
"ConnectionPoolSize":10,
"GlobalCacheOptions": {
"CacheKeyType": 3 //CacheKeyType為3時啟用別名格式化快取Key,可節省快取Key的鍵長度
}
}
}
- 註冊分散式快取並配置類型名與別名的對應關係
builder.Services.AddDistributedCache(distributedCacheOptions =>
{
distributedCacheOptions.UseStackExchangeRedisCache();
}, typeAliasOptions =>
{
typeAliasOptions.GetAllTypeAliasFunc = () => new Dictionary<string, string>()
{
{ "String", "s" }//當類型為String時,格式化後的Key為 s:key
};
});
通過指定類型與別名的對應關係,從而使得最終形成較短的快取Key,以達到節省存儲空間的目的,快取Key生成規則可查看
多級快取示例
多級快取註冊
方案一. 通過本地配置文件註冊
- 修改
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
}
}
- 添加多級快取並使用分散式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,否則返回falseGetKeys<T>
、GetKeysAsync<T>
: 根據key pattern 得到符合規則的所有快取KeyGetByKeyPattern<T>
、GetByKeyPatternAsync<T>
: 根據key pattern 得到符合規則的所有快取Key、Value集合HashIncrementAsync
: 將指定的快取Key的值增加Value,並返回增長後的結果HashDecrementAsync
: 將指定的快取Key的值減少Value,並返回減少後的結果- 支援設置最小的Value,避免減少後的值低於設置的最小值,執行失敗則返回: -1
KeyExpire<T>
、KeyExpireAsync<T>
: 設置快取Key的生命周期
以下方法不執行快取Key格式化, 應傳入快取完整Key:
Remove
、RemoveAsync
: 將指定的快取Key (快取Key集合) 從快取中移除Refresh
、RefreshAsync
: 刷新指定的快取Key (快取Key集合) 的生命周期- 適用於未被刪除、絕對過期時間沒有到,但相對過期時間快到的快取
Exists
、ExistsAsync
: 如果在快取中找到,則返回true,否則返回falseGetKeys
、GetKeysAsync
: 根據key pattern 得到符合規則的所有快取Key- 例: 傳入User*,可得到快取中以User開頭的所有快取Key
KeyExpire
、KeyExpireAsync
: 設置快取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為空字元串時,可直接使用
IDistributedCacheClient
或IMultilevelCacheClient
, 默認註冊不指定Name時,則其Name為空字元串,可不通過Factory創建
總結
Masa Framework
提供了分散式快取以及多級快取的實現,其中有幾個優秀的功能:
- 多級快取提供了快取更新後同步更新記憶體快取功能
- 當我們的服務是多副本時,不必擔心會快取更新後其它副本由於記憶體快取未過期,導致獲取到過期的快取數據,大大提升我們的用戶體驗
- 支援滑動過期以及絕對過期混合使用
- 避免無用的快取長時間被持久化,但對於熱點數據又可以避免打到Redis或者資料庫
- 配置支援熱更新,配置更新後同步生效,無需重啟項目
- 快取Key支援格式化,可根據當前快取值類型與傳入快取Key結合形成新的快取Key,提高了開發效率以及程式碼可讀性
- 比如獲取用戶id為1的數據,可通過
Client.Get<User>("1")
,而無需:Client.Get<User>("User.1")
- 比如獲取用戶id為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,歡迎聯繫我們