我拍了拍Redis,被移出了群聊···
自從上次被拉入群聊之后(那天,我被拉入一個(gè) Redis 群聊···),我就從一個(gè)人單打獨(dú)斗變成了團(tuán)隊(duì)合作。
Redis 的新煩惱
在小伙伴們的共同努力下,不僅有主從復(fù)制可以數(shù)據(jù)備份,還有哨兵節(jié)點(diǎn)負(fù)責(zé)監(jiān)控管理,我現(xiàn)在也可以拍拍胸脯說我們是高可用服務(wù)了!
但是,幸福的日子沒過太久,我們就笑不起來了。
不知道是我們的工作太出色,還是業(yè)務(wù)發(fā)展太快,程序員們對(duì)我們養(yǎng)成了依賴,什么都往我們這里寫,數(shù)據(jù)量越來越大,我們承受了這個(gè)年紀(jì)不該有的壓力~
雖然有主從復(fù)制+哨兵,但只能解決高可用的問題,解決不了數(shù)據(jù)量大的問題!
因?yàn)樵蹅兛雌饋砣耸侄?,但都是存?chǔ)的全量數(shù)據(jù),所以對(duì)于數(shù)據(jù)容量提升并沒有什么幫助。
集群時(shí)代
這一天,我找到了大白和小黑,咱們仨合計(jì)了一下,一個(gè)節(jié)點(diǎn)的力量不足,但眾人劃槳可以開大船啊,我們決定把三個(gè)人的內(nèi)存空間“拼”起來,每個(gè)人負(fù)責(zé)一部分?jǐn)?shù)據(jù),合體進(jìn)化成一個(gè)大的緩存服務(wù)器,進(jìn)入集群 Cluster 時(shí)代!
集群,集群,首要問題當(dāng)然是團(tuán)隊(duì)建設(shè)啦!我們得想一套辦法來組建團(tuán)隊(duì),還要考慮到以后可能會(huì)擴(kuò)容,會(huì)有新的伙伴加入我們,我們仨憋了半天,抄襲人家 TCP 的三次握手,也搞了一個(gè)握手協(xié)議出來。
想要加入集群,得有一個(gè)介紹人才行,通過團(tuán)隊(duì)里的任何一個(gè)成員都行。
就比如說我吧,只要告訴我 IP 和端口,我就給他發(fā)送一個(gè) MEET 信息,發(fā)起握手,對(duì)方得回我一個(gè) PONG 信息同意入伙,最后我再回他一個(gè) PING 信息,三次握手就完成了!
然后,我再把這件事告訴團(tuán)隊(duì)中其他成員,新的伙伴就算正式成為我們的一份子了。
第二件很重要的事情就是要解決數(shù)據(jù)存儲(chǔ)的公平問題,不能旱的旱死,澇的澇死,我們爭論了很久,最后決定學(xué)習(xí)人家哈希表的方法。
我們總共劃分了 16384 個(gè)哈希桶,我們把它叫做槽位 Slot,程序員可以按照我們能力大小給我們各自分配一部分槽位。
比如我們團(tuán)隊(duì):
- 我:0-4000
- 大白:4001-9000
- 小黑:9001-16383
我比較菜,只分到了 4000 個(gè),小黑老哥最辛苦,要負(fù)責(zé) 7000+ 個(gè)槽位,正所謂能力越大,責(zé)任越大,誰叫他內(nèi)存空間最大呢。
數(shù)據(jù)讀寫的時(shí)候,對(duì)鍵值做一下哈希計(jì)算,映射到哪個(gè)槽,就由誰負(fù)責(zé)。
為了讓大家的信息達(dá)成一致,啟動(dòng)的時(shí)候,每個(gè)人都得把自己負(fù)責(zé)的槽位信息告訴其他伙伴。
一共有一萬多個(gè)槽,要通知其他小伙伴,需要傳輸?shù)臄?shù)據(jù)量還挺大的,后來我們仨又商量了一下。
為了壓縮數(shù)據(jù)空間,每個(gè)槽位干脆就用一個(gè) bit 來表示,自己負(fù)責(zé)這一位就是 1,否則就是 0,總共也才 16384 個(gè) bit,也就是 2048 個(gè)字節(jié),傳輸起來輕便快捷,一口氣就發(fā)送過去了。
- struct clusterNode {
- // ...
- unsigned char *slots[16384/8];
- // ...
- };
這樣傳輸?shù)臄?shù)據(jù)是輕量了,但真正工作的時(shí)候還是不方便,遇到讀寫數(shù)據(jù)的時(shí)候,總不能挨個(gè)去看誰的那一位是 1 吧。
干脆一步到位,用空間換時(shí)間,我們又準(zhǔn)備了一個(gè)超大的數(shù)組來存儲(chǔ)每個(gè)槽由哪個(gè)節(jié)點(diǎn)來負(fù)責(zé),通過上面的方式拿到信息后,就更新到這里來:
- struct clusterNode *slots[16384];
這樣一來,遇到數(shù)據(jù)訪問的時(shí)候,我們就能快速知道這個(gè)數(shù)據(jù)是由誰來負(fù)責(zé)了。
對(duì)了,這 16384 個(gè)槽位必須都得有人來負(fù)責(zé),我們整個(gè)集群才算是正常工作,處于上線狀態(tài),否則就是下線狀態(tài)。
你想啊,萬一哪個(gè)鍵值哈希映射后的槽位沒人負(fù)責(zé),那該從哪里讀,又該寫到哪里去呢?所以我們要工作,一個(gè)槽都不能少!
集群數(shù)據(jù)的訪問
數(shù)據(jù)分派的問題解決了,我們團(tuán)隊(duì)總算可以正式上線工作了!
和原來不同的是,數(shù)據(jù)讀寫的時(shí)候多了一個(gè)步驟:得先檢查數(shù)據(jù)是不是由自己負(fù)責(zé)。
如果是自己負(fù)責(zé),那就進(jìn)行處理,不然的話,就要返回一個(gè) MOVED 錯(cuò)誤給請(qǐng)求端,同時(shí)把槽號(hào)、IP 和端口告訴他,讓他知道該去找誰處理。嘿嘿,這個(gè) MOVED 我們也是抄襲的 HTTP 中的 302 跳轉(zhuǎn)~
不過程序員們是感知不到的,他們都是用封裝好的庫來操作,才不會(huì)親自寫代碼來跟我通信呢~
一開始的工作很順利,但沒過多久就出事兒了:
隨后我們開始了數(shù)據(jù)遷移,還把這一套流程標(biāo)準(zhǔn)化了,留著為以后新入伙的朋友分配數(shù)據(jù)。
經(jīng)過一段時(shí)間的磨合,我們集群小分隊(duì)配合的越來越默契。
不過光靠咱們仨還是不行,萬一哪天有人掛了,整個(gè)集群就得下線了!咱們?nèi)齻€(gè)每人至少得有一個(gè) backup 才行!
于是我找到了原來的一幫小弟,讓他們也加入我們,繼續(xù)給我們當(dāng)起了從節(jié)點(diǎn),平時(shí)當(dāng)我們的 backup,從我們這里復(fù)制數(shù)據(jù),一旦我們遇到故障,他們就能快速頂上。
有了集群工作+主從復(fù)制,我們現(xiàn)在不僅高可用,數(shù)據(jù)容量也大大提升了,就算以后不夠用了也有辦法擴(kuò)容,我們又過上了舒服的日子~
作者:軒轅之風(fēng)
編輯:陶家龍
出處:轉(zhuǎn)載自公眾號(hào)編程技術(shù)宇宙(ID:xuanyuancoding)