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