3萬(wàn)字聊聊什么是Redis(八)
大家好,我是Leo
上一篇我們介紹了
Redis處理并發(fā)的問(wèn)題。 Redis分布式鎖的實(shí)現(xiàn)。 RedLock算法。
繼上篇Redis技術(shù)總結(jié)七,我們繼續(xù)聊聊Redis的相關(guān)技術(shù)!
這篇主要是介紹一下Redis事務(wù)機(jī)制ACID的實(shí)現(xiàn),Redis主從同步的實(shí)戰(zhàn)細(xì)節(jié)問(wèn)題,
推薦閱讀
Redis如何實(shí)現(xiàn)事務(wù)ACID
什么是ACID
Redis能否實(shí)現(xiàn)事務(wù)ACID屬性呢?
我們可以先來(lái)解釋一下什么是ACID
A原子性:要不全部成功,要不全部失敗 C一致性:事務(wù)執(zhí)行前后是一致的,不能因?yàn)槭聞?wù)A執(zhí)行時(shí)看到的字段A是1,準(zhǔn)備提交時(shí),字段A已經(jīng)被事務(wù)B改成了2。這樣是不行的。 I隔離性:執(zhí)行事務(wù)時(shí),其他操作無(wú)法存取到正在執(zhí)行事務(wù)訪問(wèn)的數(shù)據(jù)。 D持久性:數(shù)據(jù)庫(kù)執(zhí)行事務(wù)后,數(shù)據(jù)的修改要被持久化保存下來(lái)。當(dāng)數(shù)據(jù)庫(kù)重啟后,數(shù)據(jù)的值需要是被修改后的值。
Redis如何實(shí)現(xiàn)事務(wù)
事務(wù)執(zhí)行的過(guò)程我們可以分為三步走
客戶端要下達(dá)一個(gè)命令表示一個(gè)事務(wù)的開(kāi)啟? MULTI客戶端把本身要執(zhí)行的操作和指令發(fā)給服務(wù)器端,這些也就是讀寫命令。服務(wù)器接收讀寫命令把他暫存在命令隊(duì)列中? 業(yè)務(wù)代碼 set get incr 等客戶端向服務(wù)器端發(fā)起一個(gè)提交事務(wù)的命令讓Redis去消化剛剛命令隊(duì)列里的命令。? EXEC
事務(wù)機(jī)制的ACID的分析
原子性
對(duì)于Redis的原子性操作,主要分兩種情況?執(zhí)行報(bào)錯(cuò)?和?入隊(duì)報(bào)錯(cuò)
執(zhí)行報(bào)錯(cuò):?執(zhí)行報(bào)錯(cuò)的話,說(shuō)明入隊(duì)的時(shí)候是不報(bào)錯(cuò)的。執(zhí)行過(guò)程中必然會(huì)有正確的指令,Redis在執(zhí)行過(guò)程中正確的會(huì)正常執(zhí)行,報(bào)錯(cuò)的指令會(huì)返回報(bào)錯(cuò)。原子性就無(wú)法保證了
入隊(duì)報(bào)錯(cuò):?入隊(duì)報(bào)錯(cuò)的話,Redis就不會(huì)執(zhí)行這段指令,所以直接返回錯(cuò)誤,可以保證原子性!
擴(kuò)展一下MySQL,MySQL事務(wù)中報(bào)錯(cuò)的話會(huì)有回滾機(jī)制,Redis中是不存在回滾機(jī)制的。一旦使用過(guò)程中Redis發(fā)生了這種情況,我們可以使用Redis提供的?
redis-check-aof?工具檢查 AOF 日志文件,這個(gè)工具可以把未完成的事務(wù)操作從 AOF 文件中去除。這樣一來(lái),我們使用 AOF 恢復(fù)實(shí)例后,事務(wù)操作不會(huì)再被執(zhí)行,從而保證了原子性。如果AOF,RDB都不開(kāi)啟就不要談數(shù)據(jù)安全性持久化這些概念了
一致性
事務(wù)的一致性保證會(huì)受到錯(cuò)誤命令、實(shí)例故障的影響。所以,我們按照命令出錯(cuò)和實(shí)例故障的發(fā)生時(shí)機(jī),分成三種情況來(lái)看。
入隊(duì)就報(bào)錯(cuò),Redis會(huì)放棄執(zhí)行,同時(shí)也保證了數(shù)據(jù)庫(kù)的一致性。 執(zhí)行就報(bào)錯(cuò),有錯(cuò)誤的命令不會(huì)被執(zhí)行,正確的命令可以正常執(zhí)行,也不會(huì)改變數(shù)據(jù)庫(kù)的一致性。 實(shí)例故障報(bào)錯(cuò),實(shí)例故障重啟后我們要根據(jù)用戶是否開(kāi)啟了AOF和RDB進(jìn)行分情況討論。
如果我們使用了 RDB 快照,因?yàn)?RDB 快照不會(huì)在事務(wù)執(zhí)行時(shí)執(zhí)行,所以,事務(wù)命令操作的結(jié)果不會(huì)被保存到 RDB 快照中,使用 RDB 快照進(jìn)行恢復(fù)時(shí),數(shù)據(jù)庫(kù)里的數(shù)據(jù)也是一致的。
如果我們使用了 AOF 日志,而事務(wù)操作還沒(méi)有被記錄到 AOF 日志時(shí),實(shí)例就發(fā)生了故障,那么,使用 AOF 日志恢復(fù)的數(shù)據(jù)庫(kù)數(shù)據(jù)是一致的。如果只有部分操作被記錄到了 AOF 日志,我們可以使用 redis-check-aof 清除事務(wù)中已經(jīng)完成的操作,數(shù)據(jù)庫(kù)恢復(fù)后也是一致的。
Redis事務(wù)機(jī)制對(duì)一致性是有保證的
隔離性
事務(wù)的隔離性主要和并發(fā)有關(guān)。并發(fā)過(guò)程中我們還可以細(xì)分兩個(gè)執(zhí)行階段。EXEC執(zhí)行前?和?EXEC執(zhí)行后
執(zhí)行前:?我們可以通過(guò)Redis提供的watch機(jī)制來(lái)實(shí)現(xiàn)隔離性
執(zhí)行后:?無(wú)法保證
什么是watch機(jī)制?
在事務(wù)執(zhí)行前,監(jiān)控一個(gè)或多個(gè)鍵的值變化情況,當(dāng)事務(wù)調(diào)用 EXEC 命令執(zhí)行時(shí),WATCH 機(jī)制會(huì)先檢查監(jiān)控的鍵是否被其它客戶端修改了。如果修改了,就放棄事務(wù)執(zhí)行,避免事務(wù)的隔離性被破壞。然后,客戶端可以再次執(zhí)行事務(wù),此時(shí),如果沒(méi)有并發(fā)修改事務(wù)數(shù)據(jù)的操作了,事務(wù)就能正常執(zhí)行,隔離性也得到了保證。
如果在執(zhí)行前我們?沒(méi)有使用watch機(jī)制,同時(shí)發(fā)生了并發(fā)請(qǐng)求,就會(huì)對(duì)數(shù)據(jù)進(jìn)行讀寫,隔離性就沒(méi)有得到保障
如果在EXEC執(zhí)行后,雖然無(wú)法保證,但是Redis的單線程的。按照入隊(duì)的先后順序執(zhí)行,所以后一個(gè)請(qǐng)求不會(huì)排的前面一個(gè)請(qǐng)求。于是?也不會(huì)破壞事務(wù)的隔離性。
持久化
Redis 是內(nèi)存數(shù)據(jù)庫(kù),所以,數(shù)據(jù)是否持久化保存完全取決于 Redis 的持久化配置模式。
如果 Redis 沒(méi)有使用 RDB 或 AOF,那么事務(wù)的持久化屬性肯定得不到保證。 如果 Redis 使用了 RDB 模式,那么,在一個(gè)事務(wù)執(zhí)行后,而下一次的 RDB 快照還未執(zhí)行前,如果發(fā)生了實(shí)例宕機(jī),這種情況下,事務(wù)修改的數(shù)據(jù)也是不能保證持久化的。 如果 Redis 采用了 AOF 模式,因?yàn)?AOF 模式的三種配置選項(xiàng) no、everysec 和 always 都會(huì)存在數(shù)據(jù)丟失的情況,所以,事務(wù)的持久性屬性也還是得不到保證。
Redis主從同步的那些問(wèn)題
主從數(shù)據(jù)不一致
主從同步時(shí),采用的是異步同步。所以無(wú)法保證主從庫(kù)數(shù)據(jù)的實(shí)時(shí)一致性。 主從同步時(shí),網(wǎng)絡(luò)因素導(dǎo)致主從數(shù)據(jù)實(shí)時(shí)性的延遲 主從同步時(shí),從庫(kù)接收到了主庫(kù)的命令。但是從庫(kù)正在處理其他復(fù)雜度過(guò)高的命令而阻塞,從庫(kù)只有處理完當(dāng)前任務(wù)后才能處理主庫(kù)的新命令。這就造成了主從延遲
解決方案
在硬件方面,我們要盡量保證主從庫(kù)間的網(wǎng)絡(luò)連接狀況良好。例如,我們要避免把主從庫(kù)部署在不同的機(jī)房,或者是避免把網(wǎng)絡(luò)通信密集的應(yīng)用和Redis 主從庫(kù)部署在一起。 監(jiān)控主從庫(kù)間的復(fù)制差值,如果主從庫(kù)差值過(guò)大我們就可以通過(guò)設(shè)置閾值的方式。干預(yù)解決主從同步的延遲問(wèn)題
Redis 的 INFO replication 命令可以查看主庫(kù)接收寫命令的進(jìn)度信息(master_repl_offset)和從庫(kù)復(fù)制寫命令的進(jìn)度信息(slave_repl_offset),所以,我們就可以開(kāi)發(fā)一個(gè)監(jiān)控程序,先用 INFO replication 命令查到主、從庫(kù)的進(jìn)度,然后,我們用 master_repl_offset 減去 slave_repl_offset,這樣就能得到從庫(kù)和主庫(kù)間的復(fù)制進(jìn)度差值了
讀到過(guò)期數(shù)據(jù)
平時(shí)應(yīng)用中讀到過(guò)期數(shù)據(jù)是比較常見(jiàn)的,我們分析一下為什么會(huì)讀到過(guò)期數(shù)據(jù)。
假如一個(gè)key的過(guò)期時(shí)間是19:51:49,剛好有個(gè)請(qǐng)求訪問(wèn)了這個(gè)key,訪問(wèn)時(shí)間是19:51:50。
key過(guò)期了正等待被回收,但是還沒(méi)有回收這段期間就被讀取了。這主要是由Redis的過(guò)期策略引起的。
過(guò)期策略分?惰性刪除和?定期刪除
惰性刪除。當(dāng)一個(gè)數(shù)據(jù)的過(guò)期時(shí)間到了以后,并不會(huì)立即刪除數(shù)據(jù),而是等到再有請(qǐng)求來(lái)讀寫這個(gè)數(shù)據(jù)時(shí),對(duì)數(shù)據(jù)進(jìn)行檢查,如果發(fā)現(xiàn)數(shù)據(jù)已經(jīng)過(guò)期了,再刪除這個(gè)數(shù)據(jù)。
這個(gè)策略的好處是盡量減少刪除操作對(duì) CPU 資源的使用,對(duì)于用不到的數(shù)據(jù),就不再浪費(fèi)時(shí)間進(jìn)行檢查和刪除了。但是,這個(gè)策略會(huì)導(dǎo)致大量已經(jīng)過(guò)期的數(shù)據(jù)留存在內(nèi)存中,占用較多的內(nèi)存資源。所以,Redis 在使用這個(gè)策略的同時(shí),還使用了第二種策略:定期刪除策略。
定期刪除策略?是指Redis 每隔一段時(shí)間(默認(rèn) 100ms),就會(huì)隨機(jī)選出一定數(shù)量的數(shù)據(jù),檢查它們是否過(guò)期,并把其中過(guò)期的數(shù)據(jù)刪除,這樣就可以及時(shí)釋放一些內(nèi)存。
清楚了這兩個(gè)刪除策略,我們?cè)賮?lái)看看它們?yōu)槭裁磿?huì)導(dǎo)致讀取到過(guò)期數(shù)據(jù)。
首先,雖然定期刪除策略可以釋放一些內(nèi)存,但是,Redis 為了避免過(guò)多刪除操作對(duì)性能產(chǎn)生影響,每次隨機(jī)檢查數(shù)據(jù)的數(shù)量并不多。如果過(guò)期數(shù)據(jù)很多,并且一直沒(méi)有再被訪問(wèn)的話,這些數(shù)據(jù)就會(huì)留存在 Redis 實(shí)例中。業(yè)務(wù)應(yīng)用之所以會(huì)讀到過(guò)期數(shù)據(jù),這些留存數(shù)據(jù)就是一個(gè)重要因素。
其次,惰性刪除策略實(shí)現(xiàn)后,數(shù)據(jù)只有被再次訪問(wèn)時(shí),才會(huì)被實(shí)際刪除。如果客戶端從主庫(kù)上讀取留存的過(guò)期數(shù)據(jù),主庫(kù)會(huì)觸發(fā)刪除操作,此時(shí),客戶端并不會(huì)讀到過(guò)期數(shù)據(jù)。但是,從庫(kù)本身不會(huì)執(zhí)行刪除操作,如果客戶端在從庫(kù)中訪問(wèn)留存的過(guò)期數(shù)據(jù),從庫(kù)并不會(huì)觸發(fā)數(shù)據(jù)刪除。那么,從庫(kù)會(huì)給客戶端返回過(guò)期數(shù)據(jù)嗎?這就和版本有關(guān)了!
版本問(wèn)題
3.2 之前的版本,從庫(kù)在服務(wù)讀請(qǐng)求時(shí),并不會(huì)判斷數(shù)據(jù)是否過(guò)期,而是會(huì)返回過(guò)期數(shù)據(jù)。 3.2 版本后,如果讀取的數(shù)據(jù)已經(jīng)過(guò)期了,從庫(kù)雖然不會(huì)刪除,但是會(huì)返回空值,這就避免了客戶端讀到過(guò)期數(shù)據(jù)
除了版本的問(wèn)題還有設(shè)置過(guò)期時(shí)間的命令有關(guān),有些命令給數(shù)據(jù)設(shè)置的過(guò)期時(shí)間在從庫(kù)上可能會(huì)被延后,導(dǎo)致應(yīng)該過(guò)期的數(shù)據(jù)又在從庫(kù)上被讀取到了。
當(dāng)主從庫(kù)全量同步時(shí),如果主庫(kù)接收到了一條 EXPIRE 命令,那么,主庫(kù)會(huì)直接執(zhí)行這條命令。這條命令會(huì)在全量同步完成后,發(fā)給從庫(kù)執(zhí)行。而從庫(kù)在執(zhí)行時(shí),就會(huì)在當(dāng)前時(shí)間的基礎(chǔ)上加上數(shù)據(jù)的存活時(shí)間,這樣一來(lái),從庫(kù)上數(shù)據(jù)的過(guò)期時(shí)間就會(huì)比主庫(kù)上延后了。
為了避免這種情況,我給你的建議是,在業(yè)務(wù)應(yīng)用中使用 EXPIREAT/PEXPIREAT 命令,把數(shù)據(jù)的過(guò)期時(shí)間設(shè)置為具體的時(shí)間點(diǎn),避免讀到過(guò)期數(shù)據(jù)。
結(jié)尾
大概總結(jié)了
Redis在事務(wù)機(jī)制ACID的相關(guān)實(shí)現(xiàn)保證 分析了使用Redis時(shí),Redis主從同步的那些問(wèn)題
由主從同步問(wèn)題展開(kāi)了主從數(shù)據(jù)不一致的原因以及解決方案,過(guò)期數(shù)據(jù)的原因以及解決方案。
這篇文章大概算是Redis第二階段的一個(gè)收尾吧。下面將從RocketMQ或者M(jìn)ybatis進(jìn)行技術(shù)的分享!
非常歡迎大家加我個(gè)人微信有關(guān)后端方面的問(wèn)題我們?cè)谌簝?nèi)一起討論!?我們下期再見(jiàn)!
長(zhǎng)按上方掃碼二維碼,加我微信,拉你進(jìn)群

