您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“如何使用redis實現分布式緩存”,內容詳細,步驟清晰,細節處理妥當,希望這篇“如何使用redis實現分布式緩存”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
分布式緩存描述:
分布式緩存重點是在分布式上,相信大家接觸過的分布式有很多中,像分布式開發,分布式部署,分布式鎖、事物、系統 等有很多。使我們對分布式本身就有一個很明確的認識,分布式就是有多個應用程序組成,可能分布在不同的服務器上,最終都是在為web端提供服務。
分布式緩存有以下幾點優點:
所有的Web服務器上的緩存數據都是相同的,不會因為應用程序不同,服務器的不同導致緩存數據的不一樣。
緩存的是獨立的不受Web服務器的重新啟動或被刪除添加的影響,也就是說這些Web的改變不到導致緩存數據的改變。
傳統的單體應用架構因為用戶的訪問量的不高,緩存的存在大多數都是存儲用戶的信息,以及一些頁面,大多數的操作都是直接和DB進行讀寫交互,這種架構簡單,也稱為簡單架構,
傳統的OA項目比如ERP,SCM,CRM等系統因為用戶量不大也是因為大多數公司業務的原因,單體應用架構還是很常用的架構,但是有些系統隨著用戶量的增加,業務的擴張擴展,導致DB的瓶頸的出現。
以下我所了解到的關于這種情況的處理有以下兩種
(1):當用戶訪問量不大,但是讀寫的數據量很大的時候,我們一般采取的是,對DB進行讀寫分離、一主多從、對硬件進行升級的方式來解決DB瓶頸的問題。
這樣的缺點也同樣純在:
1、用戶量大的時候怎么辦?,
2、對于性能的提升有限,
3、性價比不高。提升一點性能就需要花費很多代價,(打個比方,現在的I/O吞吐量是0.9的需要提升到1.0,我們在增加機器配置的情況下這個價格確實很可觀的)
(2):當用戶訪問量也增加的時候,我們就需要引入緩存了來解決了,一張圖描述緩存的大致的作用。
緩存主要針對的是不經常發生改變的并且訪問量很大的數據,DB數據庫可以理解為只作為數據固化的或者只用來讀取經常發生改變的數據,上圖中我沒有畫SET的操作,就是想特意說明一下,緩存的存在可以作為一個臨時的數據庫,我們可以通過定時的任務的方式去同步緩存和數據庫中的數據,這樣做的好處是可以轉移數據庫的壓力到緩存中。
緩存的出現解決了數據庫壓力的問題,但是當以下情況發生的時候,緩存就不在起到作用了,緩存穿透、緩存擊穿、緩存雪崩這三種情況。
緩存穿透:我們的程序中用緩存的時候一般采取的是先去緩存中查詢我們想要的緩存數據,如果緩存中不存在我們想要的數據的話,緩存就失去了做用(緩存失效)我們就是需要伸手向DB庫去要數據,這個時候這種動作過多數據庫就崩潰了,這種情況需要我們去預防了。比如說:我們向緩存獲取一個用戶信息,但是故意去輸入一個緩存中不存在的用戶信息,這樣就避過了緩存,把壓力重新轉移到數據上面了。對于這種問題我們可以采取,把第一次訪問的數據進行緩存,因為緩存查不到用戶信息,數據庫也查詢不到用戶信息,這個時候避免重復的訪問我們把這個請求緩存起來,把壓力重新轉向緩存中,有人會有疑問了,當訪問的參數有上萬個都是不重復的參數并且都是可以躲避緩存的怎么辦,我們同樣把數據存起來設置一個較短過期時間清理緩存。
緩存擊穿:事情是這樣的,對于一些設置了過期時間的緩存KEY,在過期的時候,程序被高并發的訪問了(緩存失效),這個時候使用互斥鎖來解決問題,
互斥鎖原理:通俗的描述就是,一萬個用戶訪問了,但是只有一個用戶可以拿到訪問數據庫的權限,當這個用戶拿到這個權限之后重新創建緩存,這個時候剩下的訪問者因為沒有拿到權限,就原地等待著去訪問緩存。
永不過期:有人就會想了,我不設置過期時間不就行了嗎?可以,但是這樣做也是有缺點的,我們需要定期的取更新緩存,這個時候緩存中的數據比較延遲。
緩存雪崩:是指多種緩存設置了同一時間過期,這個時候大批量的數據訪問來了,(緩存失效)數據庫DB的壓力又上來了。解決方法在設置過期時間的時候在過期時間的基礎上增加一個隨機數盡可能的保證緩存不會大面積的同事失效。
項目準備
1、首先安裝Redis
2、然后下載安裝:客戶端工具:RedisDesktopManager(方便管理)
3、在我們的項目Nuget中 引用 Microsoft.Extensions.Caching.Redis
為此我們新建一個ASP.NET Core MVC項目,在項目Startup類的ConfigureServices方法中先注冊Redis服務:
public void ConfigureServices(IServiceCollection services) { //將Redis分布式緩存服務添加到服務中 services.AddDistributedRedisCache(options => { //用于連接Redis的配置 Configuration.GetConnectionString("RedisConnectionString")讀取配置信息的串 options.Configuration = "localhost";// Configuration.GetConnectionString("RedisConnectionString"); //Redis實例名DemoInstance options.InstanceName = "DemoInstance"; }); services.AddMvc(); }
也可以在上面注冊Redis服務的時候,指定Redis服務器的IP地址、端口號和登錄密碼:
public void ConfigureServices(IServiceCollection services) { //將Redis分布式緩存服務添加到服務中 services.AddDistributedRedisCache(options => { //用于連接Redis的配置 Configuration.GetConnectionString("RedisConnectionString")讀取配置信息的串 options.Configuration = "192.168.1.105:6380,password=1qaz@WSX3edc$RFV";//指定Redis服務器的IP地址、端口號和登錄密碼 //Redis實例名DemoInstance options.InstanceName = "DemoInstance"; }); services.AddMvc(); }
后面我們會解釋上面options.InstanceName設置的Redis實例名DemoInstance是用來做什么的
此外還可以在services.AddDistributedRedisCache方法中指定Redis服務器的超時時間,如果調用后面介紹的IDistributedCache接口中的方法,對Redis服務器進行的操作超時了,會拋出RedisConnectionException和RedisTimeoutException異常,所以下面我們在注冊Redis服務的時候,指定了三個超時時間:
public void ConfigureServices(IServiceCollection services) { //將Redis分布式緩存服務添加到服務中 services.AddDistributedRedisCache(options => { options.ConfigurationOptions = new StackExchange.Redis.ConfigurationOptions() { Password = "1qaz@WSX3edc$RFV", ConnectTimeout = 5000,//設置建立連接到Redis服務器的超時時間為5000毫秒 SyncTimeout = 5000,//設置對Redis服務器進行同步操作的超時時間為5000毫秒 ResponseTimeout = 5000//設置對Redis服務器進行操作的響應超時時間為5000毫秒 }; options.ConfigurationOptions.EndPoints.Add("192.168.1.105:6380"); options.InstanceName = "DemoInstance"; }); services.AddMvc(); }
其中ConnectTimeout是建立連接到Redis服務器的超時時間,而SyncTimeout和ResponseTimeout是對Redis服務器進行數據操作的超時時間。注意上面我們使用了options.ConfigurationOptions屬性來設置Redis服務器的IP地址、端口號和登錄密碼
IDistributedCache 接口
在項目中引用:using Microsoft.Extensions.Caching.Distributed; 使用IDistributedCache
IDistributedCache接口包含同步和異步方法。 接口允許在分布式緩存實現中添加、檢索和刪除項。 IDistributedCache接口包含以下方法:
Get、 GetAsync
采用字符串鍵并以byte[]形式檢索緩存項(如果在緩存中找到)。
Set、SetAsync
使用字符串鍵向緩存添加或更改項(byte[]形式)。
Refresh、RefreshAsync
根據鍵刷新緩存中的項,并重置其可調過期超時值(如果有)。
Remove、RemoveAsync
根據鍵刪除緩存項。如果傳入Remove方法的鍵在Redis中不存在,Remove方法不會報錯,只是什么都不會發生而已,但是如果傳入Remove方法的參數為null,則會拋出異常。
如上所述,由于IDistributedCache接口的Set和Get方法,是通過byte[]字節數組來向Redis存取數據的,所以從某種意義上來說不是很方便,下面我封裝了一個RedisCache類,可以向Redis中存取任何類型的數據。
其中用到了Json.NET Nuget包,來做Json格式的序列化和反序列化:
using Microsoft.Extensions.Caching.Distributed; using Newtonsoft.Json; using System.Text; namespace AspNetCoreRedis.Assembly { /// <summary> /// RedisCache緩存操作類 /// </summary> public class RedisCache { protected IDistributedCache cache; /// <summary> /// 通過IDistributedCache來構造RedisCache緩存操作類 /// </summary> /// <param name="cache">IDistributedCache對象</param> public RedisCache(IDistributedCache cache) { this.cache = cache; } /// <summary> /// 添加或更改Redis的鍵值,并設置緩存的過期策略 /// </summary> /// <param name="key">緩存鍵</param> /// <param name="value">緩存值</param> /// <param name="distributedCacheEntryOptions">設置Redis緩存的過期策略,可以用其設置緩存的絕對過期時間(AbsoluteExpiration或AbsoluteExpirationRelativeToNow),也可以設置緩存的滑動過期時間(SlidingExpiration)</param> public void Set(string key, object value, DistributedCacheEntryOptions distributedCacheEntryOptions) { //通過Json.NET序列化緩存對象為Json字符串 //調用JsonConvert.SerializeObject方法時,設置ReferenceLoopHandling屬性為ReferenceLoopHandling.Ignore,來避免Json.NET序列化對象時,因為對象的循環引用而拋出異常 //設置TypeNameHandling屬性為TypeNameHandling.All,這樣Json.NET序列化對象后的Json字符串中,會包含序列化的類型,這樣可以保證Json.NET在反序列化對象時,去讀取Json字符串中的序列化類型,從而得到和序列化時相同的對象類型 var stringObject = JsonConvert.SerializeObject(value, new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, TypeNameHandling = TypeNameHandling.All }); var bytesObject = Encoding.UTF8.GetBytes(stringObject);//將Json字符串通過UTF-8編碼,序列化為字節數組 cache.Set(key, bytesObject, distributedCacheEntryOptions);//將字節數組存入Redis Refresh(key);//刷新Redis } /// <summary> /// 查詢鍵值是否在Redis中存在 /// </summary> /// <param name="key">緩存鍵</param> /// <returns>true:存在,false:不存在</returns> public bool Exist(string key) { var bytesObject = cache.Get(key);//從Redis中獲取鍵值key的字節數組,如果沒獲取到,那么會返回null if (bytesObject == null) { return false; } return true; } /// <summary> /// 從Redis中獲取鍵值 /// </summary> /// <typeparam name="T">緩存的類型</typeparam> /// <param name="key">緩存鍵</param> /// <param name="isExisted">是否獲取到鍵值,true:獲取到了,false:鍵值不存在</param> /// <returns>緩存的對象</returns> public T Get<T>(string key, out bool isExisted) { var bytesObject = cache.Get(key);//從Redis中獲取鍵值key的字節數組,如果沒獲取到,那么會返回null if (bytesObject == null) { isExisted = false; return default(T); } var stringObject = Encoding.UTF8.GetString(bytesObject);//通過UTF-8編碼,將字節數組反序列化為Json字符串 isExisted = true; //通過Json.NET反序列化Json字符串為對象 //調用JsonConvert.DeserializeObject方法時,也設置TypeNameHandling屬性為TypeNameHandling.All,這樣可以保證Json.NET在反序列化對象時,去讀取Json字符串中的序列化類型,從而得到和序列化時相同的對象類型 return JsonConvert.DeserializeObject<T>(stringObject, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All }); } /// <summary> /// 從Redis中刪除鍵值,如果鍵值在Redis中不存在,該方法不會報錯,只是什么都不會發生 /// </summary> /// <param name="key">緩存鍵</param> public void Remove(string key) { cache.Remove(key);//如果鍵值在Redis中不存在,IDistributedCache.Remove方法不會報錯,但是如果傳入的參數key為null,則會拋出異常 } /// <summary> /// 從Redis中刷新鍵值 /// </summary> /// <param name="key">緩存鍵</param> public void Refresh(string key) { cache.Refresh(key); } } }
使用測試
然后我們在ASP.NET Core MVC項目中,新建一個CacheController,然后在其Index方法中來測試RedisCache類的相關方法:
public class CacheController : Controller { protected RedisCache redisCache; //由于我們前面在Startup類的ConfigureServices方法中調用了services.AddDistributedRedisCache來注冊Redis服務,所以ASP.NET Core MVC會自動依賴注入下面的IDistributedCache cache參數 public CacheController(IDistributedCache cache) { redisCache = new RedisCache(cache); } public IActionResult Index() { bool isExisted; isExisted = redisCache.Exist("abc");//查詢鍵值"abc"是否存在 redisCache.Remove("abc");//刪除不存在的鍵值"abc",不會報錯 string key = "Key01";//定義緩存鍵"Key01" string value = "This is a demo key !";//定義緩存值 redisCache.Set(key, value, new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) });//設置鍵值"Key01"到Redis,使用絕對過期時間,AbsoluteExpirationRelativeToNow設置為當前系統時間10分鐘后過期 //也可以通過AbsoluteExpiration屬性來設置絕對過期時間為一個具體的DateTimeOffset時間點 //redisCache.Set(key, value, new DistributedCacheEntryOptions() //{ // AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(10) //});//設置鍵值"Key01"到Redis,使用絕對過期時間,AbsoluteExpiration設置為當前系統時間10分鐘后過期 var getVaue = redisCache.Get<string>(key, out isExisted);//從Redis獲取鍵值"Key01",可以看到getVaue的值為"This is a demo key !" value = "This is a demo key again !";//更改緩存值 redisCache.Set(key, value, new DistributedCacheEntryOptions() { SlidingExpiration = TimeSpan.FromMinutes(10) });//將更改后的鍵值"Key01"再次緩存到Redis,這次使用滑動過期時間,SlidingExpiration設置為10分鐘 getVaue = redisCache.Get<string>(key, out isExisted);//再次從Redis獲取鍵值"Key01",可以看到getVaue的值為"This is a demo key again !" redisCache.Remove(key);//從Redis中刪除鍵值"Key01" return View(); } }
前面我們在項目的Startup類ConfigureServices方法中,調用services.AddDistributedRedisCache注冊Redis服務的時候,有設置options.InstanceName = "DemoInstance",那么這個InstanceName到底有什么用呢?
當我們在上面的CacheController中調用Index方法的下面代碼后:
string key = "Key01";//定義緩存鍵"Key01" string value = "This is a demo key !";//定義緩存值 redisCache.Set(key, value, new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) });//設置鍵值"Key01"到Redis,使用絕對過期時間,AbsoluteExpirationRelativeToNow設置為當前系統時間10分鐘后過期
我們使用redis-cli登錄到Redis服務器中,使用Keys *指令查看當前Redis服務中存儲的所有鍵時,可以看到結果如下:
可以看到雖然我們代碼中向Redis存入的鍵是"Key01",但是實際上在Redis服務中存儲的鍵是"DemoInstanceKey01",所以實際上真正存入Redis服務中的鍵是“InstanceName+鍵”這種組合鍵,因此我們可以通過設置不同的InstanceName來為不同的Application在Redis中做數據隔離,這就是InstanceName的作用
讀到這里,這篇“如何使用redis實現分布式緩存”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。