溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

java高并發(fā)系統(tǒng)設(shè)計(jì)之緩存案例

發(fā)布時(shí)間:2020-10-12 13:41:06 來(lái)源:億速云 閱讀:320 作者:小新 欄目:編程語(yǔ)言

java高并發(fā)系統(tǒng)設(shè)計(jì)之緩存案例?這個(gè)問(wèn)題可能是我們?nèi)粘W(xué)習(xí)或工作經(jīng)常見(jiàn)到的。希望通過(guò)這個(gè)問(wèn)題能讓你收獲頗深。下面是小編給大家?guī)?lái)的參考內(nèi)容,讓我們一起來(lái)看看吧!

常見(jiàn)硬件組件的延時(shí)情況如下圖:java高并發(fā)系統(tǒng)設(shè)計(jì)之緩存案例

從這些數(shù)據(jù)中,你可以看到,做一次內(nèi)存尋址大概需要 100ns,而做一次磁盤的查找則需要 10ms。可見(jiàn),我們使用內(nèi)存作為緩存的存儲(chǔ)介質(zhì)相比于以磁盤作為主要存儲(chǔ)介質(zhì)的數(shù)據(jù)庫(kù)來(lái)說(shuō),性能上會(huì)提高多個(gè)數(shù)量級(jí)。所以,內(nèi)存是最常見(jiàn)的一種緩存數(shù)據(jù)的介質(zhì)。

一、緩存案例

1、TLB

Linux 內(nèi)存管理是通過(guò)一個(gè)叫做 MMU(Memory Management Unit)的硬件,來(lái)實(shí)現(xiàn)從虛擬地址到物理地址的轉(zhuǎn)換的,但是如果每次轉(zhuǎn)換都要做這么復(fù)雜計(jì)算的話,無(wú)疑會(huì)造成性能的損耗,所以我們會(huì)借助一個(gè)叫做 TLB(Translation Lookaside Buffer)的組件來(lái)緩存最近轉(zhuǎn)換過(guò)的虛擬地址,和物理地址的映射。TLB 就是一種緩存組件。

2、抖音

平臺(tái)上的短視頻實(shí)際上是使用內(nèi)置的網(wǎng)絡(luò)播放器來(lái)完成的。網(wǎng)絡(luò)播放器接收的是數(shù)據(jù)流,將數(shù)據(jù)下載下來(lái)之后經(jīng)過(guò)分離音視頻流,解碼等流程后輸出到外設(shè)設(shè)備上播放。播放器中通常會(huì)設(shè)計(jì)一些緩存的組件,在未打開(kāi)視頻時(shí)緩存一部分視頻數(shù)據(jù),比如我們打開(kāi)抖音,服務(wù)端可能一次會(huì)返回三個(gè)視頻信息,我們?cè)诓シ诺谝粋€(gè)視頻的時(shí)候,播放器已經(jīng)幫我們緩存了第二、三個(gè)視頻的部分?jǐn)?shù)據(jù),這樣在看第二個(gè)視頻的時(shí)候就可以給用戶“秒開(kāi)”的感覺(jué)。

3、HTTP協(xié)議緩存

當(dāng)我們第一次請(qǐng)求靜態(tài)的資源時(shí),比如一張圖片,服務(wù)端除了返回圖片信息,在響應(yīng)頭里面還有一個(gè)“Etag”的字段。瀏覽器會(huì)緩存圖片信息以及這個(gè)字段的值。當(dāng)下一次再請(qǐng)求這個(gè)圖片的時(shí)候,瀏覽器發(fā)起的請(qǐng)求頭里面會(huì)有一個(gè)“If-None-Match”的字段,并且把緩存的“Etag”的值寫進(jìn)去發(fā)給服務(wù)端。服務(wù)端比對(duì)圖片信息是否有變化,如果沒(méi)有,則返回瀏覽器一個(gè) 304 的狀態(tài)碼,瀏覽器會(huì)繼續(xù)使用緩存的圖片信息。通過(guò)這種緩存協(xié)商的方式,可以減少網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)大小,從而提升頁(yè)面展示性能。java高并發(fā)系統(tǒng)設(shè)計(jì)之緩存案例

二、緩存分類

1、靜態(tài)緩存

靜態(tài)緩存在 Web 1.0 時(shí)期是非常著名的,它一般通過(guò)生成 Velocity 模板或者靜態(tài) HTML 文件來(lái)實(shí)現(xiàn)靜態(tài)緩存,在 Nginx 上部署靜態(tài)緩存可以減少對(duì)于后臺(tái)應(yīng)用服務(wù)器的壓力

2、分布式緩存

分布式緩存的大名可謂是如雷貫耳了,我們平時(shí)耳熟能詳?shù)?Memcached、Redis 就是分布式緩存的典型例子。它們性能強(qiáng)勁,通過(guò)一些分布式的方案組成集群可以突破單機(jī)的限制。所以在整體架構(gòu)中,分布式緩存承擔(dān)著非常重要的角色

3、本地緩存

Guava Cache 或者是 Ehcache 等,它們和應(yīng)用程序部署在同一個(gè)進(jìn)程中,優(yōu)勢(shì)是不需要跨網(wǎng)絡(luò)調(diào)度,速度極快,所以可以用來(lái)阻擋短時(shí)間內(nèi)的熱點(diǎn)查詢。

三、緩存的讀寫策略

1、Cache Aside策略

在更新數(shù)據(jù)時(shí)不更新緩存,而是刪除緩存中的數(shù)據(jù),在讀取數(shù)據(jù)時(shí),發(fā)現(xiàn)緩存中沒(méi)了數(shù)據(jù)之后,再?gòu)臄?shù)據(jù)庫(kù)中讀取數(shù)據(jù),更新到緩存中。java高并發(fā)系統(tǒng)設(shè)計(jì)之緩存案例

這個(gè)策略就是我們使用緩存最常見(jiàn)的策略,Cache Aside 策略(也叫旁路緩存策略),這個(gè)策略數(shù)據(jù)以數(shù)據(jù)庫(kù)中的數(shù)據(jù)為準(zhǔn),緩存中的數(shù)據(jù)是按需加載的。

Cache Aside 策略是我們?nèi)粘i_(kāi)發(fā)中最經(jīng)常使用的緩存策略,不過(guò)我們?cè)谑褂脮r(shí)也要學(xué)會(huì)依情況而變,并不是一成不變的。Cache Aside 存在的最大的問(wèn)題是當(dāng)寫入比較頻繁時(shí),緩存中的數(shù)據(jù)會(huì)被頻繁地清理,這樣會(huì)對(duì)緩存的命中率有一些影響。如果你的業(yè)務(wù)對(duì)緩存命中率有嚴(yán)格的要求,那么可以考慮兩種解決方案:

一種做法是在更新數(shù)據(jù)時(shí)也更新緩存,只是在更新緩存前先加一個(gè)分布式鎖,因?yàn)檫@樣在同一時(shí)間只允許一個(gè)線程更新緩存,就不會(huì)產(chǎn)生并發(fā)問(wèn)題了。當(dāng)然這么做對(duì)于寫入的性能會(huì)有一些影響(推薦);

另一種做法同樣也是在更新數(shù)據(jù)時(shí)更新緩存,只是給緩存加一個(gè)較短的過(guò)期時(shí)間,這樣即使出現(xiàn)緩存不一致的情況,緩存的數(shù)據(jù)也會(huì)很快過(guò)期,對(duì)業(yè)務(wù)的影響也是可以接受。

2、Read/Write Through

這個(gè)策略的核心原則是用戶只與緩存打交道,由緩存和數(shù)據(jù)庫(kù)通信,寫入或者讀取數(shù)據(jù)。java高并發(fā)系統(tǒng)設(shè)計(jì)之緩存案例

Write Through

的策略是這樣的:先查詢要寫入的數(shù)據(jù)在緩存中是否已經(jīng)存在,如果已經(jīng)存在,則更新緩存中的數(shù)據(jù),并且由緩存組件同步更新到數(shù)據(jù)庫(kù)中,如果緩存中數(shù)據(jù)不存在,我們把這種情況叫做“Write Miss(寫失效)”。一般來(lái)說(shuō),我們可以選擇兩種“Write Miss”方式:一個(gè)是“Write Allocate(按寫分配)”,做法是寫入緩存相應(yīng)位置,再由緩存組件同步更新到數(shù)據(jù)庫(kù)中;另一個(gè)是“No-write allocate(不按寫分配)”,做法是不寫入緩存中,而是直接更新到數(shù)據(jù)庫(kù)中。 我們看到 Write Through 策略中寫數(shù)據(jù)庫(kù)是同步的,這對(duì)于性能來(lái)說(shuō)會(huì)有比較大的影響,因?yàn)橄啾扔趯懢彺?,同步寫?shù)據(jù)庫(kù)的延遲就要高很多了。通過(guò)Write Back策略異步的更新數(shù)據(jù)庫(kù)。

Read Through

策略就簡(jiǎn)單一些,它的步驟是這樣的:先查詢緩存中數(shù)據(jù)是否存在,如果存在則直接返回,如果不存在,則由緩存組件負(fù)責(zé)從數(shù)據(jù)庫(kù)中同步加載數(shù)據(jù)。

3、Write Back

這個(gè)策略的核心思想是在寫入數(shù)據(jù)時(shí)只寫入緩存,并且把緩存塊兒標(biāo)記為“臟”的。而臟塊兒只有被再次使用時(shí)才會(huì)將其中的數(shù)據(jù)寫入到后端存儲(chǔ)中。 在“Write Miss”的情況下,我們采用的是“Write Allocate”的方式,也就是在寫入后端存儲(chǔ)的同時(shí)要寫入緩存,這樣我們?cè)谥蟮膶懻?qǐng)求中都只需要更新緩存即可,而無(wú)需更新后端存儲(chǔ)了。注意與上面的write through策略作區(qū)分。java高并發(fā)系統(tǒng)設(shè)計(jì)之緩存案例

我們?cè)谧x取緩存時(shí)如果發(fā)現(xiàn)緩存命中則直接返回緩存數(shù)據(jù)。如果緩存不命中則尋找一個(gè)可用的緩存塊兒,如果這個(gè)緩存塊兒是“臟”的,就把緩存塊兒中之前的數(shù)據(jù)寫入到后端存儲(chǔ)中,并且從后端存儲(chǔ)加載數(shù)據(jù)到緩存塊兒,如果不是臟的,則由緩存組件將后端存儲(chǔ)中的數(shù)據(jù)加載到緩存中,最后我們將緩存設(shè)置為不是臟的,返回?cái)?shù)據(jù)就好了。java高并發(fā)系統(tǒng)設(shè)計(jì)之緩存案例

write back策略多用于向磁盤中寫數(shù)據(jù)。例如:操作系統(tǒng)層面的 Page Cache、日志的異步刷盤、消息隊(duì)列中消息的異步寫入磁盤等。因?yàn)檫@個(gè)策略在性能上的優(yōu)勢(shì)毋庸置疑,它避免了直接寫磁盤造成的隨機(jī)寫問(wèn)題,畢竟寫內(nèi)存和寫磁盤的隨機(jī) I/O 的延遲相差了幾個(gè)數(shù)量級(jí)呢。

四、緩存高可用

緩存的命中率是緩存需要監(jiān)控的數(shù)據(jù)指標(biāo),緩存的高可用可以一定程度上減少緩存穿透的概率,提升系統(tǒng)的穩(wěn)定性。緩存的高可用方案主要包括客戶端方案、中間代理層方案和服務(wù)端方案三大類:

1、客戶端方案

在客戶端方案中,你需要關(guān)注緩存的寫和讀兩個(gè)方面: 寫入數(shù)據(jù)時(shí),需要把被寫入緩存的數(shù)據(jù)分散到多個(gè)節(jié)點(diǎn)中,即進(jìn)行數(shù)據(jù)分片; 讀數(shù)據(jù)時(shí),可以利用多組的緩存來(lái)做容錯(cuò),提升緩存系統(tǒng)的可用性。關(guān)于讀數(shù)據(jù),這里可以使用主從和多副本兩種策略,兩種策略是為了解決不同的問(wèn)題而提出的。 具體的實(shí)現(xiàn)細(xì)節(jié)包括:數(shù)據(jù)分片、主從、多副本

數(shù)據(jù)分片

一致性Hash算法。在這個(gè)算法中,我們將整個(gè) Hash 值空間組織成一個(gè)虛擬的圓環(huán),然后將緩存節(jié)點(diǎn)的 IP 地址或者主機(jī)名做 Hash 取值后,放置在這個(gè)圓環(huán)上。當(dāng)我們需要確定某一個(gè) Key 需要存取到哪個(gè)節(jié)點(diǎn)上的時(shí)候,先對(duì)這個(gè) Key 做同樣的 Hash 取值,確定在環(huán)上的位置,然后按照順時(shí)針?lè)较蛟诃h(huán)上“行走”,遇到的第一個(gè)緩存節(jié)點(diǎn)就是要訪問(wèn)的節(jié)點(diǎn)。java高并發(fā)系統(tǒng)設(shè)計(jì)之緩存案例

這時(shí)如果在 Node 1 和 Node 2 之間增加一個(gè) Node 5,你可以看到原本命中 Node 2 的 Key 3 現(xiàn)在命中到 Node 5,而其它的 Key 都沒(méi)有變化;同樣的道理,如果我們把 Node 3 從集群中移除,那么只會(huì)影響到 Key 5 。所以你看,在增加和刪除節(jié)點(diǎn)時(shí),只有少量的 Key 會(huì)“漂移”到其它節(jié)點(diǎn)上,而大部分的 Key 命中的節(jié)點(diǎn)還是會(huì)保持不變,從而可以保證命中率不會(huì)大幅下降。 【提示】一致性hash出現(xiàn)的緩存雪崩現(xiàn)象使用虛擬節(jié)點(diǎn)解決。一致性hash分片與hash分片的區(qū)別在于,緩存命中率的問(wèn)題,hash分片在存在機(jī)器加入或是減少的情況時(shí)候,會(huì)導(dǎo)致緩存失效,緩存命中率下降。

主從

Redis 本身支持主從的部署方式,但是 Memcached 并不支持,Memcached 的主從機(jī)制是如何在客戶端實(shí)現(xiàn)的。為每一組 Master 配置一組 Slave,更新數(shù)據(jù)時(shí)主從同步更新。讀取時(shí),優(yōu)先從 Slave 中讀數(shù)據(jù),如果讀取不到數(shù)據(jù)就穿透到 Master 讀取,并且將數(shù)據(jù)回種到 Slave 中以保持 Slave 數(shù)據(jù)的熱度。主從機(jī)制最大的優(yōu)點(diǎn)就是當(dāng)某一個(gè) Slave 宕機(jī)時(shí),還會(huì)有 Master 作為兜底,不會(huì)有大量請(qǐng)求穿透到數(shù)據(jù)庫(kù)的情況發(fā)生,提升了緩存系統(tǒng)的高可用性。

多副本

主從方式已經(jīng)能夠解決大部分場(chǎng)景的問(wèn)題,但是對(duì)于極端流量的場(chǎng)景下,一組 Slave 通常來(lái)說(shuō)并不能完全承擔(dān)所有流量,Slave 網(wǎng)卡帶寬可能成為瓶頸。為了解決這個(gè)問(wèn)題,我們考慮在 Master/Slave 之前增加一層副本層,整體架構(gòu)是這樣的:java高并發(fā)系統(tǒng)設(shè)計(jì)之緩存案例

這個(gè)方案中,當(dāng)客戶端發(fā)起查詢請(qǐng)求時(shí),請(qǐng)求首先會(huì)先從多個(gè)副本組中選取一個(gè)副本組發(fā)起查詢,如果查詢失敗,就繼續(xù)查詢 Master/Slave,并且將查詢的結(jié)果回種到所有副本組中,避免副本組中臟數(shù)據(jù)的存在?;诔杀镜目紤],每一個(gè)副本組容量比 Master 和 Slave 要小,因此它只存儲(chǔ)了更加熱的數(shù)據(jù)。在這套架構(gòu)中,Master 和 Slave 的請(qǐng)求量會(huì)大大減少,為了保證它們存儲(chǔ)數(shù)據(jù)的熱度,在實(shí)踐中我們會(huì)把 Master 和 Slave 作為一組副本組使用。

2、中間代理層

業(yè)界也有很多中間代理層方案,比如 Facebook 的Mcrouter,Twitter 的Twemproxy,豌豆莢的Codis。它們的原理基本上可以由一張圖來(lái)概括:java高并發(fā)系統(tǒng)設(shè)計(jì)之緩存案例

3、服務(wù)端方案

Redis 在 2.4 版本中提出了 Redis Sentinel 模式來(lái)解決主從 Redis 部署時(shí)的高可用問(wèn)題,它可以在主節(jié)點(diǎn)掛了以后自動(dòng)將從節(jié)點(diǎn)提升為主節(jié)點(diǎn),保證整體集群的可用性,整體的架構(gòu)如下圖所示:java高并發(fā)系統(tǒng)設(shè)計(jì)之緩存案例

redis Sentinel 也是集群部署的,這樣可以避免 Sentinel 節(jié)點(diǎn)掛掉造成無(wú)法自動(dòng)故障恢復(fù)的問(wèn)題,每一個(gè) Sentinel 節(jié)點(diǎn)都是無(wú)狀態(tài)的。在 Sentinel 中會(huì)配置 Master 的地址,Sentinel 會(huì)時(shí)刻監(jiān)控 Master 的狀態(tài),當(dāng)發(fā)現(xiàn) Master 在配置的時(shí)間間隔內(nèi)無(wú)響應(yīng),就認(rèn)為 Master 已經(jīng)掛了,Sentinel 會(huì)從從節(jié)點(diǎn)中選取一個(gè)提升為主節(jié)點(diǎn),并且把所有其他的從節(jié)點(diǎn)作為新主的從節(jié)點(diǎn)。Sentinel 集群內(nèi)部在仲裁的時(shí)候,會(huì)根據(jù)配置的值來(lái)決定當(dāng)有幾個(gè) Sentinel 節(jié)點(diǎn)認(rèn)為主掛掉可以做主從切換的操作,也就是集群內(nèi)部需要對(duì)緩存節(jié)點(diǎn)的狀態(tài)達(dá)成一致才行。

【提示】上述客戶端到sentinel集群的連線是虛線,因?yàn)閷?duì)于緩存的寫入和讀取請(qǐng)求不會(huì)經(jīng)過(guò) Sentinel 節(jié)點(diǎn)。

五、緩存穿透

1、帕累托

互聯(lián)網(wǎng)系統(tǒng)的數(shù)據(jù)訪問(wèn)模型一般會(huì)遵從“80/20 原則”。“80/20 原則”又稱為帕累托法則,是意大利經(jīng)濟(jì)學(xué)家帕累托提出的一個(gè)經(jīng)濟(jì)學(xué)的理論。簡(jiǎn)單來(lái)說(shuō),它是指在一組事物中,最重要的部分通常只占 20%,而其他的 80% 并沒(méi)有那么重要。把它應(yīng)用到數(shù)據(jù)訪問(wèn)的領(lǐng)域,就是我們會(huì)經(jīng)常訪問(wèn) 20% 的熱點(diǎn)數(shù)據(jù),而另外的 80% 的數(shù)據(jù)則不會(huì)被經(jīng)常訪問(wèn)。既然緩存的容量有限,并且大部分的訪問(wèn)只會(huì)請(qǐng)求 20% 的熱點(diǎn)數(shù)據(jù),那么理論上說(shuō),我們只需要在有限的緩存空間里存儲(chǔ) 20% 的熱點(diǎn)數(shù)據(jù)就可以有效地保護(hù)脆弱的后端系統(tǒng)了,也就可以放棄緩存另外 80% 的非熱點(diǎn)數(shù)據(jù)了。所以這種少量的緩存穿透是不可避免的,但是對(duì)系統(tǒng)是沒(méi)有損害的。

2、回種空值

當(dāng)我們從數(shù)據(jù)庫(kù)中查詢到空值或者發(fā)生異常時(shí),我們可以向緩存中回種一個(gè)空值。但是因?yàn)榭罩挡⒉皇菧?zhǔn)確的業(yè)務(wù)數(shù)據(jù),并且會(huì)占用緩存的空間,所以我們會(huì)給這個(gè)空值加一個(gè)比較短的過(guò)期時(shí)間,讓空值在短時(shí)間之內(nèi)能夠快速過(guò)期淘汰。回種空值雖然能夠阻擋大量穿透的請(qǐng)求,但如果有大量的空值緩存,也就會(huì)浪費(fèi)緩存的存儲(chǔ)空間,如果緩存空間被占滿了,還會(huì)剔除掉一些已經(jīng)被緩存的用戶信息反而會(huì)造成緩存命中率的下降。所以這個(gè)方案,我建議你在使用的時(shí)候應(yīng)該評(píng)估一下緩存容量是否能夠支撐。如果需要大量的緩存節(jié)點(diǎn)來(lái)支持,那么就無(wú)法通過(guò)通過(guò)回種空值的方式來(lái)解決,這時(shí)你可以考慮使用布隆過(guò)濾器。

3、布隆過(guò)濾器

1970 年布隆提出了一種布隆過(guò)濾器的算法,用來(lái)判斷一個(gè)元素是否在一個(gè)集合中。這種算法由一個(gè)二進(jìn)制數(shù)組和一個(gè) Hash 算法組成。它的基本思路如下:我們把集合中的每一個(gè)值按照提供的 Hash 算法算出對(duì)應(yīng)的 Hash 值,然后將 Hash 值對(duì)數(shù)組長(zhǎng)度取模后得到需要計(jì)入數(shù)組的索引值,并且將數(shù)組這個(gè)位置的值從 0 改成 1。在判斷一個(gè)元素是否存在于這個(gè)集合中時(shí),你只需要將這個(gè)元素按照相同的算法計(jì)算出索引值,如果這個(gè)位置的值為 1 就認(rèn)為這個(gè)元素在集合中,否則則認(rèn)為不在集合中。java高并發(fā)系統(tǒng)設(shè)計(jì)之緩存案例

如何使用布隆過(guò)濾器解決緩存穿透呢?

以存儲(chǔ)用戶信息的表為例進(jìn)行講解。首先我們初始化一個(gè)很大的數(shù)組,比方說(shuō)長(zhǎng)度為 20 億的數(shù)組,接下來(lái)我們選擇一個(gè) Hash 算法,然后我們將目前現(xiàn)有的所有用戶的 ID 計(jì)算出 Hash 值并且映射到這個(gè)大數(shù)組中,映射位置的值設(shè)置為 1,其它值設(shè)置為 0。新注冊(cè)的用戶除了需要寫入到數(shù)據(jù)庫(kù)中之外,它也需要依照同樣的算法更新布隆過(guò)濾器的數(shù)組中相應(yīng)位置的值。那么當(dāng)我們需要查詢某一個(gè)用戶的信息時(shí),先查詢這個(gè) ID 在布隆過(guò)濾器中是否存在,如果不存在就直接返回空值,而不需要繼續(xù)查詢數(shù)據(jù)庫(kù)和緩存,這樣就可以極大地減少異常查詢帶來(lái)的緩存穿透。java高并發(fā)系統(tǒng)設(shè)計(jì)之緩存案例

布隆過(guò)濾器優(yōu)點(diǎn):

(1)性能高。無(wú)論是寫入操作還是讀取操作,時(shí)間復(fù)雜度都是 O(1) 是常量值

(2)節(jié)省空間。比如,20 億的數(shù)組需要 2000000000/8/1024/1024 = 238M 的空間,而如果使用數(shù)組來(lái)存儲(chǔ),假設(shè)每個(gè)用戶 ID 占用 4 個(gè)字節(jié)的空間,那么存儲(chǔ) 20 億用戶需要 2000000000 * 4 / 1024 / 1024 = 7600M 的空間,是布隆過(guò)濾器的 32 倍。

布隆過(guò)濾器缺點(diǎn):

(1)它在判斷元素是否在集合中時(shí)是有一定錯(cuò)誤幾率的,比如它會(huì)把不是集合中的元素判斷為處在集合中。

原因:Hash算法本身的缺陷。

解決方案:使用多個(gè) Hash 算法為元素計(jì)算出多個(gè) Hash 值,只有所有 Hash 值對(duì)應(yīng)的數(shù)組中的值都為 1 時(shí),才會(huì)認(rèn)為這個(gè)元素在集合中。

(2)不支持刪除元素。布隆過(guò)濾器不支持刪除元素的缺陷也和 Hash 碰撞有關(guān)。舉一個(gè)例子,假如兩個(gè)元素 A 和 B 都是集合中的元素,它們有相同的 Hash 值,它們就會(huì)映射到數(shù)組的同一個(gè)位置。這時(shí)我們刪除了 A,數(shù)組中對(duì)應(yīng)位置的值也從 1 變成 0,那么在判斷 B 的時(shí)候發(fā)現(xiàn)值是 0,也會(huì)判斷 B 是不在集合中的元素,就會(huì)得到錯(cuò)誤的結(jié)論。

解決方案:我會(huì)讓數(shù)組中不再只有 0 和 1 兩個(gè)值,而是存儲(chǔ)一個(gè)計(jì)數(shù)。比如如果 A 和 B 同時(shí)命中了一個(gè)數(shù)組的索引,那么這個(gè)位置的值就是 2,如果 A 被刪除了就把這個(gè)值從 2 改為 1。這個(gè)方案中的數(shù)組不再存儲(chǔ) bit 位,而是存儲(chǔ)數(shù)值,也就會(huì)增加空間的消耗。

4、狗樁效應(yīng)

比方說(shuō)當(dāng)有一個(gè)極熱點(diǎn)的緩存項(xiàng),它一旦失效會(huì)有大量請(qǐng)求穿透到數(shù)據(jù)庫(kù),這會(huì)對(duì)數(shù)據(jù)庫(kù)造成瞬時(shí)極大的壓力,我們把這個(gè)場(chǎng)景叫做“dog-pile effect”(狗樁效應(yīng))。解決狗樁效應(yīng)的思路是盡量地減少緩存穿透后的并發(fā),方案也比較簡(jiǎn)單:

(1)在代碼中控制在某一個(gè)熱點(diǎn)緩存項(xiàng)失效之后啟動(dòng)一個(gè)后臺(tái)線程,穿透到數(shù)據(jù)庫(kù),將數(shù)據(jù)加載到緩存中,在緩存未加載之前,所有訪問(wèn)這個(gè)緩存的請(qǐng)求都不再穿透而直接返回。

(2)通過(guò)在 Memcached 或者 Redis 中設(shè)置分布式鎖,只有獲取到鎖的請(qǐng)求才能夠穿透到數(shù)據(jù)庫(kù)

六、CDN

1、靜態(tài)資源加速的原因

在我們的系統(tǒng)中存在著大量的靜態(tài)資源請(qǐng)求:對(duì)于移動(dòng) APP 來(lái)說(shuō),這些靜態(tài)資源主要是圖片、視頻和流媒體信息;對(duì)于 Web 網(wǎng)站來(lái)說(shuō),則包括了 JavaScript 文件、CSS 文件、靜態(tài) HTML 文件等等。它們的讀請(qǐng)求量極大并且對(duì)訪問(wèn)速度的要求很高還占據(jù)了很高的帶寬,這時(shí)會(huì)出現(xiàn)訪問(wèn)速度慢帶寬被占滿影響動(dòng)態(tài)請(qǐng)求的問(wèn)題,那么你就需要考慮如何針對(duì)這些靜態(tài)資源進(jìn)行讀加速了。

2、CDN

靜態(tài)資源訪問(wèn)的關(guān)鍵點(diǎn)是就近訪問(wèn),即北京用戶訪問(wèn)北京的數(shù)據(jù),杭州用戶訪問(wèn)杭州的數(shù)據(jù),這樣才可以達(dá)到性能的最優(yōu)。我們考慮在業(yè)務(wù)服務(wù)器的上層增加一層特殊的緩存,用來(lái)承擔(dān)絕大部分對(duì)于靜態(tài)資源的訪問(wèn),這一層特殊緩存的節(jié)點(diǎn)需要遍布在全國(guó)各地,這樣可以讓用戶選擇最近的節(jié)點(diǎn)訪問(wèn)。緩存的命中率也需要一定的保證,盡量減少訪問(wèn)資源存儲(chǔ)源站的請(qǐng)求數(shù)量(回源請(qǐng)求)。這一層緩存就是CDN。

CDN(Content Delivery Network/Content Distribution Network,內(nèi)容分發(fā)網(wǎng)絡(luò))。簡(jiǎn)單來(lái)說(shuō),CDN 就是將靜態(tài)的資源分發(fā)到位于多個(gè)地理位置機(jī)房中的服務(wù)器上,因此它能很好地解決數(shù)據(jù)就近訪問(wèn)的問(wèn)題,也就加快了靜態(tài)資源的訪問(wèn)速度。

3、搭建CDN系統(tǒng)

搭建一個(gè) CDN 系統(tǒng)需要考慮哪兩點(diǎn):

(1)如何將用戶的請(qǐng)求映射到 CDN 節(jié)點(diǎn)上

你可能會(huì)覺(jué)得這很簡(jiǎn)單啊,只需要告訴用戶 CDN 節(jié)點(diǎn)的 IP 地址,然后請(qǐng)求這個(gè) IP 地址上面部署的 CDN 服務(wù)就可以了啊。但是,并不是這樣,需要把ip替換為相應(yīng)的域名。那么如何做到這一點(diǎn)呢?這就需要依靠 DNS 來(lái)幫我們解決域名映射的問(wèn)題了。DNS(Domain Name System,域名系統(tǒng))實(shí)際上就是一個(gè)存儲(chǔ)域名和 IP 地址對(duì)應(yīng)關(guān)系的分布式數(shù)據(jù)庫(kù)。而域名解析的結(jié)果一般有兩種,一種叫做“A 記錄”,返回的是域名對(duì)應(yīng)的 IP 地址;另一種是“CNAME 記錄”,返回的是另一個(gè)域名,也就是說(shuō)當(dāng)前域名的解析要跳轉(zhuǎn)到另一個(gè)域名的解析上。

舉個(gè)例子:比如你的公司的一級(jí)域名叫做 example.com,那么你可以把你的圖片服務(wù)的域名定義為“img.example.com”,然后將這個(gè)域名的解析結(jié)果的 CNAME 配置到 CDN 提供的域名上,比如 uclound 可能會(huì)提供一個(gè)域名是“80f21f91.cdn.ucloud.com.cn”這個(gè)域名。這樣你的電商系統(tǒng)使用的圖片地址可以是“img.example.com/1.jpg”。

用戶在請(qǐng)求這個(gè)地址時(shí),DNS 服務(wù)器會(huì)將域名解析到 80f21f91.cdn.ucloud.com.cn 域名上,然后再將這個(gè)域名解析為 CDN 的節(jié)點(diǎn) IP,這樣就可以得到 CDN 上面的資源數(shù)據(jù)了。

域名層級(jí)解析優(yōu)化

因?yàn)橛蛎馕鲞^(guò)程是分級(jí)的,每一級(jí)有專門的域名服務(wù)器承擔(dān)解析的職責(zé),所以域名的解析過(guò)程有可能需要跨越公網(wǎng)做多次 DNS 查詢,在性能上是比較差的。一個(gè)解決的思路是:在 APP 啟動(dòng)時(shí)對(duì)需要解析的域名做預(yù)先解析,然后把解析的結(jié)果緩存到本地的一個(gè) LRU 緩存里面。這樣當(dāng)我們要使用這個(gè)域名的時(shí)候,只需要從緩存中直接拿到所需要的 IP 地址就好了,如果緩存中不存在才會(huì)走整個(gè) DNS 查詢的過(guò)程。同時(shí)為了避免 DNS 解析結(jié)果的變更造成緩存內(nèi)數(shù)據(jù)失效,我們可以啟動(dòng)一個(gè)定時(shí)器定期地更新緩存中的數(shù)據(jù)。

(2)如何根據(jù)用戶的地理位置信息選擇到比較近的節(jié)點(diǎn)。

GSLB(Global Server Load Balance,全局負(fù)載均衡)的含義是對(duì)于部署在不同地域的服務(wù)器之間做負(fù)載均衡,下面可能管理了很多的本地負(fù)載均衡組件。它有兩方面的作用:一方面,它是一種負(fù)載均衡服務(wù)器,負(fù)載均衡,顧名思義嘛,指的是讓流量平均分配使得下面管理的服務(wù)器的負(fù)載更平均;另一方面,它還需要保證流量流經(jīng)的服務(wù)器與流量源頭在地緣上是比較接近的。

GSLB 可以通過(guò)多種策略來(lái)保證返回的 CDN 節(jié)點(diǎn)和用戶盡量保證在同一地緣區(qū)域,比如說(shuō)可以將用戶的 IP 地址按照地理位置劃分為若干個(gè)區(qū)域,然后將 CDN 節(jié)點(diǎn)對(duì)應(yīng)到一個(gè)區(qū)域上,根據(jù)用戶所在區(qū)域來(lái)返回合適的節(jié)點(diǎn);也可以通過(guò)發(fā)送數(shù)據(jù)包測(cè)量 RTT 的方式來(lái)決定返回哪一個(gè)節(jié)點(diǎn)。

總結(jié):DNS 技術(shù)是 CDN 實(shí)現(xiàn)中使用的核心技術(shù),可以將用戶的請(qǐng)求映射到 CDN 節(jié)點(diǎn)上;DNS 解析結(jié)果需要做本地緩存,降低 DNS 解析過(guò)程的響應(yīng)時(shí)間;GSLB 可以給用戶返回一個(gè)離著他更近的節(jié)點(diǎn),加快靜態(tài)資源的訪問(wèn)速度。

拓展

(1)百度域名的解析過(guò)程

一開(kāi)始,域名解析請(qǐng)求先會(huì)檢查本機(jī)的 hosts 文件,查看是否有 www.baidu.com 對(duì)應(yīng)的 IP;如果沒(méi)有的話,就請(qǐng)求 Local DNS 是否有域名解析結(jié)果的緩存,如果有就返回標(biāo)識(shí)是從非權(quán)威 DNS 返回的結(jié)果;如果沒(méi)有就開(kāi)始 DNS 的迭代查詢。先請(qǐng)求根 DNS,根 DNS 返回頂級(jí) DNS(.com)的地址;再請(qǐng)求.com 頂級(jí) DNS 得到 baidu.com 的域名服務(wù)器地址;再?gòu)?baidu.com 的域名服務(wù)器中查詢到 www.baidu.com 對(duì)應(yīng)的 IP 地址,返回這個(gè) IP 地址的同時(shí)標(biāo)記這個(gè)結(jié)果是來(lái)自于權(quán)威 DNS 的結(jié)果,同時(shí)寫入 Local DNS 的解析結(jié)果緩存,這樣下一次的解析同一個(gè)域名就不需要做 DNS 的迭代查詢了。

(2)CDN延時(shí)

一般我們會(huì)通過(guò) CDN 廠商的接口將靜態(tài)的資源寫入到某一個(gè) CDN 節(jié)點(diǎn)上,再由 CDN 內(nèi)部的同步機(jī)制將資源分散同步到每個(gè) CDN 節(jié)點(diǎn),即使 CDN 內(nèi)部網(wǎng)絡(luò)經(jīng)過(guò)了優(yōu)化,這個(gè)同步的過(guò)程是有延時(shí)的,一旦我們無(wú)法從選定的 CDN 節(jié)點(diǎn)上獲取到數(shù)據(jù),我們就不得不從源站獲取數(shù)據(jù),而用戶網(wǎng)絡(luò)到源站的網(wǎng)絡(luò)可能會(huì)跨越多個(gè)主干網(wǎng),這樣不僅性能上有損耗也會(huì)消耗源站的帶寬,帶來(lái)更高的研發(fā)成本。所以我們?cè)谑褂?CDN 的時(shí)候需要關(guān)注 CDN 的命中率和源站的帶寬情況。

感謝各位的閱讀!看完上述內(nèi)容,你們對(duì)java高并發(fā)系統(tǒng)設(shè)計(jì)之緩存案例大概了解了嗎?希望文章內(nèi)容對(duì)大家有所幫助。如果想了解更多相關(guān)文章內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI