環境:
- aspnetcore 3.1.1
- vs2019 16.4.5
.netcore的本地緩存請參考:.net core中使用緩存之MemoryCache(本機內存)
一、分佈式緩存介紹
分佈式的緩存由多個應用程序服務器共享,緩存中的信息不存儲在單獨的 Web 服務器的內存中,並且緩存的數據可用於所有應用服務器。這具有幾個優點:
- 所有 Web 服務器上的緩存數據都是一致的。
- 緩存的數據在 Web 服務器重新啓動後和部署後仍然存在。
- 對數據庫的請求變的更少 。
aspnetcore框架定義了分佈式緩存的接口IDistributedCache
,我們在自己的代碼中應該與這個接口做交互,同樣,aspnetcore平臺上的所有分佈式緩存實現也應該實現這個接口。我們來看下這個接口定義的方法:
- Get,GetAsync :
接受字符串鍵,並檢索緩存項作爲 byte[] 數組(如果在緩存中找到)。 - Set,SetAsync:
使用字符串鍵將項(作爲 byte[] 數組)添加到緩存中。 - Refresh,RefreshAsync :
基於其鍵刷新緩存中的項,並重置其可調過期超時值(如果有)。 - Remove,RemoveAsync:
會根據其字符串鍵刪除緩存項。
aspnetcore已經內置了分佈式內存緩存,並以nuget包形式提供了分佈式 SQL Server 緩存和分佈式 Redis 緩存的實現,下面具體看一下:
二、分佈式內存緩存:
aspnetcore提供的這個緩存實現是爲了開發測試用的,其實它並沒有實現真正的分佈式。你觀察它的源碼後會發現,它內部調用的還是IMemoryCache
。下面看一下它的使用方式:
2.1 第一步:向容器註冊服務
public void ConfigureServices(IServiceCollection services)
{
services.AddDistributedMemoryCache();
}
2.2 第二步:Controller中調用
namespace _2webapidemo.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
private readonly ILogger<WeatherForecastController> _logger;
private readonly IDistributedCache distributedCache;
public WeatherForecastController(ILogger<WeatherForecastController> logger,/* IMemoryCache cache, */IDistributedCache distributedCache)
{
_logger = logger;
this.distributedCache = distributedCache;
}
[HttpGet]
public async Task<IEnumerable<WeatherForecast>> Get()
{
#region 測試分佈式內存緩存
var key = Guid.NewGuid().ToString();
var message = "Hello, World!";
var value = Encoding.UTF8.GetBytes(message);
Console.WriteLine("Connecting to cache");
Console.WriteLine("Connected");
Console.WriteLine("Cache item key: {0}", key);
Console.WriteLine($"Setting value '{message}' in cache");
await distributedCache.SetAsync(key,value,new DistributedCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromSeconds(10)));
Console.WriteLine("Set");
Console.WriteLine("Getting value from cache");
value = await distributedCache.GetAsync(key);
if (value != null)
{
Console.WriteLine("Retrieved: " + Encoding.UTF8.GetString(value, 0, value.Length));
}
else
{
Console.WriteLine("Not Found");
}
Console.WriteLine("Refreshing value in cache");
await distributedCache.RefreshAsync(key);
Console.WriteLine("Refreshed");
Console.WriteLine("Removing value from cache");
await distributedCache.RemoveAsync(key);
Console.WriteLine("Removed");
Console.WriteLine("Getting value from cache again");
value = await distributedCache.GetAsync(key);
if (value != null)
{
Console.WriteLine("Retrieved: " + Encoding.UTF8.GetString(value, 0, value.Length));
}
else
{
Console.WriteLine("Not Found");
}
#endregion
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
}
上面代碼的控制檯輸出如下圖:
三、分佈式SqlServer緩存
aspnetcore框架提供的這個實現可以讓我們很方便的把sqlserver當成一個緩存容器,它的源碼也很簡單,就是對一張緩存表的增刪改查。我們看一下它的源碼文件:
有興趣的可以點開看看裏面的內容,裏面操作數據庫沒有依賴EFCore,而是直接使用的SqlConnection、SqlCommand等這些原始對象。下面來看一下它的使用步驟:
3.1 第一步:準備sqlserver數據庫
你可以手動建表或者是使用dotnet sql-cache
工具自動建表,兩種方法任選其一,但是要注意三個名稱“數據庫名稱”、“模式名稱”和“表名稱”,因爲這三個名稱要在代碼中進行配置。下面以數據庫test
的dbo
下的表global_cache
爲例說明兩種方案:
3.1.1 方案1:手動建表
直接執行下面腳本即可:
--****************<global_cache>**************
if exists (select 1
from sysobjects
where id = object_id('global_cache')
and type = 'U')
begin
drop table global_cache
print '已刪除表:global_cache'
end
go
create table [global_cache] (
[Id] nvarchar(449) not null unique,
[Value] varbinary(max) not null ,
[ExpiresAtTime] datetimeoffset(7) not null ,
[SlidingExpirationInSeconds] bigint ,
[AbsoluteExpiration] datetimeoffset(7)
)
ALTER TABLE [global_cache] ADD CONSTRAINT PK_gene_global_cache_Id PRIMARY KEY(Id)
--************索引<Index_ExpiresAtTime>*****************
CREATE NONCLUSTERED INDEX Index_ExpiresAtTime ON global_cache(ExpiresAtTime)
--************索引</Index_ExpiresAtTime>*****************
print '已創建:global_cache'
--****************</global_cache>**************
3.1.2 方案2:使用dotnet sql-cache
工具:
這種方法是微軟官方提供的(參考:分佈式 SQL Server 緩存)
1)、首先,你要確保你電腦中安裝了sql-cache
工具,如果沒有的話,在cmd中執行如下命令安裝:
dotnet tool install --global dotnet-sql-cache --version 3.1.1
注意,sql-cache
的版本要和.netcore的版本保持一致,否則,建議卸載重裝,卸載的命令如下:
dotnet tool uninstall dotnet-sql-cache --global
2)、然後, 執行如下命令建表
dotnet sql-cache create "Data Source=.;Initial Catalog=test;User ID=sa;Password=xxxxxx;" dbo global_cache
執行完後提示:“Table and index were created successfully.” 則表示建表成功!
3.3 第二步:引入nuget包
雖然微軟默認提供了分佈式SqlServer緩存的實現,但並沒有集成到aspnetcore框架中,所以我們需要將nuget包引入進來:
3.2 第二步:向容器註冊服務
public void ConfigureServices(IServiceCollection services)
{
services.AddDistributedSqlServerCache(options=>
{
options.ConnectionString = "Data Source=.;Initial Catalog=test;User ID=sa;Password=sa;";
options.SchemaName = "dbo";
options.TableName = "global_cache";
});
services.AddControllers();
}
3.3 第三步:Controller中調用
同分布式內存。
附:程序運行過程中sqlserver數據庫中存儲的情況:
四、分佈式Redis緩存
redis服務器搭建參考:redis入門(1): linux下安裝redis與簡單配置。
redis客戶端下載地址:
鏈接:https://pan.baidu.com/s/1l2NPkhgVdnEM89vpJIwGfA
提取碼:beg4
說明:
aspnetcore中分佈式Redis緩存的源碼很簡單,因爲它對redis的操作依賴於StackExchange.Redis
,先來看看它的源碼文件有哪些:
可以看到源碼非常少。我們知道redis中有五大數據類型:string(字符串)、hash(哈希)、list(列表)、set(集合)和zset(sorted set:有序集合),這個分佈式緩存實現的時候僅使用了hash(哈希)這一種格式進行存儲。下面直接看使用步驟:
4.1 第一步:準備redis服務
假設你已經準備好了redis服務爲:
ip:192.168.3.28
port:6379
password:123456
4.2 第二步:引入nuget包
雖然微軟默認實現了分佈式redis緩存,但是並未將程序集集成在aspnetcore框架裏面,所以需要我們手動添加上去:
4.3 第三步:向容器註冊服務
public void ConfigureServices(IServiceCollection services)
{
services.AddStackExchangeRedisCache(options =>
{
//options.Configuration = "192.168.3.28:6379";
options.ConfigurationOptions = new StackExchange.Redis.ConfigurationOptions()
{
EndPoints = { { "192.168.3.28", 6379 } },
Password = "123456"
};
options.InstanceName = "TestRedisCache";
});
services.AddControllers();
}
4.4 第四步:Controller中調用
同分布式內存緩存
附:代碼運行過程中redis存儲數據截圖
從上面的截圖中可以看到,存儲的時候八absexp和sldexp也存儲了進去,也就是一個緩存項被當成一個hash存儲了起來。
五、總結
從上面的介紹中可以看到,雖然我們分別使用了Sqlserver、Redis緩存方式但調用代碼不用做任何改變,而這正是由於我們調用的是IDistributedCache
接口提供的服務。