本篇內(nèi)容介紹了“Java并發(fā)編程的數(shù)據(jù)庫(kù)與緩存數(shù)據(jù)一致性方案怎么實(shí)現(xiàn)”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
創(chuàng)新互聯(lián)擁有一支富有激情的企業(yè)網(wǎng)站制作團(tuán)隊(duì),在互聯(lián)網(wǎng)網(wǎng)站建設(shè)行業(yè)深耕十余年,專業(yè)且經(jīng)驗(yàn)豐富。十余年網(wǎng)站優(yōu)化營(yíng)銷經(jīng)驗(yàn),我們已為上千多家中小企業(yè)提供了成都做網(wǎng)站、成都網(wǎng)站制作解決方案,按需定制制作,設(shè)計(jì)滿意,售后服務(wù)無(wú)憂。所有客戶皆提供一年免費(fèi)網(wǎng)站維護(hù)!
在分布式并發(fā)系統(tǒng)中,數(shù)據(jù)庫(kù)與緩存數(shù)據(jù)一致性是一項(xiàng)富有挑戰(zhàn)性的技術(shù)難點(diǎn)。假設(shè)有完善的工業(yè)級(jí)分布式事務(wù)解決方案,那么數(shù)據(jù)庫(kù)與緩存數(shù)據(jù)一致性便迎刃而解,實(shí)際上,目前分布式事務(wù)不成熟。
在數(shù)據(jù)庫(kù)與緩存數(shù)據(jù)一致解決方式中,有各種聲音。
先操作數(shù)據(jù)庫(kù)后緩存還是先緩存后數(shù)據(jù)庫(kù)
緩存是更新還是刪除
在并發(fā)系統(tǒng)中,數(shù)據(jù)庫(kù)與緩存雙寫場(chǎng)景下,為了追求更大的并發(fā)量,操作數(shù)據(jù)庫(kù)與緩存顯而易見不會(huì)同步進(jìn)行。前者操作成功后者以異步的方式進(jìn)行。
關(guān)系型數(shù)據(jù)庫(kù)作為成熟的工業(yè)級(jí)數(shù)據(jù)存儲(chǔ)方案,有完善的事務(wù)處理機(jī)制,數(shù)據(jù)一旦落盤,不考慮硬件故障,可以負(fù)責(zé)任的說(shuō)數(shù)據(jù)不會(huì)丟失。
所謂緩存,無(wú)非是存儲(chǔ)在內(nèi)存中的數(shù)據(jù),服務(wù)一旦重啟,緩存數(shù)據(jù)全部丟失。既然稱之為緩存,那么時(shí)刻做好了緩存數(shù)據(jù)丟失的準(zhǔn)備。盡管redis有持久化機(jī)制,是否能夠保證百分之百持久化?Redis將數(shù)據(jù)異步持久化到磁盤有不可,緩存是緩存,數(shù)據(jù)庫(kù)是數(shù)據(jù)庫(kù),兩個(gè)不同的東西。把緩存當(dāng)數(shù)據(jù)庫(kù)使用是一件極其危險(xiǎn)的事情。
從數(shù)據(jù)安全的角度來(lái)講,先操作數(shù)據(jù)庫(kù),然后以異步的方式操作緩存,響應(yīng)用戶請(qǐng)求。
緩存是更新還是刪除,對(duì)應(yīng)懶漢式和飽漢式,從處理線程安全實(shí)踐來(lái)講,刪除緩存操作相對(duì)難度低一些。如果在刪除緩存的前提下滿足了查詢性能,那么優(yōu)先選擇刪除緩存。
更新緩存盡管能夠提高查詢效率,然后帶來(lái)的線程并發(fā)臟數(shù)據(jù)處理起來(lái)較麻煩,序言引入MQ等其它消息中間件,因此非必要不推薦。
理解線程并發(fā)所帶來(lái)問(wèn)題的關(guān)鍵是先理解系統(tǒng)中斷,操作系統(tǒng)在任務(wù)調(diào)度時(shí),中斷隨時(shí)都在發(fā)生,這是線程數(shù)據(jù)不一致產(chǎn)生的根源。以4和8線程CPU為例,同一時(shí)刻最多處理8個(gè)線程,然而操作系統(tǒng)管理的線程遠(yuǎn)遠(yuǎn)超過(guò)8個(gè),因此線程們以一種看似并行的方式進(jìn)行。
在非并發(fā)環(huán)境中,使用如下方式查詢數(shù)據(jù)并無(wú)不妥:先查詢緩存,如果緩存數(shù)據(jù)不存在,查詢數(shù)據(jù)庫(kù),更新緩存,返回結(jié)果。
public BuOrder getOrder(Long orderId) { String key = ORDER_KEY_PREFIX + orderId; BuOrder buOrder = RedisUtils.getObject(key, BuOrder.class); if (buOrder != null) { return buOrder; } BuOrder order = getById(orderId); RedisUtils.setObject(key, order, 5, TimeUnit.MINUTES); return order; }
如果在高并發(fā)環(huán)境中有一個(gè)嚴(yán)重缺陷:當(dāng)緩存失效時(shí),大量查詢請(qǐng)求涌入,瞬間全部打到DB上,輕則數(shù)據(jù)庫(kù)連接資源耗盡,用戶端響應(yīng)500錯(cuò)誤,重則數(shù)據(jù)庫(kù)壓力過(guò)大服務(wù)宕機(jī)。
因此在并發(fā)環(huán)境中,需要對(duì)上述代碼進(jìn)行修改,使用分布式鎖。大量請(qǐng)求涌入時(shí),獲得鎖的線程有機(jī)會(huì)訪問(wèn)數(shù)據(jù)庫(kù)查詢數(shù)據(jù),其余線程阻塞。當(dāng)查詢完數(shù)據(jù)并更新緩存,然后釋放鎖。等待的線程重新檢查緩存,發(fā)現(xiàn)能夠獲取到數(shù)據(jù),直接將緩存數(shù)據(jù)響應(yīng)。
這里提到分布式鎖,那么使用表鎖還是行鎖呢?使用分布式行鎖提高并發(fā)量;使用二次檢查機(jī)制,確保等待獲得鎖的線程能夠快速返回結(jié)果
@Override public BuOrder getOrder(Long orderId) { /* 如果緩存不存在,則添加分布式鎖更新緩存 */ String key = ORDER_KEY_PREFIX + orderId; BuOrder order = RedisUtils.getObject(key, BuOrder.class); if (order != null) { return order; } String orderLock = ORDER_LOCK + orderId; RLock lock = redissonClient.getLock(orderLock); if (lock.tryLock()) { order = RedisUtils.getObject(key, BuOrder.class); if (order != null) { LockOptional.ofNullable(lock).ifLocked(RLock::unlock); return order; } BuOrder buOrder = getById(orderId); RedisUtils.setObject(key, buOrder, 5, TimeUnit.MINUTES); LockOptional.ofNullable(lock).ifLocked(RLock::unlock); } return RedisUtils.getObject(key, BuOrder.class); }
非并發(fā)環(huán)境中,如下代碼盡管可能會(huì)產(chǎn)生數(shù)據(jù)不一致問(wèn)題(數(shù)據(jù)被覆蓋)。盡管使用數(shù)據(jù)庫(kù)層面樂觀鎖能夠解決數(shù)據(jù)被覆蓋問(wèn)題,然而無(wú)效更新流量依舊會(huì)流向數(shù)據(jù)庫(kù)。
public Boolean editOrder(BuOrder order) { /* 更新數(shù)據(jù)庫(kù) */ updateById(order); /* 刪除緩存 */ RedisUtils.deleteObject(OrderServiceImpl.ORDER_KEY_PREFIX + order.getOrderId()); return true; }
上面分析中使用數(shù)據(jù)庫(kù)樂觀鎖能夠解決并發(fā)更新中數(shù)據(jù)被覆蓋的問(wèn)題,然而當(dāng)同一行記錄被修改后,版本號(hào)發(fā)生改變,后續(xù)并發(fā)流向數(shù)據(jù)庫(kù)的請(qǐng)求為無(wú)效流量。減小數(shù)據(jù)庫(kù)壓力的首要策略是將無(wú)效流量攔截在數(shù)據(jù)庫(kù)之前。
使用分布式鎖能夠保證并發(fā)流量有序訪問(wèn)數(shù)據(jù)庫(kù),考慮到數(shù)據(jù)庫(kù)層面已經(jīng)使用了樂觀鎖,第二個(gè)及以后獲得鎖的線程操作數(shù)據(jù)庫(kù)為無(wú)效流量。
線程在獲得鎖時(shí)采用超時(shí)退出的策略,等待獲得鎖的線程超時(shí)快速退出,快速響應(yīng)用戶請(qǐng)求,重試更新數(shù)據(jù)操作。
public Boolean editOrder(BuOrder order) { String orderLock = ORDER_LOCK + order.getOrderId(); RLock lock = redissonClient.getLock(orderLock); try { /* 超時(shí)未獲取到鎖,快速失敗,用戶端重試 */ if (lock.tryLock(1, TimeUnit.SECONDS)) { /* 更新數(shù)據(jù)庫(kù) */ updateById(order); /* 刪除緩存 */ RedisUtils.deleteObject(OrderServiceImpl.ORDER_KEY_PREFIX + order.getOrderId()); /* 釋放鎖 */ LockOptional.ofNullable(lock).ifLocked(RLock::unlock); return true; } } catch (InterruptedException e) { e.printStackTrace(); } return false; }
上述代碼使用了封裝鎖的工具類。
<dependency> <groupId>xin.altitude.cms</groupId> <artifactId>ucode-cms-common</artifactId> <version>1.4.3.2</version> </dependency>
LockOptional
根據(jù)鎖的狀態(tài)執(zhí)行后續(xù)操作。
接下來(lái)討論先更新數(shù)據(jù)庫(kù),后刪除緩存是否存在并發(fā)問(wèn)題。
(1)緩存剛好失效
(2)請(qǐng)求A查詢數(shù)據(jù)庫(kù),得一個(gè)舊值
(3)請(qǐng)求B將新值寫入數(shù)據(jù)庫(kù)
(4)請(qǐng)求B刪除緩存
(5)請(qǐng)求A將查到的舊值寫入緩存
上述并發(fā)問(wèn)題出現(xiàn)的關(guān)鍵是第5步比第3、4步后發(fā)生,由操作系統(tǒng)中斷不確定因素可知,此種情況卻有發(fā)生的可能。
從實(shí)際情況來(lái)看,將數(shù)據(jù)寫入Redis遠(yuǎn)比將數(shù)據(jù)寫入數(shù)據(jù)庫(kù)耗時(shí)要短,盡管發(fā)生的概率較低,但仍會(huì)發(fā)生。
(1)增加緩存過(guò)期時(shí)間
增加緩存過(guò)期時(shí)間允許一定時(shí)間范圍內(nèi)臟數(shù)據(jù)存在,直到下一次并發(fā)更新出現(xiàn),可能會(huì)出現(xiàn)臟數(shù)據(jù)。臟數(shù)據(jù)會(huì)周期性存在。
(2)更新和查詢共用一把行鎖
更新和查詢共用一把行分布式鎖,上述問(wèn)題不復(fù)存在。當(dāng)讀請(qǐng)求獲取到鎖時(shí),寫請(qǐng)求處于阻塞狀態(tài)(超時(shí)會(huì)快速失敗返回),能夠保證步驟5在步驟3之前進(jìn)行。
(3)延遲刪除緩存
使用RabbitMQ延遲刪除緩存,去除步驟5的影響。使用異步的方式進(jìn)行,幾乎不影響性能。
數(shù)據(jù)庫(kù)有事務(wù)機(jī)制保證操作成功與否;Redis單條指令具有原子性,然后組合起來(lái)卻不具備原子特征,具體來(lái)說(shuō)是數(shù)據(jù)庫(kù)操作成功,然后應(yīng)用異常掛掉,導(dǎo)致Redis緩存未刪除。Redis服務(wù)網(wǎng)絡(luò)連接超時(shí)出現(xiàn)此問(wèn)題。
如果設(shè)置有緩存過(guò)期時(shí)間,那么在緩存尚未過(guò)期前,臟數(shù)據(jù)一直存在。如果未設(shè)置過(guò)期時(shí)間,那么直到下一次修改數(shù)據(jù)前,臟數(shù)據(jù)一直存在。(數(shù)據(jù)庫(kù)數(shù)據(jù)已經(jīng)發(fā)生改變,緩存尚未更新)
在操作數(shù)據(jù)庫(kù)前,向RabbitMQ寫入一條延遲刪除緩存的消息,然后執(zhí)行數(shù)據(jù)庫(kù)操作,執(zhí)行緩存刪除操作。不管代碼層面緩存是否刪除成功,MQ刪除緩存作為保底操作。
“Java并發(fā)編程的數(shù)據(jù)庫(kù)與緩存數(shù)據(jù)一致性方案怎么實(shí)現(xiàn)”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
網(wǎng)頁(yè)名稱:Java并發(fā)編程的數(shù)據(jù)庫(kù)與緩存數(shù)據(jù)一致性方案怎么實(shí)現(xiàn)
本文來(lái)源:http://aaarwkj.com/article2/gjoooc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供外貿(mào)網(wǎng)站建設(shè)、搜索引擎優(yōu)化、品牌網(wǎng)站建設(shè)、小程序開發(fā)、網(wǎng)站策劃、響應(yīng)式網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)