理論 | 秒殺系統(tǒng)設(shè)計(jì)注意點(diǎn)
在秒殺的場景中,對于系統(tǒng)的要求其實(shí)就三個(gè)字:快、準(zhǔn)、穩(wěn)。
本文主要內(nèi)容:
五個(gè)架構(gòu)原則
數(shù)據(jù)要盡量少
首先是指用戶請求的數(shù)據(jù)能少就少。請求的數(shù)據(jù)包括上傳給系統(tǒng)的數(shù)據(jù)和系統(tǒng)返回給用戶的數(shù)據(jù)(通常就是網(wǎng)頁)。
請求數(shù)要盡量少
用戶請求的頁面返回后,瀏覽器渲染這個(gè)頁面還要包含其他的額外請求,比如說,這個(gè)頁面依賴的 CSS/JavaScript、圖片,以及 Ajax 請求等等都定義為“額外請求”,這些額外請求應(yīng)該盡量少。
路徑要盡量短
就是用戶發(fā)出請求到返回?cái)?shù)據(jù)這個(gè)過程中,需求經(jīng)過的中間的節(jié)點(diǎn)數(shù)。
依賴要盡量少
指的是要完成一次用戶請求必須依賴的系統(tǒng)或者服務(wù),這里的依賴指的是強(qiáng)依賴。
高可用
系統(tǒng)中的單點(diǎn)可以說是系統(tǒng)架構(gòu)上的一個(gè)大忌,因?yàn)閱吸c(diǎn)意味著沒有備份,風(fēng)險(xiǎn)不可控,我們設(shè)計(jì)分布式系統(tǒng)最重要的原則就是“消除單點(diǎn)”,另外一種叫法“高可用”。
架構(gòu)是一種平衡的藝術(shù),而最好的架構(gòu)一旦脫離了它所適應(yīng)的場景,一切都將是空談。我們需要記住的是,這里所說的幾點(diǎn)都只是一個(gè)個(gè)方向而已,我們應(yīng)該盡量往這些方向上去努力,但也要考慮平衡其他因素。
如何做動(dòng)靜分離
何為動(dòng)靜數(shù)據(jù)
那到底什么才是動(dòng)靜分離呢?所謂“動(dòng)靜分離”,其實(shí)就是把用戶請求的數(shù)據(jù)(如 HTML 頁面)劃分為“動(dòng)態(tài)數(shù)據(jù)”和“靜態(tài)數(shù)據(jù)”。簡單來說,“動(dòng)態(tài)數(shù)據(jù)”和“靜態(tài)數(shù)據(jù)”的主要區(qū)別就是看頁面中輸出的數(shù)據(jù)是否和 URL、瀏覽者、時(shí)間、地域相關(guān),以及是否含有 Cookie 等私密數(shù)據(jù)。
很多媒體類的網(wǎng)站,某一篇文章的內(nèi)容不管是你訪問還是我訪問,它都是一樣的。所以它就是一個(gè)典型的靜態(tài)數(shù)據(jù),但是它是個(gè)動(dòng)態(tài)頁面。
我們?nèi)绻F(xiàn)在訪問淘寶的首頁,每個(gè)人看到的頁面可能都是不一樣的,淘寶首頁中包含了很多根據(jù)訪問者特征推薦的信息,而這些個(gè)性化的數(shù)據(jù)就可以理解為動(dòng)態(tài)數(shù)據(jù)了。
怎樣對靜態(tài)數(shù)據(jù)做緩存呢?
第一,你應(yīng)該把靜態(tài)數(shù)據(jù)緩存到離用戶最近的地方。靜態(tài)數(shù)據(jù)就是那些相對不會(huì)變化的數(shù)據(jù),因此我們可以把它們緩存起來。緩存到哪里呢?常見的就三種,用戶瀏覽器里、CDN 上或者在服務(wù)端的 Cache 中。你應(yīng)該根據(jù)情況,把它們盡量緩存到離用戶最近的地方。
第二,靜態(tài)化改造就是要直接緩存 HTTP 連接。相較于普通的數(shù)據(jù)緩存而言,你肯定還聽過系統(tǒng)的靜態(tài)化改造。靜態(tài)化改造是直接緩存 HTTP 連接而不是僅僅緩存數(shù)據(jù),如下圖所示,Web 代理服務(wù)器根據(jù)請求 URL,直接取出對應(yīng)的 HTTP 響應(yīng)頭和響應(yīng)體然后直接返回,這個(gè)響應(yīng)過程簡單得連 HTTP 協(xié)議都不用重新組裝,甚至連 HTTP 請求頭也不需要解析。
第三,讓誰來緩存靜態(tài)數(shù)據(jù)也很重要。不同語言寫的 Cache 軟件處理緩存數(shù)據(jù)的效率也各不相同。以 Java 為例,因?yàn)?Java 系統(tǒng)本身也有其弱點(diǎn)(比如不擅長處理大量連接請求,每個(gè)連接消耗的內(nèi)存較多,Servlet 容器解析 HTTP 協(xié)議較慢),所以你可以不在 Java 層做緩存,而是直接在 Web 服務(wù)器層上做,這樣你就可以屏蔽 Java 語言層面的一些弱點(diǎn);而相比起來,Web 服務(wù)器(如 Nginx、Apache、Varnish)也更擅長處理大并發(fā)的靜態(tài)文件請求。
如何做動(dòng)靜分離的改造
- URL 唯一化
- 分離瀏覽者相關(guān)的因素
- 分離時(shí)間因素
- 異步化地域因素
- 去掉 Cookie
動(dòng)靜分離的幾種架構(gòu)方案
根據(jù)架構(gòu)上的復(fù)雜度,有 3 種方案可選:
實(shí)體機(jī)單機(jī)部署:
統(tǒng)一 Cache 層:
加上CDN層:
CDN 化部署方案還有以下幾個(gè)特點(diǎn):
- 把整個(gè)頁面緩存在用戶瀏覽器中;
- 如果強(qiáng)制刷新整個(gè)頁面,也會(huì)請求 CDN;
- 實(shí)際有效請求,只是用戶對“刷新?lián)寣?rdquo;按鈕的點(diǎn)擊。
秒殺系統(tǒng)熱點(diǎn)數(shù)據(jù)如何處理?
什么是“熱點(diǎn)”
熱點(diǎn)分為熱點(diǎn)操作和熱點(diǎn)數(shù)據(jù)。
所謂“熱點(diǎn)操作”,例如大量的刷新頁面、大量的添加購物車、雙十一零點(diǎn)大量的下單等都屬于此類操作。對系統(tǒng)來說,這些操作可以抽象為“讀請求”和“寫請求”,這兩種熱點(diǎn)請求的處理方式大相徑庭,讀請求的優(yōu)化空間要大一些,而寫請求的瓶頸一般都在存儲層,優(yōu)化的思路就是根據(jù) CAP 理論做平衡,這個(gè)內(nèi)容我在“減庫存”一文再詳細(xì)介紹。
而“熱點(diǎn)數(shù)據(jù)”比較好理解,那就是用戶的熱點(diǎn)請求對應(yīng)的數(shù)據(jù)。而熱點(diǎn)數(shù)據(jù)又分為“靜態(tài)熱點(diǎn)數(shù)據(jù)”和“動(dòng)態(tài)熱點(diǎn)數(shù)據(jù)”。
所謂“靜態(tài)熱點(diǎn)數(shù)據(jù)”,就是能夠提前預(yù)測的熱點(diǎn)數(shù)據(jù)。例如,我們可以通過賣家報(bào)名的方式提前篩選出來,通過報(bào)名系統(tǒng)對這些熱點(diǎn)商品進(jìn)行打標(biāo)。另外,我們還可以通過大數(shù)據(jù)分析來提前發(fā)現(xiàn)熱點(diǎn)商品,比如我們分析歷史成交記錄、用戶的購物車記錄,來發(fā)現(xiàn)哪些商品可能更熱門、更好賣,這些都是可以提前分析出來的熱點(diǎn)。
所謂“動(dòng)態(tài)熱點(diǎn)數(shù)據(jù)”,就是不能被提前預(yù)測到的,系統(tǒng)在運(yùn)行過程中臨時(shí)產(chǎn)生的熱點(diǎn)。例如,賣家在抖音上做了廣告,然后商品一下就火了,導(dǎo)致它在短時(shí)間內(nèi)被大量購買。
由于熱點(diǎn)操作是用戶的行為,我們不好改變,但能做一些限制和保護(hù),所以本文我主要針對熱點(diǎn)數(shù)據(jù)來介紹如何進(jìn)行優(yōu)化。
發(fā)現(xiàn)熱點(diǎn)數(shù)據(jù)
- 發(fā)現(xiàn)靜態(tài)熱點(diǎn)數(shù)據(jù)
- 發(fā)現(xiàn)動(dòng)態(tài)熱點(diǎn)數(shù)據(jù)
處理熱點(diǎn)數(shù)據(jù)
優(yōu)化
優(yōu)化熱點(diǎn)數(shù)據(jù)最有效的辦法就是緩存熱點(diǎn)數(shù)據(jù),如果熱點(diǎn)數(shù)據(jù)做了動(dòng)靜分離,那么可以長期緩存靜態(tài)數(shù)據(jù)。但是,緩存熱點(diǎn)數(shù)據(jù)更多的是“臨時(shí)”緩存,即不管是靜態(tài)數(shù)據(jù)還是動(dòng)態(tài)數(shù)據(jù),都用一個(gè)隊(duì)列短暫地緩存數(shù)秒鐘,由于隊(duì)列長度有限,可以采用 LRU 淘汰算法替換。
限制
限制更多的是一種保護(hù)機(jī)制,限制的辦法也有很多,例如對被訪問商品的 ID 做一致性 Hash,然后根據(jù) Hash 做分桶,每個(gè)分桶設(shè)置一個(gè)處理隊(duì)列,這樣可以把熱點(diǎn)商品限制在一個(gè)請求隊(duì)列里,防止因某些熱點(diǎn)商品占用太多的服務(wù)器資源,而使其他請求始終得不到服務(wù)器的處理資源。
隔離
秒殺系統(tǒng)設(shè)計(jì)的第一個(gè)原則就是將這種熱點(diǎn)數(shù)據(jù)隔離出來,不要讓 1% 的請求影響到另外的 99%,隔離出來后也更方便對這 1% 的請求做針對性的優(yōu)化 。其中隔離又可以分為:業(yè)務(wù)隔離、系統(tǒng)隔離、數(shù)據(jù)隔離。
流量削峰怎么做
就像城市里的道路,因?yàn)榇嬖谠绺叻搴屯砀叻宓膯栴},所以有了錯(cuò)峰限行的解決方案。
削峰的存在,一是可以讓服務(wù)端處理變得更加平穩(wěn),二是可以節(jié)省服務(wù)器的資源成本。
針對秒殺這一場景,削峰從本質(zhì)上來說就是更多地延緩用戶請求的發(fā)出,以便減少和過濾掉一些無效請求,它遵從“請求數(shù)要盡量少”的原則。
流量削峰思路
排隊(duì)
要對流量進(jìn)行削峰,最容易想到的解決方案就是用消息隊(duì)列來緩沖瞬時(shí)流量,把同步的直接調(diào)用轉(zhuǎn)換成異步的間接推送,中間通過一個(gè)隊(duì)列在一端承接瞬時(shí)的流量洪峰,在另一端平滑地將消息推送出去。
除了消息隊(duì)列,類似的排隊(duì)方式還有很多,例如:
- 利用線程池加鎖等待也是一種常用的排隊(duì)方式;
- 先進(jìn)先出、先進(jìn)后出等常用的內(nèi)存排隊(duì)算法的實(shí)現(xiàn)方式;
- 把請求序列化到文件中,然后再順序地讀文件(例如基于 MySQL binlog 的同步機(jī)制)來恢復(fù)請求等方式。
可以看到,這些方式都有一個(gè)共同特征,就是把“一步的操作”變成“兩步的操作”,其中增加的一步操作用來起到緩沖的作用。
性能優(yōu)化
- 減少編碼
- 減少序列化
- Java 極致優(yōu)化
- 并發(fā)讀優(yōu)化
“減庫存”核心邏輯
這是非常重要的,其他所有步驟都是做一些輔助性的。庫存 100 件就賣 100 件,在數(shù)據(jù)庫里減到 0 就好了啊,這有什么麻煩的?是的,理論上是這樣,但是具體到業(yè)務(wù)場景中,“減庫存”就不是這么簡單了。
減庫存有哪幾種方式
在商品頁面點(diǎn)了“立即購買”按鈕,核對信息之后點(diǎn)擊“提交訂單”,這一步稱為下單操作。下單之后,你只有真正完成付款操作才能算真正購買,也就是俗話說的“落袋為安”。
減庫存操作一般有如下幾個(gè)方式:
下單減庫存
即當(dāng)買家下單后,在商品的總庫存中減去買家購買數(shù)量。下單減庫存是最簡單的減庫存方式,也是控制最精確的一種,下單時(shí)直接通過數(shù)據(jù)庫的事務(wù)機(jī)制控制商品庫存,這樣一定不會(huì)出現(xiàn)超賣的情況。但是你要知道,有些人下完單可能并不會(huì)付款。
付款減庫存
即買家下單后,并不立即減庫存,而是等到有用戶付款后才真正減庫存,否則庫存一直保留給其他買家。但因?yàn)楦犊顣r(shí)才減庫存,如果并發(fā)比較高,有可能出現(xiàn)買家下單后付不了款的情況,因?yàn)榭赡苌唐芬呀?jīng)被其他人買走了。
預(yù)扣庫存
這種方式相對復(fù)雜一些,買家下單后,庫存為其保留一定的時(shí)間(如 10 分鐘),超過這個(gè)時(shí)間,庫存將會(huì)自動(dòng)釋放,釋放后其他買家就可以繼續(xù)購買。在買家付款前,系統(tǒng)會(huì)校驗(yàn)該訂單的庫存是否還有保留:如果沒有保留,則再次嘗試預(yù)扣;如果庫存不足(也就是預(yù)扣失敗)則不允許繼續(xù)付款;如果預(yù)扣成功,則完成付款并實(shí)際地減去庫存。
高可用建設(shè)應(yīng)該從哪里著手
說到系統(tǒng)的高可用建設(shè),它其實(shí)是一個(gè)系統(tǒng)工程,需要考慮到系統(tǒng)建設(shè)的各個(gè)階段,也就是說它其實(shí)貫穿了系統(tǒng)建設(shè)的整個(gè)生命周期,如下圖所示:
架構(gòu)階段
架構(gòu)階段主要考慮系統(tǒng)的可擴(kuò)展性和容錯(cuò)性,要避免系統(tǒng)出現(xiàn)單點(diǎn)問題。例如多機(jī)房單元化部署,即使某個(gè)城市的某個(gè)機(jī)房出現(xiàn)整體故障,仍然不會(huì)影響整體網(wǎng)站的運(yùn)轉(zhuǎn)。
編碼階段
編碼最重要的是保證代碼的健壯性,例如涉及遠(yuǎn)程調(diào)用問題時(shí),要設(shè)置合理的超時(shí)退出機(jī)制,防止被其他系統(tǒng)拖垮,也要對調(diào)用的返回結(jié)果集有預(yù)期,防止返回的結(jié)果超出程序處理范圍,最常見的做法就是對錯(cuò)誤異常進(jìn)行捕獲,對無法預(yù)料的錯(cuò)誤要有默認(rèn)處理結(jié)果。
測試階段
測試主要是保證測試用例的覆蓋度,保證最壞情況發(fā)生時(shí),我們也有相應(yīng)的處理流程。
發(fā)布階段
發(fā)布時(shí)也有一些地方需要注意,因?yàn)榘l(fā)布時(shí)最容易出現(xiàn)錯(cuò)誤,因此要有緊急的回滾機(jī)制。
運(yùn)行階段
運(yùn)行時(shí)是系統(tǒng)的常態(tài),系統(tǒng)大部分時(shí)間都會(huì)處于運(yùn)行態(tài),運(yùn)行態(tài)最重要的是對系統(tǒng)的監(jiān)控要準(zhǔn)確及時(shí),發(fā)現(xiàn)問題能夠準(zhǔn)確報(bào)警并且報(bào)警數(shù)據(jù)要準(zhǔn)確詳細(xì),以便于排查問題。
故障發(fā)生
故障發(fā)生時(shí)首先最重要的就是及時(shí)止損,例如由于程序問題導(dǎo)致商品價(jià)格錯(cuò)誤,那就要及時(shí)下架商品或者關(guān)閉購買鏈接,防止造成重大資產(chǎn)損失。然后就是要能夠及時(shí)恢復(fù)服務(wù),并定位原因解決問題。
在遇到大流量時(shí),我們應(yīng)該怎么最大化的保障我們的系統(tǒng)正常運(yùn)行呢?
降級
所謂“降級”,就是當(dāng)系統(tǒng)的容量達(dá)到一定程度時(shí),限制或者關(guān)閉系統(tǒng)的某些非核心功能,從而把有限的資源保留給更核心的業(yè)務(wù)。它是一個(gè)有目的、有計(jì)劃的執(zhí)行過程,所以對降級我們一般需要有一套預(yù)案來配合執(zhí)行。如果我們把它系統(tǒng)化,就可以通過預(yù)案系統(tǒng)和開關(guān)系統(tǒng)來實(shí)現(xiàn)降級。
限流
限流就是當(dāng)系統(tǒng)容量達(dá)到瓶頸時(shí),我們需要通過限制一部分流量來保護(hù)系統(tǒng),并做到既可以人工執(zhí)行開關(guān),也支持自動(dòng)化保護(hù)的措施。
客戶端限流和服務(wù)端限流的優(yōu)缺點(diǎn):
客戶端限流,好處可以限制請求的發(fā)出,通過減少發(fā)出無用請求從而減少對系統(tǒng)的消耗。缺點(diǎn)就是當(dāng)客戶端比較分散時(shí),沒法設(shè)置合理的限流閾值:如果閾值設(shè)的太小,會(huì)導(dǎo)致服務(wù)端沒有達(dá)到瓶頸時(shí)客戶端已經(jīng)被限制;而如果設(shè)的太大,則起不到限制的作用。
服務(wù)端限流,好處是可以根據(jù)服務(wù)端的性能設(shè)置合理的閾值,而缺點(diǎn)就是被限制的請求都是無效的請求,處理這些無效的請求本身也會(huì)消耗服務(wù)器資源。
常見限流算法
計(jì)數(shù)器(固定窗口)算法
計(jì)數(shù)器算法是使用計(jì)數(shù)器在周期內(nèi)累加訪問次數(shù),當(dāng)達(dá)到設(shè)定的限流值時(shí),觸發(fā)限流策略。下一個(gè)周期開始時(shí),進(jìn)行清零,重新計(jì)數(shù)。
此算法在單機(jī)還是分布式環(huán)境下實(shí)現(xiàn)都非常簡單,使用redis的incr原子自增性和線程安全即可輕松實(shí)現(xiàn)。
滑動(dòng)窗口算法
滑動(dòng)窗口算法是將時(shí)間周期分為N個(gè)小周期,分別記錄每個(gè)小周期內(nèi)訪問次數(shù),并且根據(jù)時(shí)間滑動(dòng)刪除過期的小周期。此算法可以很好的解決固定窗口算法的臨界問題。
漏桶算法
漏桶算法是訪問請求到達(dá)時(shí)直接放入漏桶,如當(dāng)前容量已達(dá)到上限(限流值),則進(jìn)行丟棄(觸發(fā)限流策略)。漏桶以固定的速率進(jìn)行釋放訪問請求(即請求通過),直到漏桶為空。
令牌桶算法
令牌桶算法是程序以r(r=時(shí)間周期/限流值)的速度向令牌桶中增加令牌,直到令牌桶滿,請求到達(dá)時(shí)向令牌桶請求令牌,如獲取到令牌則通過請求,否則觸發(fā)限流策略
拒絕服務(wù)
如果限流還不能解決問題,最后一招就是直接拒絕服務(wù)了。當(dāng)系統(tǒng)負(fù)載達(dá)到一定閾值時(shí),例如 CPU 使用率達(dá)到 90% 或者系統(tǒng) load 值達(dá)到 2*CPU 核數(shù)時(shí),系統(tǒng)直接拒絕所有請求,這種方式是最暴力但也最有效的系統(tǒng)保護(hù)方式。例如秒殺系統(tǒng),我們在如下幾個(gè)環(huán)節(jié)設(shè)計(jì)過載保護(hù):
在最前端的 Nginx 上設(shè)置過載保護(hù),當(dāng)機(jī)器負(fù)載達(dá)到某個(gè)值時(shí)直接拒絕 HTTP 請求并返回 503 錯(cuò)誤碼,在 Java 層同樣也可以設(shè)計(jì)過載保護(hù)。
拒絕服務(wù)可以說是一種不得已的兜底方案,用以防止最壞情況發(fā)生,防止因把服務(wù)器壓跨而長時(shí)間徹底無法提供服務(wù)。像這種系統(tǒng)過載保護(hù)雖然在過載時(shí)無法提供服務(wù),但是系統(tǒng)仍然可以運(yùn)作,當(dāng)負(fù)載下降時(shí)又很容易恢復(fù),所以每個(gè)系統(tǒng)和每個(gè)環(huán)節(jié)都應(yīng)該設(shè)置這個(gè)兜底方案,對系統(tǒng)做最壞情況下的保護(hù)。
緩存問題
緩存雪崩
數(shù)據(jù)未加載到緩存中,或者緩存同時(shí)在大范圍中失效,導(dǎo)致所有請求查找數(shù)據(jù)庫,導(dǎo)致數(shù)據(jù)庫、CPU 和內(nèi)存過載,甚至停機(jī)。
一個(gè)簡單的雪崩過程:
1) Redis 集群的大面積故障;
2) 緩存失敗,但仍有大量請求訪問緩存服務(wù) Redis;
3) 在大量 Redis 請求失敗后,請求轉(zhuǎn)向數(shù)據(jù)庫;
4) 數(shù)據(jù)庫請求急劇增加,導(dǎo)致數(shù)據(jù)庫被打死;
5) 由于你應(yīng)用程序服務(wù)大部分都依賴于數(shù)據(jù)庫和 Redis 服務(wù),它很快就會(huì)導(dǎo)致服務(wù)器集群的雪崩,最后整個(gè)系統(tǒng)將徹底崩潰。
解決辦法:
事前:高可用的緩存
高可用的緩存是防止出現(xiàn)整個(gè)緩存故障。即使個(gè)別節(jié)點(diǎn),機(jī)器甚甚至機(jī)房都關(guān)閉,系統(tǒng)仍然可以提供服務(wù),Redis 哨兵(Sentinel) 和 Redis 集群(Cluster) 都可以做到高可用。
事中:緩存降級(臨時(shí)支持)
當(dāng)訪問次數(shù)急劇增加導(dǎo)致服務(wù)出現(xiàn)問題時(shí),我們?nèi)绾未_保服務(wù)仍然可用。在國內(nèi)使用比較多的是 Hystrix,它通過熔斷、降級、限流三個(gè)手段來降低雪崩發(fā)生后的損失。只要確保數(shù)據(jù)庫不死,系統(tǒng)總可以響應(yīng)請求,每年的春節(jié) 12306 我們不都是這么過來的嗎?只要還可以響應(yīng)起碼還有搶到票的機(jī)會(huì)。
事后:Redis 備份和快速預(yù)熱
1) Redis 數(shù)據(jù)備份和恢復(fù)
2) 快速緩存預(yù)熱
緩存擊穿
緩存擊穿意味著當(dāng)熱點(diǎn)數(shù)據(jù)存儲到期時(shí),多個(gè)線程同時(shí)請求熱點(diǎn)數(shù)據(jù)。因?yàn)榫彺鎰傔^期,所有并發(fā)請求都會(huì)到數(shù)據(jù)庫查詢數(shù)據(jù)。
解決辦法:
實(shí)際上,在大多數(shù)實(shí)際業(yè)務(wù)場景中,緩存擊穿是實(shí)時(shí)發(fā)生的,但不會(huì)對數(shù)據(jù)庫造成太大壓力,因?yàn)橐话愕墓緲I(yè)務(wù),并發(fā)量不會(huì)那么高。當(dāng)然如果你不幸有這種情況,你可以通過設(shè)置這些熱點(diǎn)鍵,使其永遠(yuǎn)不會(huì)過期。另一種方法是通過互斥鎖來控制查詢數(shù)據(jù)庫的線程訪問,但這種會(huì)導(dǎo)致系統(tǒng)的吞吐率下降,需要實(shí)際情況使用。
緩存穿透
緩存穿透是指查詢一個(gè)一定不存在的數(shù)據(jù),因?yàn)榫彺嬷幸矡o該數(shù)據(jù)的信息,則會(huì)直接去數(shù)據(jù)庫層進(jìn)行查詢,從系統(tǒng)層面來看像是穿透了緩存層直接達(dá)到db,從而稱為緩存穿透,沒有了緩存層的保護(hù),這種查詢一定不存在的數(shù)據(jù)對系統(tǒng)來說可能是一種危險(xiǎn),如果有人惡意用這種一定不存在的數(shù)據(jù)來頻繁請求系統(tǒng),不,準(zhǔn)確的說是攻擊系統(tǒng),請求都會(huì)到達(dá)數(shù)據(jù)庫層導(dǎo)致db癱瘓從而引起系統(tǒng)故障。
解決方案:
緩存穿透業(yè)內(nèi)的解決方案已經(jīng)比較成熟,主要常用的有以下幾種:
布隆過濾器:類似于哈希表的一種算法,用所有可能的查詢條件生成一個(gè)bitmap,在進(jìn)行數(shù)據(jù)庫查詢之前會(huì)使用這個(gè)bitmap進(jìn)行過濾,如果不在其中則直接過濾,從而減輕數(shù)據(jù)庫層面的壓力。
空值緩存:一種比較簡單的解決辦法,在第一次查詢完不存在的數(shù)據(jù)后,將該key與對應(yīng)的空值(null或者對象里只有key)也放入緩存中,只不過設(shè)定為較短的失效時(shí)間,例如幾分鐘,這樣則可以應(yīng)對短時(shí)間的大量的該key攻擊,設(shè)置為較短的失效時(shí)間是因?yàn)樵撝悼赡軜I(yè)務(wù)無關(guān),存在意義不大,且該次的查詢也未必是攻擊者發(fā)起,無過久存儲的必要,故可以早點(diǎn)失效。
總結(jié)
由于本篇文章屬于理論篇,所以全篇沒有一行代碼,但是文中提出來的基本上就是秒殺系統(tǒng)所發(fā)生過的,每個(gè)系統(tǒng)可能發(fā)生的問題不同而已。
本文轉(zhuǎn)載自微信公眾號「Java后端技術(shù)全棧」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系Java后端技術(shù)全棧公眾號。