目錄
? ? 1 消息隊(duì)列
? ? 2 緩存
? ? 3 分布式系統(tǒng)
? ? 4 Dubbo
? ? 5 數(shù)據(jù)庫(kù)參考:
? ? · 中華石杉視頻
1 消息隊(duì)列
1.1 為什么使用消息隊(duì)列
????其實(shí)就是問(wèn)問(wèn)你消息隊(duì)列都有哪些使用場(chǎng)景,然后你項(xiàng)目里具體是什么場(chǎng)景,說(shuō)說(shuō)你在這個(gè)場(chǎng)景里用消息隊(duì)列是什么?
? ? 三大作用:解耦、異步、削峰
(1)解耦


(2)異步


(3)削峰


1.2 消息隊(duì)列有什么優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)點(diǎn)上面已經(jīng)說(shuō)了,就是在特殊場(chǎng)景下有其對(duì)應(yīng)的好處,解耦、異步、削峰。缺點(diǎn)呢?顯而易見的
(1)系統(tǒng)可用性降低:系統(tǒng)引入的外部依賴越多,越容易掛掉,本來(lái)你就是A系統(tǒng)調(diào)用BCD三個(gè)系統(tǒng)的接口就好了,人ABCD四個(gè)系統(tǒng)好好的,沒啥問(wèn)題,你偏加個(gè)MQ進(jìn)來(lái),萬(wàn)一MQ掛了咋整?MQ掛了,整套系統(tǒng)崩潰了,你不就完了么。
(2)系統(tǒng)復(fù)雜性提高:硬生生加個(gè)MQ進(jìn)來(lái),你怎么保證消息沒有重復(fù)消費(fèi)?怎么處理消息丟失的情況?怎么保證消息傳遞的順序性?頭大頭大,問(wèn)題一大堆,痛苦不已
(3)一致性問(wèn)題:A系統(tǒng)處理完了直接返回成功了,人都以為你這個(gè)請(qǐng)求就成功了;但是問(wèn)題是,要是BCD三個(gè)系統(tǒng)那里,BD兩個(gè)系統(tǒng)寫庫(kù)成功了,結(jié)果C系統(tǒng)寫庫(kù)失敗了,咋整?你這數(shù)據(jù)就不一致了。
(4)所以消息隊(duì)列實(shí)際是一種非常復(fù)雜的架構(gòu),你引入它有很多好處,但是也得針對(duì)它帶來(lái)的壞處做各種額外的技術(shù)方案和架構(gòu)來(lái)規(guī)避掉,最好之后,你會(huì)發(fā)現(xiàn),媽呀,系統(tǒng)復(fù)雜度提升了一個(gè)數(shù)量級(jí),也許是復(fù)雜了10倍。但是關(guān)鍵時(shí)刻,用,還是得用的。

1.3 kafka、activemq、rabbitmq、rocketmq都有什么優(yōu)點(diǎn)和缺點(diǎn)
1.4 如何保證消息隊(duì)列的高可用
1.5 如何保證消息不被重復(fù)消費(fèi)?。ㄈ绾伪WC消息消費(fèi)時(shí)的冪等性)

得結(jié)合業(yè)務(wù)來(lái)思考:
(1)比如你拿個(gè)數(shù)據(jù)要寫庫(kù),你先根據(jù)主鍵查一下,如果這數(shù)據(jù)都有了,你就別插入了,update一下好吧
(2)比如你是寫redis,那沒問(wèn)題了,反正每次都是set,天然冪等性
(3)內(nèi)存Set
(4)比如你不是上面兩個(gè)場(chǎng)景,那做的稍微復(fù)雜一點(diǎn),你需要讓生產(chǎn)者發(fā)送每條數(shù)據(jù)的時(shí)候,里面加一個(gè)全局唯一的id,類似訂單id之類的東西,然后你這里消費(fèi)到了之后,先根據(jù)這個(gè)id去比如redis里查一下,之前消費(fèi)過(guò)嗎?如果沒有消費(fèi)過(guò),你就處理,然后這個(gè)id寫redis。如果消費(fèi)過(guò)了,那你就別處理了,保證別重復(fù)處理相同的消息即可。
1.6如何保證消息的可靠性傳輸(如何處理消息丟失的問(wèn)題)
1.7 如何保證消息的順序性?
1.7.1 順序會(huì)錯(cuò)亂的倆場(chǎng)景
(1)rabbitmq:一個(gè)queue,多個(gè)consumer,這不明顯亂了

(2)kafka:一個(gè)topic,一個(gè)partition,一個(gè)consumer,內(nèi)部多線程,這不也明顯亂了

1.7.2 那如何保證消息的順序性呢?
(1)rabbitmq:拆分多個(gè)queue,每個(gè)queue一個(gè)consumer,就是多一些queue而已,確實(shí)是麻煩點(diǎn);或者就一個(gè)queue但是對(duì)應(yīng)一個(gè)consumer,然后這個(gè)consumer內(nèi)部用內(nèi)存隊(duì)列做排隊(duì),然后分發(fā)給底層不同的worker來(lái)處理

(2)kafka:一個(gè)topic,一個(gè)partition,一個(gè)consumer,內(nèi)部單線程消費(fèi),寫N個(gè)內(nèi)存queue,然后N個(gè)線程分別消費(fèi)一個(gè)內(nèi)存queue即可

1.8大量消息在mq里積壓了幾個(gè)小時(shí)了還沒解決
1)先修復(fù)consumer的問(wèn)題,確保其恢復(fù)消費(fèi)速度,然后將現(xiàn)有cnosumer都停掉
2)新建一個(gè)topic,partition是原來(lái)的10倍,臨時(shí)建立好原先10倍或者20倍的queue數(shù)量
3)然后寫一個(gè)臨時(shí)的分發(fā)數(shù)據(jù)的consumer程序,這個(gè)程序部署上去消費(fèi)積壓的數(shù)據(jù),消費(fèi)之后不做耗時(shí)的處理,直接均勻輪詢寫入臨時(shí)建立好的10倍數(shù)量的queue
4)接著臨時(shí)征用10倍的機(jī)器來(lái)部署consumer,每一批consumer消費(fèi)一個(gè)臨時(shí)queue的數(shù)據(jù)
5)這種做法相當(dāng)于是臨時(shí)將queue資源和consumer資源擴(kuò)大10倍,以正常的10倍速度來(lái)消費(fèi)數(shù)據(jù)
6)等快速消費(fèi)完積壓數(shù)據(jù)之后,得恢復(fù)原先部署架構(gòu),重新用原先的consumer機(jī)器來(lái)消費(fèi)消息

2 緩存
2.1為啥在項(xiàng)目里要用緩存呢?
1)高性能

????假設(shè)這么個(gè)場(chǎng)景,你有個(gè)操作,一個(gè)請(qǐng)求過(guò)來(lái),吭哧吭哧你各種亂七八糟操作mysql,半天查出來(lái)一個(gè)結(jié)果,耗時(shí)600ms。但是這個(gè)結(jié)果可能接下來(lái)幾個(gè)小時(shí)都不會(huì)變了,或者變了也可以不用立即反饋給用戶。那么此時(shí)咋辦?
????緩存啊,折騰600ms查出來(lái)的結(jié)果,扔緩存里,一個(gè)key對(duì)應(yīng)一個(gè)value,下次再有人查,別走mysql折騰600ms了。直接從緩存里,通過(guò)一個(gè)key查出來(lái)一個(gè)value,2ms搞定。性能提升300倍。
????這就是所謂的高性能。就是把你一些復(fù)雜操作耗時(shí)查出來(lái)的結(jié)果,如果確定后面不咋變了,然后但是馬上還有很多讀請(qǐng)求,那么直接結(jié)果放緩存,后面直接讀緩存就好了。
2)高并發(fā)

????mysql這么重的數(shù)據(jù)庫(kù),壓根兒設(shè)計(jì)不是讓你玩兒高并發(fā)的,雖然也可以玩兒,但是天然支持不好。mysql單機(jī)支撐到2000qps也開始容易報(bào)警了。
????所以要是你有個(gè)系統(tǒng),高峰期一秒鐘過(guò)來(lái)的請(qǐng)求有1萬(wàn),那一個(gè)mysql單機(jī)絕對(duì)會(huì)死掉。你這個(gè)時(shí)候就只能上緩存,把很多數(shù)據(jù)放緩存,別放mysql。緩存功能簡(jiǎn)單,說(shuō)白了就是key-value式操作,單機(jī)支撐的并發(fā)量輕松一秒幾萬(wàn)十幾萬(wàn),支撐高并發(fā)so easy。單機(jī)承載并發(fā)量是mysql單機(jī)的幾十倍。
2.2 用了緩存之后會(huì)有啥不良的后果
1)緩存與數(shù)據(jù)庫(kù)雙寫不一致
2)緩存雪崩
3)緩存穿透
4)緩存并發(fā)競(jìng)爭(zhēng)
2.3 Redis的線程模型

1)文件事件處理器
????redis基于reactor模式開發(fā)了網(wǎng)絡(luò)事件處理器,這個(gè)處理器叫做文件事件處理器,file event handler。這個(gè)文件事件處理器,是單線程的,redis才叫做單線程的模型,采用IO多路復(fù)用機(jī)制同時(shí)監(jiān)聽多個(gè)socket,根據(jù)socket上的事件來(lái)選擇對(duì)應(yīng)的事件處理器來(lái)處理這個(gè)事件。
????文件事件處理器的結(jié)構(gòu)包含4個(gè)部分:多個(gè)socket,IO多路復(fù)用程序,文件事件分派器,事件處理器(命令請(qǐng)求處理器、命令回復(fù)處理器、連接應(yīng)答處理器,等等)。
????多個(gè)socket可能并發(fā)的產(chǎn)生不同的操作,每個(gè)操作對(duì)應(yīng)不同的文件事件,但是IO多路復(fù)用程序會(huì)監(jiān)聽多個(gè)socket,但是會(huì)將socket放入一個(gè)隊(duì)列中排隊(duì),每次從隊(duì)列中取出一個(gè)socket給事件分派器,事件分派器把socket給對(duì)應(yīng)的事件處理器。
2)文件事件
????當(dāng)socket變得可讀時(shí)(比如客戶端對(duì)redis執(zhí)行write操作,或者close操作),或者有新的可以應(yīng)答的sccket出現(xiàn)時(shí)(客戶端對(duì)redis執(zhí)行connect操作),socket就會(huì)產(chǎn)生一個(gè)AE_READABLE事件。
當(dāng)socket變得可寫的時(shí)候(客戶端對(duì)redis執(zhí)行read操作),socket會(huì)產(chǎn)生一個(gè)AE_WRITABLE事件。
IO多路復(fù)用程序可以同時(shí)監(jiān)聽AE_REABLE和AE_WRITABLE兩種事件,要是一個(gè)socket同時(shí)產(chǎn)生了AE_READABLE和AE_WRITABLE兩種事件,那么文件事件分派器優(yōu)先處理AE_REABLE事件,然后才是AE_WRITABLE事件。
3)文件事件處理器
如果是客戶端要連接redis,那么會(huì)為socket關(guān)聯(lián)連接應(yīng)答處理器
如果是客戶端要寫數(shù)據(jù)到redis,那么會(huì)為socket關(guān)聯(lián)命令請(qǐng)求處理器
如果是客戶端要從redis讀數(shù)據(jù),那么會(huì)為socket關(guān)聯(lián)命令回復(fù)處理器
4)客戶端與redis通信的一次流程
????在redis啟動(dòng)初始化的時(shí)候,redis會(huì)將連接應(yīng)答處理器跟AE_READABLE事件關(guān)聯(lián)起來(lái),接著如果一個(gè)客戶端跟redis發(fā)起連接,此時(shí)會(huì)產(chǎn)生一個(gè)AE_READABLE事件,然后由連接應(yīng)答處理器來(lái)處理跟客戶端建立連接,創(chuàng)建客戶端對(duì)應(yīng)的socket,同時(shí)將這個(gè)socket的AE_READABLE事件跟命令請(qǐng)求處理器關(guān)聯(lián)起來(lái)。
????當(dāng)客戶端向redis發(fā)起請(qǐng)求的時(shí)候(不管是讀請(qǐng)求還是寫請(qǐng)求,都一樣),首先就會(huì)在socket產(chǎn)生一個(gè)AE_READABLE事件,然后由對(duì)應(yīng)的命令請(qǐng)求處理器來(lái)處理。這個(gè)命令請(qǐng)求處理器就會(huì)從socket中讀取請(qǐng)求相關(guān)數(shù)據(jù),然后進(jìn)行執(zhí)行和處理。
????接著redis這邊準(zhǔn)備好了給客戶端的響應(yīng)數(shù)據(jù)之后,就會(huì)將socket的AE_WRITABLE事件跟命令回復(fù)處理器關(guān)聯(lián)起來(lái),當(dāng)客戶端這邊準(zhǔn)備好讀取響應(yīng)數(shù)據(jù)時(shí),就會(huì)在socket上產(chǎn)生一個(gè)AE_WRITABLE事件,會(huì)由對(duì)應(yīng)的命令回復(fù)處理器來(lái)處理,就是將準(zhǔn)備好的響應(yīng)數(shù)據(jù)寫入socket,供客戶端來(lái)讀取。
????命令回復(fù)處理器寫完之后,就會(huì)刪除這個(gè)socket的AE_WRITABLE事件和命令回復(fù)處理器的關(guān)聯(lián)關(guān)系。
2.4為啥redis單線程模型也能效率這么高?
1)純內(nèi)存操作
2)核心是基于非阻塞的IO多路復(fù)用機(jī)制
3)單線程反而避免了多線程的頻繁上下文切換問(wèn)題(百度)
2.5 redis都有哪些數(shù)據(jù)類型?分別在哪些場(chǎng)景下使用比較合適?
(1)string
這是最基本的類型了,沒啥可說(shuō)的,就是普通的set和get,做簡(jiǎn)單的kv緩存
(2)hash
????這個(gè)是類似map的一種結(jié)構(gòu),這個(gè)一般就是可以將結(jié)構(gòu)化的數(shù)據(jù),比如一個(gè)對(duì)象(前提是這個(gè)對(duì)象沒嵌套其他的對(duì)象)給緩存在redis里,然后每次讀寫緩存的時(shí)候,可以就操作hash里的某個(gè)字段。
key=150
value={
? “id”: 150,
? “name”: “zhangsan”,
? “age”: 20
}
hash類的數(shù)據(jù)結(jié)構(gòu),主要是用來(lái)存放一些對(duì)象,把一些簡(jiǎn)單的對(duì)象給緩存起來(lái),后續(xù)操作的時(shí)候,你可以直接僅僅修改這個(gè)對(duì)象中的某個(gè)字段的值
(3)list
有序列表,這個(gè)是可以玩兒出很多花樣的
· 微博,某個(gè)大v的粉絲,就可以以list的格式放在redis里去緩存
key=某大v
value=[zhangsan,lisi, wangwu]
· 比如可以通過(guò)list存儲(chǔ)一些列表型的數(shù)據(jù)結(jié)構(gòu),類似粉絲列表了、文章的評(píng)論列表了之類的東西
· 比如可以通過(guò)lrange命令,就是從某個(gè)元素開始讀取多少個(gè)元素,可以基于list實(shí)現(xiàn)分頁(yè)查詢,這個(gè)很棒的一個(gè)功能,基于redis實(shí)現(xiàn)簡(jiǎn)單的高性能分頁(yè),可以做類似微博那種下拉不斷分頁(yè)的東西,性能高,就一頁(yè)一頁(yè)走
· 比如可以搞個(gè)簡(jiǎn)單的消息隊(duì)列,從list頭懟進(jìn)去,從list尾巴那里弄出來(lái)
(4)set
無(wú)序集合,自動(dòng)去重
直接基于set將系統(tǒng)里需要去重的數(shù)據(jù)扔進(jìn)去,自動(dòng)就給去重了,如果你需要對(duì)一些數(shù)據(jù)進(jìn)行快速的全局去重,你當(dāng)然也可以基于jvm內(nèi)存里的HashSet進(jìn)行去重,但是如果你的某個(gè)系統(tǒng)部署在多臺(tái)機(jī)器上呢?得基于redis進(jìn)行全局的set去重
可以基于set玩兒交集、并集、差集的操作,比如交集吧,可以把兩個(gè)人的粉絲列表整一個(gè)交集,看看倆人的共同好友是誰(shuí)?對(duì)吧
把兩個(gè)大v的粉絲都放在兩個(gè)set中,對(duì)兩個(gè)set做交集
(5)sorted set
排序的set,去重但是可以排序,寫進(jìn)去的時(shí)候給一個(gè)分?jǐn)?shù),自動(dòng)根據(jù)分?jǐn)?shù)排序,這個(gè)可以玩兒很多的花樣,最大的特點(diǎn)是有個(gè)分?jǐn)?shù)可以自定義排序規(guī)則
比如說(shuō)你要是想根據(jù)時(shí)間對(duì)數(shù)據(jù)排序,那么可以寫入進(jìn)去的時(shí)候用某個(gè)時(shí)間作為分?jǐn)?shù),人家自動(dòng)給你按照時(shí)間排序了
排行榜:將每個(gè)用戶以及其對(duì)應(yīng)的什么分?jǐn)?shù)寫入進(jìn)去,zadd board score username,接著zrevrange board 0 99,就可以獲取排名前100的用戶;zrank board username,可以看到用戶在排行榜里的排名。
2.6 Redis的內(nèi)存淘汰機(jī)制
2.6.1 往redis里寫的數(shù)據(jù)怎么沒了?
????數(shù)據(jù)太多,內(nèi)存滿了,或者觸發(fā)了什么條件,redis lru,自動(dòng)給你清理掉了一些最近很少使用的數(shù)據(jù)
redis的內(nèi)存占用過(guò)多的時(shí)候,此時(shí)會(huì)進(jìn)行內(nèi)存淘汰,有如下一些策略:
1)noeviction:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),新寫入操作會(huì)報(bào)錯(cuò),這個(gè)一般沒人用吧,實(shí)在是太惡心了
2)allkeys-lru:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),在鍵空間中,移除最近最少使用的key(這個(gè)是最常用的)
3)allkeys-random:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),在鍵空間中,隨機(jī)移除某個(gè)key,這個(gè)一般沒人用吧,為啥要隨機(jī),肯定是把最近最少使用的key給干掉啊
4)volatile-lru:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),在設(shè)置了過(guò)期時(shí)間的鍵空間中,移除最近最少使用的key(這個(gè)一般不太合適)
5)volatile-random:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),在設(shè)置了過(guò)期時(shí)間的鍵空間中,隨機(jī)移除某個(gè)key
6)volatile-ttl:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),在設(shè)置了過(guò)期時(shí)間的鍵空間中,有更早過(guò)期時(shí)間的key優(yōu)先移除
2.6.2 數(shù)據(jù)明明都過(guò)期了,怎么還占用著內(nèi)存?。?/h3>
定期刪除+惰性刪除
所謂定期刪除,指的是redis默認(rèn)是每隔100ms就隨機(jī)抽取一些設(shè)置了過(guò)期時(shí)間的key,檢查其是否過(guò)期,如果過(guò)期就刪除。假設(shè)redis里放了10萬(wàn)個(gè)key,都設(shè)置了過(guò)期時(shí)間,你每隔幾百毫秒,就檢查10萬(wàn)個(gè)key,那redis基本上就死了,cpu負(fù)載會(huì)很高的,消耗在你的檢查過(guò)期key上了。注意,這里可不是每隔100ms就遍歷所有的設(shè)置過(guò)期時(shí)間的key,那樣就是一場(chǎng)性能上的災(zāi)難。實(shí)際上redis是每隔100ms隨機(jī)抽取一些key來(lái)檢查和刪除的。
但是問(wèn)題是,定期刪除可能會(huì)導(dǎo)致很多過(guò)期key到了時(shí)間并沒有被刪除掉,那咋整呢?所以就是惰性刪除了。這就是說(shuō),在你獲取某個(gè)key的時(shí)候,redis會(huì)檢查一下,這個(gè)key如果設(shè)置了過(guò)期時(shí)間那么是否過(guò)期了?如果過(guò)期了此時(shí)就會(huì)刪除,不會(huì)給你返回任何東西。
2.7 redis如何通過(guò)讀寫分離來(lái)承載讀請(qǐng)求QPS超過(guò)10萬(wàn)+?
2.7.1 redis高并發(fā)跟整個(gè)系統(tǒng)的高并發(fā)之間的關(guān)系
redis,你要搞高并發(fā)的話,不可避免,要把底層的緩存搞得很好
mysql,高并發(fā),做到了,那么也是通過(guò)一系列復(fù)雜的分庫(kù)分表,訂單系統(tǒng),事務(wù)要求的,QPS到幾萬(wàn),比較高了
要做一些電商的商品詳情頁(yè),真正的超高并發(fā),QPS上十萬(wàn),甚至是百萬(wàn),一秒鐘百萬(wàn)的請(qǐng)求量
光是redis是不夠的,但是redis是整個(gè)大型的緩存架構(gòu)中,支撐高并發(fā)的架構(gòu)里面,非常重要的一個(gè)環(huán)節(jié)
首先,你的底層的緩存中間件,緩存系統(tǒng),必須能夠支撐的起我們說(shuō)的那種高并發(fā),其次,再經(jīng)過(guò)良好的整體的緩存架構(gòu)的設(shè)計(jì)(多級(jí)緩存架構(gòu)、熱點(diǎn)緩存),支撐真正的上十萬(wàn),甚至上百萬(wàn)的高并發(fā)
2.7.2 redis不能支撐高并發(fā)的瓶頸在哪里?
單機(jī)

2.7.3 如果redis要支撐超過(guò)10萬(wàn)+的并發(fā),那應(yīng)該怎么做?
單機(jī)的redis幾乎不太可能說(shuō)QPS超過(guò)10萬(wàn)+,除非一些特殊情況,比如你的機(jī)器性能特別好,配置特別高,物理機(jī),維護(hù)做的特別好,而且你的整體的操作不是太復(fù)雜
單機(jī)在幾萬(wàn)
讀寫分離,一般來(lái)說(shuō),對(duì)緩存,一般都是用來(lái)支撐讀高并發(fā)的,寫的請(qǐng)求是比較少的,可能寫請(qǐng)求也就一秒鐘幾千,一兩千
大量的請(qǐng)求都是讀,一秒鐘二十萬(wàn)次讀
讀寫分離
主從架構(gòu) -> 讀寫分離 -> 支撐10萬(wàn)+讀QPS的架構(gòu)

2.8 關(guān)于Redis的主從復(fù)制
2.8.1 redis replication的核心機(jī)制

(1)redis采用異步方式復(fù)制數(shù)據(jù)到slave節(jié)點(diǎn),不過(guò)redis 2.8開始,slave node會(huì)周期性地確認(rèn)自己每次復(fù)制的數(shù)據(jù)量
(2)一個(gè)master node是可以配置多個(gè)slave node的
(3)slave node也可以連接其他的slave node
(4)slave node做復(fù)制的時(shí)候,是不會(huì)block master node的正常工作的
(5)slave node在做復(fù)制的時(shí)候,也不會(huì)block對(duì)自己的查詢操作,它會(huì)用舊的數(shù)據(jù)集來(lái)提供服務(wù); 但是復(fù)制完成的時(shí)候,需要?jiǎng)h除舊數(shù)據(jù)集,加載新數(shù)據(jù)集,這個(gè)時(shí)候就會(huì)暫停對(duì)外服務(wù)了
(6)slave node主要用來(lái)進(jìn)行橫向擴(kuò)容,做讀寫分離,擴(kuò)容的slave node可以提高讀的吞吐量
slave,高可用性,有很大的關(guān)系
2.8.2 master持久化對(duì)于主從架構(gòu)的安全保障的意義
如果采用了主從架構(gòu),那么建議必須開啟master node的持久化!
(1)不建議用slave node作為master node的數(shù)據(jù)熱備,因?yàn)槟菢拥脑?,如果你關(guān)掉master的持久化,可能在master宕機(jī)重啟的時(shí)候數(shù)據(jù)是空的,然后可能一經(jīng)過(guò)復(fù)制,salve node數(shù)據(jù)也丟了
? ? ·master -> RDB和AOF都關(guān)閉了 -> 全部在內(nèi)存中
? ? ·master宕機(jī),重啟,是沒有本地?cái)?shù)據(jù)可以恢復(fù)的,然后就會(huì)直接認(rèn)為自己IDE數(shù)據(jù)是空的
? ? ·master就會(huì)將空的數(shù)據(jù)集同步到slave上去,所有slave的數(shù)據(jù)全部清空
100%的數(shù)據(jù)丟失,故master節(jié)點(diǎn),必須要使用持久化機(jī)制
(2)第二個(gè),master的各種備份方案,要不要做,萬(wàn)一說(shuō)本地的所有文件丟失了; 從備份中挑選一份rdb去恢復(fù)master; 這樣才能確保master啟動(dòng)的時(shí)候,是有數(shù)據(jù)的
即使采用了后續(xù)講解的高可用機(jī)制,slave node可以自動(dòng)接管master node,但是也可能sentinal還沒有檢測(cè)到master failure,master node就自動(dòng)重啟了,還是可能導(dǎo)致上面的所有slave node數(shù)據(jù)清空故障
2.8.3 主從復(fù)制底層原理
(1)主從架構(gòu)的核心原理
當(dāng)啟動(dòng)一個(gè)slave node的時(shí)候,它會(huì)發(fā)送一個(gè)PSYNC命令給master node
如果這是slave node重新連接master node,那么master node僅僅會(huì)復(fù)制給slave部分缺少的數(shù)據(jù); 否則如果是slave node第一次連接master node,那么會(huì)觸發(fā)一次full resynchronization
開始full resynchronization的時(shí)候,master會(huì)啟動(dòng)一個(gè)后臺(tái)線程,開始生成一份RDB快照文件,同時(shí)還會(huì)將從客戶端收到的所有寫命令緩存在內(nèi)存中。RDB文件生成完畢之后,master會(huì)將這個(gè)RDB發(fā)送給slave,slave會(huì)先寫入本地磁盤,然后再?gòu)谋镜卮疟P加載到內(nèi)存中。然后master會(huì)將內(nèi)存中緩存的寫命令發(fā)送給slave(異步),slave也會(huì)同步這些數(shù)據(jù)。

slave node如果跟master node有網(wǎng)絡(luò)故障,斷開了連接,會(huì)自動(dòng)重連。master如果發(fā)現(xiàn)有多個(gè)slave node都來(lái)重新連接,僅僅會(huì)啟動(dòng)一個(gè)rdb save操作,用一份數(shù)據(jù)服務(wù)所有slave node。
(2)主從復(fù)制的斷點(diǎn)續(xù)傳
從redis 2.8開始,就支持主從復(fù)制的斷點(diǎn)續(xù)傳,如果主從復(fù)制過(guò)程中,網(wǎng)絡(luò)連接斷掉了,那么可以接著上次復(fù)制的地方,繼續(xù)復(fù)制下去,而不是從頭開始復(fù)制一份
master node會(huì)在內(nèi)存中常見一個(gè)backlog,master和slave都會(huì)保存一個(gè)replica offset還有一個(gè)master id,offset就是保存在backlog中的。如果master和slave網(wǎng)絡(luò)連接斷掉了,slave會(huì)讓master從上次的replica offset開始繼續(xù)復(fù)制
但是如果沒有找到對(duì)應(yīng)的offset,那么就會(huì)執(zhí)行一次resynchronization
(3)無(wú)磁盤化復(fù)制
master在內(nèi)存中直接創(chuàng)建rdb,然后發(fā)送給slave,不會(huì)在自己本地落地磁盤了
repl-diskless-sync
repl-diskless-sync-delay,等待一定時(shí)長(zhǎng)再開始復(fù)制,因?yàn)橐雀鄐lave重新連接過(guò)來(lái)
(4)過(guò)期key處理
slave不會(huì)過(guò)期key,只會(huì)等待master過(guò)期key。如果master過(guò)期了一個(gè)key,或者通過(guò)LRU淘汰了一個(gè)key,那么會(huì)模擬一條del命令發(fā)送給slave。
2.9 哨兵機(jī)制
2.9.1 簡(jiǎn)介
2.9.1.1 哨兵的介紹
sentinal,中文名是哨兵
· 哨兵是redis集群架構(gòu)中非常重要的一個(gè)組件,主要功能如下
(1)集群監(jiān)控,負(fù)責(zé)監(jiān)控redis master和slave進(jìn)程是否正常工作
(2)消息通知,如果某個(gè)redis實(shí)例有故障,那么哨兵負(fù)責(zé)發(fā)送消息作為報(bào)警通知給管理員
(3)故障轉(zhuǎn)移,如果master node掛掉了,會(huì)自動(dòng)轉(zhuǎn)移到slave node上
(4)配置中心,如果故障轉(zhuǎn)移發(fā)生了,通知client客戶端新的master地址
· 哨兵本身也是分布式的,作為一個(gè)哨兵集群去運(yùn)行,互相協(xié)同工作
(1)故障轉(zhuǎn)移時(shí),判斷一個(gè)master node是宕機(jī)了,需要大部分的哨兵都同意才行,涉及到了分布式選舉的問(wèn)題
(2)即使部分哨兵節(jié)點(diǎn)掛掉了,哨兵集群還是能正常工作的,因?yàn)槿绻粋€(gè)作為高可用機(jī)制重要組成部分的故障轉(zhuǎn)移系統(tǒng)本身是單點(diǎn)的,那就很坑爹了
2.9.1.2 哨兵的核心知識(shí)
(1)哨兵至少需要3個(gè)實(shí)例,來(lái)保證自己的健壯性
(2)哨兵 + redis主從的部署架構(gòu),是不會(huì)保證數(shù)據(jù)零丟失的,只能保證redis集群的高可用性
(3)對(duì)于哨兵 + redis主從這種復(fù)雜的部署架構(gòu),盡量在測(cè)試環(huán)境和生產(chǎn)環(huán)境,都進(jìn)行充足的測(cè)試和演練
2.9.1.3 為什么redis哨兵集群只有2個(gè)節(jié)點(diǎn)無(wú)法正常工作?
哨兵集群必須部署2個(gè)以上節(jié)點(diǎn)
如果哨兵集群僅僅部署了個(gè)2個(gè)哨兵實(shí)例,quorum=1
+----+???????? +----+
| M1 |---------|R1 |
| S1 |???????? | S2 |
+----+???????? +----+
Configuration:quorum = 1
master宕機(jī),s1和s2中只要有1個(gè)哨兵認(rèn)為master宕機(jī)就可以還行切換,同時(shí)s1和s2中會(huì)選舉出一個(gè)哨兵來(lái)執(zhí)行故障轉(zhuǎn)移
同時(shí)這個(gè)時(shí)候,需要majority,也就是大多數(shù)哨兵都是運(yùn)行的,2個(gè)哨兵的majority就是2(2的majority=2,3的majority=2,5的majority=3,4的majority=2),2個(gè)哨兵都運(yùn)行著,就可以允許執(zhí)行故障轉(zhuǎn)移
但是如果整個(gè)M1和S1運(yùn)行的機(jī)器宕機(jī)了,那么哨兵只有1個(gè)了,此時(shí)就沒有majority來(lái)允許執(zhí)行故障轉(zhuǎn)移,雖然另外一臺(tái)機(jī)器還有一個(gè)R1,但是故障轉(zhuǎn)移不會(huì)執(zhí)行
2.9.1.4 經(jīng)典的3節(jié)點(diǎn)哨兵集群
?????? +----+
?????? | M1 |
?????? | S1 |
?????? +----+
????????? |
+----+??? |???+----+
| R2 |----+----|R3 |
| S2 |???????? | S3 |
+----+???????? +----+
Configuration:
quorum = 2,majority
如果M1所在機(jī)器宕機(jī)了,那么三個(gè)哨兵還剩下2個(gè),S2和S3可以一致認(rèn)為master宕機(jī),然后選舉出一個(gè)來(lái)執(zhí)行故障轉(zhuǎn)移
同時(shí)3個(gè)哨兵的majority是2,所以還剩下的2個(gè)哨兵運(yùn)行著,就可以允許執(zhí)行故障轉(zhuǎn)移
2.9.2? redis哨兵主備切換的數(shù)據(jù)丟失問(wèn)題:異步復(fù)制、集群腦裂
2.9.2.1 兩種數(shù)據(jù)丟失的情況
主備切換的過(guò)程,可能會(huì)導(dǎo)致數(shù)據(jù)丟失
(1)異步復(fù)制導(dǎo)致的數(shù)據(jù)丟失
因?yàn)閙aster -> slave的復(fù)制是異步的,所以可能有部分?jǐn)?shù)據(jù)還沒復(fù)制到slave,master就宕機(jī)了,此時(shí)這些部分?jǐn)?shù)據(jù)就丟失了

(2)腦裂導(dǎo)致的數(shù)據(jù)丟失
腦裂,也就是說(shuō),某個(gè)master所在機(jī)器突然脫離了正常的網(wǎng)絡(luò),跟其他slave機(jī)器不能連接,但是實(shí)際上master還運(yùn)行著
此時(shí)哨兵可能就會(huì)認(rèn)為master宕機(jī)了,然后開啟選舉,將其他slave切換成了master
這個(gè)時(shí)候,集群里就會(huì)有兩個(gè)master,也就是所謂的腦裂
此時(shí)雖然某個(gè)slave被切換成了master,但是可能client還沒來(lái)得及切換到新的master,還繼續(xù)寫向舊master的數(shù)據(jù)可能也丟失了
因此舊master再次恢復(fù)的時(shí)候,會(huì)被作為一個(gè)slave掛到新的master上去,自己的數(shù)據(jù)會(huì)清空,重新從新的master復(fù)制數(shù)據(jù)

2.9.2.2 解決異步復(fù)制和腦裂導(dǎo)致的數(shù)據(jù)丟失
min-slaves-to-write 1
min-slaves-max-lag 10
要求至少有1個(gè)slave,數(shù)據(jù)復(fù)制和同步的延遲不能超過(guò)10秒
如果說(shuō)一旦所有的slave,數(shù)據(jù)復(fù)制和同步的延遲都超過(guò)了10秒鐘,那么這個(gè)時(shí)候,master就不會(huì)再接收任何請(qǐng)求了
上面兩個(gè)配置可以減少異步復(fù)制和腦裂導(dǎo)致的數(shù)據(jù)丟失
(1)減少異步復(fù)制的數(shù)據(jù)丟失
有了min-slaves-max-lag這個(gè)配置,就可以確保說(shuō),一旦slave復(fù)制數(shù)據(jù)和ack延時(shí)太長(zhǎng),就認(rèn)為可能master宕機(jī)后損失的數(shù)據(jù)太多了,那么就拒絕寫請(qǐng)求,這樣可以把master宕機(jī)時(shí)由于部分?jǐn)?shù)據(jù)未同步到slave導(dǎo)致的數(shù)據(jù)丟失降低的可控范圍內(nèi)

(2)減少腦裂的數(shù)據(jù)丟失
如果一個(gè)master出現(xiàn)了腦裂,跟其他slave丟了連接,那么上面兩個(gè)配置可以確保說(shuō),如果不能繼續(xù)給指定數(shù)量的slave發(fā)送數(shù)據(jù),而且slave超過(guò)10秒沒有給自己ack消息,那么就直接拒絕客戶端的寫請(qǐng)求
這樣腦裂后的舊master就不會(huì)接受client的新數(shù)據(jù),也就避免了數(shù)據(jù)丟失
上面的配置就確保了,如果跟任何一個(gè)slave丟了連接,在10秒后發(fā)現(xiàn)沒有slave給自己ack,那么就拒絕新的寫請(qǐng)求
因此在腦裂場(chǎng)景下,最多就丟失10秒的數(shù)據(jù)

2.9.3 一些底層機(jī)制
2.9.3.1?哨兵集群的自動(dòng)發(fā)現(xiàn)機(jī)制
哨兵互相之間的發(fā)現(xiàn),是通過(guò)redis的pub/sub系統(tǒng)實(shí)現(xiàn)的,每個(gè)哨兵都會(huì)往__sentinel__:hello這個(gè)channel里發(fā)送一個(gè)消息,這時(shí)候所有其他哨兵都可以消費(fèi)到這個(gè)消息,并感知到其他的哨兵的存在
每隔兩秒鐘,每個(gè)哨兵都會(huì)往自己監(jiān)控的某個(gè)master+slaves對(duì)應(yīng)的__sentinel__:hello channel里發(fā)送一個(gè)消息,內(nèi)容是自己的host、ip和runid還有對(duì)這個(gè)master的監(jiān)控配置
每個(gè)哨兵也會(huì)去監(jiān)聽自己監(jiān)控的每個(gè)master+slaves對(duì)應(yīng)的__sentinel__:hello channel,然后去感知到同樣在監(jiān)聽這個(gè)master+slaves的其他哨兵的存在
每個(gè)哨兵還會(huì)跟其他哨兵交換對(duì)master的監(jiān)控配置,互相進(jìn)行監(jiān)控配置的同步
2.9.3.2?slave->master選舉算法
如果一個(gè)master被認(rèn)為odown了,而且majority哨兵都允許了主備切換,那么某個(gè)哨兵就會(huì)執(zhí)行主備切換操作,此時(shí)首先要選舉一個(gè)slave來(lái)
會(huì)考慮slave的一些信息
(1)跟master斷開連接的時(shí)長(zhǎng)
(2)slave優(yōu)先級(jí)
(3)復(fù)制offset
(4)run id
如果一個(gè)slave跟master斷開連接已經(jīng)超過(guò)了down-after-milliseconds的10倍,外加master宕機(jī)的時(shí)長(zhǎng),那么slave就被認(rèn)為不適合選舉為master
(down-after-milliseconds* 10) + milliseconds_since_master_is_in_SDOWN_state
接下來(lái)會(huì)對(duì)slave進(jìn)行排序
(1)按照slave優(yōu)先級(jí)進(jìn)行排序,slave priority越低,優(yōu)先級(jí)就越高
(2)如果slave priority相同,那么看replica offset,哪個(gè)slave復(fù)制了越多的數(shù)據(jù),offset越靠后,優(yōu)先級(jí)就越高
(3)如果上面兩個(gè)條件都相同,那么選擇一個(gè)run id比較小的那個(gè)slave
2.10?Redis持久化
2.10.1 意義
????redis持久化的意義,在于故障恢復(fù)

如果沒有持久化的話,redis遇到災(zāi)難性故障的時(shí)候,就會(huì)丟失所有的數(shù)據(jù)
如果通過(guò)持久化將數(shù)據(jù)搞一份兒在磁盤上去,然后定期比如說(shuō)同步和備份到一些云存儲(chǔ)服務(wù)上去,那么就可以保證數(shù)據(jù)不丟失全部,還是可以恢復(fù)一部分?jǐn)?shù)據(jù)回來(lái)的
2.10.2 RDB與AOF
2.10.2.1 RDB和AOF兩種持久化機(jī)制的介紹
RDB持久化機(jī)制,對(duì)redis中的數(shù)據(jù)執(zhí)行周期性的持久化
AOF機(jī)制對(duì)每條寫入命令作為日志,以append-only的模式寫入一個(gè)日志文件中,在redis重啟的時(shí)候,可以通過(guò)回放AOF日志中的寫入指令來(lái)重新構(gòu)建整個(gè)數(shù)據(jù)集
如果我們想要redis僅僅作為純內(nèi)存的緩存來(lái)用,那么可以禁止RDB和AOF所有的持久化機(jī)制
通過(guò)RDB或AOF,都可以將redis內(nèi)存中的數(shù)據(jù)給持久化到磁盤上面來(lái),然后可以將這些數(shù)據(jù)備份到別的地方去,比如說(shuō)阿里云,云服務(wù)。如果redis掛了,服務(wù)器上的內(nèi)存和磁盤上的數(shù)據(jù)都丟了,可以從云服務(wù)上拷貝回來(lái)之前的數(shù)據(jù),放到指定的目錄中,然后重新啟動(dòng)redis,redis就會(huì)自動(dòng)根據(jù)持久化數(shù)據(jù)文件中的數(shù)據(jù),去恢復(fù)內(nèi)存中的數(shù)據(jù),繼續(xù)對(duì)外提供服務(wù)
如果同時(shí)使用RDB和AOF兩種持久化機(jī)制,那么在redis重啟的時(shí)候,會(huì)使用AOF來(lái)重新構(gòu)建數(shù)據(jù),因?yàn)锳OF中的數(shù)據(jù)更加完整
2.10.2.2 RDB持久化機(jī)制的優(yōu)點(diǎn)
(1)RDB會(huì)生成多個(gè)數(shù)據(jù)文件,每個(gè)數(shù)據(jù)文件都代表了某一個(gè)時(shí)刻中redis的數(shù)據(jù),這種多個(gè)數(shù)據(jù)文件的方式,非常適合做冷備,可以將這種完整的數(shù)據(jù)文件發(fā)送到一些遠(yuǎn)程的安全存儲(chǔ)上去,比如說(shuō)Amazon的S3云服務(wù)上去,在國(guó)內(nèi)可以是阿里云的ODPS分布式存儲(chǔ)上,以預(yù)定好的備份策略來(lái)定期備份redis中的數(shù)據(jù)
(2)RDB對(duì)redis對(duì)外提供的讀寫服務(wù),影響非常小,可以讓redis保持高性能,因?yàn)閞edis主進(jìn)程只需要fork一個(gè)子進(jìn)程,讓子進(jìn)程執(zhí)行磁盤IO操作來(lái)進(jìn)行RDB持久化即可
(3)相對(duì)于AOF持久化機(jī)制來(lái)說(shuō),直接基于RDB數(shù)據(jù)文件來(lái)重啟和恢復(fù)redis進(jìn)程,更加快速
2.10.2.3 RDB持久化機(jī)制的缺點(diǎn)
(1)如果想要在redis故障時(shí),盡可能少的丟失數(shù)據(jù),那么RDB沒有AOF好。一般來(lái)說(shuō),RDB數(shù)據(jù)快照文件,都是每隔5分鐘,或者更長(zhǎng)時(shí)間生成一次,這個(gè)時(shí)候就得接受一旦redis進(jìn)程宕機(jī),那么會(huì)丟失最近5分鐘的數(shù)據(jù)
(2)RDB每次在fork子進(jìn)程來(lái)執(zhí)行RDB快照數(shù)據(jù)文件生成的時(shí)候,如果數(shù)據(jù)文件特別大,可能會(huì)導(dǎo)致對(duì)客戶端提供的服務(wù)暫停數(shù)毫秒,或者甚至數(shù)秒
2.10.2.4 AOF持久化機(jī)制的優(yōu)點(diǎn)
(1)AOF可以更好的保護(hù)數(shù)據(jù)不丟失,一般AOF會(huì)每隔1秒,通過(guò)一個(gè)后臺(tái)線程執(zhí)行一次fsync操作,最多丟失1秒鐘的數(shù)據(jù)
(2)AOF日志文件以append-only模式寫入,所以沒有任何磁盤尋址的開銷,寫入性能非常高,而且文件不容易破損,即使文件尾部破損,也很容易修復(fù)
(3)AOF日志文件即使過(guò)大的時(shí)候,出現(xiàn)后臺(tái)重寫操作,也不會(huì)影響客戶端的讀寫。因?yàn)樵趓ewrite log的時(shí)候,會(huì)對(duì)其中的指導(dǎo)進(jìn)行壓縮,創(chuàng)建出一份需要恢復(fù)數(shù)據(jù)的最小日志出來(lái)。再創(chuàng)建新日志文件的時(shí)候,老的日志文件還是照常寫入。當(dāng)新的merge后的日志文件ready的時(shí)候,再交換新老日志文件即可。
(4)AOF日志文件的命令通過(guò)非??勺x的方式進(jìn)行記錄,這個(gè)特性非常適合做災(zāi)難性的誤刪除的緊急恢復(fù)。比如某人不小心用flushall命令清空了所有數(shù)據(jù),只要這個(gè)時(shí)候后臺(tái)rewrite還沒有發(fā)生,那么就可以立即拷貝AOF文件,將最后一條flushall命令給刪了,然后再將該AOF文件放回去,就可以通過(guò)恢復(fù)機(jī)制,自動(dòng)恢復(fù)所有數(shù)據(jù)
2.10.2.5 AOF持久化機(jī)制的缺點(diǎn)
(1)對(duì)于同一份數(shù)據(jù)來(lái)說(shuō),AOF日志文件通常比RDB數(shù)據(jù)快照文件更大
? ? Redis通過(guò)內(nèi)存淘汰機(jī)制,淘汰內(nèi)存數(shù)據(jù)后,AOF中指令和當(dāng)前數(shù)據(jù)的相關(guān)性逐漸變小,通過(guò)AOF重寫來(lái)解決AOF膨脹的問(wèn)題。


(2)AOF開啟后,支持的寫QPS會(huì)比RDB支持的寫QPS低,因?yàn)锳OF一般會(huì)配置成每秒fsync一次日志文件,當(dāng)然,每秒一次fsync,性能也還是很高的
(3)以前AOF發(fā)生過(guò)bug,就是通過(guò)AOF記錄的日志,進(jìn)行數(shù)據(jù)恢復(fù)的時(shí)候,沒有恢復(fù)一模一樣的數(shù)據(jù)出來(lái)。所以說(shuō),類似AOF這種較為復(fù)雜的基于命令日志/merge/回放的方式,比基于RDB每次持久化一份完整的數(shù)據(jù)快照文件的方式,更加脆弱一些,容易有bug。不過(guò)AOF就是為了避免rewrite過(guò)程導(dǎo)致的bug,因此每次rewrite并不是基于舊的指令日志進(jìn)行merge的,而是基于當(dāng)時(shí)內(nèi)存中的數(shù)據(jù)進(jìn)行指令的重新構(gòu)建,這樣健壯性會(huì)好很多。
2.10.2.6 RDB和AOF到底該如何選擇
(1)不要僅僅使用RDB,因?yàn)槟菢訒?huì)導(dǎo)致你丟失很多數(shù)據(jù)
(2)也不要僅僅使用AOF,因?yàn)槟菢佑袃蓚€(gè)問(wèn)題,第一,你通過(guò)AOF做冷備,沒有RDB做冷備,來(lái)的恢復(fù)速度更快; 第二,RDB每次簡(jiǎn)單粗暴生成數(shù)據(jù)快照,更加健壯,可以避免AOF這種復(fù)雜的備份和恢復(fù)機(jī)制的bug
(3)綜合使用AOF和RDB兩種持久化機(jī)制,用AOF來(lái)保證數(shù)據(jù)不丟失,作為數(shù)據(jù)恢復(fù)的第一選擇; 用RDB來(lái)做不同程度的冷備,在AOF文件都丟失或損壞不可用的時(shí)候,還可以使用RDB來(lái)進(jìn)行快速的數(shù)據(jù)恢復(fù)
2.11 Redis Cluster集群
2.11.1 海量數(shù)據(jù)處理
(1)redis的集群架構(gòu) redis cluster
支撐N個(gè)redis master node,每個(gè)master node都可以掛載多個(gè)slave node
讀寫分離的架構(gòu),對(duì)于每個(gè)master來(lái)說(shuō),寫就寫到master,然后讀就從mater對(duì)應(yīng)的slave去讀
高可用,因?yàn)槊總€(gè)master都有salve節(jié)點(diǎn),那么如果mater掛掉,redis cluster這套機(jī)制,就會(huì)自動(dòng)將某個(gè)slave切換成master
redis cluster(多master + 讀寫分離 + 高可用)
我們只要基于redis cluster去搭建redis集群即可,不需要手工去搭建replication復(fù)制+主從架構(gòu)+讀寫分離+哨兵集群+高可用


(2)redis cluster vs. replication + sentinal
? ? · 如果你的數(shù)據(jù)量很少,主要是承載高并發(fā)高性能的場(chǎng)景,比如你的緩存一般就幾個(gè)G,單機(jī)足夠了
? ? · replication,一個(gè)mater,多個(gè)slave,要幾個(gè)slave跟你的要求的讀吞吐量有關(guān)系,然后自己搭建一個(gè)sentinal集群,去保證redis主從架構(gòu)的高可用性,就可以了
? ? · redis cluster,主要是針對(duì)海量數(shù)據(jù)+高并發(fā)+高可用的場(chǎng)景,海量數(shù)據(jù),如果你的數(shù)據(jù)量很大,那么建議就用redis cluster
2.11.2?分布式數(shù)據(jù)存儲(chǔ)的核心算法,數(shù)據(jù)分布的算法
hash算法 -> 一致性hash算法(memcached) -> redis cluster,hash slot算法
用不同的算法,就決定了在多個(gè)master節(jié)點(diǎn)的時(shí)候,數(shù)據(jù)如何分布到這些節(jié)點(diǎn)上去,解決這個(gè)問(wèn)題
2.11.2.1 redis cluster介紹
redis cluster
(1)自動(dòng)將數(shù)據(jù)進(jìn)行分片,每個(gè)master上放一部分?jǐn)?shù)據(jù)
(2)提供內(nèi)置的高可用支持,部分master不可用時(shí),還是可以繼續(xù)工作的
在redis cluster架構(gòu)下,每個(gè)redis要放開兩個(gè)端口號(hào),比如一個(gè)是6379,另外一個(gè)就是加10000的端口號(hào),比如16379
16379端口號(hào)是用來(lái)進(jìn)行節(jié)點(diǎn)間通信的,也就是cluster bus的東西,集群總線。cluster bus的通信,用來(lái)進(jìn)行故障檢測(cè),配置更新,故障轉(zhuǎn)移授權(quán)
cluster bus用了另外一種二進(jìn)制的協(xié)議,主要用于節(jié)點(diǎn)間進(jìn)行高效的數(shù)據(jù)交換,占用更少的網(wǎng)絡(luò)帶寬和處理時(shí)間
2.11.2.2 最老土的hash算法和弊端(大量緩存重建)

2.11.2.3 一致性hash算法(自動(dòng)緩存遷移)+虛擬節(jié)點(diǎn)(自動(dòng)負(fù)載均衡)


2.11.2.4 redis cluster的hash slot算法
redis cluster有固定的16384個(gè)hash slot,對(duì)每個(gè)key計(jì)算CRC16值,然后對(duì)16384取模,可以獲取key對(duì)應(yīng)的hash slot
redis cluster中每個(gè)master都會(huì)持有部分slot,比如有3個(gè)master,那么可能每個(gè)master持有5000多個(gè)hash slot
hash slot讓node的增加和移除很簡(jiǎn)單,增加一個(gè)master,就將其他master的hash slot移動(dòng)部分過(guò)去,減少一個(gè)master,就將它的hash slot移動(dòng)到其他master上去
移動(dòng)hash slot的成本是非常低的
客戶端的api,可以對(duì)指定的數(shù)據(jù),讓他們走同一個(gè)hash slot,通過(guò)hash tag來(lái)實(shí)現(xiàn)

2.11.3 集群節(jié)點(diǎn)間的通信
2.11.3.1 基礎(chǔ)通信原理
(1)redis cluster節(jié)點(diǎn)間采取gossip協(xié)議進(jìn)行通信
跟集中式不同,不是將集群元數(shù)據(jù)(節(jié)點(diǎn)信息,故障,等等)集中存儲(chǔ)在某個(gè)節(jié)點(diǎn)上,而是互相之間不斷通信,保持整個(gè)集群所有節(jié)點(diǎn)的數(shù)據(jù)是完整的
集中式:好處在于,元數(shù)據(jù)的更新和讀取,時(shí)效性非常好,一旦元數(shù)據(jù)出現(xiàn)了變更,立即就更新到集中式的存儲(chǔ)中,其他節(jié)點(diǎn)讀取的時(shí)候立即就可以感知到; 不好在于,所有的元數(shù)據(jù)的跟新壓力全部集中在一個(gè)地方,可能會(huì)導(dǎo)致元數(shù)據(jù)的存儲(chǔ)有壓力

gossip:好處在于,元數(shù)據(jù)的更新比較分散,不是集中在一個(gè)地方,更新請(qǐng)求會(huì)陸陸續(xù)續(xù),打到所有節(jié)點(diǎn)上去更新,有一定的延時(shí),降低了壓力; 缺點(diǎn),元數(shù)據(jù)更新有延時(shí),可能導(dǎo)致集群的一些操作會(huì)有一些滯后

(2)10000端口
每個(gè)節(jié)點(diǎn)都有一個(gè)專門用于節(jié)點(diǎn)間通信的端口,就是自己提供服務(wù)的端口號(hào)+10000,比如7001,那么用于節(jié)點(diǎn)間通信的就是17001端口
每隔節(jié)點(diǎn)每隔一段時(shí)間都會(huì)往另外幾個(gè)節(jié)點(diǎn)發(fā)送ping消息,同時(shí)其他幾點(diǎn)接收到ping之后返回pong
(3)交換的信息
故障信息,節(jié)點(diǎn)的增加和移除,hash slot信息,等等
2.11.3.2 gossip協(xié)議
gossip協(xié)議包含多種消息,包括ping,pong,meet,fail,等等
(1)meet: 某個(gè)節(jié)點(diǎn)發(fā)送meet給新加入的節(jié)點(diǎn),讓新節(jié)點(diǎn)加入集群中,然后新節(jié)點(diǎn)就會(huì)開始與其他節(jié)點(diǎn)進(jìn)行通信
redis-trib.rbadd-node
其實(shí)內(nèi)部就是發(fā)送了一個(gè)gossip meet消息,給新加入的節(jié)點(diǎn),通知那個(gè)節(jié)點(diǎn)去加入我們的集群
(2)ping: 每個(gè)節(jié)點(diǎn)都會(huì)頻繁給其他節(jié)點(diǎn)發(fā)送ping,其中包含自己的狀態(tài)還有自己維護(hù)的集群元數(shù)據(jù),互相通過(guò)ping交換元數(shù)據(jù)
每個(gè)節(jié)點(diǎn)每秒都會(huì)頻繁發(fā)送ping給其他的集群,ping,頻繁的互相之間交換數(shù)據(jù),互相進(jìn)行元數(shù)據(jù)的更新
(3)pong: 返回ping和meet,包含自己的狀態(tài)和其他信息,也可以用于信息廣播和更新
(4)fail: 某個(gè)節(jié)點(diǎn)判斷另一個(gè)節(jié)點(diǎn)fail之后,就發(fā)送fail給其他節(jié)點(diǎn),通知其他節(jié)點(diǎn),指定的節(jié)點(diǎn)宕機(jī)了
2.11.3.3 ping消息深入
ping很頻繁,而且要攜帶一些元數(shù)據(jù),所以可能會(huì)加重網(wǎng)絡(luò)負(fù)擔(dān)
每個(gè)節(jié)點(diǎn)每秒會(huì)執(zhí)行10次ping,每次會(huì)選擇5個(gè)最久沒有通信的其他節(jié)點(diǎn)
當(dāng)然如果發(fā)現(xiàn)某個(gè)節(jié)點(diǎn)通信延時(shí)達(dá)到了cluster_node_timeout / 2,那么立即發(fā)送ping,避免數(shù)據(jù)交換延時(shí)過(guò)長(zhǎng),落后的時(shí)間太長(zhǎng)了
比如說(shuō),兩個(gè)節(jié)點(diǎn)之間都10分鐘沒有交換數(shù)據(jù)了,那么整個(gè)集群處于嚴(yán)重的元數(shù)據(jù)不一致的情況,就會(huì)有問(wèn)題
所以cluster_node_timeout可以調(diào)節(jié),如果調(diào)節(jié)比較大,那么會(huì)降低發(fā)送的頻率
每次ping,一個(gè)是帶上自己節(jié)點(diǎn)的信息,還有就是帶上1/10其他節(jié)點(diǎn)的信息,發(fā)送出去,進(jìn)行數(shù)據(jù)交換
至少包含3個(gè)其他節(jié)點(diǎn)的信息,最多包含總節(jié)點(diǎn)-2個(gè)其他節(jié)點(diǎn)的信息
2.11.4 高可用性與主備切換原理
redis cluster的高可用的原理,幾乎跟哨兵是類似的
(1)判斷節(jié)點(diǎn)宕機(jī)
如果一個(gè)節(jié)點(diǎn)認(rèn)為另外一個(gè)節(jié)點(diǎn)宕機(jī),那么就是pfail,主觀宕機(jī)
如果多個(gè)節(jié)點(diǎn)都認(rèn)為另外一個(gè)節(jié)點(diǎn)宕機(jī)了,那么就是fail,客觀宕機(jī),跟哨兵的原理幾乎一樣,sdown,odown
在cluster-node-timeout內(nèi),某個(gè)節(jié)點(diǎn)一直沒有返回pong,那么就被認(rèn)為pfail
如果一個(gè)節(jié)點(diǎn)認(rèn)為某個(gè)節(jié)點(diǎn)pfail了,那么會(huì)在gossip ping消息中,ping給其他節(jié)點(diǎn),如果超過(guò)半數(shù)的節(jié)點(diǎn)都認(rèn)為pfail了,那么就會(huì)變成fail
(2)從節(jié)點(diǎn)過(guò)濾
對(duì)宕機(jī)的master node,從其所有的slave node中,選擇一個(gè)切換成master node
檢查每個(gè)slave node與master node斷開連接的時(shí)間,如果超過(guò)了cluster-node-timeout * cluster-slave-validity-factor,那么就沒有資格切換成master
這個(gè)也是跟哨兵是一樣的,從節(jié)點(diǎn)超時(shí)過(guò)濾的步驟
(3)從節(jié)點(diǎn)選舉
哨兵:對(duì)所有從節(jié)點(diǎn)進(jìn)行排序,slave priority,offset,run id
每個(gè)從節(jié)點(diǎn),都根據(jù)自己對(duì)master復(fù)制數(shù)據(jù)的offset,來(lái)設(shè)置一個(gè)選舉時(shí)間,offset越大(復(fù)制數(shù)據(jù)越多)的從節(jié)點(diǎn),選舉時(shí)間越靠前,優(yōu)先進(jìn)行選舉
所有的master node開始slave選舉投票,給要進(jìn)行選舉的slave進(jìn)行投票,如果大部分master node(N/2 + 1)都投票給了某個(gè)從節(jié)點(diǎn),那么選舉通過(guò),那個(gè)從節(jié)點(diǎn)可以切換成master
從節(jié)點(diǎn)執(zhí)行主備切換,從節(jié)點(diǎn)切換為主節(jié)點(diǎn)
(4)與哨兵比較
????整個(gè)流程跟哨兵相比,非常類似,所以說(shuō),redis cluster功能強(qiáng)大,直接集成了replication和sentinal的功能
2.11.4 緩存問(wèn)題
2.11.4.1 緩存雪崩


緩存雪崩的事前事中事后的解決方案
事前:redis高可用,主從+哨兵,redis cluster,避免全盤崩潰
事中:本地ehcache緩存 + hystrix限流&降級(jí),避免MySQL被打死
事后:redis持久化,快速恢復(fù)緩存數(shù)據(jù)
2.11.4.2?緩存穿透
緩存穿透的解決方法

另外還有,布隆過(guò)濾器可以有效緩解緩存穿透的問(wèn)題。
2.11.5 緩存+數(shù)據(jù)庫(kù)雙寫一致性問(wèn)題
2.11.5.1 最經(jīng)典的緩存+數(shù)據(jù)庫(kù)讀寫的模式,cache aside pattern
(1)Cache Aside Pattern
? ? · 讀的時(shí)候,先讀緩存,緩存沒有的話,那么就讀數(shù)據(jù)庫(kù),然后取出數(shù)據(jù)后放入緩存,同時(shí)返回響應(yīng)
? ? · 更新的時(shí)候,先刪除緩存,然后再更新數(shù)據(jù)庫(kù)
(2)為什么是刪除緩存,而不是更新緩存呢?
????原因很簡(jiǎn)單,很多時(shí)候,復(fù)雜點(diǎn)的緩存的場(chǎng)景,因?yàn)榫彺嬗械臅r(shí)候,不簡(jiǎn)單是數(shù)據(jù)庫(kù)中直接取出來(lái)的值
????更新緩存的代價(jià)是很高的
????是不是說(shuō),每次修改數(shù)據(jù)庫(kù)的時(shí)候,都一定要將其對(duì)應(yīng)的緩存去跟新一份?也許有的場(chǎng)景是這樣的,但是對(duì)于比較復(fù)雜的緩存數(shù)據(jù)計(jì)算的場(chǎng)景,就不是這樣了。28法則,黃金法則,20%的數(shù)據(jù),占用了80%的訪問(wèn)量。其實(shí)刪除緩存,而不是更新緩存,就是一個(gè)lazy計(jì)算的思想,不要每次都重新做復(fù)雜的計(jì)算,不管它會(huì)不會(huì)用到,而是讓它到需要被使用的時(shí)候再重新計(jì)算。
2.11.5.2 高并發(fā)場(chǎng)景下的緩存+數(shù)據(jù)庫(kù)雙寫不一致問(wèn)題分析與解決方案設(shè)計(jì)
(1)高并發(fā)情況下的現(xiàn)象
????數(shù)據(jù)發(fā)生了變更,先刪除了緩存,然后要去修改數(shù)據(jù)庫(kù),此時(shí)還沒修改,一個(gè)請(qǐng)求過(guò)來(lái),去讀緩存,發(fā)現(xiàn)緩存空了,去查詢數(shù)據(jù)庫(kù),查到了修改前的舊數(shù)據(jù),放到了緩存中,數(shù)據(jù)變更的程序完成了數(shù)據(jù)庫(kù)的修改。完了,數(shù)據(jù)庫(kù)和緩存中的數(shù)據(jù)不一樣了。
(2)解決辦法:數(shù)據(jù)庫(kù)與緩存更新與讀取操作進(jìn)行異步串行化
????更新數(shù)據(jù)的時(shí)候,根據(jù)數(shù)據(jù)的唯一標(biāo)識(shí),將操作路由之后,發(fā)送到一個(gè)jvm內(nèi)部的隊(duì)列中
????讀取數(shù)據(jù)的時(shí)候,如果發(fā)現(xiàn)數(shù)據(jù)不在緩存中,那么將重新讀取數(shù)據(jù)+更新緩存的操作,根據(jù)唯一標(biāo)識(shí)路由之后,也發(fā)送同一個(gè)jvm內(nèi)部的隊(duì)列中
????一個(gè)隊(duì)列對(duì)應(yīng)一個(gè)工作線程,每個(gè)工作線程串行拿到對(duì)應(yīng)的操作,然后一條一條的執(zhí)行。此時(shí)如果一個(gè)讀請(qǐng)求過(guò)來(lái),讀到了空的緩存,那么可以先將緩存更新的請(qǐng)求發(fā)送到隊(duì)列中,此時(shí)會(huì)在隊(duì)列中積壓,然后同步等待緩存更新完成。一個(gè)隊(duì)列中,其實(shí)多個(gè)更新緩存請(qǐng)求串在一起是沒意義的,因此可以做過(guò)濾,如果發(fā)現(xiàn)隊(duì)列中已經(jīng)有一個(gè)更新緩存的請(qǐng)求了,那么就不用再放個(gè)更新請(qǐng)求操作進(jìn)去了,直接等待前面的更新操作請(qǐng)求完成即可。
2.12 redis的并發(fā)競(jìng)爭(zhēng)問(wèn)題是什么?如何解決這個(gè)問(wèn)題?了解Redis事務(wù)的CAS方案嗎?

2.13 生產(chǎn)環(huán)境中的redis是怎么部署的?Demo
redis cluster,10臺(tái)機(jī)器,5臺(tái)機(jī)器部署了redis主實(shí)例,另外5臺(tái)機(jī)器部署了redis的從實(shí)例,每個(gè)主實(shí)例掛了一個(gè)從實(shí)例,5個(gè)節(jié)點(diǎn)對(duì)外提供讀寫服務(wù),每個(gè)節(jié)點(diǎn)的讀寫高峰qps可能可以達(dá)到每秒5萬(wàn),5臺(tái)機(jī)器最多是25萬(wàn)讀寫請(qǐng)求/s。
機(jī)器是什么配置?32G內(nèi)存+8核CPU+1T磁盤,但是分配給redis進(jìn)程的是10g內(nèi)存,一般線上生產(chǎn)環(huán)境,redis的內(nèi)存盡量不要超過(guò)10g,超過(guò)10g可能會(huì)有問(wèn)題。
5臺(tái)機(jī)器對(duì)外提供讀寫,一共有50g內(nèi)存。
因?yàn)槊總€(gè)主實(shí)例都掛了一個(gè)從實(shí)例,所以是高可用的,任何一個(gè)主實(shí)例宕機(jī),都會(huì)自動(dòng)故障遷移,redis從實(shí)例會(huì)自動(dòng)變成主實(shí)例繼續(xù)提供讀寫服務(wù)
你往內(nèi)存里寫的是什么數(shù)據(jù)?每條數(shù)據(jù)的大小是多少?商品數(shù)據(jù),每條數(shù)據(jù)是10kb。100條數(shù)據(jù)是1mb,10萬(wàn)條數(shù)據(jù)是1g。常駐內(nèi)存的是200萬(wàn)條商品數(shù)據(jù),占用內(nèi)存是20g,僅僅不到總內(nèi)存的50%。
目前高峰期每秒就是3500左右的請(qǐng)求量
2.14 總結(jié)
redis高并發(fā):主從架構(gòu),一主多從,一般來(lái)說(shuō),很多項(xiàng)目其實(shí)就足夠了,單主用來(lái)寫入數(shù)據(jù),單機(jī)幾萬(wàn)QPS,多從用來(lái)查詢數(shù)據(jù),多個(gè)從實(shí)例可以提供每秒10萬(wàn)的QPS。
redis高可用:如果你做主從架構(gòu)部署,其實(shí)就是加上哨兵就可以了,就可以實(shí)現(xiàn),任何一個(gè)實(shí)例宕機(jī),自動(dòng)會(huì)進(jìn)行主備切換。
redis高并發(fā)的同時(shí),還需要容納大量的數(shù)據(jù):一主多從,每個(gè)實(shí)例都容納了完整的數(shù)據(jù),比如redis主就10G的內(nèi)存量,其實(shí)你就最對(duì)只能容納10g的數(shù)據(jù)量。如果你的緩存要容納的數(shù)據(jù)量很大,達(dá)到了幾十g,甚至幾百g,或者是幾t,那你就需要redis集群,而且用redis集群之后,可以提供可能每秒幾十萬(wàn)的讀寫并發(fā)。