簡介以及區別
ASP.NET Core 緩存Caching,.NET Core 中為我們提供了Caching 的組件。
目前Caching 組件提供了三種存儲方式。
Memory
Redis
SqlServer
1.MemoryCache
Cache是一個絕大多數項目會用到的一個技術 為了減少磁盤的讀取次數,提高程序性能,將頻繁讀取的配置文件緩存到內存中,加速配置的讀取。並且,在磁盤的配置文件更改后,更新緩存
- 絕對過期支持
- 滑動過期支持(指定一個時間,TimeSpan,指定時間內有被Get緩存時間則順延,否則過期)
- 過期回調
- 自定義過期
倉庫地址是:https://github.com/aspnet/Caching
IMemoryCache,它表示存儲在 Web 服務器內存中的緩存,內存緩存可以存儲任何對象,存儲形式鍵值對
Memorycache無法進行持久化
Memorycache只支持簡單的key/value數據結構
Memorycache 是多線程 速度快
2.Redis
簡單來說 redis 就是一個數據庫,不過與傳統數據庫不同的是 redis 的數據是存在內存中的,所以讀寫速度非常快,因此 redis 被廣泛應用於緩存方向。另外,redis 也經常用來做分布式鎖。redis 提供了多種數據類型來支持不同的業務場景。除此之外,redis 支持事務 、持久化、LUA腳本、LRU驅動事件、多種集群方案。
Redis是一個支持持久化的內存數據庫,通過持久化機制把內存中的數據同步到硬盤文件來保證數據持久化 ,當Redis重啟后通過把硬盤文件重新加載到內存,就能達到恢復數據的目的。
如果你對數據持久化和數據同步有所要求,那么推薦你選擇Redis,因為這兩個特性Memcached都不具備。即使你只是希望在升級或者重啟系統后緩存數據不會丟失,選擇Redis也是明智的。
Redis支持數據類型比如key-value string list hash zset 等
Redis緩存數據庫(多個) Redis緩存文件夾(多個目錄)
Redis只能使用單線程,性能受限於CPU性能
區別
對於 redis 和 memcached 我總結了下面四點。現在公司一般都是用 redis 來實現緩存,而且 redis 自身也越來越強大了!
1、redis支持更豐富的數據類型(支持更復雜的應用場景):Redis不僅僅支持簡單的k/v類型的數據,同時還提供list,set,zset,hash等數據結構的存儲。memcache支持簡單的數據類型,String。
2、Redis支持數據的持久化,可以將內存中的數據保持在磁盤中,重啟的時候可以再次加載進行使用,而Memecache把數據全部存在內存之中。
3、集群模式:memcached沒有原生的集群模式,需要依靠客戶端來實現往集群中分片寫入數據;但是 redis 目前是原生支持 cluster 模式的.
4、Memcached是多線程,非阻塞IO復用的網絡模型;Redis使用單線程的多路 IO 復用模型。
應用場景
redis:數據量較小的更性能操作和運算上。
memcache:用於在動態系統中減少數據庫負載,提升性能;做緩存,提高性能(適合讀多寫少,對於數據量比較大,可以采用sharding)。
1.MemoryCache使用
Session緩存和Cache緩存的區別如下:
(1)最大的區別是Cache提供緩存依賴來更新數據,而Session只能依靠定義的緩存時間來判斷緩存數據是否有效。
(2)即使應用程序終止,只要Cache.Add方法中定義的緩存時間未過期,下次開啟應用程序時,緩存的數據依然存在。而Session緩存只是存在於一次會話中,會話結束后,數據也就失效了。
(3)Session容易丟失,導致數據的不確定性,而Cache不會出現這種情況。
(4)由於Session是每次會話就被加載,所以不適宜存放大量信息,否則會導致服務器的性能降低。而Cache則主要用來保存大容量信息,如數據庫中的多個表。
需要特別注意:為了提高Cache的有效利用率,建議對於不經常改動的數據使用Cache。
public void ConfigureServices(IServiceCollection services) { //如何處理session services.AddSession(); //memoryCache services.AddMemoryCache(); //....... } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { //啟用session app.UseSession(); app.UseRouting(); //...... }
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
// Add framework services.
}
private IMemoryCache _cache;
public LongLongController(IMemoryCache memoryCache) { _cache = memoryCache; }
1、方法:TryGetValue 及 方法緩存的存取(在TryGetValue 中,Out 參數的類型應與緩存中存儲的值的類型一致。否則TryGetValue 中的Out參數永遠為NULL。)
public IActionResult Index()
{
string cacheKey_2 = "CacheKey"; List<string> cacheEntry; //如果緩存沒有過期,則Out測試就是緩存存儲的值,注意存放值的類型應該和Out參數一致。 var bol = _cache.TryGetValue<List<string>>(cacheKey_2, out cacheEntry); //判斷緩存是否存在 if (!bol) { List<string> lst = new List<string>() { "陳大六", "陳卧龍", "陳新宇", "劉麗", "花國鋒" }; var cacheEntryOptions = new MemoryCacheEntryOptions() .SetSlidingExpiration(TimeSpan.FromMinutes(10)); _cache.Set(cacheKey_2, lst, cacheEntryOptions); } ViewBag.cacheEntry = _cache.Get(cacheKey_2); return View(); }
2、設置緩存的過期時間,可采用絕對過期時間或相對過期時間兩種模式;
相對過期時間設置方式:
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds(10));
注意上述代碼中的備注;假設我們設置一個緩存的相對過期時間為10秒,緩存由A創建,十秒中內,B進入可系統,並讀取了緩存,這時緩存的有效時間又會變成十秒,同理,只要緩存在十秒鍾內不間斷有其他人訪問,則緩存永遠不會過期。如果大於十秒,則緩存過期。
絕對過期時間設置方式:
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromSeconds(10));
絕對過期時間不管期間有沒有人訪問,在時間過后,就會過期,清空。
3、移除緩存
_cache.Remove(cacheKey_2);
4、緩存的優先級分為四種:永不過期 大於 高優先級 大於 一般優先級 大於 低優先級。
/緩存優先級 (程序壓力大時,會根據優先級自動回收)

5、緩存過期后,執行回調函數,
public IActionResult cacheCallback()
{
List<string> lst = new List<string>() { "陳大六", "陳卧龍", "陳新宇", "劉麗", "花國鋒" }; //緩存回調 10秒過期會回調 string cacheKey = "cacheKey"; _cache.Set(cacheKey, lst, new MemoryCacheEntryOptions() .SetAbsoluteExpiration(TimeSpan.FromSeconds(10)) .RegisterPostEvictionCallback((key, value, reason, substate) => { //調用回調函數 GetIntList(key,value,reason,substate); })); // return View(); } public void GetIntList(object key,object value, object reason, object substate) { List<string> lst=(List<string>)value; //說白了就是被釋放了 Console.WriteLine($"鍵{key}的緩存因:{reason}而改變。"); foreach(var item in lst) { Console.WriteLine(item); } }
6、緩存回調 根據Token過期
public ActionResult CacheToken()
{
List<string> lst = new List<string>() { "陳大六", "陳卧龍", "陳新宇", "劉麗", "花國鋒" }; string cacheKey = "CacheToken"; //緩存回調 根據Token過期 var cts = new CancellationTokenSource(); _cache.Set(cacheKey, lst, new MemoryCacheEntryOptions() .AddExpirationToken(new CancellationChangeToken(cts.Token)) .RegisterPostEvictionCallback((key, value, reason, substate) => { Console.WriteLine($"鍵{key}值{value}改變,因為{reason}"); })); cts.Cancel(); //執行到Cancel()方法時,會執行回調刪除 return View(); }
2.Redis下載
redis安裝包:https://github.com/microsoftarchive/redis/releases
redis客戶端:https://redisdesktop.com/download 最新的要付費可以使用舊版
https://github.com/uglide/RedisDesktopManager/releases/tag/0.8.8
為什么要用 redis?/為什么要用緩存?
主要從“高性能”和“高並發”這兩點來看待這個問題。
高性能:
假如用戶第一次訪問數據庫中的某些數據。這個過程會比較慢,因為是從硬盤上讀取的。將該用戶訪問的數據存在緩存中,這樣下一次再訪問這些數據的時候就可以直接從緩存中獲取了。操作緩存就是直接操作內存,所以速度相當快。如果數據庫中的對應數據改變的之后,同步改變緩存中相應的數據即可!
高並發:
直接操作緩存能夠承受的請求是遠遠大於直接訪問數據庫的,所以我們可以考慮把數據庫中的部分數據轉移到緩存中去,這樣用戶的一部分請求會直接到緩存這里而不用經過數據庫。
redis 的線程模型
redis 內部使用文件事件處理器 file event handler,這個文件事件處理器是單線程的,所以 redis 才叫做單線程的模型。它采用 IO 多路復用機制同時監聽多個 socket,根據 socket 上的事件來選擇對應的事件處理器進行處理。
文件事件處理器的結構包含 4 個部分:
多個 socket
IO 多路復用程序
文件事件分派器
事件處理器(連接應答處理器、命令請求處理器、命令回復處理器)
多個 socket 可能會並發產生不同的操作,每個操作對應不同的文件事件,但是 IO 多路復用程序會監聽多個 socket,會將 socket 產生的事件放入隊列中排隊,事件分派器每次從隊列中取出一個事件,把該事件交給對應的事件處理器進行處理。
3.StackExchange.Redis
在NuGet上安裝StackExchange.Redis,然后在appsettings.json文件里面添加Redis相關配置信息:
{ "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*", "Redis": { "Default": { "Connection": "127.0.0.1:6379", "InstanceName": "local", "DefaultDB": 8 } } }
幫助類
using StackExchange.Redis; using System; using System.Collections.Concurrent; namespace RedisDemo { public class RedisHelper : IDisposable { //連接字符串 private string _connectionString; //實例名稱 private string _instanceName; //默認數據庫 private int _defaultDB; private ConcurrentDictionary<string, ConnectionMultiplexer> _connections; public RedisHelper(string connectionString, string instanceName, int defaultDB = 0) { _connectionString = connectionString; _instanceName = instanceName; _defaultDB = defaultDB; _connections = new ConcurrentDictionary<string, ConnectionMultiplexer>(); } /// <summary> /// 獲取ConnectionMultiplexer /// </summary> /// <returns></returns> private ConnectionMultiplexer GetConnect() { return _connections.GetOrAdd(_instanceName, p => ConnectionMultiplexer.Connect(_connectionString)); } /// <summary> /// 獲取數據庫 /// </summary> /// <param name="configName"></param> /// <param name="db">默認為0:優先代碼的db配置,其次config中的配置</param> /// <returns></returns> public IDatabase GetDatabase() { return GetConnect().GetDatabase(_defaultDB); } public IServer GetServer(string configName = null, int endPointsIndex = 0) { var confOption = ConfigurationOptions.Parse(_connectionString); return GetConnect().GetServer(confOption.EndPoints[endPointsIndex]); } public ISubscriber GetSubscriber(string configName = null) { return GetConnect().GetSubscriber(); } public void Dispose() { if (_connections != null && _connections.Count > 0) { foreach (var item in _connections.Values) { item.Close(); } } } } }
在Startup.cs類的ConfigureServices方法里面添加服務注入:
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace RedisDemo { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { //redis緩存 var section = Configuration.GetSection("Redis:Default"); //連接字符串 string _connectionString = section.GetSection("Connection").Value; //實例名稱 string _instanceName = section.GetSection("InstanceName").Value; //默認數據庫 int _defaultDB = int.Parse(section.GetSection("DefaultDB").Value ?? "0"); services.AddSingleton(new RedisHelper(_connectionString, _instanceName, _defaultDB)); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvc(); } } }
新建一個控制器,然后通過構造函數注入:
using Microsoft.AspNetCore.Mvc; using StackExchange.Redis; namespace RedisDemo.Controllers { [Route("api/redis")] [ApiController] public class RedisController : ControllerBase { private readonly IDatabase _redis; public RedisController(RedisHelper client) { _redis = client.GetDatabase(); } [HttpGet] public string Get() { // 往Redis里面存入數據 _redis.StringSet("Name", "Tom"); // 從Redis里面取數據 string name = _redis.StringGet("Name"); return name; } } }
CSRedisCore
在NuGet上安裝CSRedisCore,然后在appsettings.json文件里面添加Redis相關配置信息:
{ "RedisServer": { "Cache": "192.168.0.3:6379,password=redis,preheat=5,idleTimeout=600,defaultDatabase=13,prefix=Cache" }, "Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Trace", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } } }
新建一個 IRedisClient 接口
1 public interface IRedisClient 2 { 3 string Get(string key); 4 Task<string> GetAsync(string key); 5 void Set(string key, object t, int expiresSec = 0); 6 Task SetAsync(string key, object t, int expiresSec = 0); 7 T Get<T>(string key) where T : new(); 8 Task<T> GetAsync<T>(string key) where T : new(); 9 }
實現接口
1 public class CustomerRedis : IRedisClient 2 { 3 public string Get(string key) 4 { 5 return RedisHelper.Get(key); 6 } 7 8 public T Get<T>(string key) where T : new() 9 { 10 return RedisHelper.Get<T>(key); 11 } 12 13 public async Task<string> GetAsync(string key) 14 { 15 return await RedisHelper.GetAsync(key); 16 } 17 18 public async Task<T> GetAsync<T>(string key) where T : new() 19 { 20 return await RedisHelper.GetAsync<T>(key); 21 } 22 23 public void Set(string key, object t, int expiresSec = 0) 24 { 25 RedisHelper.Set(key, t, expiresSec); 26 } 27 28 public async Task SetAsync(string key, object t, int expiresSec = 0) 29 { 30 await RedisHelper.SetAsync(key, t, expiresSec); 31 } 32 }
Startup
1 services.AddScoped<IRedisClient, CustomerRedis>(); 2 3 var redisConn = Configuration["Cache:RedisConnection"]; 4 //services.Configure<WeChatPayOptions>(Configuration.GetSection("WeChatPay")); 5 var csredis = new CSRedis.CSRedisClient(redisConn); 6 RedisHelper.Initialization(csredis);
調用
1 private readonly IRedisClient _redisClient; 2 public ValuesController(IRedisClient redisClient) 3 { 4 this._redisClient = redisClient; 5 } 6 7 [HttpGet("test")] 8 public async Task<ActionResult> Test() 9 { 10 await this._redisClient.SetAsync("test", "test",100); 11 var res = await this._redisClient.GetAsync("test"); 12 return Ok(res); 13 }
github地址:https://github.com/2881099/csredis