經常使用的SQL數據庫的數據都是存在磁盤中的,雖然在數據庫底層也作了對應的緩存來減小數據庫的IO壓力,但因爲數據庫的緩存通常是針對查詢的內容,並且粒度也比較小,通常只有表中的數據沒有發生變更的時候,數據庫的緩存纔會產生做用,但這並不能減小業務邏輯對數據庫的增刪改操做的IO壓力,所以緩存技術應運而生,該技術實現了對熱點數據的高速緩存,能夠大大緩解後端數據庫的壓力。react
主流應用架構linux
客戶端在對數據庫發起請求時,先到緩存層查看是否有所需的數據,若是緩存層存有客戶端所需的數據,則直接從緩存層返回,不然進行穿透查詢,對數據庫進行查詢,若是在數據庫中查詢到該數據,則將該數據回寫到緩存層,以便下次客戶端再次查詢可以直接從緩存層獲取數據。redis
緩存中間件 -- Memcache和Redis的區別算法
Memcache:代碼層相似Hash數據庫
1.支持簡單數據類型後端
2.不支持數據持久化存儲設計模式
3.不支持主從緩存
4.不支持分片安全
Redis服務器
1.數據類型豐富
2.支持數據磁盤持久化存儲
3.支持主從
4.支持分片
爲何Redis能這麼快
Redis的效率很高,官方給出的數據是100000+QPS(query per second),這是由於:
1.Redis徹底基於內存,絕大部分請求是純粹的內存操做,執行效率高。
2.Redis使用單進程單線程模型的(K,V)數據庫,將數據存儲在內存中,存取均不會受到硬盤IO的限制,所以其執行速度極快,另外單線程也能處理高併發請求,還能夠避免頻繁上下文切換和鎖的競爭,若是想要多核運行也能夠啓動多個實例。
3.數據結構簡單,對數據操做也簡單,Redis不使用表,不會強制用戶對各個關係進行關聯,不會有複雜的關係限制,其存儲結構就是鍵值對,相似於HashMap,HashMap最大的優勢就是存取的時間複雜度爲O(1)。
4.Redis使用多路I/O複用模型,爲非阻塞IO(非阻塞IO會另寫一篇解釋,能夠先行百度)。
注:Redis採用的I/O多路複用函數:epoll/kqueue/evport/select
選用策略:
1.因地制宜,優先選擇時間複雜度爲O(1)的I/O多路複用函數做爲底層實現。
2.因爲select要遍歷每個IO,因此其時間複雜度爲O(n),一般被做爲保底方案。
3.基於react設計模式監聽I/O事件。
String
最基本的數據類型,其值最大可存儲512M,二進制安全(Redis的String能夠包含任何二進制數據,包含jpg對象等)。
注:若是重複寫入key相同的鍵值對,後寫入的會將以前寫入的覆蓋。
Hash
String元素組成的字典,適用於存儲對象。
List
列表,按照String元素插入順序排序。其順序爲後進先出。因爲其具備棧的特性,因此能夠實現如「最新消息排行榜」這類的功能。
Set
String元素組成的無序集合,經過哈希表實現(增刪改查時間複雜度爲O(1)),不容許重複。
另外,當咱們使用smembers遍歷set中的元素時,其順序也是不肯定的,是經過hash運算事後的結果。Redis還對集合提供了求交集、並集、差集等操做,能夠實現如同共同關注,共同好友等功能。
Sorted Set
經過分數來爲集合中的成員進行從小到大的排序。
更高級的Redis類型
用於計數的HyperLogLog、用於支持存儲地理位置信息的Geo。
從海量Key裏查詢出某一個固定前綴的Key
假設redis中有十億條key,如何從這麼多key中找到固定前綴的key?
方法1:使用KEYS [pattern]:查找全部符合給定模式pattern的key
使用keys [pattern]指令能夠找到全部符合pattern條件的key,可是keys會一次性返回全部符合條件的key,因此會形成redis的卡頓,假設redis此時正在生產環境下,使用該命令就會形成隱患,另外若是一次性返回全部key,對內存的消耗在某些條件下也是巨大的。 例:
keys test* //返回全部以test爲前綴的key
方法2:使用SCAN cursor [MATCH pattern] [COUNT count]
cursor:遊標 MATCH pattern:查詢key的條件 count:返回的條數 SCAN是一個基於遊標的迭代器,須要基於上一次的遊標延續以前的迭代過程。SCAN以0做爲遊標,開始一次新的迭代,直到命令返回遊標0完成一次遍歷。此命令並不保證每次執行都返回某個給定數量的元素,甚至會返回0個元素,但只要遊標不是0,程序都不會認爲SCAN命令結束,可是返回的元素數量大機率符合count參數。另外,SCAN支持模糊查詢。 例:
SCAN 0 MATCH test* COUNT 10 //每次返回10條以test爲前綴的key
如何經過Redis實現分佈式鎖
分佈式鎖
分佈式鎖是控制分佈式系統之間共同訪問共享資源的一種鎖的實現。若是一個系統,或者不一樣系統的不一樣主機之間共享某個資源時,每每須要互斥,來排除干擾,知足數據一致性。
分佈式鎖須要解決的問題以下:
1.互斥性:任意時刻只有一個客戶端獲取到鎖,不能有兩個客戶端同時獲取到鎖。
2.安全性:鎖只能被持有該鎖的客戶端刪除,不能由其它客戶端刪除。
3.死鎖:獲取鎖的客戶端由於某些緣由而宕機繼而沒法釋放鎖,其它客戶端再也沒法獲取鎖而致使死鎖,此時須要有特殊機制來避免死鎖。
4.容錯:當各個節點,如某個redis節點宕機的時候,客戶端仍然可以獲取鎖或釋放鎖。
如何使用redis實現分佈式鎖
使用SETNX實現
SETNX key value:若是key不存在,則建立並賦值。該命令時間複雜度爲O(1),若是設置成功,則返回1,不然返回0。
因爲SETNX指令操做簡單,且是原子性的,因此初期的時候常常被人們做爲分佈式鎖,咱們在應用的時候,能夠在某個共享資源區以前先使用SETNX指令,查看是否設置成功,若是設置成功則說明前方沒有客戶端正在訪問該資源,若是設置失敗則說明有客戶端正在訪問該資源,那麼當前客戶端就須要等待。可是若是真的這麼作,就會存在一個問題,由於SETNX是長久存在的,因此假設一個客戶端正在訪問資源,而且上鎖,那麼當這個客戶端結束訪問時,該鎖依舊存在,後來者也沒法成功獲取鎖,這個該如何解決呢?
因爲SETNX並不支持傳入EXPIRE參數,因此咱們能夠直接使用EXPIRE指令來對特定的key來設置過時時間。
用法:EXPIRE key seconds
程序:
RedisService redisService = SpringUtils.getBean(RedisService.class); long status = redisService.setnx(key,"1"); if(status == 1){ redisService.expire(key,expire); doOcuppiedWork(); }
這段程序存在的問題:假設程序運行到第二行出現異常,那麼程序來不及設置過時時間就結束了,則key會一直存在,等同於鎖一直被持有沒法釋放。出現此問題的根本緣由爲:原子性得不到知足。
解決:從Redis2.6.12版本開始,咱們就可使用Set操做,將Setnx和expire融合在一塊兒執行,具體作法以下。
SET KEY value [EX seconds] [PX milliseconds] [NX|XX]
EX second:設置鍵的過時時間爲second秒。
PX millisecond:設置鍵的過時時間爲millisecond毫秒。
NX:只在鍵不存在時,纔對鍵進行設置操做。
XX:只在鍵已經存在時,纔對鍵進行設置操做。
注:SET操做成功完成時纔會返回OK,不然返回nil。
有了SET咱們就能夠在程序中使用相似下面的代碼實現分佈式鎖了:
RedisService redisService = SpringUtils.getBean(RedisService.class); String result = redisService.set(lockKey,requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,expireTime); if("OK.equals(result)"){ doOcuppiredWork(); }
如何實現異步隊列
使用Redis中的List做爲隊列
使用上文所說的Redis的數據結構中的List做爲隊列 Rpush生產消息,LPOP消費消息。
此時咱們能夠看到,該隊列是使用rpush生產隊列,使用lpop消費隊列。在這個生產者-消費者隊列裏,當lpop沒有消息時,證實該隊列中沒有元素,而且生產者尚未來得及生產新的數據。
缺點:lpop不會等待隊列中有值以後再消費,而是直接進行消費。
彌補:能夠經過在應用層引入Sleep機制去調用LPOP重試。
使用BLPOP key [key...] timeout
BLPOP key [key ...] timeout:阻塞直到隊列有消息或者超時。
缺點:按照此種方法,咱們生產後的數據只能提供給各個單一消費者消費可否實現生產一次就能讓多個消費者消費呢?
pub/sub:主題訂閱者模式
發送者(pub)發送消息,訂閱者(sub)接收消息。 訂閱者能夠訂閱任意數量的頻道
pub/sub模式的缺點:
消息的發佈是無狀態的,沒法保證可達。對於發佈者來講,消息是「即發即失」的,此時若是某個消費者在生產者發佈消息時下線,從新上線以後,是沒法接收該消息的,要解決該問題須要使用專業的消息隊列,如kafka...此處再也不贅述。
Redis持久化
什麼是持久化
持久化,即將數據持久存儲,而不因斷電或其它各類複雜外部環境影響數據的完整性。因爲Redis將數據存儲在內存而不是磁盤中,因此內存一旦斷電,Redis中存儲的數據也隨即消失,這每每是用戶不指望的,因此Redis有持久化機制來保證數據的安全性。
Redis如何作持久化
Redis目前有兩種持久化方式,即RDB和AOF,RDB是經過保存某個時間點的全量數據快照實現數據的持久化,當恢復數據時,直接經過rdb文件中的快照,將數據恢復。
RDB(快照)持久化:保存某個時間點的全量數據快照
RDB持久化會在某個特定的間隔保存那個時間點的全量數據的快照。 RDB配置文件: redis.conf:
save 900 1 #在900s內若是有1條數據被寫入,則產生一次快照。 save 300 10 #在300s內若是有10條數據被寫入,則產生一次快照 save 60 10000 #在60s內若是有10000條數據被寫入,則產生一次快照 stop-writes-on-bgsave-error yes #stop-writes-on-bgsave-error : 若是爲yes則表示,當備份進程出錯的時候, 主進程就中止進行接受新的寫入操做,這樣是爲了保護持久化的數據一致性的問題。
RDB的建立與載入
SAVE:阻塞Redis的服務器進程,直到RDB文件被建立完畢。SAVE命令不多被使用,由於其會阻塞主線程來保證快照的寫入,因爲Redis是使用一個主線程來接收全部客戶端請求,這樣會阻塞全部客戶端請求。
BGSAVE:該指令會Fork出一個子進程來建立RDB文件,不阻塞服務器進程,子進程接收請求並建立RDB快照,父進程繼續接收客戶端的請求。子進程在完成文件的建立時會向父進程發送信號,父進程在接收客戶端請求的過程當中,在必定的時間間隔經過輪詢來接收子進程的信號。咱們也能夠經過使用lastsave指令來查看bgsave是否執行成功,lastsave能夠返回最後一次執行成功bgsave的時間。
自動化觸發RDB持久化的方式
1.根據redis.conf配置裏的SAVE m n 定時觸發(實際上使用的是BGSAVE)
2.主從複製時,主節點自動觸發。
3.執行Debug Reload
4.執行Shutdown且沒有開啓AOF持久化。
BGSAVE的原理
啓動:
1.檢查是否存在子進程正在執行AOF或者RDB的持久化任務。若是有則返回false。
2.調用Redis源碼中的rdbSaveBackground方法,方法中執行fork()產生子進程執行rdb操做。
3.關於fork()中的Copy-On-Write
fork()在linux中建立子進程採用Copy-On-Write(寫時拷貝技術),即若是有多個調用者同時要求相同資源(如內存或磁盤上的數據存儲),他們會共同獲取相同的指針指向相同的資源,直到某個調用者試圖修改資源的內容時,系統纔會真正複製一份專用副本給調用者,而其它調用者所見到的最初的資源仍然保持不變。
RDB持久化方式的缺點
1.內存數據全量同步,數據量大的情況下,會因爲I/O而嚴重影響性能。
2.可能會由於Redis宕機而丟失從當前至最近一次快照期間的數據。
AOF(Append-Only-File)持久化:保存寫狀態
AOF持久化是經過保存Redis的寫狀態來記錄數據庫的。相對RDB來講,RDB持久化是經過備份數據庫的狀態來記錄數據庫,而AOF持久化是備份數據庫接收到的指令。
1.AOF記錄除了查詢之外的全部變動數據庫狀態的指令。
2.以增量的形式追加保存到AOF文件中。
開啓AOF持久化
1.打開redis.conf配置文件,將appendonly屬性改成yes。
2.修改appendfsync屬性,該屬性能夠接收三種參數,分別是always,everysec,no,always表示老是即時將緩衝區內容寫入AOF文件當中,everysec表示每隔一秒將緩衝區內容寫入AOF文件,no表示將寫入文件操做交由操做系統決定,通常來講,操做系統考慮效率問題,會等待緩衝區被填滿再將緩衝區數據寫入AOF文件中。
appendonly yes #appendsync always appendfsync everysec # appendfsync no
日誌重寫解決AOF文件不斷增大的問題
隨着寫操做的不斷增長,AOF文件會愈來愈大。假設遞增一個計數器100次,若是使用RDB持久化方式,咱們只要保存最終結果100便可,而AOF持久化方式須要記錄下這100次遞增操做的指令,而事實上要恢復這條記錄,只須要執行一條命令就行,因此那一百條命令實際能夠精簡爲一條。Redis支持這樣的功能,在不中斷前臺服務的狀況下,能夠重寫AOF文件,一樣使用到了COW(寫時拷貝)。重寫過程以下:
1.調用fork(),建立一個子進程。
2.子進程把新的AOF寫到一個臨時文件裏,不依賴原來的AOF文件。
3.主進程持續將新的變更同時寫到內存和原來的AOF裏。
4.主進程獲取子進程重寫AOF的完成信號,往新AOF同步增量變更。
5.使用新的AOF文件替換掉舊的AOF文件。
AOF和RDB的優缺點
RDB優勢:全量數據快照,文件小,恢復快。
RDB缺點:沒法保存最近一次快照以後的數據。
AOF優勢:可讀性高,適合保存增量數據,數據不易丟失。
AOF缺點:文件體積大,恢復時間長。
RDB-AOF混合持久化方式
redis4.0以後推出了此種持久化方式,RDB做爲全量備份,AOF做爲增量備份,而且將此種方式做爲默認方式使用。
在上述兩種方式中,RDB方式是將全量數據寫入RDB文件,這樣寫入的特色是文件小,恢復快,但沒法保存最近一次快照以後的數據,AOF則將redis指令存入文件中,這樣又會形成文件體積大,恢復時間長等弱點。
在RDB-AOF方式下,持久化策略首先將緩存中數據以RDB方式全量寫入文件,再將寫入後新增的數據以AOF的方式追加在RDB數據的後面,在下一次作RDB持久化的時候將AOF的數據從新以RDB的形式寫入文件。這種方式既能夠提升讀寫和恢復效率,也能夠減小文件大小,同時能夠保證數據的完整性。在此種策略的持久化過程當中,子進程會經過管道從父進程讀取增量數據,在以RDB格式保存全量數據時,也會經過管道讀取數據,同時不會形成管道阻塞。能夠說,在此種方式下的持久化文件,前半段是RDB格式的全量數據,後半段是AOF格式的增量數據。此種方式是目前較爲推薦的一種持久化方式。
Redis數據的恢復
RDB和AOF文件共存狀況下的恢復流程
從圖可知,Redis啓動時會先檢查AOF是否存在,若是AOF存在則直接加載AOF,若是不存在AOF,則直接加載RDB文件。
Pineline
Pipeline和Linux的管道相似,它可讓Redis批量執行指令。
Redis基於請求/響應模型,單個請求處理須要一一應答。若是須要同時執行大量命令,則每條命令都須要等待上一條命令執行完畢後才能繼續執行,這中間不只僅多了RTT,還屢次使用了系統IO。Pipeline因爲能夠批量執行指令,因此能夠節省屢次IO和請求響應往返的時間。可是若是指令之間存在依賴關係,則建議分批發送指令。
Redis的同步機制
主從同步原理
Redis通常是使用一個Master節點來進行寫操做,而若干個Slave節點進行讀操做,Master和Slave分別表明了一個個不一樣的RedisServer實例,另外按期的數據備份操做也是單獨選擇一個Slave去完成,這樣能夠最大程度發揮Redis的性能,爲的是保證數據的弱一致性和最終一致性。另外,Master和Slave的數據不是必定要即時同步的,可是在一段時間後Master和Slave的數據是趨於同步的,這就是最終一致性。
全同步過程
1.Slave發送sync命令到Master。
2.Master啓動一個後臺進程,將Redis中的數據快照保存到文件中。
3.Master將保存數據快照期間接收到的寫命令緩存起來。
4.Master完成寫文件操做後,將該文件發送給Slave。
5.使用新的AOF文件替換掉舊的AOF文件。
6.Master將這期間收集的增量寫命令發送給Slave端。
增量同步過程
1.Master接收到用戶的操做指令,判斷是否須要傳播到Slave。
2.將操做記錄追加到AOF文件。
3.將操做傳播到其它Slave:1.對齊主從庫;2.往響應緩存寫入指令。
4.將緩存中的數據發送給Slave。
Redis Sentinel(哨兵)
主從模式弊端:當Master宕機後,Redis集羣將不能對外提供寫入操做。Redis Sentinel可解決這一問題。
解決主從同步Master宕機後的主從切換問題:
1.監控:檢查主從服務器是否運行正常。
2.提醒:經過API向管理員或者其它應用程序發送故障通知。
3.自動故障遷移:主從切換(在Master宕機後,將其中一個Slave轉爲Master,其餘的
Slave從該節點同步數據)。
Redis集羣
原理:如何從海量數據裏快速找到所需?
分片
按照某種規則去劃分數據,分散存儲在多個節點上。經過將數據分到多個Redis服務器上,來減輕單個Redis服務器的壓力。
一致性Hash算法
既然要將數據進行分片,那麼一般的作法就是獲取節點的Hash值,而後根據節點數求模,但這樣的方法有明顯的弊端,當Redis節點數須要動態增長或減小的時候,會形成大量的Key沒法被命中。因此Redis中引入了一致性Hash算法。該算法對2^32 取模,將Hash值空間組成虛擬的圓環,整個圓環按順時針方向組織,每一個節點依次爲0、一、2...2^32-1,以後將每一個服務器進行Hash運算,肯定服務器在這個Hash環上的地址,肯定了服務器地址後,對數據使用一樣的Hash算法,將數據定位到特定的Redis服務器上。若是定位到的地方沒有Redis服務器實例,則繼續順時針尋找,找到的第一臺服務器即該數據最終的服務器位置。
Hash環的數據傾斜問題
Hash環在服務器節點不多的時候,容易遇到服務器節點不均勻的問題,這會形成數據傾斜,數據傾斜指的是被緩存的對象大部分集中在Redis集羣的其中一臺或幾臺服務器上。
如上圖,一致性Hash算法運算後的數據大部分被存放在A節點上,而B節點只存放了少許的數據,長此以往A節點將被撐爆。
針對這一問題,能夠引入虛擬節點解決。簡單地說,就是爲每個服務器節點計算多個Hash,每一個計算結果位置都放置一個此服務器節點,稱爲虛擬節點,能夠在服務器IP或者主機名後放置一個編號實現。
例如上圖:將NodeA和NodeB兩個節點分爲Node A#1-A#3 NodeB#1-B#3。
結語
這篇準(tou)備(lan)了至關久的時間,由於有些東西總感受本身拿不許不敢往上寫,差點自閉,就算如今發出來了也感受有不少地方是須要改動的。若是有同窗以爲哪裏寫的不對勁的,評論區或者私聊我...嗯,我不要你以爲,我要我以爲。