您當前的位置:首頁 > 農業

網際網路Java知識點整理(三),分散式快取

作者:由 首席 發表于 農業時間:2019-07-14

1、為啥要用快取?

兩個目的,高效能和高併發。

高效能:

有一個請求過來,這個請求要從mysql資料庫查詢很多資料後返回一個結果,這些處理大概需要花費幾百ms,然後這個結果可能很久一段時間都不會變化。有很多人來請求,每個人都要花費幾百ms來處理然後返回結果。我們如果把結果儲存在快取裡,有人來請求就直接把快取裡的值返回,這樣一來就第一次請求的人可能需要耗費幾百ms來處理資料,之後的人來請求就直接從快取裡獲取資料就好了,這樣一來大大提高了效能。

高併發:

mysql資料庫可以扛住的併發量可能也就在2000qps左右,一旦高峰期每秒有1W請求過來,如果直接懟到資料庫那裡去,絕對可以把mysql搞死,這樣就悲劇了。我們應該使用快取,將常用的資料儲存起來,如果有請求過來的話,大部分都可以從快取裡獲取資料,只有少部分快取裡沒有的那些資料才會走到mysql這裡來。這樣一來單機可以扛住的併發量就大大提高了,1萬qps根本不算什麼。

2、redis和memcached有什麼區別?redis的執行緒模型是什麼?為什麼redis是單執行緒的但是還可以支撐高併發?

redis和memcached的區別:

redis和memcached的主要區別在於,redis可以支援多種資料結構,memcached僅支援String。memcached裡,需要將資料拿到客戶端進行修改後再set回去,大大增加了網路I/O開銷,而redis中,即使進行復雜操作也同GET/SET一樣高效。還有一點就是memcached不支援叢集模式。

redis的執行緒模型是什麼?

redis內部有一個檔案事件處理器(file event handler),這個檔案事件處理器是單執行緒的,因此redis也被稱為單執行緒的模型。檔案事件處理器分為4個部分:多個socked,I/O多路複用,檔案事件分派器和檔案事件處理器。其中,檔案事件處理器又分為3個:連線應答處理器、命令請求處理器還有就是命令回覆處理器。

網際網路Java知識點整理(三),分散式快取

客戶端和redis伺服器首先要建立連線,這個時候會產生一個AE_READABLE事件,IO多路複用程式會不停的監聽產生的事件,然後將事件壓進記憶體佇列中,這時檔案分派處理器發現佇列中有事件了,它就會根據事件型別去查詢檔案事件處理器,剛才產生的是建立連線的AE_READABLE事件,這個時候它就會將事件傳送給連線應答處理器。然後客戶端這裡傳送了一個set key的命令過來,這個時候會又產生了一個AE_READABLE事件,被IO多路複用監聽到了,它又將事件壓如佇列中,這個時候檔案分派處理器發現有了一個事件,它發現連線是已經建立的,就會將這個AE_READABLE事件傳送給命令請求處理器,這個處理器就會在記憶體中處理set key命令,並且與命令回覆處理器建立聯絡。最後客戶端這裡準備好接收處理結果的時候,會產生一個AE_WRITEABLE事件,IO多路複用程式發現又有一個事件過來了,它還是會將這個事件壓到記憶體佇列裡由檔案分派處理器分派,這時由於發現是一個AE_WRITEABLE事件,他就會將事件傳送給命令回覆處理器,命令回覆處理器就會把上次處理的set value結果返回給客戶端並解除此次AE_WRITEABLE事件與命令回覆處理器的關係。

為什麼redis是單執行緒的但是還可以支撐高併發?

1、IO多路複用程式不阻塞事件,它不停的監聽事件,有事件來就把它壓到記憶體佇列中去。2、處理操作都在記憶體中進行,本身速度就非常快。3、正因為是單執行緒,節省了多執行緒中執行緒上下文切換的時間。

3、redis都有哪些資料型別?分別在哪些場景下使用比較合適?

redis都有哪些資料型別?

5種資料型別:String、List、Hash、Set、SortedSet

分別在哪些場景下使用比較合適?

String:簡單的key-value對;List列表,列表裡可以放各種物件,這個就可以很多種玩法了;Hash:一個key裡面可以放一個Hash,然後每個Hash物件裡又可以繼續放key-value的鍵值對;Set:無序集合,可以自動去重;然後就可以使用Set自帶的一些方法來做並集交集之類的;SortedSet:有序集合,可以去重且自動排序,而且可以自己定義排序規則。

4、redis的過期策略都有哪些?記憶體淘汰機制都有哪些?

redis的過期策略都有哪些?

定期刪除和惰性刪除。定期刪除是指,預設每隔100ms,redis會隨機抽取一些設定了過期時間的key來檢查這些key是否已經過期,如果過期的話就刪除。惰性刪除是指,redis不主動去刪除key,而是等待別人來使用這個key,如果使用的時候發現這個key已經過期,則刪除該key,不給你返回。

記憶體淘汰機制有哪些?

有六種淘汰機制:1、noeviction:不做記憶體淘汰,如果發現記憶體滿了,再往redis裡面寫資料就報錯;2、allkeys-lru:當記憶體不夠的時候,查詢最近一段時間裡最少使用的key來把它刪除;3、allkeys-radom:當記憶體不夠時,隨機查詢記憶體中的key來進行刪除;4、volatile-lru:當記憶體不夠時,在記憶體中設定了過期時間的key裡查詢最近最少使用的key來刪除;5、volatile-radom:當記憶體不夠時,在記憶體中設定了過期時間的key裡隨機查詢key來刪除;6、volatile-ttl:當記憶體不夠時,在記憶體中設定了過期時間的key中,優先刪除那些較早設定過期時間的key。

5、如何保證Redis的高併發和高可用?redis的主從複製原理能介紹一下麼?redis的哨兵原理能介紹一下麼?

如何保證Redis的高併發和高可用?

redis單機可以支撐的qps大概在於萬級左右,使用讀寫分離一般都可以很好的支撐高併發,因為實際情況中寫的請求要遠遠小於讀的請求。我們可以使用主從架構來實現,一個master node多個slave node。主負責寫操作,讀操作全部走從節點,如果發現QPS還在增加,現有的從節點快到極限的情況下,我們還可以進行水平擴容就是增加salve node的機器。redis的高可用架構叫做故障轉移或者稱為主備切換。哨兵叢集會自動檢測到某個master node故障了,然後它會從slave node中選舉一個作為新的master node來快速恢復服務。

redis的主從複製原理:

1、如果slave node第一次連線master node,這個時候master node在後臺會啟動一個執行緒來寫當前快取內容的RDB檔案,並且將之後收到的寫命令快取在記憶體中,當RDB檔案建立成功後,master node就會將這個RDB檔案傳送給slave node。slave 會把RDB內容寫入磁碟然後再將內容讀入記憶體中。然後master會把記憶體中快取的寫命令再發送給slave,讓slave做同步。slave node如果跟master node有網路問題導致斷開連線了,之後會嘗試重連,master發現有很多slave都重連過來了,這時它僅僅會執行一次RDB save操作,生成一個RDB檔案出來給所有slave同步。2、斷點續傳。在master node的記憶體中有一個backlog,裡面儲存了master id 和 上次同步的位置offset。salve node由於網路問題重新連線過來的時候,slave就會從offset之後的位置開始複製。如果沒有找到offset就會進行一次全量複製操作。3、我門還可以將無磁碟化複製引數開啟,這個時候master node建立的RDB就不會落磁碟,直接在記憶體中建立後就傳送給slave。這裡還有一個delay引數,可以設定等待多久後開始複製,因為要等儘可能多的slave node連線過來。4、slave node不會過期key,master node透過lru淘汰了部分key,這些key會透過模擬del命令傳送給slave。

redis的哨兵原理:

1、哨兵可以對redis叢集進行監控,檢視master node和slave node是否正常工作;2、訊息通知,如果某個redis例項有故障,它會發送訊息給管理員;3、故障轉移,如果master node掛了,它會快速從slave node中選舉一個新的節點作為master node;4、如果故障轉移發生了,它會將新的master node的地址傳送給客戶端。

哨兵的核心知識:

1、哨兵至少需要3個例項來保證自己的健壯性;2、哨兵+redis主從架構只能保證高可用性,不能保證訊息的零丟失。3、為什麼哨兵一定要3個節點以上才能保證執行,2個節點就不行?因為哨兵有一個多數原則,當要進行故障轉移的時候需要多數哨兵授權才能進行。3臺機器中有2臺機器授權就行;如果2臺機器,需要2臺機器都授權才行;但是如果2臺機器中有1臺掛了,這時只有1臺機器了,那就沒人可以授權進行故障轉移。

6、資料丟失的兩種情況

非同步資料複製,因為master->slave進行資料複製是透過非同步進行的,在傳輸過程中如果master掛了,資料就丟了。

腦裂問題。有一個master由於網路問題突然脫離網路了,但是實際上還在執行中。這個時候哨兵就會進行故障轉移,重新從slave node中選舉一個新的master node出來,在這個期間,由於客戶端還和之前的master node保持著連線,源源不斷的進行著資料互動。之後網路恢復,這時自己就會被當做一個slave node掛到叢集中去,新的master node就會將資料同步過來,這時之前網路出問題這段時間內的資料就丟失了。

怎麼避免?1、避免非同步資料複製問題,這裡有一個引數,min-slaves-max-lag,這個引數可以設定延遲時間,如果master和所有slave中的延遲時間超過了之前引數的設定,就認為master掛了,這個時候就會不再接收任何請求來減少損失。2、有一個min-slaves-to-write引數,決定一個master下至少應該掛有多少個slave,如果當前master下的slave數量少於配置值,就認為發生了腦裂,master就會拒絕客戶端的寫請求。這兩個引數可以一同使用來減少損失。

7、redis哨兵的多個核心底層原理的深入解析(包含slave選舉演算法)

主觀宕機和客觀宕機,如果一個哨兵認為一個master宕機了,這就是主觀宕機;如果quorum數量的哨兵都認為這個master宕機了,這就變成了客觀宕機。

進行故障轉移時的主從切換,需要選舉一個slave來作為新的master node。選舉時需要考慮的幾個方面:(1)、與master斷開的時長;(2)、slave的優先順序(引數配置);(3)、offset;(4)、run id。如果slave與master斷開的時長超過down-after-milliseconds的十倍並加上master的宕機時長,這個slave就不適合當master。然後按照優先順序排序,越小的排在前面;之後再按照offset來排序,offset值越大說明同步master的資料就越多,就應該排在前面;最後再按照run id來排序,小的排前面。按照這個順序選舉出一個slave。

quorum和majority。當有quorum數量的哨兵認為master宕機,就變成客觀宕機。這時就會選舉一個哨兵出來進行故障轉移。這個哨兵必須要獲得一定數量哨兵的授權才能進行操作。當majority>=quorum則只要majority數量的哨兵授權就行,但是當quorum>majority時,則需要quorum數量的哨兵授權。

執行切換的那個哨兵在進行切換後,會從新的master node這裡拿到一個configuration epoch,也就是version。每次切換的version必須是唯一的,如果第一個哨兵切換失敗,則會有另一個哨兵在等待一段時間(配置)後再次切換,這時就又會產生一個新的version。

configuration傳播。哨兵完成切換後,就會將master配置資訊儲存到本地,然後同步給其他哨兵。這時version就很重要了,新的master配置是跟著新的version的,其他哨兵根據version大小來更新自己的master配置。

8、怎麼保證redis掛掉之後再重啟資料可以進行恢復?

需要對redis資料進行持久化操作。有兩種持久化機制,RDB和AOF。RDB是指在某一時間點對redis內部資料做一份全量快照。AOF是指對寫操作保留日誌,以append-only模式寫入日誌檔案。如果我們僅僅希望redis做記憶體快取,那麼不應該進行任何持久化操作,但是這樣的話機器一旦重啟,記憶體中的快取資料就都丟失了。

RDB持久化的優點:1、RDB持久化會在不同時間點生成多個RDB檔案,這些檔案可以儲存在異地磁碟中作為冷備。2、RDB持久化操作不會對效能產生太多影響,因為是另開一個執行緒專門做磁碟IO操作。3、RDB檔案裡儲存的本身就是資料,恢復redis時速度較快。

RDB持久化的缺點:1、RDB對於資料的丟失率來看,沒有AOF那麼好,因為它是每隔一段時間去做持久化操作,如果在進行操作途中宕機,那麼這段時間的資料就都丟失了。2、如果RDB檔案特別大,在RDB快照生成的時候,有可能會暫停服務一小段時間,對客戶端產生影響。

AOF持久化的優點:1、AOF持久化可以更好的保留資料。一般是每隔1秒就執行一次,要丟也就只丟1秒的資料。2、AOF以append-only模式寫日誌檔案,沒有任何磁碟定址開銷,因此效率非常高,也不容易破損,即使破損了也只是檔案尾部,容易修復。3、AOF檔案即使過大導致後臺重寫日誌時,也不會對客戶端產生影響。因為在進行rewrite log的時候,會對指令進行壓縮,創建出一份最小指令日誌。在建立指令日誌檔案的時候,老的指令檔案還可以繼續進行讀寫,當merge後的日誌檔案好了後,再替換老的日誌檔案。4、AOF檔案記錄的都是寫命令,可讀性高,容易恢復。比如有人不小心執行了flushall命令,那我們只需要開啟AOF檔案,將最後一條flushall命令刪除,然後執行恢復就可以恢復所有資料。

AOF持久化的缺點:1、檔案較RDB要大;AOF開啟後效能會比開啟RDB要低一些。

AOF和RDB要怎麼選擇?不能只用RDB來進行冷備,這樣資料丟失太多;也不能只做AOF來進行冷備,因為恢復速度比較慢,而且健壯性沒有簡單粗暴的RDB來的好。因此要綜合來看,恢復首先選擇AOF,來減少資料丟失。當AOF檔案損壞或者不可用的時候,我們要用RDB檔案來進行恢復。

9、redis cluster叢集模式的原理

如何在讀寫分離+高可用的模式下,繼續擴容支撐海量資料?

使用redis cluster叢集就可以做到橫向擴容。一個master多個slave作為一個叢集,我們可以按照需求多配置幾個這樣的叢集。

分散式資料儲存的核心演算法:hash演算法、一致性hash演算法(memcached)、hash slot演算法(redis cluster):

1、hash演算法:對key進行hash後master節點數進行取模。比如3個master節點,對key進行hash後對3取模,獲得的結果肯定在0到2之間,然後路由到對應的叢集上就好了。一般是沒有問題的,但是如果其中某一個master節點掛了,然後節點數就變成了2,然後再取模再路由到剩餘的2個節點上,這樣key就很難對應到快取的值,大量的流量就會被打到資料庫那裡,很可能會把資料庫打死;2、一致性hash演算法:將節點分佈在圓環上,每個節點都對應一個值,也是一樣的對key進行hash後取值,將這個值落在圓環上後會順時針去找距離自己最近的那個節點。這樣如果某一個節點宕機,只會導致這個節點的key找不到對應的快取,而不會影響到其他節點的key,還有一個快取熱點問題,某一個區域的值特別多,就很容易導致大量的請求都湧入同一個節點,導致效能瓶頸問題。不過這個問題可以透過增加虛擬節點,使得分佈更加分散,這樣來解決;3、hash slot演算法:在每個節點上分配hash slot,一共有16384個hash slot。對每個key計算CRC16值,然後對16384取模後決定這個key落在哪個節點上。hash slot的增加和移除很簡單,增加一個master可以從其他節點移動一些hash slot過來,如果去除一個節點,也可以從這個節點移動hash slot到其他節點上。還是以3個節點為例,如果1個節點掛了,因為每個節點上的hash slot的值是固定的,因此影響的僅僅是這臺機器上的key而不會影響其他節點。

10、redis cluster叢集的高可用性和主備切換

redis cluster節點間採取gossip協議進行通訊,好處在於元資料分散在各個節點上,不是集中在一個地方,更新請求會陸陸續續打到各個節點上去,降低了壓力;缺點是更新有延時,可能導致集群裡的一些操作有滯後。

redis cluster的高可用的原理,幾乎跟哨兵是類似的。1、判斷節點宕機:如果一個節點認為另一個節點宕機,那就是pfail主觀宕機,如果多個節點都認為這個節點宕機了,那就是fail客觀宕機。在cluster-node-timeout內如果某一個節點一直沒有返回pong,則可認為這個節點pfail,那麼會在gossig ping訊息中,ping給其他節點,如果超過半數節點都認為這個節點宕機了,那麼就變成fail。2、從節點過濾:從宕機master節點的slave節點中需要選舉一個節點接替master節點。檢查每個slave節點和master節點的斷開時間,如果斷開時間超過cluster-node-timeout * cluster-slave-validity-factor則這個節點沒有資格參加選舉。3、從節點選舉:對所有節點進行排序,slave priroity、offset(倒序)、run id,順序越靠前優先進行選舉。所有master節點對參加選舉的slave進行投票,獲得超過半數票數那個slave節點就會提升成master節點,進行主備切換。

11、快取雪崩和快取穿透

快取雪崩:

假設每天高峰期有每秒5000個請求過來,正常情況下4000個請求都會被快取擋住,然後剩餘1000個請求走資料庫。然後突然有一天快取掛了,1秒鐘內這5000個請求都被打到資料庫這裡,資料庫瞬間就被打死了,導致系統也就崩潰了。重啟資料庫後又一次被打死,由於快取問題導致系統崩潰稱之為雪崩。

快取雪崩解決方案:

在系統和redis快取之間增加一層ehcache,一個請求過來先去查詢ehcache,找不到再去找redis還是找不到再去資料庫查詢,查詢到後將結果再回寫redis和ehcache。如果redis掛了,這個時候還有一層快取ehcache可以檔住一部分請求。然後增加一個限流元件(hystrix元件),比如每秒5000個請求過來,限流元件就設定每秒2000個請求允許過來進入資料庫查詢。然後再開發一個降級元件,將剩餘的3000個請求呼叫這個元件,返回一些提示出去。

快取穿透:

快取穿透一般出現在駭客惡意攻擊中。比如一秒鐘發過來5000個請求,其中1000個請求是正常使用者請求走了快取,剩餘4000個請求是駭客發出的亂七八糟的請求,肯定在快取中沒有,然後就走到了資料庫端,悲劇了,資料庫被打死了。

快取穿透解決方案:

不管什麼請求過來就儲存在快取中,即使資料庫查詢不到就寫一個空值在快取中去。下次再來就不會走到資料庫那裡去。

12、如何保證快取與資料庫的雙寫一致性?

最經典的快取+資料庫讀寫的模式,cache aside pattern:

讀的時候先讀快取,如果快取不存在,則讀取資料庫然後將讀取的值再次儲存在快取中。更新的時候,先刪除快取後然後更新資料庫。為什麼是刪除快取而不是更新呢?如果每次更新資料都觸發一次快取更新的話,這樣比較浪費資源,原因在於大多數的資料其實被讀取的機率不是很高,但是有可能被經常更新。每做一次更新都要重新更新一次快取,顯然不合適。我們將快取刪除,然後如果要用到的時候再做一次計算後儲存快取,這樣可以避免多次無用的更新操作,是一種lazy計算的思想。

高併發的場景下,經典的快取+資料庫讀寫的模式,cache aside pattern有什麼問題?

上面我們看到了,更新一個數據是會先刪除快取然後去更新資料庫。有一個請求過來,這個時候快取已經被刪除了,但是資料庫還沒有更新成功,它一看快取是空的,則會去資料庫裡獲取資料這個時候資料就是老的,快取裡也儲存了老的資料。然後資料更新成功,導致快取中的資料和資料庫裡的資料不一致。

怎麼解決上面的問題?

如果每天的是上億的流量,每秒併發讀是幾萬,每秒只要有資料更新的請求,就可能會出現上述的資料庫+快取不一致的情況,高併發了以後,問題是很多的。我們可以在記憶體中開闢多個佇列。當有資料過來我們可以對資料的唯一標識進行hash路由,使相同路由的資料可以定位到唯一的佇列中去。當執行的是更新操作的時候,可以直接將這個請求壓入佇列中,如果發現是讀請求,並且快取裡值是空的場合也把它壓入佇列中。佇列中的請求使用一個執行緒來序列處理,這樣的話就可以保證讀取的資料都是最新的。

高併發的場景下,該解決方案要注意的問題:

1、一定要注意阻塞問題,因為在更新處理完後才會處理讀請求,因此如果有大量讀請求的場合有可能會阻塞。這個時候我們就可以做一下最佳化。當一個讀請求處理後,之後的讀請求可以不走佇列,可以直接從快取中獲取,直到又有一個新的更新操作過來,或者快取值為空。2、使用這個方案需要事先進行好壓測,一定要搞清楚在多少併發量的情況下目前的架構可以滿足效能要求,如果超過這個併發量則需要增加機器來增加佇列。3、如果伺服器部署了多個例項,必須保證對於同一個資料標識的key一定要路由到同一臺伺服器的同一個佇列上。這個可以由nginx來保證路由到相同的伺服器例項上。

13、redis的併發競爭問題該如何解決?

問題產生的原因:

高併發的場景下,有多個請求都要更新一個key,這些更新其實是有時序的,不能先後順序錯亂。但是很遺憾,由於網路等問題,請求過來的先後順序有變更,這樣一來實際更新快取的順序也就亂了,最後導致出問題。

解決方案:

首先,要使用分散式鎖保證在同一時間點只有一個請求可以處理這個key,其他請求的讀寫都掛起等待,只有等上一個請求處理完成後才能處理讀寫。然後我們更新的時候加上版本號,更新成功後版本號+1。在更新快取的時候比較版本號,只有比自己版本號新的那個資料才去做更新,否則不更新。

14、生產環境的redis叢集的部署架構是什麼樣的?

redis cluster,10臺機器,5臺機器部署了redis主例項,另外5臺機器部署了redis的從例項,每個主例項掛了一個從例項,5個節點對外提供讀寫服務,每個節點的讀寫高峰qps可能可以達到每秒5萬,5臺機器最多是25萬讀寫請求/s。

機器是什麼配置?32G記憶體+8核CPU+1T磁碟,但是分配給redis程序的是10g記憶體。5臺機器對外提供讀寫,一共有50g記憶體。因為每個主例項都掛了一個從例項,所以是高可用的,任何一個主例項宕機,都會自動故障遷移,redis從例項會自動變成主例項繼續提供讀寫服務。

你往記憶體裡寫的是什麼資料?每條資料的大小是多少?商品資料,每條資料是10kb。100條資料是1mb,10萬條資料是1g。常駐記憶體的是200萬條商品資料,佔用記憶體是20g,僅僅不到總記憶體的50%。目前高峰期每秒就是3500左右的請求量。

標簽: Redis  master  快取  節點  slave