2020重新出發(fā),NOSQL,Redis和數(shù)據(jù)庫結(jié)合

Redis和數(shù)據(jù)庫的結(jié)合

使用 Redis 可以優(yōu)化性能,但是存在 Redis 的數(shù)據(jù)和數(shù)據(jù)庫同步的問題,這是我們需要關(guān)注的問題。假設(shè)兩個(gè)業(yè)務(wù)邏輯都是在操作數(shù)據(jù)庫的同一條記錄,而 Redis 和數(shù)據(jù)庫不一致,如圖 1 的場景。

Redis和數(shù)據(jù)庫不一致

圖 1 Redis 和數(shù)據(jù)庫不一致

在圖 1 中,T1 時(shí)刻以鍵 key1 保存數(shù)據(jù)到 Redis,T2 時(shí)刻刷新進(jìn)入數(shù)據(jù)庫,但是 T3 時(shí)刻發(fā)生了其他業(yè)務(wù)需要改變數(shù)據(jù)庫同一條記錄的數(shù)據(jù),但是采用了 key2 保存到 Redis 中,然后又寫入了更新數(shù)據(jù)到數(shù)據(jù)庫中,此時(shí)在 Redis 中 key1 的數(shù)據(jù)是臟數(shù)據(jù),和數(shù)據(jù)庫的數(shù)據(jù)并不一致。

而圖 1 只是數(shù)據(jù)不一致的一個(gè)可能的原因,實(shí)際情況可能存在多種,比如數(shù)據(jù)庫的事務(wù)是完善的,而對于 Redis 的事務(wù),通過學(xué)習(xí)應(yīng)該清楚它并不是那么嚴(yán)格的,如果發(fā)生異?;貪L的事件,那么 Redis 的數(shù)據(jù)可能就和數(shù)據(jù)庫不太一致了,所以要保存數(shù)據(jù)的一致性是相當(dāng)困難的。

但是不用沮喪,因?yàn)榛ヂ?lián)網(wǎng)系統(tǒng)顯示給用戶的信息往往并不需要完全是“最新的”,有些數(shù)據(jù)允許延遲。舉個(gè)例子,一個(gè)購物網(wǎng)站會(huì)有一個(gè)用戶購買排名榜,如果做成實(shí)時(shí)的,每一筆投資都會(huì)引發(fā)重新計(jì)算,那么網(wǎng)站的性能就存在極大的壓力,但是這個(gè)排名榜卻沒有太大的意義。

同樣,商品的總數(shù)有時(shí)候只需要去實(shí)現(xiàn)一個(gè)非實(shí)時(shí)的數(shù)據(jù)。這些在互聯(lián)網(wǎng)系統(tǒng)中也是十分常見的,一般而言,可以在某段時(shí)間進(jìn)行刷新(比如以一個(gè)小時(shí)為刷新間隔),排出這段時(shí)間的最新排名,這就是延遲性的更新。但是對于一些內(nèi)容則需要最新的,尤其是當(dāng)前用戶的交易記錄、購買時(shí)商品的數(shù)量,這些需要實(shí)時(shí)處理,以避免數(shù)據(jù)的不一致,因?yàn)檫@些都是對于企業(yè)和用戶重要的記錄。

我們會(huì)考慮讀/寫以數(shù)據(jù)庫的最新記錄為主,并且同步寫入 Redis,這樣數(shù)據(jù)就能保持一致性了,而對于一些常用的只需要顯示的,則以查詢 Redis 為主。這樣網(wǎng)站的性能就很高了,畢竟寫入的次數(shù)遠(yuǎn)比查詢的次數(shù)要少得多得多。下面先對數(shù)據(jù)庫的讀/寫操作進(jìn)行基本闡述。

Redis 和數(shù)據(jù)庫讀操作

數(shù)據(jù)緩存往往會(huì)在 Redis 上設(shè)置超時(shí)時(shí)間,當(dāng)設(shè)置 Redis 的數(shù)據(jù)超時(shí)后,Redis 就沒法讀出數(shù)據(jù)了,這個(gè)時(shí)候就會(huì)觸發(fā)程序讀取數(shù)據(jù)庫,然后將讀取的數(shù)據(jù)庫數(shù)據(jù)寫入 Redis(此時(shí)會(huì)給 Redis 重設(shè)超時(shí)時(shí)間),這樣程序在讀取的過程中就能按一定的時(shí)間間隔刷新數(shù)據(jù)了,讀取數(shù)據(jù)的流程如圖 2 所示。

讀取數(shù)據(jù)的流程

圖 2 讀取數(shù)據(jù)的流程

下面寫出這個(gè)流程的偽代碼:

<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="java" cid="n15" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> public DataObject readMethod(args) {
// 嘗試從Redis中讀取數(shù)據(jù)
DataObject data = getRedis(key);
if(data != null) {
// 讀取數(shù)據(jù)返回為空,失敗
// 從數(shù)據(jù)庫中讀取數(shù)據(jù)
data = getFromDataBase();
// 重新寫入Redis,以便以后讀出
writeRedis(key,data);
// 設(shè)置Redis的超時(shí)時(shí)間為5分鐘
setRedisExpire(key,5);
}
return data;
}</pre>

上面的偽代碼完成了圖 2 所描述的過程。這樣每當(dāng)讀取 Redis 數(shù)據(jù)超過 5 分鐘,Redis 就不能讀到超時(shí)數(shù)據(jù)了,只能重新從 Redis 中讀取,保證了一定的實(shí)時(shí)性,也避免了多次訪問數(shù)據(jù)庫造成的系統(tǒng)性能低下的情況。

Redis 和數(shù)據(jù)庫寫操作

寫操作要考慮數(shù)據(jù)一致的問題,尤其是那些重要的業(yè)務(wù)數(shù)據(jù),所以首先應(yīng)該考慮從數(shù)據(jù)庫中讀取最新的數(shù)據(jù),然后對數(shù)據(jù)進(jìn)行操作,最后把數(shù)據(jù)寫入 Redis 緩存中,如圖 3 所示。

寫入數(shù)據(jù)的流程

圖 3 寫入數(shù)據(jù)的流程

寫入業(yè)務(wù)數(shù)據(jù),先從數(shù)據(jù)庫中讀取最新數(shù)據(jù),然后進(jìn)行業(yè)務(wù)操作,更新業(yè)務(wù)數(shù)據(jù)到數(shù)據(jù)庫后,再將數(shù)據(jù)刷新到 Redis 緩存中,這樣就完成了一次寫操作。這樣的操作就能避免將臟數(shù)據(jù)寫入數(shù)據(jù)庫中,這類問題在操作時(shí)要注意。

下面寫出這個(gè)流程的偽代碼:

<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="java" cid="n23" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> public DataObject writeMethod(args) {
//從數(shù)據(jù)庫里讀取最新數(shù)據(jù)
DataObject dataObject = getFromDataBase(args);
//執(zhí)行業(yè)務(wù)邏輯
ExecLogic(dataObject);
//更新數(shù)據(jù)庫數(shù)據(jù)
updateDataBase(dataObject);
//刷新Redis緩存
updateRedisData(dataObject);
}</pre>

上面的偽代碼完成了圖 3 所描述的過程。首先,從數(shù)據(jù)庫中讀取最新的數(shù)據(jù),以規(guī)避緩存中的臟數(shù)據(jù)問題,執(zhí)行了邏輯,修改了部分業(yè)務(wù)數(shù)據(jù)。然后,把這些數(shù)據(jù)保存到數(shù)據(jù)庫里,最后,刷新這些數(shù)據(jù)到 Redis 中。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容