一尔艇、Redis事務(wù)
Redis實現(xiàn)了基本的事務(wù)功能受葛,但是不具有回滾功能。Redis通過使用 MULTI
和EXEC
兩個命令來開啟事務(wù)株搔,Redis會先緩存所有包圍在事務(wù)中的命令,直到EXEC
被調(diào)用才會將所有的命令一起發(fā)送給Redis服務(wù)榄笙。Redis保證事務(wù)的原子性和隔離性邪狞,也就是說事務(wù)中的命令要么全部執(zhí)行祷蝌,要么全部不執(zhí)行茅撞,一但事務(wù)開始執(zhí)行Redis服務(wù)會暫停其它客戶單發(fā)送的請求,也就是事務(wù)中的命令按順序執(zhí)行并且不會被其它客戶端打擾巨朦。
由于Redis事務(wù)執(zhí)行過程中不加鎖米丘,所以多線程或者多客戶單執(zhí)行過程中會出現(xiàn)競爭條件。比如一個客戶端A通過查詢發(fā)現(xiàn)庫存還有1個可用糊啡,于是下單拄查,但是命令還未發(fā)往Redis服務(wù),于此同時客戶端B通過查詢也發(fā)現(xiàn)庫存還有1個可用棚蓄,于是也下單堕扶,因為緩存或者網(wǎng)絡(luò)等各種原因客戶端B的命令可能會先于客戶端A到達Redis服務(wù)碍脏,于是客戶端B事務(wù)執(zhí)行成功,客戶端A產(chǎn)生了錯誤(但是它并沒有意識到這點稍算,也就是庫存現(xiàn)在是負的)典尾。為了解決這個問題,Redis使用了一個叫check-and-set
的WATCH
命令糊探,也就是樂觀鎖钾埂。沿用上面的例子,但是這次在使用事務(wù)之前先用WATCH
命令監(jiān)視庫存科平,然后按照順序執(zhí)行褥紫,客戶端B事務(wù)執(zhí)行成功,客戶端A事務(wù)執(zhí)行失數苫邸(通過WATCH
命令可以在執(zhí)行事務(wù)的時候意識到庫存已經(jīng)被修改)髓考,此時客戶端A可以重新執(zhí)行事務(wù),然后查詢得知庫存為0弃酌,于是放棄事務(wù)并通知用戶绳军。WATCH
命令雖然可以保證數(shù)據(jù)正確,但是缺點是效率低矢腻,因為一但Redis負載高的話门驾,沖突會指數(shù)上升,客戶端需要不斷的重復(fù)執(zhí)行事務(wù)才有可能成功多柑。
二奶是、Redis鎖
WATCH
命令雖然可以保證數(shù)據(jù)的正確性,但是效率實在是不高竣灌,所以使用鎖(排它性鎖)將獲得更高的效率聂沙。Redis并沒有打算實現(xiàn)這種鎖,不過好在通過Redis的相關(guān)命令也是可以自己實現(xiàn)鎖功能(通過Redis中的key來實現(xiàn)初嘹,成功獲取鎖設(shè)置一個key及汉,釋放鎖刪除那個key,通過判斷key是否存在來確定是否可以獲取鎖)屯烦。自己實現(xiàn)鎖功能需要注意的是鎖的釋放坷随,比如客戶端A獲取了庫存鎖,但是突然宕機了驻龟,這樣其它客戶端就無法獲取鎖了温眉。所以在自定義鎖的時候,可以通過Redis的key過期功能翁狐,給鎖(一般就是Redis中的key)添加一個過期時間类溢,這樣就算客戶端宕機了鎖也能自動釋放。另外需要注意的是鎖的過期時間露懒,因為不是所有的操作都是簡單的闯冷,有些客戶端可能需要花很長的時間才能完成事務(wù)砂心,如果客戶端在事務(wù)完成前,鎖就自動釋放了蛇耀,這樣會造成數(shù)據(jù)錯誤计贰。一般解決方法是在鎖快要釋放之前,重新刷新鎖的過期時間蒂窒,也就是續(xù)約鎖躁倒。自己實現(xiàn)鎖還需要考慮很多東西,好在redisson這個開源庫實現(xiàn)了Redis鎖洒琢,可以直接參考或者使用秧秉。
三、Redis分布式鎖
Redis分布式鎖基本上就是上面所說的自定義實現(xiàn)的鎖衰抑,因為Redis可以被多個應(yīng)用訪問象迎,所以通過Redis鎖就可以讓多個應(yīng)用通過鎖來訪問資源。但是一般的Redis分布式鎖都存在一個問題呛踊,就是對于故障轉(zhuǎn)移的能力不夠砾淌。比如Redis主服務(wù)A和從服務(wù)A1組成了一個Redis服務(wù),客戶端C申請了鎖谭网,但是在A向A1同步數(shù)據(jù)前(相對客戶端來說A和A1之間同步數(shù)據(jù)是異步的)宕機了汪厨,然后根據(jù)故障轉(zhuǎn)移規(guī)則A1升級成了主服務(wù),但是這時A1是沒有C所申請的鎖的信息愉择,如果這時其它客戶端申請相同的鎖劫乱,那么它就能獲取鎖,此時兩個客戶端都擁有了相同的鎖锥涕。
針對這種情況Redis官方開發(fā)了一種叫Redlock
的分布式鎖衷戈,相對于前面所說的實現(xiàn)(單臺Redis主服務(wù)),Redlock
使用多臺Redis主服務(wù)的方式來實現(xiàn)层坠。簡單來說如果有N臺主服務(wù)殖妇,那么在指定時間范圍內(nèi)獲取到N/2+1臺主服務(wù)中的鎖,那么就獲取鎖成功破花。也就是如果現(xiàn)在有5臺主服務(wù)谦趣,那么至少要在3臺主服務(wù)中獲取到鎖,Redlock
才算獲取成功旧乞。這么做主要是為了防止某一臺服務(wù)宕機后蔚润,其它服務(wù)還能正常運行磅氨。
Redlock
算法原理可以參考https://redis.io/topics/distlock
redisson這個開源庫實現(xiàn)了Redlock
四尺栖、Redis分片
Redis實現(xiàn)了主從服務(wù),但是這種方式只是擴展了讀請求烦租,對于寫請求依然是單服務(wù)延赌。為了擴展寫請求需要一組相互獨立Redis主服務(wù)除盏,并且客戶端需要自行將數(shù)據(jù)分片到不同的Redis主服務(wù)中。Redis分片的原理基本上就是crc32(key) mod N
挫以,其中N表示主服務(wù)的個數(shù)者蠕。
分片在不同軟件層次中實現(xiàn):
1、客戶端分片掐松,由客戶端實現(xiàn)將數(shù)據(jù)分片到哪個服務(wù)中
2踱侣、代理分片,由代理決定將數(shù)據(jù)分片到哪個服務(wù)中
3大磺、查詢路由抡句,客戶端隨機訪問一臺服務(wù),由服務(wù)決定是自己處理還是路由到其它服務(wù)器杠愧。Redis Cluster
實現(xiàn)了混合形式的查詢路由待榔,也就是在查詢路由的基礎(chǔ)上,添加一個緩存表流济,這張表會逐步記錄key與服務(wù)器之間的關(guān)系锐锣。比如客戶端首先將key1發(fā)往服務(wù)器A,然后服務(wù)器A發(fā)現(xiàn)key1應(yīng)該由服務(wù)器B處理绳瘟,它會將這個路由信息告訴客戶端雕憔,客戶端緩存表會記錄key1由服務(wù)器B處理
,當(dāng)?shù)诙尾樵僰ey1的時候糖声,客戶端會直接請求服務(wù)器B橘茉。
分片實現(xiàn)方案:
1、一致性哈希算法姨丈,這個算法在分布式應(yīng)用中比較廣泛畅卓,可以動態(tài)擴容和縮容。但是這個算法不太適合將Redis用來存儲持久化數(shù)據(jù)蟋恬,主要是因為這個算法沒有數(shù)據(jù)遷移功能翁潘。比如原本key1存儲在服務(wù)器A,擴容后key1需要存儲在服務(wù)器B歼争,這時key1的數(shù)據(jù)在邏輯上已經(jīng)丟失了拜马。這個算法最適合將Redis用來緩存數(shù)據(jù),第一個是因為這個算法在擴容和縮容時只會影響一部分key的映射沐绒,第二個受影響的key就算丟失了數(shù)據(jù)也不會對應(yīng)用造成影響俩莽,第三個通過對key設(shè)置過期時間,那些受影響的key的老數(shù)據(jù)會自動刪除乔遮。
2扮超、預(yù)分片,這個方案比較適合將Redis用來存儲持久化數(shù)據(jù)。預(yù)分片的原理就是提前預(yù)測好未來需要多少臺Redis服務(wù)出刷。假設(shè)未來最多需要10臺服務(wù)器璧疗,那么在一開始的時候就在一臺服務(wù)器上開啟10個Redis服務(wù)。等到內(nèi)存不夠的時候馁龟,準(zhǔn)備一臺新的服務(wù)器崩侠,將5個Redis服務(wù)遷移到新的服務(wù)器中,通過Redis的數(shù)據(jù)備份坷檩、主從復(fù)制等功能這個方案完全可以實現(xiàn)却音。一致性哈希算法也可以用作預(yù)分片的哈希算法,只要限制它擴容或縮容矢炼。
分片缺點:
1僧家、涉及多個key的操作通常不允許,比如集合的交集裸删、差集等操作八拱。主要是分片后多個key不在同一服務(wù)中,分片之間也不可能移動數(shù)據(jù)涯塔。
2肌稻、同時操作多個key,則不能使用Redis事務(wù)匕荸。
3爹谭、數(shù)據(jù)備份會比較復(fù)雜,因為現(xiàn)在數(shù)據(jù)分散在各個分片中榛搔。
4诺凡、不太支持動態(tài)擴容或縮容,只能在一定條件下使用践惑。
四腹泌、Redis Cluster
Redis Cluster
是官方實現(xiàn)的一種分片方案。它沒有使用一致性哈希算法尔觉,而是使用了一種叫哈希槽的概念凉袱。Redis Cluster
一共有16384個哈希槽,然后使用CRC16(key) mod 16384
來計算每個key所對應(yīng)的哈希槽侦铜。Redis Cluster
中的每個節(jié)點(redis服務(wù))都會負責(zé)維護一部分哈希槽专甩,比如你的集群中有3個節(jié)點,那么:
- Node A contains hash slots from 0 to 5500.
- Node B contains hash slots from 5501 to 11000.
- Node C contains hash slots from 11001 to 16383.
假如你通過CRC16(key) mod 16384
算出來的結(jié)果是5501钉稍,那么當(dāng)你第一次查詢的時候客戶端會隨機訪問一個節(jié)點涤躲,比如Node A,它會首先查看5501是否由自己負責(zé)贡未,如果是就直接處理种樱,如果不是就告訴客戶端Node B負責(zé)處理蒙袍,客戶端需要重新向Node B發(fā)送請求。
Redis Cluster
相比之前的分片方案缸托,它具有高擴展性和伸縮性左敌,一致性哈希算法也可以擴展和伸縮但是它無法遷移數(shù)據(jù)瘾蛋。比如現(xiàn)在有A俐镐、B、C三個節(jié)點哺哼,你需要添加D佩抹,那么首先將D添加到集群中,然后從A取董、B棍苹、C中分別遷移一部分哈希槽到D(哈希槽所關(guān)聯(lián)的數(shù)據(jù)會一同轉(zhuǎn)移)。如果要刪除節(jié)點A茵汰,首先需要將A負責(zé)的哈希槽移動到B 枢里、C,然后就可以關(guān)閉節(jié)點A蹂午。
Redis Cluster
允許你在一定范圍內(nèi)執(zhí)行涉及多個key的操作栏豺,包括交集、差集豆胸、事務(wù)等操作奥洼,只要你所操作的key都關(guān)聯(lián)到同一哈希槽。這個可以通過hash tags
的技術(shù)強制將不同的key映射到同一哈希槽晚胡。比如hello{word}灵奖、say{word}、test{word}
這3個key用來計算哈希槽的部分不是整體估盘,而是包圍在花括號中的word
瓷患,這樣它們所關(guān)聯(lián)的哈希槽就是同一個。
Redis Cluster
使用主從模式來備份數(shù)據(jù)和提供可靠性遣妥,假設(shè)有A尉尾、B、C三個主節(jié)點燥透,以及A1沙咏、B1、C1三個從節(jié)點班套,那么每個節(jié)點都有一個備份從節(jié)點肢藐,如果A節(jié)點宕機了,那么A1會被推選為新的主節(jié)點替換A節(jié)點吱韭。當(dāng)然如果A1也宕機了吆豹,那么集群將停止工作鱼的。
Redis Cluster
無法保證數(shù)據(jù)的強一致性
,也就是說在一些條件下集群已經(jīng)對客戶端確認的寫入依然會丟失痘煤。假設(shè)有A凑阶、B、C三個主節(jié)點衷快,以及A1宙橱、B1、C1三個從節(jié)點蘸拔,A接收寫入并且向客戶端確認师郑,在A向A1同步數(shù)據(jù)前宕機了,然后A1被推選為新的主節(jié)點替換A调窍,那么剛剛的寫入數(shù)據(jù)將丟失宝冕。類似的情況也會發(fā)生在網(wǎng)絡(luò)分區(qū)階段,主節(jié)點少部分的分區(qū)一般也會丟失一部分數(shù)據(jù)邓萨。不過集群對數(shù)據(jù)丟失會限制在一定時間窗口內(nèi)地梨。