版權聲明:本文為部落客原創文章,遵循 CC 4.0 BY-SA 版權協定,轉載請附上原文出處連結和本聲明。
本文連結:https://blog.csdn.net/zhengzhaoyang122/article/details/82184029
1、什麼是緩存?
☞ 緩存就是資料交換的緩沖區(稱作:Cache),當某一硬體要讀取資料時,會首先從緩存彙總查詢資料,有則直接執行,不存在時從記憶體中擷取。由于緩存的資料比記憶體快的多,是以緩存的作用就是幫助硬體更快的運作。
☞ 緩存往往使用的是RAM(斷電既掉的非永久存儲),是以在用完後還是會把檔案送到硬碟等存儲器中永久存儲。電腦中最大緩存就是記憶體條,硬碟上也有16M或者32M的緩存。
☞ 高速緩存是用來協調CPU與主存之間存取速度的差異而設定的。一般CPU工作速度高,但記憶體的工作速度相對較低,為了解決這個問題,通常使用高速緩存,高速緩存的存取速度介于CPU與主存之間。系統将一些CPU在最近幾個時間段經常通路的内容存在高速緩存,這樣就在一定程度上緩解了由于主存速度低造成的CPU“停工待料”的情況。
☞ 緩存就是把一些外存上的資料儲存在記憶體上而已,為什麼儲存在記憶體上,我們運作的所有程式裡面的變量都是存放在記憶體中的,是以如果想将值放入記憶體上,可以通過變量的方式存儲。在JAVA中一些緩存一般都是通過Map集合來實作的。
▁▂▃▅▆ :緩存在不同的場景下,作用是不一樣的具體舉例說明:
✔ 作業系統磁盤緩存 ——> 減少磁盤機械操作。
✔ 資料庫緩存——>減少檔案系統IO。
✔ 應用程式緩存——>減少對資料庫的查詢。
✔ Web伺服器緩存——>減少應用伺服器請求。
✔ 用戶端浏覽器緩存——>減少對網站的通路。
2、常見的緩存政策有哪些,如何做到緩存(比如redis)與DB裡的資料一緻性,你們項目中用到了什麼緩存系統,如何設計的。
1)、由于不同系統的資料通路模式不同,同一種緩存政策很難在不同的資料通路模式下取得滿意的性能,研究人員提出不同緩存政策以适應不同的需求。
緩存政策的分類:
1)、基于通路的時間:此類算法按各緩存項被通路時間來組織緩存隊列,決定替換對象。如LRU
2)、基于通路頻率:此類算法用緩存項的被通路頻率來組織緩存。如LFU、LRU2、2Q、LIRS。
3)、通路時間與頻率兼顧:通過兼顧通路時間和頻率。使得資料模式在變化時緩存政策仍有較好性能。如FBR、LRUF、ALRFU。多數此類算法具有一個可調或自适應參數,通過該參數的調節使緩存政策在基于通路時間與頻率間取得一個平衡。
4)、基于通路模式:某些應用有較明确的資料通路特點,進而産生與其相适應的緩存政策。如專用的VoD系統設計的A&L緩存政策,同時适應随機、順序兩種通路模式的SARC政策。
2)、資料不一緻性産生的原因:
【1】、先操作緩存,再寫資料庫成功之前,如果有讀請求發生,可能導緻舊資料入緩存,引發資料不一緻。在分布式環境下,資料的讀寫都是并發的,一個服務多機器部署,對同一個資料進行讀寫,在資料庫層面并不能保證完成順序,就有可能後讀的操作先完成(讀取到的是髒資料),如果不采用給緩存設定過期時間政策,該資料永遠都是髒資料。
【解決辦法】:1)、可采用更新前後雙删除緩存政策。
2)、可以通過“串行化”解決,保證同一個資料的讀寫落在同一個後端服務上。
【2】、先操作資料庫,再清除緩存。如果删緩存失敗了,就會出現資料不一緻問題。
【解決辦法】:1)、将删除失敗的key值存入隊列中重複删除,如下圖:
(1)更新資料庫資料。
(2)緩存因為種種問題删除失敗。
(3)将需要删除的key發送至消息隊列。
(4)自己消費消息,獲得需要删除的key。
(5)繼續重試删除操作,直到成功。
缺點:對業務線代碼造成大量的侵入。于是有了方案二。
2)、方案二:通過訂閱binlog擷取需要重新删除的Key值資料。在應用程式中,另起一段程式,獲得這個訂閱程式傳來的消息,進行删除緩存操作。
(1)更新資料庫資料
(2)資料庫會将操作資訊寫入binlog日志當中
(3)訂閱程式提取出所需要的資料以及key
(4)另起一段非業務代碼,獲得該資訊
(5)嘗試删除緩存操作,發現删除失敗
(6)将這些資訊發送至消息隊列
(7)重新從消息隊列中獲得該資料,重試操作。
3、如何防止緩存穿透、緩存擊穿、緩存雪崩和緩存重新整理。
【1】、緩存穿透:緩存穿透是說收到一個請求,但是該請求緩存中不存在,隻能去資料庫中查詢,然後放進緩存。但當有好多請求同時通路同一個資料時,業務系統把這些請求全發到了資料庫;或者惡意構造一個邏輯上不存在的資料,然後大量發送這個請求,這樣每次都會被發送到資料庫,最總導緻資料庫挂掉。
解決的辦法:對于惡意通路,一種思路是先做校驗,對惡意資料直接過濾掉,不要發送至資料庫層;第二種思路是緩存空結果,就是對查詢不存在的資料也記錄在緩存中,這樣就可以有效的減少查詢資料庫的次數。非惡意通路,結合緩存擊穿說明。
【2】、緩存擊穿:上面提到的某個資料沒有,然後好多請求查詢資料庫,可以歸為緩存擊穿的範疇:對于熱點資料,當緩存失效的一瞬間,所有的請求都被下放到資料庫去請求更新緩存,資料庫被壓垮。
解決的辦法:防範此類問題,一種思路是加全局鎖,就是所有通路某個資料的請求都共享一個鎖,獲得鎖的那個才有資格去通路資料庫,其他線程必須等待。但現在大部分系統都是分布式的,本地鎖無法控制其他伺服器也等待,是以要用到全局鎖,比如Redis的setnx實作全局鎖。另一種思想是對即将過期的資料進行主動重新整理,比如新起一個線程輪詢資料,或者比如把所有的資料劃分為不同的緩存區間,定期分區間重新整理資料。第二個思路與緩存雪崩有點關系。
【3】、緩存雪崩:緩存雪崩是指當我們給所有的緩存設定了同樣的過期時間,當某一時刻,整個緩存的資料全部過期了,然後瞬間所有的請求都被抛向了資料庫,資料庫就崩掉了。
解決的辦法:解決思路要麼是分治,劃分更小的緩存區間,按區間過期;要麼給每個key的過期時間加一個随機值,避免同時過期,達到錯峰重新整理緩存的目的。
【4】、緩存重新整理:既清空緩存 ,一般在insert、update、delete操作後就需要重新整理緩存,如果不執行就會出現髒資料。但當緩存請求的系統蹦掉後,傳回給緩存的值為null。
4、Redis記憶體用完會發生什麼?
如果達到設定的上限,Redis的寫指令會傳回錯誤資訊(但是讀指令還是可以正常傳回),或者将Redis當緩存使用,配置緩存淘汰機制,當Redis達到記憶體的上線時會沖掉舊的資料。
5、Redis的List結構相關的操作。
【1】、PUSH操作:是從隊列頭部和尾部增加節點的操作。
①、RPUSH KEY VALUE [VALUE ...] :從隊列的右端入隊一個或者多個資料,如果key值不存在,會自動建立一個空的清單。如果對應的key不是一個List,則會傳回一個錯誤。
②、LPUSH KEY VALUE [VALUE...] :從隊列的左邊入隊一個或多個元素。複雜度O(1)。
③、RPUSHX KEY VALUE:從隊列的右邊入隊一個元素,僅隊列存在時有效,當隊列不存在時,不進行任何操作。
④、LPUSHX KEY VALUE:從隊列的左邊入隊一個元素,僅隊列存在時有效。當隊列不存在時,不進行任何操作。
【2】、POP操作:擷取并删除頭尾節點的操作。
①、LPOP KEY:從隊列左邊出隊一個元素,複雜度O(1)。如果list為空,則傳回nil。
②、RPOP KEY:從隊列的右邊出隊一個元素,複雜度O(1)。如果list為空,則傳回nil。
③、BLPOP KEY[KEY...] TIMEOUT:删除&擷取KEY中最左邊的第一個元素,當隊列為空時,阻塞TIMEOUT時間,機關是秒(這個時間内嘗試擷取KEY中的資料),超過TIMEOUT後如果仍未資料則傳回(nil)。
1 redis> BLPOP queue 1
2 (nil)
3 (1.10s)
④、BRPOP KEY[KEY...] TIMEOUT:删除&擷取KEY中最後一個元素,或阻塞TIMEOUT。如上↑
【3】、POP and PUSH
①、RPOPLPUSH KEY1 KEY2:删除KEY1中最後一個元素,将其追加到KEY2的最左端。
②、BRPOPLPUSH KEY1 KEY2 TIMEOUT:彈出KEY1清單的值,将它推到KEY2清單,并傳回它;或阻塞TIMEOUT時間,直到有一個可用。
【4】、其他
①、LLEN KEY:擷取隊列(List)的長度。
②、LRANG KEY START STOP:從清單中擷取指定(START-STOP)長度的元素。負數表示從右向左數。需要注意的是,超出範圍的下标不會産生錯誤:如果start>end,會得到空清單,如果end超過隊尾,則Redis會将其當做清單的最後一個元素。
1 redis> rpush q1 a b c d f e g
2 (integer) 7
3 redis> lrange q1 0 -1
4 1) "a"
5 2) "b"
6 3) "c"
7 4) "d"
8 5) "f"
9 6) "e"
10 7) "g"
③、 LINDEX KEY INDEX:擷取一個元素,通過其索引清單。我們之前介紹的操作都是對list的兩端進行的,是以算法複雜度都隻有O(1)。而這個操作是指定位置來進行的,每次操作,list都得找到對應的位置,是以算法複雜度為O(N)。list的下表是從0開始的,index為負的時候是從右向左數。-1表示最後一個元素。當下标超出的時候,會傳回nul。是以不用像操作數組一樣擔心範圍越界的情況。
④、LSET KEY INDEX:重置隊列中INDEX位置的值。當index越界的時候,這裡會報異常。
⑤、LREM KEY COUNT VALUE:從清單中删除COUNT個VALUE元素。COUNT參數有三種情況:
☛ count > 0: 表示從頭向尾(左到右)移除值為value的元素。
☛ count < 0: 表示從尾向頭(右向左)移除值為value的元素。
☛ count = 0: 表示移除所有值為value的元素。
⑥、LTRIM KEY START STOP:修剪到指定範圍内的清單,相當與截取,隻保留START-STOP之間的資料。
1 redis> rpush q a b c d e f g
3 redis> lrange q 0 -1
8 5) "e"
9 6) "f"
11 redis> ltrim q 1 4
12 OK
13 redis> lrange q 0 -1
14 1) "b"
15 2) "c"
16 3) "d"
17 4) "e"
⑦、LINSERT KEY BEFORE|AFTER 元素 VALUE:在清單中的另一個元素之前或之後插入VAULE。當key不存在時,這個List被視為空清單,任何操作都不會發生。當key存在,但儲存的不是List,則會報error。該指令會傳回修改之後的List的長度,如果找不到元素,則會傳回-1。
6、Redis的資料結構都有哪些。
1】、String:可以是字元串,整數或者浮點數,對整個字元串或者字元串中的一部分執行操作,對整個整數或者浮點執行自增(increment)或者自減(decrement)操作。
2】、List:一個連結清單,連結清單上的每個節點都包含了一個字元串,連結清單的兩端推入或者彈出元素,根據偏移量對連結清單進行修剪(trim),讀取單個或者多個元素,根據值查找或者移除元素。可參考5
3】、Set:包含字元串的無序收集器(unordered collection)、并且被包含的每個字元串都是獨一無二的。添加,擷取,移除單個元素,檢查一個元素是否存在于集合中,計算交集(sinter),并集(suion),差集(sdiff),從集合裡面随機擷取元素。
4】、SortSet:是一個排好序的Set,它在Set的基礎上增加了一個順序屬性score,這個屬性在添加修改元素時可以指定,每次指定後,SortSet會自動重新按新的值排序。sorted set的内部使用HashMap和跳躍表(SkipList)來保證資料的存儲和有序,HashMap裡放的是成員到score的映射,而跳躍表裡存放的是所有的成員,排序依據是HashMap裡存的score。
192.168.2.129:6379> zadd myzset 1 "one" 2 "two" 3 "three" //添加元素
(integer) 3
192.168.2.129:6379> zrange myzset 0 -1
1) "one"
2) "two"
3) "three"
192.168.2.129:6379> zrange myzset 0 -1 withscores
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
192.168.2.129:6379> zrem myzset one //删除元素
(integer) 1
1) "two"
2) "2"
4) "3"
192.168.2.129:6379>
5】、hash:Hash是一個String類型的field和value之間的映射表,即redis的Hash資料類型的key(hash表名稱)對應的value實際的内部存儲結構為一個HashMap,是以Hash特别适合存儲對象。相對于把一個對象的每個屬性存儲為String類型,将整個對象存儲在Hash類型中會占用更少記憶體。
192.168.2.129:6379> hset myhash name zhangsan
192.168.2.129:6379> hset myhash age 20
192.168.2.129:6379> hget myhash name
"zhangsan"
192.168.2.129:6379> hget myhash age
"20"
192.168.2.129:6379>
7、Redis的使用要注意什麼,講講持久化方式,記憶體設定,叢集的應用和優劣勢,淘汰政策等。
使用階段我們從資料存儲和資料擷取兩個方面來說明開發時的注意事項:
1)、資料存儲:因為記憶體空間的局限性,注定了能存儲的資料量有限,如何在有限的空間記憶體儲更多的資料資訊是我們應該關注的。Redis記憶體儲的都是鍵值對,那麼如何減小鍵值對所占據的記憶體空間就是空間優化的本質。在能清晰表達業務含義的基礎上盡可能縮減Key的字元長度,比如一個鍵是user:{id}:logintime ,可以使用業務屬性的簡寫來u:{id}:lgt,隻要能清晰表達業務意義,使用簡寫形式是有其必要性的。在不影響使用的情況下,縮減Value的資料大小。如果Value是較大的資料資訊,比如圖檔,大文本等,可以使用壓縮工具壓縮過後再存入Redis;如果Value是對象序列化或者gson資訊,可以考慮去除非必要的業務屬性。
減少鍵值對的數量,對于大量的String類型的小對象,可以嘗試使用Hash的形式組合他們,在Hash對象内Field數量少于1000,且Value的字元長度小于40時,内部使用ziplist的編碼形式,能夠極大的降低小對象占據的記憶體空間。
Redis内維護了一個[0-9999]的整數對象池,類似Java内的運作時常量池,隻建立一個常量,使用時都去引用這個常量,是以當存儲的value是這個範圍内的數字時均是引用向都一個記憶體位址,是以能夠降低一些記憶體空間耗費。但是共享對象池和maxmemory+LRU的記憶體回收政策沖突,因為共享Value對象的lru值也共享,難以通過lru知道哪個Key的最後引用時間,是以永遠也不能回收記憶體。如果多次資料操作要求原子性,可使用Multi來實作Redis的事務。
2)、資料查詢:Redis是一種資料庫,和其他資料庫一樣,操作時也需要有連接配接對象,連接配接對象的建立和銷毀也需要耗費資源,複用連接配接對象很有必要,是以推薦使用連接配接池來管理連接配接。Redis資料存儲在記憶體中,查詢很快,但不代表連接配接也很快。一次Redis查詢可能IO部分占據了請求時間的絕大部分比例,縮短IO時間是開發過程中很需要注意的一點。對于一個業務内的多次查詢,考慮使用Pipeline,将多次查詢合并為一次查詢,指令會被執行多次,但是隻有一個IO傳輸,能夠有效的提高響應速度。
對于多次String類型的查詢,使用mget,将多次請求合并為一次,同時指令和會被合并為一次,能有效提高響應速度,對于Hash内多個Field查詢,使用hmget,起到和mget同樣的效果。Redis是單線程執行的,也就是說同一時間隻能執行一條指令,如果一條指令執行的時間較長,其他線程在此期間均會被阻塞,是以在操作Redis時要注意操作指令的涉及的資料量,盡量降低單次操作的執行時間。
持久化方式:RDB時間點快照 AOF記錄伺服器執行的所有寫操作指令,并在伺服器啟動時,通過重新執行這些指令來還原資料集。可參考14深度解析
記憶體設定:maxmemory used_memory
虛拟記憶體: vm-enabled yes
叢集的應用和優劣勢參考9
記憶體優化:http://www.infoq.com/cn/articles/tq-redis-memory-usage-optimization-storage
淘汰政策:http://wiki.jikexueyuan.com/project/redis/data-elimination-mechanism.html
8、Redis2和Redis3的差別,redis3内部通訊機制。
叢集方式的差別:Redis3采用Cluster,Redis2采用用戶端分區方案和代理方案
通信過程說明:
1) 叢集中的每個節點都會單獨開辟一個TCP通道, 用于節點之間彼此通信, 通信端口号在基礎端口上加10000。
2) 每個節點在固定周期内通過特定規則選擇幾個節點發送ping消息。
3) 接收到ping消息的節點用pong消息作為響應。
9、目前Redis叢集有哪些玩法,各自優缺點,場景。
1)、資料共享:Redis提供多個節點執行個體間的資料共享,也就是Redis A,B,C,D彼此之間的資料是同步的,同樣彼此之間也可以通信,而對于用戶端操作的keys是由Redis系統自行配置設定到各個節點中。
2)、主從複制:Redis的多個執行個體間通信時,一旦其中的一個節點故障,那麼Redis叢集就不能繼續正常工作,是以需要一種複制機制(Master-Slave)機制,做到一旦節點A故障了,那麼其從節點A1和A2就可以接管并繼續提供與A同樣的工作服務,當然如果節點A,A1,A2節點都出現問題,那麼同樣這個叢集不會繼續保持工作,但是這種情況比較罕見,即使出現了,也會及時發現并修複使用。建議:部署主從複制機制(Master-Slave)。
3)、哈希槽值:Redis叢集中使用哈希槽來存儲用戶端的keys,而在Redis中,目前存在16384個哈希槽,它們被全部配置設定給所有的節點,正如上圖所示,所有的哈希槽值被節點A,B,C配置設定完成了。
參考:https://www.cnblogs.com/RENQIWEI1995/p/8931678.html
10、Memcache的原理,哪些資料适合放在緩存中。
首先要說明一點,MemCache的資料存放在記憶體中,存放在記憶體中個人認為意味着幾點:
1)、通路資料的速度比傳統的關系型資料庫要快,因為Oracle、MySQL這些傳統的關系型資料庫為了保持資料的持久性,資料存放在硬碟中,IO操作速度慢
2)、MemCache的資料存放在記憶體中同時意味着隻要MemCache重新開機了,資料就會消失
3)、既然MemCache的資料存放在記憶體中,那麼勢必受到機器位數的限制,這個之前的文章寫過很多次了,32位機器最多隻能使用2GB的記憶體空間,64位機器可以認為沒有上限。
然後我們來看一下MemCache的原理,MemCache最重要的莫不是記憶體配置設定的内容了,MemCache采用的記憶體配置設定方式是固定空間配置設定,還是自己畫一張圖說明:
這張圖檔裡面涉及了slab_class、slab、page、chunk四個概念,它們之間的關系是:
【1】、MemCache将記憶體空間分為一組slab
【2】、每個slab下又有若幹個page,每個page預設是1M,如果一個slab占用100M記憶體的話,那麼這個slab下應該有100個page
【3】、每個page裡面包含一組chunk,chunk是真正存放資料的地方,同一個slab裡面的chunk的大小是固定的
【4】、有相同大小chunk的slab被組織在一起,稱為slab_class
MemCache記憶體配置設定的方式稱為allocator,slab的數量是有限的,幾個、十幾個或者幾十個,這個和啟動參數的配置相關。
MemCache中的value過來存放的地方是由value的大小決定的,value總是會被存放到與chunk大小最接近的一個slab中,比如slab[1]的chunk大小為80位元組、slab[2]的chunk大小為100位元組、slab[3]的chunk大小為128位元組(相鄰slab内的chunk基本以1.25為比例進行增長,MemCache啟動時可以用-f指定這個比例),那麼過來一個88位元組的value,這個value将被放到2号slab中。放slab的時候,首先slab要申請記憶體,申請記憶體是以page為機關的,是以在放入第一個資料的時候,無論大小為多少,都會有1M大小的page被配置設定給該slab。申請到page後,slab會将這個page的記憶體按chunk的大小進行切分,這樣就變成了一個chunk數組,最後從這個chunk數組中選擇一個用于存儲資料。
如果這個slab中沒有chunk可以配置設定了怎麼辦,如果MemCache啟動沒有追加-M(禁止LRU,這種情況下記憶體不夠會報Out Of Memory錯誤),那麼MemCache會把這個slab中最近最少使用的chunk中的資料清理掉,然後放上最新的資料。針對MemCache的記憶體配置設定及回收算法,總結三點:
【1】、MemCache的記憶體配置設定chunk裡面會有記憶體浪費,88位元組的value配置設定在128位元組(緊接着大的用)的chunk中,就損失了30位元組,但是這也避免了管理記憶體碎片的問題
【2】、MemCache的LRU算法不是針對全局的,是針對slab的
【3】、應該可以了解為什麼MemCache存放的value大小是限制的,因為一個新資料過來,slab會先以page為機關申請一塊記憶體,申請的記憶體最多就隻有1M,是以value大小自然不能大于1M了。
再總結MemCache的特性和限制:
¤上面已經對于MemCache做了一個比較詳細的解讀,這裡再次總結MemCache的限制和特性:
1】、MemCache中可以儲存的item資料量是沒有限制的,隻要記憶體足夠
2】、MemCache單程序在32位機中最大使用記憶體為2G,這個之前的文章提了多次了,64位機則沒有限制
3】、Key最大為250個位元組,超過該長度無法存儲
4】、單個item最大資料是1MB,超過1MB的資料不予存儲
5】、MemCache服務端是不安全的,比如已知某個MemCache節點,可以直接telnet過去,并通過flush_all讓已經存在的鍵值對立即失效
6】、不能夠周遊MemCache中所有的item,因為這個操作的速度相對緩慢且會阻塞其他的操作
7】、MemCache的高性能源自于兩階段哈希結構:第一階段在用戶端,通過Hash算法根據Key值算出一個節點;第二階段在服務端,通過一個内部的Hash算法,查找真正的item并傳回給用戶端。從實作的角度看,MemCache是一個非阻塞的、基于事件的伺服器程式
8】、MemCache設定添加某一個Key值的時候,傳入expiry為0表示這個Key值永久有效,這個Key值也會在30天之後失效。
MemCache适合存儲:變化頻繁,具有不穩定性的資料,不需要實時入庫, (比如使用者線上狀态、線上人數..)門戶網站的新聞等,覺得頁面靜态化仍不能滿足要求,可以放入到memcache中.(配合jquey的ajax請求)。
11、Redis和Memcached 的記憶體管理的差別。
可參考部落格1:http://lib.csdn.net/article/redis/55323
可參考部落格2:https://www.cnblogs.com/work115/p/5584646.html
12、Redis的并發競争問題如何解決,了解Redis事務的CAS操作嗎。
Redis為單程序單線程模式,采用隊列模式将并發通路變為串行通路。Redis本身沒有鎖的概念,Redis對于多個用戶端連接配接并不存在競争,但是在Jedis用戶端對Redis進行并發通路時會發生連接配接逾時、資料轉換錯誤、阻塞、用戶端關閉連接配接等問題,這些問題均是由于用戶端連接配接混亂造成。對此有2種解決方法:
【1】用戶端角度,為保證每個用戶端間正常有序與Redis進行通信,對連接配接進行池化,同時對用戶端讀寫Redis操作采用内部鎖synchronized。
【2】伺服器角度,利用setnx實作鎖。MULTI,EXEC,DISCARD,WATCH 四個指令是 Redis 事務的四個基礎指令。其中:
☆ MULTI,告訴 Redis 伺服器開啟一個事務。注意,隻是開啟,而不是執行
☆ EXEC,告訴 Redis 開始執行事務
☆ DISCARD,告訴 Redis 取消事務
☆ WATCH,監視某一個鍵值對,它的作用是在事務執行之前如果監視的鍵值被修改,事務會被取消。
Redis事務機制:https://www.jianshu.com/p/d777eb9f27df
CAS操作:https://www.jianshu.com/p/d777eb9f27df
13、Redis的選舉算法和流程是怎樣的。
Raft采用心跳機制觸發Leader選舉。系統啟動後,全部節點初始化為Follower,term為0.節點如果收到了RequestVote或者AppendEntries,就會保持自己的Follower身份。如果一段時間内沒收到AppendEntries消息直到選舉逾時,說明在該節點的逾時時間内還沒發現Leader,Follower就會轉換成Candidate,自己開始競選Leader。一旦轉化為Candidate,該節點立即開始下面幾件事情:
1)、增加自己的term。
2)、啟動一個新的定時器。
3)、給自己投一票。
4)、向所有其他節點發送RequestVote,并等待其他節點的回複。
✔如果在這過程中收到了其他節點發送的AppendEntries,就說明已經有Leader産生,自己就轉換成Follower,選舉結束。
✔如果在計時器逾時前,節點收到多數節點的同意投票,就轉換成Leader。同時向所有其他節點發送AppendEntries,告知自己成為了Leader。
✔每個節點在一個term内隻能投一票,采取先到先得的政策,Candidate前面說到已經投給了自己,Follower會投給第一個收到RequestVote的節點。每個Follower有一個計時器,在計時器逾時時仍然沒有接受到來自Leader的心跳RPC, 則自己轉換為Candidate, 開始請求投票,就是上面的的競選Leader步驟。
✔如果多個Candidate發起投票,每個Candidate都沒拿到多數的投票(Split Vote),那麼就會等到計時器逾時後重新成為Candidate,重複前面競選Leader步驟。
✔Raft協定的定時器采取随機逾時時間,這是選舉Leader的關鍵。每個節點定時器的逾時時間随機設定,随機選取配置時間的1倍到2倍之間。由于随機配置,是以各個Follower同時轉成Candidate的時間一般不一樣,在同一個term内,先轉為Candidate的節點會先發起投票,進而獲得多數票。多個節點同時轉換為Candidate的可能性很小。即使幾個Candidate同時發起投票,在該term内有幾個節點獲得一樣高的票數,隻是這個term無法選出Leader。由于各個節點定時器的逾時時間随機生成,那麼最先進入下一個term的節點,将更有機會成為Leader。連續多次發生在一個term内節點獲得一樣高票數在理論上幾率很小,實際上可以認為完全不可能發生。一般1-2個term類,Leader就會被選出來。
Sentinel的選舉流程:
Sentinel叢集正常運作的時候每個節點epoch相同,當需要故障轉移的時候會在叢集中選出Leader執行故障轉移操作。Sentinel采用了Raft協定實作了Sentinel間選舉Leader的算法,不過也不完全跟論文描述的步驟一緻。Sentinel叢集運作過程中故障轉移完成,所有Sentinel又會恢複平等。Leader僅僅是故障轉移操作出現的角色。
選舉流程:
1)、某個Sentinel認定master客觀下線的節點後,該Sentinel會先看看自己有沒有投過票,如果自己已經投過票給其他Sentinel了,在2倍故障轉移的逾時時間自己就不會成為Leader。相當于它是一個Follower。
2)、如果該Sentinel還沒投過票,那麼它就成為Candidate。
3)、和Raft協定描述的一樣,成為Candidate,Sentinel需要完成幾件事情
【1】更新故障轉移狀态為start
【2】目前epoch加1,相當于進入一個新term,在Sentinel中epoch就是Raft協定中的term。
【3】更新自己的逾時時間為目前時間随機加上一段時間,随機時間為1s内的随機毫秒數。
【4】向其他節點發送is-master-down-by-addr指令請求投票。指令會帶上自己的epoch。
【5】給自己投一票,在Sentinel中,投票的方式是把自己master結構體裡的leader和leader_epoch改成投給的Sentinel和它的epoch。
4)、其他Sentinel會收到Candidate的is-master-down-by-addr指令。如果Sentinel目前epoch和Candidate傳給他的epoch一樣,說明他已經把自己master結構體裡的leader和leader_epoch改成其他Candidate,相當于把票投給了其他Candidate。投過票給别的Sentinel後,在目前epoch内自己就隻能成為Follower。
5)、Candidate會不斷的統計自己的票數,直到他發現認同他成為Leader的票數超過一半而且超過它配置的quorum(quorum可以參考《redis sentinel設計與實作》)。Sentinel比Raft協定增加了quorum,這樣一個Sentinel能否當選Leader還取決于它配置的quorum。
6)、如果在一個選舉時間内,Candidate沒有獲得超過一半且超過它配置的quorum的票數,自己的這次選舉就失敗了。
7)、如果在一個epoch内,沒有一個Candidate獲得更多的票數。那麼等待超過2倍故障轉移的逾時時間後,Candidate增加epoch重新投票。
8)、如果某個Candidate獲得超過一半且超過它配置的quorum的票數,那麼它就成為了Leader。
9)、與Raft協定不同,Leader并不會把自己成為Leader的消息發給其他Sentinel。其他Sentinel等待Leader從slave選出master後,檢測到新的master正常工作後,就會去掉客觀下線的辨別,進而不需要進入故障轉移流程。
14、Redis的持久化的機制,AOF和RDB的差別。
Redis的持久化機制:Redis提供兩種方式進行持久化,一種是RDB持久化(原理是将Reids在記憶體中的資料庫記錄定時dump到磁盤上的RDB持久化),另外一種是AOF(append only file)持久化(原理是将Reids的記錄檔以追加的方式寫入檔案)。
AOF和RDB的差別:RDB持久化是指在指定的時間間隔内将記憶體中的資料集快照寫入磁盤,實際操作過程是fork一個子程序,先将資料集寫入臨時檔案,寫入成功後,再替換之前的檔案,用二進制壓縮存儲。
AOF持久化以日志的形式記錄伺服器所處理的每一個寫、删除操作,查詢操作不會記錄,以文本的方式記錄,可以打開檔案看到詳細的操作記錄。
二者優缺點:RDB存在哪些優勢:
1)、一旦采用該方式,那麼你的整個Redis資料庫将隻包含一個檔案,這對于檔案備份而言是非常完美的。比如,你可能打算每個小時歸檔一次最近24小時的資料,同時還要每天歸檔一次最近30天的資料。通過這樣的備份政策,一旦系統出現災難性故障,我們可以非常容易的進行恢複。
2)、對于災難恢複而言,RDB是非常不錯的選擇。因為我們可以非常輕松的将一個單獨的檔案壓縮後再轉移到其它存儲媒體上。
3)、性能最大化。對于Redis的服務程序而言,在開始持久化時,它唯一需要做的隻是fork出子程序,之後再由子程序完成這些持久化的工作,這樣就可以極大的避免服務程序執行IO操作了。
4)、相比于AOF機制,如果資料集很大,RDB的啟動效率會更高。
RDB又存在哪些劣勢:
1)、如果你想保證資料的高可用性,即最大限度的避免資料丢失,那麼RDB将不是一個很好的選擇。因為系統一旦在定時持久化之前出現當機現象,此前沒有來得及寫入磁盤的資料都将丢失。
2)、由于RDB是通過fork子程序來協助完成資料持久化工作的,是以,如果當資料集較大時,可能會導緻整個伺服器停止服務幾百毫秒,甚至是1秒鐘。
AOF的優勢有哪些:
1)、該機制可以帶來更高的資料安全性,即資料持久性。Redis中提供了3中同步政策,即每秒同步、每修改同步和不同步。事實上,每秒同步也是異步完成的,其效率也是非常高的,所差的是一旦系統出現當機現象,那麼這一秒鐘之内修改的資料将會丢失。而每修改同步,我們可以将其視為同步持久化,即每次發生的資料變化都會被立即記錄到磁盤中。可以預見,這種方式在效率上是最低的。至于無同步,無需多言,我想大家都能正确的了解它。
2)、由于該機制對日志檔案的寫入操作采用的是append模式,是以在寫入過程中即使出現當機現象,也不會破壞日志檔案中已經存在的内容。然而如果我們本次操作隻是寫入了一半資料就出現了系統崩潰問題,不用擔心,在Redis下一次啟動之前,我們可以通過redis-check-aof工具來幫助我們解決資料一緻性的問題。
3)、如果日志過大,Redis可以自動啟用rewrite機制。即Redis以append模式不斷的将修改資料寫入到老的磁盤檔案中,同時Redis還會建立一個新的檔案用于記錄此期間有哪些修改指令被執行。是以在進行rewrite切換時可以更好的保證資料安全性。
4)、AOF包含一個格式清晰、易于了解的日志檔案用于記錄所有的修改操作。事實上,我們也可以通過該檔案完成資料的重建。
AOF的劣勢有哪些:
1)、對于相同數量的資料集而言,AOF檔案通常要大于RDB檔案。RDB 在恢複大資料集時的速度比 AOF 的恢複速度要快。
2)、根據同步政策的不同,AOF在運作效率上往往會慢于RDB。總之,每秒同步政策的效率是比較高的,同步禁用政策的效率和RDB一樣高效。
二者選擇的标準,就是看系統是願意犧牲一些性能,換取更高的緩存一緻性(aof),還是願意寫操作頻繁的時候,不啟用備份來換取更高的性能,待手動運作save的時候,再做備份(rdb)。rdb這個就更有些 eventually consistent的意思了。
15、Redis的叢集怎麼同步的資料的。
參考部落格:https://www.cnblogs.com/amei0/p/8177076.html
16、知道哪些Redis的優化操作。
參考部落格:https://www.cnblogs.com/duanxz/p/5447402.html
17、Reids的主從複制機制原理。
參考部落格:https://blog.csdn.net/simba_1986/article/details/77528250
18、Redis的線程模型是什麼。
參考部落格:https://www.cnblogs.com/barrywxx/p/8570821.html
19、請思考一個方案,設計一個可以控制緩存總體大小的自動适應的本地緩存。