關(guān)于分布式多級(jí)緩存架構(gòu),也許你一直考慮的太簡(jiǎn)單了
這篇想聊的話題是:「分布式多級(jí)緩存架構(gòu)的終章」,如何解決大流量、高并發(fā)這樣的業(yè)務(wù)場(chǎng)景,取決于你能不能成為這個(gè)領(lǐng)域金字塔上層的高手? 能不能把這個(gè)問題思考清楚決定了你的成長(zhǎng)速度。
很多人在一個(gè)行業(yè)5年、10年,依然未達(dá)到這個(gè)行業(yè)的中層甚至還停留在底層,因?yàn)樗麄儚膩聿魂P(guān)心這樣的話題。作為砥礪前行的踐行者,我覺得有必要給大家來分享一下。
開篇
服務(wù)端緩存是整個(gè)緩存體系中的重頭戲,從開始的網(wǎng)站架構(gòu)演進(jìn)中,想必你已看到服務(wù)端緩存在系統(tǒng)性能的重要性。
但數(shù)據(jù)庫(kù)確是整個(gè)系統(tǒng)中的“半吊子|慢性子”,有時(shí)數(shù)據(jù)庫(kù)調(diào)優(yōu)卻能夠以小搏大,在不改變架構(gòu)和代碼邏輯的前提下,緩存參數(shù)的調(diào)整往往是條捷徑。
在系統(tǒng)開發(fā)的過程中,可直接在平臺(tái)側(cè)使用緩存框架,當(dāng)緩存框架無法滿足系統(tǒng)對(duì)性能的要求時(shí),就需要在應(yīng)用層自主開發(fā)應(yīng)用級(jí)緩存。
緩存常用的就是Redis這東西,那到底什么是平臺(tái)級(jí)、應(yīng)用級(jí)緩存呢?
后面給大家揭曉。但有一點(diǎn)可表明,「平臺(tái)級(jí)就是你所選擇什么開發(fā)語(yǔ)言來實(shí)現(xiàn)緩存」,而應(yīng)用級(jí)緩存,則是通過應(yīng)用程序來達(dá)到目的。
01數(shù)據(jù)庫(kù)緩存
為何說數(shù)據(jù)庫(kù)是“慢性子”呢?對(duì)現(xiàn)在喜歡「快」的你來說,慢是解決不了問題的。就好像總感覺感覺妹子回復(fù)慢
因?yàn)閿?shù)據(jù)庫(kù)屬于「IO密集型應(yīng)」用,「主要負(fù)責(zé)數(shù)據(jù)的管理及存儲(chǔ)」。數(shù)據(jù)一多查詢本身就有可能變慢, 這也是為啥數(shù)據(jù)上得了臺(tái)面時(shí),查詢愛用索引提速的原因。當(dāng)然數(shù)據(jù)庫(kù)自身也有“緩存”來解決這個(gè)問題。
數(shù)據(jù)多了查詢不應(yīng)該都慢嗎?小白說吒吒輝你不懂額
。。。這個(gè),你說的也不全是,還得分情況。例如:數(shù)據(jù)有上億行
「原因:」
- 因?yàn)楹?jiǎn)單的SQL的結(jié)果不會(huì)特別多。你請(qǐng)求也不大,磁盤跟的上
- 并發(fā)總量超過磁盤吞吐上限,是誰都沒招
就算你們不喜歡吒吒輝,我也要奮筆疾書
數(shù)據(jù)庫(kù)緩存是自身一類特殊的緩存機(jī)制。大多數(shù)數(shù)據(jù)庫(kù)不需要配置就可以快速運(yùn)行,但并沒有為特定的需求進(jìn)行優(yōu)化。在數(shù)據(jù)庫(kù)調(diào)優(yōu)的時(shí)候,緩存優(yōu)化你可以考慮下。
以MySQL為例,MySQL中使用了查詢緩沖機(jī)制,將SELECT語(yǔ)句和查詢結(jié)果存放在緩沖區(qū)中,以鍵值對(duì)的形式存儲(chǔ)。以后對(duì)于同樣的SELECT語(yǔ)句,將直接從緩沖區(qū)中讀取結(jié)果,以節(jié)省查詢時(shí)間,提高了SQL查詢的效率。
1.1.MySQL查詢緩存
Query cache作用于整個(gè)MySQL實(shí)例,「主要用于緩存MySQL中的ResultSet」,也就是一條SQL語(yǔ)句執(zhí)行的結(jié)果集,所以它只針對(duì)select語(yǔ)句。
當(dāng)打開 Query Cache 功能,MySQL在接收到一條select語(yǔ)句的請(qǐng)求后,如果該語(yǔ)句滿足Query Cache的條件,MySQL會(huì)直接根據(jù)預(yù)先設(shè)定好的HASH算法將接收到的select語(yǔ)句以字符串方式進(jìn)行 hash,然后到Query Cache中直接查找是否已經(jīng)緩存。
如果結(jié)果集已經(jīng)在緩存中,該select請(qǐng)求就會(huì)直接將數(shù)據(jù)返回,從而省略后面所有的步驟(如SQL語(yǔ)句的解析,優(yōu)化器優(yōu)化以及向存儲(chǔ)引擎請(qǐng)求數(shù)據(jù)等),從而極大地提高了性能。
當(dāng)然,若數(shù)據(jù)變化非常頻繁的情況下,使用Query Cache可能會(huì)得不償失。
這是為啥,用它不是提速嗎?咋還得不償失
因?yàn)镸ySQL只要涉及到數(shù)據(jù)更改,就會(huì)重新維護(hù)緩存。
- 如果SQL請(qǐng)求量比較大,你在維護(hù)的時(shí)候,就透過緩存走磁盤檢索。這樣數(shù)據(jù)庫(kù)的壓力肯定大。
- 重建緩存數(shù)據(jù),它需要mysql后臺(tái)線程來工作。也會(huì)增加數(shù)據(jù)庫(kù)的負(fù)載。
所以在MySQL8已經(jīng)取消了它。故一般在讀多寫少,數(shù)據(jù)不怎么變化的場(chǎng)景可用它,例如:博客
Query Cache使用需要多個(gè)參數(shù)配合,其中最為關(guān)鍵的是query_cache_size和query_cache_type, 前者用于設(shè)置緩存ResultSet的內(nèi)存大小,后者設(shè)置在何種場(chǎng)景下使用Query Cache。
這樣可以通過計(jì)算Query Cache的命中率來進(jìn)行調(diào)整緩存大小。
1.2.檢驗(yàn)Query Cache的合理性
檢查Query Cache設(shè)置的是否合理,可以通過在MySQL控制臺(tái)執(zhí)行以下命令觀察:
- SHOW VARIABLES LIKE '%query_cache%';
- SHOW STATUS LIKE 'Qcache%'; 通過檢查以下幾個(gè)參數(shù)可以知道query_cache_size設(shè)置得是否合理:
- Qcache_inserts:表示Cache多少次未命中然后插入到緩存
- Qcache_hits: 表示命中多少次,它可反映出緩存的使用效果。
如果Qcache_hits的值非常大,則表明查詢緩沖使用非常頻繁,如果該值較小反而會(huì)影響效率,那么可以考慮不用查詢緩存;
- Qcache_lowmem_prunes: 表示多少條Query因?yàn)閮?nèi)存不足而被清除出Query_Cache。
如果Qcache_lowmem_prunes的值非常大,則表明經(jīng)常出現(xiàn)緩沖不夠的情況,因增加緩存容量。
- Qcache_free_blocks:表示緩存區(qū)的碎片
Qcache_free_blocks值非常大,則表明緩存區(qū)中的碎片很多,可能需要尋找合適的機(jī)會(huì)進(jìn)行整理。
通過 「Qcache_hits」 和 「Qcache_inserts」 兩個(gè)參數(shù)可以算出Query Cache的命中率:
通過 Qcache_lowmem_prunes 和 Qcache_free_memory 相互結(jié)合,能更清楚地了解到系統(tǒng)中Query Cache的內(nèi)存大小是否真的足夠,是否頻繁的出現(xiàn)因內(nèi)存不足而有Query被換出的情況。
1.3.InnoDB的緩存性能
當(dāng)選擇 InnoDB 時(shí),「innodb_buffer_pool_size」 參數(shù)可能是影響性能的最為關(guān)鍵的一個(gè)參數(shù),它用來設(shè)置緩存「InnoDB索引及數(shù)據(jù)塊、自適應(yīng)HASH、寫緩沖等內(nèi)存區(qū)域大小」,更像是Oracle數(shù)據(jù)庫(kù)的 db_cache_size。
簡(jiǎn)單來說,當(dāng)操作InnoDB表的時(shí)候,「返回的所有數(shù)據(jù)或者查詢過程中用到的任何一個(gè)索引塊,都會(huì)在這個(gè)內(nèi)存區(qū)域中去查詢一遍」。
和「MyISAM引擎中的 key_buffer_size」 一樣,innodb_buffer_pool_size設(shè)置了 InnoDB 引擎需求最大的一塊內(nèi)存區(qū)域,直接關(guān)系到InnoDB存儲(chǔ)引擎的性能,所以如果有足夠的內(nèi)存,盡可將該參數(shù)設(shè)置到足夠大,將盡可能多的InnoDB的索引及數(shù)據(jù)都放入到該緩存區(qū)域中,直至全部。
說到緩存肯定少不了,緩存命中率。那innodb該如何計(jì)算?
計(jì)算出緩存命中率后,在根據(jù)命中率來對(duì)innodb_buffer_pool_size 參數(shù)大小進(jìn)行優(yōu)化
「除開查詢緩存。數(shù)據(jù)庫(kù)查詢的性能也與MySQL的連接數(shù)有關(guān)」
table_cache 用于設(shè)置 table 高速緩存的數(shù)量。
show global status like 'open%_tables'; # 查看參數(shù)
由于每個(gè)客戶端連接都會(huì)至少訪問一個(gè)表,因此該參數(shù)與max_connections有關(guān)。當(dāng)某一連接訪問一個(gè)表時(shí),MySQL會(huì)檢查當(dāng)前已緩存表的數(shù)量。
如果該表已經(jīng)在緩存中打開,則會(huì)直接訪問緩存中的表以加快查詢速度;如果該表未被緩存,則會(huì)將當(dāng)前的表添加進(jìn)緩存在進(jìn)行查詢。
在執(zhí)行緩存操作之前,table_cache參數(shù)用于限制緩存表的最大數(shù)目:
如果當(dāng)前已經(jīng)緩存的表未達(dá)到table_cache數(shù)目,則會(huì)將新表添加進(jìn)來;若已經(jīng)達(dá)到此值,MySQL將根據(jù)緩存表的最后查詢時(shí)間、查詢率等規(guī)則釋放之前的緩存。
02平臺(tái)級(jí)緩存
什么是平臺(tái)級(jí)緩存,說的這個(gè)玄乎?
平臺(tái)級(jí)緩存是指你所用什么開發(fā)語(yǔ)言,具體選擇的是那個(gè)平臺(tái),畢竟緩存本身就是提供給上層調(diào)用。主要針對(duì)帶有緩存特性的應(yīng)用框架,或者可用于緩存功能的專用庫(kù)。
如:
- PHP中的Smarty模板庫(kù)
- Java中,緩存框架更多,如Ehcache,Cacheonix,Voldemort,JBoss Cache,OSCache等等。
Ehcache是現(xiàn)在最流行的純Java開源緩存框架,配置簡(jiǎn)單、結(jié)構(gòu)清晰、功能強(qiáng)大,是從hibernate的緩存開始被廣泛使用起來的。EhCache有如下特點(diǎn):
Ehcache的系統(tǒng)結(jié)構(gòu)如圖所示:
什么是分布式緩存呢?好像我還沒搞明白,小吒哥
首先得看看恒古不變的“分布式”,即它是獨(dú)立的部署到多個(gè)服務(wù)節(jié)點(diǎn)上或者獨(dú)立的進(jìn)程,彼此之間僅僅通過消息傳遞進(jìn)行通信和協(xié)調(diào)。
「也就是說分布式緩存,它要么是在單機(jī)上有多個(gè)實(shí)例,要么就獨(dú)立的部署到不同服務(wù)器,從而把緩存分散到各處」
最后通過客戶端連接到對(duì)應(yīng)的節(jié)點(diǎn)來進(jìn)行緩存操作。
Voldemort是一款基于Java開發(fā)的分布式鍵-值緩存系統(tǒng),像JBoss的緩存一樣,Voldemort同樣支持多臺(tái)服務(wù)器之間的緩存同步,以增強(qiáng)系統(tǒng)的可靠性和讀取性能。
Voldemort有如下特點(diǎn):
Voldemort的邏輯架構(gòu)圖
Voldemort相當(dāng)于是Amazon Dynamo的一個(gè)開源實(shí)現(xiàn),LinkedIn用它解決了網(wǎng)站的高擴(kuò)展性存儲(chǔ)問題。
簡(jiǎn)單來說,就平臺(tái)級(jí)緩存而言,只需要在框架側(cè)配置一下屬性即可,而不需要調(diào)用特定的方法或函數(shù)。
系統(tǒng)中引入緩存技術(shù)往往就是從平臺(tái)級(jí)緩存開始,平臺(tái)級(jí)緩存也通常會(huì)作為一級(jí)緩存使用。
既然平臺(tái)級(jí)緩存都使用框架配置來實(shí)現(xiàn),這咋實(shí)現(xiàn)緩存的分布式呢?節(jié)點(diǎn)之間都沒有互相的消息通訊了
如果單看,框架緩存的調(diào)用,那確實(shí)沒辦法做到分布式緩存,因?yàn)樽陨頉]得像Redis那樣分布式的部署方式,通過網(wǎng)絡(luò)把各節(jié)點(diǎn)連接 。
但本地平臺(tái)緩存可通過遠(yuǎn)程過程調(diào)用,來操作分布在各個(gè)節(jié)點(diǎn)上的平臺(tái)緩存數(shù)據(jù)。
在 Ehcache 中:
03應(yīng)用級(jí)緩存
當(dāng)平臺(tái)級(jí)緩存不能滿足系統(tǒng)的性能時(shí),就要考慮使用應(yīng)用級(jí)緩存。應(yīng)用級(jí)緩存,需要開發(fā)者通過代碼來實(shí)現(xiàn)緩存機(jī)制。
有些許 一方有難,八方支援 的感覺。自己搞不定 ,請(qǐng)教別人
這是NoSQL的戰(zhàn)場(chǎng),不論是Redis還是MongoDB,以及Memcached都可作為應(yīng)用級(jí)緩存的技術(shù)支持。
「一種典型的方式是每分鐘或一段時(shí)間后統(tǒng)一生成某類頁(yè)面存儲(chǔ)在緩存中,或者可以在熱數(shù)據(jù)變化時(shí)更新緩存。」
為啥平臺(tái)緩存還不能滿足系統(tǒng)性能要求呢?它不是還可以減少應(yīng)用緩存的網(wǎng)絡(luò)開銷嗎 那你得看這幾點(diǎn):
3.1面向Redis的緩存應(yīng)用
Redis是一款開源的、基于BSD許可的高級(jí)鍵值對(duì)緩存和存儲(chǔ)系統(tǒng),例如:新浪微博有著幾乎世界上最大的Redis集群。
為何新浪微博是世界上最大的Redis集群呢?
微博是一個(gè)社交平臺(tái),其中用戶關(guān)注與被關(guān)注、微博熱搜榜、點(diǎn)擊量、高可用、緩存穿透等業(yè)務(wù)場(chǎng)景和技術(shù)問題。Redis都有對(duì)應(yīng)的hash、ZSet、bitmap、cluster等技術(shù)方案來解決。
在這種數(shù)據(jù)關(guān)系復(fù)雜、易變化的場(chǎng)景上面用到它會(huì)顯得很簡(jiǎn)單。比如:
「用戶關(guān)注與取消」:用hash就可以很方便的維護(hù)用戶列表,你可以直接找到key,然后更改value里面的關(guān)注用戶即可。
如果你像 memcache ,那只能先序列化好用戶關(guān)注列表存儲(chǔ),更改在反序列化。然后再緩存起來,像大V有幾百萬、上千萬的用戶,一旦關(guān)注/取消。當(dāng)前任務(wù)的操作就會(huì)有延遲。
Reddis主要功能特點(diǎn)
-
主從同步
Redis支持主從同步,數(shù)據(jù)可以從主服務(wù)器向任意數(shù)量的從服務(wù)器同步, 「從服務(wù)器可做為」關(guān)聯(lián)其他從服務(wù)器的主服務(wù)器。這使得Redis可執(zhí)行單層樹狀復(fù)制。
-
發(fā)布/訂閱
由于實(shí)現(xiàn)了 「發(fā)布/訂閱機(jī)制」,使得從服務(wù)器在任何地方同步樹的時(shí)候,可訂閱一個(gè)頻道并接收主服務(wù)器完整的消息發(fā)布記錄。同步對(duì)讀取操作的可擴(kuò)展性和數(shù)據(jù)冗余很有幫助。
-
集群
Redis 3.0版本加入cluster功能, 「解決了Redis單點(diǎn)無法橫向擴(kuò)展的問題」。Redis集群采用無中心節(jié)點(diǎn)方式實(shí)現(xiàn),無需proxy代理,客戶端直接與Redis集群的每個(gè)節(jié)點(diǎn)連接,根據(jù)同樣的哈希算法計(jì)算出key對(duì)應(yīng)的slot,然后直接在slot對(duì)應(yīng)的Redis上執(zhí)行命令。
從Redis視角來看,響應(yīng)時(shí)間是最苛刻的條件,增加一層帶來的開銷是不能接受的。因此,Redis實(shí)現(xiàn)了客戶端對(duì)節(jié)點(diǎn)的直接訪問,為了去中心化,節(jié)點(diǎn)之間通過Gossip協(xié)議交換相互的狀態(tài),以及探測(cè)新加入的節(jié)點(diǎn)信息。Redis集群支持動(dòng)態(tài)加入節(jié)點(diǎn),動(dòng)態(tài)遷移slot,以及自動(dòng)故障轉(zhuǎn)移。
Redis集群的架構(gòu)示意如圖所示。
那什么是 Gossip 協(xié)議呢?感覺好高大上,各種協(xié)議頻繁出現(xiàn)
Gossip 協(xié)議是一個(gè)多播協(xié)議,基本思想是:
一個(gè)節(jié)點(diǎn)想要分享一些信息給網(wǎng)絡(luò)中的其他的一些節(jié)點(diǎn)。于是,它周期性的隨機(jī)選擇一些節(jié)點(diǎn),并把信息傳遞給這些節(jié)點(diǎn)。這些收到信息的節(jié)點(diǎn)接下來會(huì)做同樣的事情,即把這些信息傳遞給其他一些隨機(jī)選擇的節(jié)點(diǎn)。直至全部的節(jié)點(diǎn)。
即,Redis集群中添加、剔除、選舉主節(jié)點(diǎn),都是基于這樣的方式。
例如:當(dāng)加入新節(jié)點(diǎn)時(shí)(meet),集群中會(huì)隨機(jī)選擇一個(gè)節(jié)點(diǎn)來邀請(qǐng)「新節(jié)點(diǎn)」,此時(shí)只有邀請(qǐng)節(jié)點(diǎn)和被邀請(qǐng)節(jié)點(diǎn)知道這件事,其余節(jié)點(diǎn)要等待 ping 消息一層一層擴(kuò)散。除了 Fail 是立即全網(wǎng)通知的,其他諸如新節(jié)點(diǎn)、節(jié)點(diǎn)重上線、從節(jié)點(diǎn)選舉成為主節(jié)點(diǎn)、槽變化等,都需要等待被通知到,所以Gossip協(xié)議也是最終一致性的協(xié)議。
這種多播的方式,是不是忽然有種好事不出門,壞事傳千里的感腳
然而,Gossip協(xié)議也有不完美的地方,例如,拜占庭問題(Byzantine)。即,如果有一個(gè)惡意傳播消息的節(jié)點(diǎn),Gossip協(xié)議的分布式系統(tǒng)就會(huì)出問題。
注:Redis集群節(jié)點(diǎn)通信消息類型
所有的Redis節(jié)點(diǎn)通過PING-PONG機(jī)制彼此互聯(lián),內(nèi)部使用二進(jìn)制協(xié)議優(yōu)化傳輸速度和帶寬。
這個(gè)ping為啥能提高傳輸速度和帶寬?感覺不大清楚,小吒哥。那這里和OSI網(wǎng)絡(luò)層級(jí)模式有關(guān)系了
在OSI網(wǎng)絡(luò)層級(jí)模型下,ping協(xié)議隸屬網(wǎng)絡(luò)層,所以它會(huì)減少網(wǎng)絡(luò)層級(jí)傳輸?shù)拈_銷,而二進(jìn)制是用最小單位0,1表示的位。
帶寬是固定的,如果你發(fā)送的數(shù)據(jù)包都很小,那傳輸就很快,并不會(huì)出現(xiàn)數(shù)據(jù)包很大還要拆包等復(fù)雜工作。
相當(dāng)于別人出差1斤多MacPro。你出差帶5斤的戰(zhàn)神電腦。
Redis的瓶頸是什么呢? 吒吒輝給安排
Redis本身就是內(nèi)存數(shù)據(jù)庫(kù),讀寫I/O是它的強(qiáng)項(xiàng),瓶頸就在單線程I/O上與內(nèi)存的容量上。目前已經(jīng)有多線程了,
例如:Redis6具備網(wǎng)絡(luò)傳輸?shù)亩嗑€程模式,keydb直接就是多線程。
啥?還沒了解多Redis6多線程模式,后面單獨(dú)搞篇來聊聊
集群節(jié)點(diǎn)故障如何發(fā)現(xiàn)?
「節(jié)點(diǎn)故障」是通過「集群中超過半數(shù)的節(jié)點(diǎn)檢測(cè)失效時(shí)才會(huì)生效」??蛻舳伺cRedis節(jié)點(diǎn)直連,客戶端不需要連接集群所有節(jié)點(diǎn),連接集群中任何一個(gè)可用節(jié)點(diǎn)即可。
Redis Cluster把所有的物理節(jié)點(diǎn)映射到slot上,cluster負(fù)責(zé)維護(hù)node、slot和value的映射關(guān)系。當(dāng)節(jié)點(diǎn)發(fā)生故障時(shí),選舉過程是集群中所有master參與的,如果半數(shù)以上master節(jié)點(diǎn)與當(dāng)前master節(jié)點(diǎn)間的通信超時(shí),則認(rèn)為當(dāng)前master節(jié)點(diǎn)掛掉。
這為何不沒得Slave節(jié)點(diǎn)參與呢?
集群模式下,請(qǐng)求在集群模式下會(huì)自動(dòng)做到讀寫分離,即讀從寫主。但現(xiàn)在是選擇主節(jié)點(diǎn)。只能由主節(jié)點(diǎn)來進(jìn)行身份參與。
畢竟集群模式下,主節(jié)點(diǎn)有多個(gè),每個(gè)從節(jié)點(diǎn)只對(duì)應(yīng)一個(gè)主節(jié)點(diǎn),那這樣,你別個(gè)家的從節(jié)點(diǎn)能夠參與選舉整個(gè)集群模式下的主節(jié)點(diǎn)嗎?
就好像小姐姐有了對(duì)象,那就是名花有主,你還能在有主的情況下,去選一個(gè)?小心遭到社會(huì)的毒打
如果集群中超過半數(shù)以上master節(jié)點(diǎn)掛掉,無論是否有slave集群,Redis的整個(gè)集群將處于不可用狀態(tài)。
當(dāng)集群不可用時(shí),所有對(duì)集群的操作都不可用,都將收到錯(cuò)誤信息:
[(error)CLUSTERDOWN The cluster is down]。
支持Redis的客戶端編程語(yǔ)言眾多,可以滿足絕大多數(shù)的應(yīng)用,如圖所示。
3.2.多級(jí)緩存實(shí)例
一個(gè)使用了Redis集群和其他多種緩存技術(shù)的應(yīng)用系統(tǒng)架構(gòu)如圖所示
負(fù)載均衡
首先,用戶的請(qǐng)求被負(fù)載均衡服務(wù)分發(fā)到Nginx上,此處常用的負(fù)載均衡算法是輪詢或者一致性哈希,輪詢可以使服務(wù)器的請(qǐng)求更加均衡,而一致性哈??梢蕴嵘齆ginx應(yīng)用的緩存命中率。
什么是一致性hash算法?
hash算法計(jì)算出的結(jié)果值本身就是唯一的,這樣就可以讓每個(gè)用戶的請(qǐng)求都落到同一臺(tái)服務(wù)器。
默認(rèn)情況下,用戶在那臺(tái)在服務(wù)器登錄,就生成會(huì)話session文件到該服務(wù)器,但如果下次請(qǐng)求重新分發(fā)給其他服務(wù)器就又需要重新登錄。
而有了一致性hash算法就可以治愈它,它把請(qǐng)求都專心交給同一臺(tái)服務(wù)器,鐵打的專一,從而避免上述問題。 當(dāng)然這里的一致性hash原理就沒給大家講了。后面安排
nginx本地緩存
請(qǐng)求進(jìn)入到Nginx應(yīng)用服務(wù)器,首先讀取本地緩存,實(shí)現(xiàn)本地緩存的方式可以是Lua Shared Dict,或者面向磁盤或內(nèi)存的 Nginx Proxy Cache,以及本地的Redis實(shí)現(xiàn)等,如果本地緩存命中則直接返回。
這本地緩存怎么感覺那么特別呢?好像你家附近的小姐姐,離得這么近,可惜吃不著。呸呸呸,跑題啦
- Lua Shard Dict是指在nginx上,通過lua開辟一塊內(nèi)存空間來存儲(chǔ)緩存數(shù)據(jù)。相當(dāng)于用的是nginx的進(jìn)程資源
- nginx Cache指nginx獲取上游服務(wù)的數(shù)據(jù)緩存到本地。
- 本地Redis指nginx和Redis部署在同一臺(tái)服務(wù)上,由nginx直接操作Redis
啥!nginx還可直接操作Redis呀,聽我細(xì)細(xì)到來
這些方式各有千秋,Lua Shard Dict 是通過Lua腳本控制緩存數(shù)據(jù)的大小并可以靈活的通過邏輯處理來修改相關(guān)緩存數(shù)據(jù)。
而Nginx Proxy Cache開發(fā)相對(duì)簡(jiǎn)單,就是獲取上游數(shù)據(jù)到本地緩存處理。而「本地Redis則需要通過lua腳本編寫邏輯來設(shè)置」,雖然操作繁瑣了,但解決了本地內(nèi)存局限的問題。
所以nginx操作Redis是需要借助于 Lua 噠
nginx本地緩存有什么優(yōu)點(diǎn)?
Nginx應(yīng)用服務(wù)器使用本地緩存可以提升整體的吞吐量,降低后端的壓力,尤其應(yīng)對(duì)熱點(diǎn)數(shù)據(jù)的反復(fù)讀取問題非常有效。
本地緩存未命中時(shí)如何解決?
如果Nginx應(yīng)用服務(wù)器的本地緩存沒有命中,就會(huì)進(jìn)一步讀取相應(yīng)的分布式緩存——Redis分布式緩存的集群,可以考慮使用主從架構(gòu)來提升性能和吞吐量,如果分布式緩存命中則直接返回相應(yīng)數(shù)據(jù),并回寫到Nginx應(yīng)用服務(wù)器的本地緩存中。
如果Redis分布式緩存也沒有命中,則會(huì)回源到Tomcat集群,在回源到Tomcat集群時(shí)也可以使用輪詢和一致性哈希作為負(fù)載均衡算法。
那我是PHP技術(shù)棧咋辦?都不會(huì)用到j(luò)ava的Tomcat呀
nginx常用于反向代理層。而這里的Tomcat更多是屬于應(yīng)用服務(wù)器,如果換成PHP,那就由php-fpm或者swoole服務(wù)來接受請(qǐng)求。即不管什么語(yǔ)言,都應(yīng)該找對(duì)應(yīng)語(yǔ)言接受請(qǐng)求分發(fā)的東西。
當(dāng)然,如果Redis分布式緩存沒有命中的話,Nginx應(yīng)用服務(wù)器還可以再嘗試一次讀主Redis集群操作,目的是防止當(dāng)從Redis集群有問題時(shí)可能發(fā)生的流量沖擊。
這樣的設(shè)計(jì)方案我在下表示看不懂
如果你網(wǎng)站流量比較大,如果一次在Redis分布式緩存中未讀取到的話,直接透過到數(shù)據(jù)庫(kù),那流量可能會(huì)把數(shù)據(jù)庫(kù)沖垮。這里的一次讀主也是考慮到Redis集群中的主從延遲問題,為的就是防止緩存擊穿。
在Tomcat | PHP-FPM集群應(yīng)用中,首先讀取本地平臺(tái)級(jí)緩存,如果平臺(tái)級(jí)緩存命中則直接返回?cái)?shù)據(jù),并會(huì)同步寫到主Redis集群,在由主從同步到從Redis集群。
此處可能存在多個(gè)Tomcat實(shí)例同時(shí)寫主Redis集群的情況,可能會(huì)造成數(shù)據(jù)錯(cuò)亂,需要注意緩存的更新機(jī)制和原子化操作。
如何保證原子化操作執(zhí)行呢?
當(dāng)多個(gè)實(shí)例要同時(shí)要寫Redis緩存時(shí),為了保持原子化,起碼得在涉及這塊業(yè)務(wù)多個(gè)的 Key 上采用lua腳本進(jìn)行封裝,然后再通過分布式鎖或去重相同請(qǐng)求并入到一個(gè)隊(duì)列來獲取,讓獲取到鎖或從隊(duì)列pop的請(qǐng)求去讀取Redis集群中的數(shù)據(jù)。
如果所有緩存都沒有命中,系統(tǒng)就只能查詢數(shù)據(jù)庫(kù)或其他相關(guān)服務(wù)獲取相關(guān)數(shù)據(jù)并返回,當(dāng)然,我們已經(jīng)知道數(shù)據(jù)庫(kù)也是有緩存的。是不是安排得明明白白。
這就是多級(jí)緩存的使用,才能保障系統(tǒng)具備優(yōu)良的性能。
什么時(shí)候,小姐姐也能明白俺的良苦心。。。。默默的獨(dú)自流下了淚水
3.3.緩存算法
緩存一般都會(huì)采用內(nèi)存來做存儲(chǔ)介質(zhì),使用索引成本相對(duì)來說還是比較高的。所以在使用緩存時(shí),需要了解緩存技術(shù)中的幾個(gè)術(shù)語(yǔ)。
緩存淘汰算法
替代策略的具體實(shí)現(xiàn)就是緩存淘汰算法。
使用頻率:
- Least-Recently-Used(LRU) 替換掉最近被請(qǐng)求最少的對(duì)象。
在CPU緩存淘汰和虛擬內(nèi)存系統(tǒng)中效果很好。然而在直接應(yīng)用與代理緩存中效果欠佳,因?yàn)閃eb訪問的時(shí)間局部性常常變化很大。
瀏覽器就一般使用了LRU作為緩存算法。新的對(duì)象會(huì)被放在緩存的頂部,當(dāng)緩存達(dá)到了容量極限,底部的對(duì)象被去除,方法就是把最新被訪問的緩存對(duì)象放到緩存池的頂部。
- Least-Frequently-Used(LFU) 替換掉訪問次數(shù)最少的緩存,這一策略意圖是保留最常用的、最流行的對(duì)象,替換掉很少使用的那些數(shù)據(jù)。
然而,有的文檔可能有很高的使用頻率,但之后再也不會(huì)用到。傳統(tǒng)的LFU策略沒有提供任何移除這類文件的機(jī)制,因此會(huì)導(dǎo)致“緩存污染”,即一個(gè)先前流行的緩存對(duì)象會(huì)在緩存中駐留很長(zhǎng)時(shí)間,這樣,就阻礙了新進(jìn)來可能會(huì)流行的對(duì)象對(duì)它的替代。
- Pitkow/Recker 替換最近最少使用的對(duì)象
除非所有對(duì)象都是今天訪問過的。如果是這樣,則替換掉最大的對(duì)象。這一策略試圖符合每日訪問Web網(wǎng)頁(yè)的特定模式。這一策略也被建議在每天結(jié)束時(shí)運(yùn)行,以釋放被“舊的”、最近最少使用的對(duì)象占用的空間。
- Adaptive Replacement Cache(ARC) ARC介于LRU和LFU之間,為了提高效果,由2個(gè)LRU組成。
第一個(gè)包含的條目是最近只被使用過一次的,而第二個(gè)LRU包含的是最近被使用過兩次的條目,因此,得到了新的對(duì)象和常用的對(duì)象。ARC能夠自我調(diào)節(jié),并且是低負(fù)載的。
- Most Recently Used(MRU) MRU與LRU是相對(duì),移除最近最多被使用的對(duì)象。
當(dāng)一次訪問過來的時(shí)候,有些事情是無法預(yù)測(cè)的,并且在存系統(tǒng)中找出最少最近使用的對(duì)象是一項(xiàng)時(shí)間復(fù)雜度非常高的運(yùn)算,這時(shí)會(huì)考慮MRU,在數(shù)據(jù)庫(kù)內(nèi)存緩存中比較常見。
訪問計(jì)數(shù)
- Least Recently Used2 (LRU2)
LRU的變種,把被兩次訪問過的對(duì)象放入緩存池,當(dāng)緩存池滿了之后,會(huì)把有兩次最少使用的緩存對(duì)象去除。
因?yàn)樾枰檶?duì)象2次,訪問負(fù)載就會(huì)隨著緩存池的增加而增加。
- Two Queues(2Q) Two Queues是LRU的另一個(gè)變種。
把被訪問的數(shù)據(jù)放到LRU的緩存中,如果這個(gè)對(duì)象再一次被訪問,就把他轉(zhuǎn)移到第二個(gè)、更大的LRU緩存,使用了多級(jí)緩存的方式。去除緩存對(duì)象是為了保持第一個(gè)緩存池是第二個(gè)緩存池的1/3。
當(dāng)緩存的訪問負(fù)載是固定的時(shí)候,把LRU換成LRU2,就比增加緩存的容量更好。
緩存容量算法
-
SIZE 替換占用空間最大的對(duì)象,這一策略通過淘汰一個(gè)大對(duì)象而不是多個(gè)小對(duì)象來提高命中率。不過,可能有些進(jìn)入緩存的小對(duì)象永遠(yuǎn)不會(huì)再被訪問。SIZE策略沒有提供淘汰這類對(duì)象的機(jī)制,也會(huì)導(dǎo)致“緩存污染”。
-
LRU-Threshold 不緩存超過某一size的對(duì)象,其他與LRU相同。
-
Log(Size)+LRU 替換size最大的對(duì)象,當(dāng)size相同時(shí),按LRU進(jìn)行替換。
緩存時(shí)間
-
Hyper-G LFU的改進(jìn)版,同時(shí)考慮上次訪問時(shí)間和對(duì)象size。
-
Lowest-Latency-First 替換下載時(shí)間最少的文檔。顯然它的目標(biāo)是最小化平均延遲。
緩存評(píng)估
- Hybrid Hybrid 有一個(gè)目標(biāo)是減少平均延遲。
對(duì)緩存中的每個(gè)文檔都會(huì)計(jì)算一個(gè)保留效用,保留效用最低的對(duì)象會(huì)被替換掉。位于服務(wù)器S的文檔f的效用函數(shù)定義如下:
Cs是與服務(wù)器s的連接時(shí)間;
bs是服務(wù)器s的帶寬;frf代表f的使用頻率;sizef是文檔f的大小,單位字節(jié)。K1和K2是常量,Cs和bs是根據(jù)最近從服務(wù)器s獲取文檔的時(shí)間進(jìn)行估計(jì)的。
- Lowest Relative Value(LRV) LRV也是基于計(jì)算緩存中文檔的保留效用,然后替換保留效用最低的文檔。
隨機(jī)與隊(duì)列算法
- First in First out(FIFO)
FIFO通過一個(gè)隊(duì)列去跟蹤所有的緩存對(duì)象,最近最常用的緩存對(duì)象放在后面,而更早的緩存對(duì)象放在前面,當(dāng)緩存容量滿時(shí),排在前面的緩存對(duì)象會(huì)被踢走,然后把新的緩存對(duì)象加進(jìn)去。
- Random Cache 隨機(jī)緩存就是隨意的替換緩存數(shù)據(jù),比FIFO機(jī)制好,在某些情況下,甚至比LRU好,但是通常LRU都會(huì)比隨機(jī)緩存更好些。
還有很多的緩存算法,例如Second Chance、Clock、Simple time-based、Extended time-based expiration、Sliding time-based expiration……各種緩存算法沒有優(yōu)劣之分,不同的實(shí)際應(yīng)用場(chǎng)景,會(huì)用到不同的緩存算法。在實(shí)現(xiàn)緩存算法的時(shí)候,通常會(huì)考慮**使用頻率、獲取成本、緩存容量和時(shí)間等因素。 **
04.使用公有云的緩存服務(wù)
國(guó)內(nèi)的共有云服務(wù)提供商如阿里云、青云、百度云等都推出了基于Redis的云存儲(chǔ)服務(wù),這些服務(wù)的有如下特點(diǎn):
- 動(dòng)態(tài)擴(kuò)容:
用戶可以通過控制面板升級(jí)所需Redis的存儲(chǔ)空間,「擴(kuò)容過程中服務(wù)不需要中斷或停止」,整個(gè)擴(kuò)容過程對(duì)用戶是透明且無感知的,而自主使用集群解決Redis平滑擴(kuò)容是個(gè)很煩瑣的任務(wù),現(xiàn)在需要用你的小手按幾下鼠標(biāo)就能搞定,大大減少了運(yùn)維的負(fù)擔(dān)。
-
數(shù)據(jù)多備:
數(shù)據(jù)保存在一主一備兩臺(tái)機(jī)器中,其中一臺(tái)機(jī)器宕機(jī)了,數(shù)據(jù)還在另外一臺(tái)機(jī)器上有備份。 -
自動(dòng)容災(zāi):
主機(jī)宕機(jī)后系統(tǒng)能自動(dòng)檢測(cè)并切換到備機(jī)上,實(shí)現(xiàn)了服務(wù)的高可用性。 -
成本較低:
在很多情況下,為使Redis的性能更好,需要購(gòu)買一臺(tái)專門的服務(wù)器用于Redis的存儲(chǔ)服務(wù),但這樣會(huì)導(dǎo)致某些資源的浪費(fèi),購(gòu)買Redis云存儲(chǔ)服務(wù)就能很好地解決這樣的問題。
有了Redis云存儲(chǔ)服務(wù),能使后臺(tái)開發(fā)人員從煩瑣的運(yùn)維中解放出來。應(yīng)用后臺(tái)服務(wù)中,如果自主搭建一個(gè)高可用、高性能的Redis集群服務(wù),是需要投入相當(dāng)?shù)倪\(yùn)維成本和精力。
如果使用云服務(wù),就沒必要投入這些成本和精力,可以讓后臺(tái)應(yīng)用的開發(fā)人員更專注于業(yè)務(wù)。
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問題,請(qǐng)聯(lián)系我們,謝謝!