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