您好,登錄后才能下訂單哦!
如何進(jìn)行Redis知識(shí)點(diǎn)的分析,很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。
是數(shù)據(jù)結(jié)構(gòu)而非類型
很多文章都會(huì)說,redis支持5種常用的數(shù)據(jù)類型,這其實(shí)是存在很大的歧義。redis里存的都是二進(jìn)制數(shù)據(jù),其實(shí)就是字節(jié)數(shù)組(byte[]),這些字節(jié)數(shù)據(jù)是沒有數(shù)據(jù)類型的,只有把它們按照合理的格式解碼后,可以變成一個(gè)字符串,整數(shù)或?qū)ο?,此時(shí)才具有數(shù)據(jù)類型。
這一點(diǎn)必須要記住。所以任何東西只要能轉(zhuǎn)化成字節(jié)數(shù)組(byte[])的,都可以存到redis里。管你是字符串、數(shù)字、對(duì)象、圖片、聲音、視頻、還是文件,只要變成byte數(shù)組。
因此redis里的String指的并不是字符串,它其實(shí)表示的是一種最簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu),即一個(gè)key只能對(duì)應(yīng)一個(gè)value。這里的key和value都是byte數(shù)組,只不過key一般是由一個(gè)字符串轉(zhuǎn)換成的byte數(shù)組,value則根據(jù)實(shí)際需要而定。
在特定情況下,對(duì)value也會(huì)有一些要求,比如要進(jìn)行自增或自減操作,那value對(duì)應(yīng)的byte數(shù)組必須要能被解碼成一個(gè)數(shù)字才行,否則會(huì)報(bào)錯(cuò)。
那么List這種數(shù)據(jù)結(jié)構(gòu),其實(shí)表示一個(gè)key可以對(duì)應(yīng)多個(gè)value,且value之間是有先后順序的,value值可以重復(fù)。
Set這種數(shù)據(jù)結(jié)構(gòu),表示一個(gè)key可以對(duì)應(yīng)多個(gè)value,且value之間是沒有先后順序的,value值也不可以重復(fù)。
Hash這種數(shù)據(jù)結(jié)構(gòu),表示一個(gè)key可以對(duì)應(yīng)多個(gè)key-value對(duì),此時(shí)這些key-value對(duì)之間的先后順序一般意義不大,這是一個(gè)按照名稱語義來訪問的數(shù)據(jù)結(jié)構(gòu),而非位置語義。
Sorted Set這種數(shù)據(jù)結(jié)構(gòu),表示一個(gè)key可以對(duì)應(yīng)多個(gè)value,value之間是有大小排序的,value值不可以重復(fù)。每個(gè)value都和一個(gè)浮點(diǎn)數(shù)相關(guān)聯(lián),該浮點(diǎn)數(shù)叫score。元素排序規(guī)則是:先按score排序,再按value排序。
相信現(xiàn)在你對(duì)這5種數(shù)據(jù)結(jié)構(gòu)有了更清晰的認(rèn)識(shí),那它們的對(duì)應(yīng)命令對(duì)你來說就是小case了。
集群帶來的問題與解決思路
集群帶來的好處是顯而易見的,比如容量增加、處理能力增強(qiáng),還可以按需要進(jìn)行動(dòng)態(tài)的擴(kuò)容、縮容。但同時(shí)也會(huì)引入一些新的問題,至少會(huì)有下面這兩個(gè)。
一是數(shù)據(jù)分配:存數(shù)據(jù)時(shí)應(yīng)該放到哪個(gè)節(jié)點(diǎn)上,取數(shù)據(jù)時(shí)應(yīng)該去哪個(gè)節(jié)點(diǎn)上找。二是數(shù)據(jù)移動(dòng):集群擴(kuò)容,新增加節(jié)點(diǎn)時(shí),該節(jié)點(diǎn)上的數(shù)據(jù)從何處來;集群縮容,要剔除節(jié)點(diǎn)時(shí),該節(jié)點(diǎn)上的數(shù)據(jù)往何處去。
上面這兩個(gè)問題有一個(gè)共同點(diǎn)就是,如何去描述和存儲(chǔ)數(shù)據(jù)與節(jié)點(diǎn)的映射關(guān)系。又因?yàn)閿?shù)據(jù)的位置是由key決定的,所以問題就演變?yōu)槿绾谓⑵鸶鱾€(gè)key和集群所有節(jié)點(diǎn)的關(guān)聯(lián)關(guān)系。
集群的節(jié)點(diǎn)是相對(duì)固定和少數(shù)的,雖然有增加節(jié)點(diǎn)和剔除節(jié)點(diǎn)。但集群里存儲(chǔ)的key,則是完全隨機(jī)、沒有規(guī)律、不可預(yù)測(cè)、數(shù)量龐多,還非?,嵥?。
這就好比一所大學(xué)和它的所有學(xué)生之間的關(guān)系。如果大學(xué)和學(xué)生直接掛鉤的話,一定會(huì)比較混亂。現(xiàn)實(shí)是它們之間又加入了好幾層,首先有院系,其次有專業(yè),再者有年級(jí),***還有班級(jí)。經(jīng)過這四層映射之后,關(guān)系就清爽很多了。
這其實(shí)是一個(gè)非常重要的結(jié)論,這個(gè)世界上沒有什么問題是不能通過加入一層來解決的。如果有,那就再加入一層。計(jì)算機(jī)里也是這樣的。
redis在數(shù)據(jù)和節(jié)點(diǎn)之間又加入了一層,把這層稱為槽(slot),因該槽主要和哈希有關(guān),又叫哈希槽。
***變成了,節(jié)點(diǎn)上放的是槽,槽里放的是數(shù)據(jù)。槽解決的是粒度問題,相當(dāng)于把粒度變大了,這樣便于數(shù)據(jù)移動(dòng)。哈希解決的是映射問題,使用key的哈希值來計(jì)算所在的槽,便于數(shù)據(jù)分配。
可以這樣來理解,你的學(xué)習(xí)桌子上堆滿了書,亂的很,想找到某本書非常困難。于是你買了幾個(gè)大的收納箱,把這些書按照書名的長(zhǎng)度放入不同的收納箱,然后把這些收納箱放到桌子上。
這樣就變成了,桌子上是收納箱,收納箱里是書籍。這樣書籍移動(dòng)很方便,搬起一個(gè)箱子就走了。尋找書籍也很方便,只要數(shù)一數(shù)書名的長(zhǎng)度,去對(duì)應(yīng)的箱子里找就行了。
其實(shí)我們也沒做什么,只是買了幾個(gè)箱子,按照某種規(guī)則把書裝入箱子。就這么簡(jiǎn)單的舉動(dòng),就徹底改變了原來一盤散沙的狀況。是不是有點(diǎn)小小的神奇呢。
一個(gè)集群只能有16384個(gè)槽,編號(hào)0-16383。這些槽會(huì)分配給集群中的所有主節(jié)點(diǎn),分配策略沒有要求??梢灾付男┚幪?hào)的槽分配給哪個(gè)主節(jié)點(diǎn)。集群會(huì)記錄節(jié)點(diǎn)和槽的對(duì)應(yīng)關(guān)系。
接下來就需要對(duì)key求哈希值,然后對(duì)16384取余,余數(shù)是幾key就落入對(duì)應(yīng)的槽里。slot = CRC16(key) % 16384。
以槽為單位移動(dòng)數(shù)據(jù),因?yàn)椴鄣臄?shù)目是固定的,處理起來比較容易,這樣數(shù)據(jù)移動(dòng)問題就解決了。
使用哈希函數(shù)計(jì)算出key的哈希值,這樣就可以算出它對(duì)應(yīng)的槽,然后利用集群存儲(chǔ)的槽和節(jié)點(diǎn)的映射關(guān)系查詢出槽所在的節(jié)點(diǎn),于是數(shù)據(jù)和節(jié)點(diǎn)就映射起來了,這樣數(shù)據(jù)分配問題就解決了。
我想說的是,一般的人只會(huì)去學(xué)習(xí)各種技術(shù),高手更在乎如何跳出技術(shù),尋求一種解決方案或思路方向,順著這個(gè)方向走下去,八九不離十能找到你想要的答案。
集群對(duì)命令操作的取舍
客戶端只要和集群中的一個(gè)節(jié)點(diǎn)建立鏈接后,就可以獲取到整個(gè)集群的所有節(jié)點(diǎn)信息。此外還會(huì)獲取所有哈希槽和節(jié)點(diǎn)的對(duì)應(yīng)關(guān)系信息,這些信息數(shù)據(jù)都會(huì)在客戶端緩存起來,因?yàn)檫@些信息相當(dāng)有用。
客戶端可以向任何節(jié)點(diǎn)發(fā)送請(qǐng)求,那么拿到一個(gè)key后到底該向哪個(gè)節(jié)點(diǎn)發(fā)請(qǐng)求呢?其實(shí)就是把集群里的那套key和節(jié)點(diǎn)的映射關(guān)系理論搬到客戶端來就行了。
所以客戶端需要實(shí)現(xiàn)一個(gè)和集群端一樣的哈希函數(shù),先計(jì)算出key的哈希值,然后再對(duì)16384取余,這樣就找到了該key對(duì)應(yīng)的哈希槽,利用客戶端緩存的槽和節(jié)點(diǎn)的對(duì)應(yīng)關(guān)系信息,就可以找到該key對(duì)應(yīng)的節(jié)點(diǎn)了。
接下來發(fā)送請(qǐng)求就可以了。還可以把key和節(jié)點(diǎn)的映射關(guān)系緩存起來,下次再請(qǐng)求該key時(shí),直接就拿到了它對(duì)應(yīng)的節(jié)點(diǎn),不用再計(jì)算一遍了。
理論和現(xiàn)實(shí)總是有差距的,集群已經(jīng)發(fā)生了變化,客戶端的緩存還沒來得及更新。肯定會(huì)出現(xiàn)拿到一個(gè)key向?qū)?yīng)的節(jié)點(diǎn)發(fā)請(qǐng)求,其實(shí)這個(gè)key已經(jīng)不在那個(gè)節(jié)點(diǎn)上了。此時(shí)這個(gè)節(jié)點(diǎn)應(yīng)該怎么辦?
這個(gè)節(jié)點(diǎn)可以去key實(shí)際所在的節(jié)點(diǎn)上拿到數(shù)據(jù)再返回給客戶端,也可以直接告訴客戶端key已經(jīng)不在我這里了,同時(shí)附上key現(xiàn)在所在的節(jié)點(diǎn)信息,讓客戶端再去請(qǐng)求一次,類似于HTTP的302重定向。
這其實(shí)是個(gè)選擇問題,也是個(gè)哲學(xué)問題。結(jié)果就是redis集群選擇了后者。因此,節(jié)點(diǎn)只處理自己擁有的key,對(duì)于不擁有的key將返回重定向錯(cuò)誤,即-MOVED key 127.0.0.1:6381,客戶端重新向這個(gè)新節(jié)點(diǎn)發(fā)送請(qǐng)求。
所以說選擇是一種哲學(xué),也是個(gè)智慧。稍后再談這個(gè)問題。先來看看另一個(gè)情況,和這個(gè)問題有些相同點(diǎn)。
redis有一種命令可以一次帶多個(gè)key,如MGET,我把這些稱為多key命令。這個(gè)多key命令的請(qǐng)求被發(fā)送到一個(gè)節(jié)點(diǎn)上,這里有一個(gè)潛在的問題,不知道大家有沒有想到,就是這個(gè)命令里的多個(gè)key一定都位于那同一個(gè)節(jié)點(diǎn)上嗎?
就分為兩種情況了,如果多個(gè)key不在同一個(gè)節(jié)點(diǎn)上,此時(shí)節(jié)點(diǎn)只能返回重定向錯(cuò)誤了,但是多個(gè)key完全可能位于多個(gè)不同的節(jié)點(diǎn)上,此時(shí)返回的重定向錯(cuò)誤就會(huì)非常亂,所以redis集群選擇不支持此種情況。
如果多個(gè)key位于同一個(gè)節(jié)點(diǎn)上呢,理論上是沒有問題的,redis集群是否支持就和redis的版本有關(guān)系了,具體使用時(shí)自己測(cè)試一下就行了。
在這個(gè)過程中我們發(fā)現(xiàn)了一件頗有意義的事情,就是讓一組相關(guān)的key映射到同一個(gè)節(jié)點(diǎn)上是非常有必要的,這樣可以提高效率,通過多key命令一次獲取多個(gè)值。
那么問題來了,如何給這些key起名字才能讓他們落到同一個(gè)節(jié)點(diǎn)上,難不成都要先計(jì)算個(gè)哈希值,再取個(gè)余數(shù),太麻煩了吧。當(dāng)然不是這樣了,redis已經(jīng)幫我們想好了。
可以來簡(jiǎn)單推理下,要想讓兩個(gè)key位于同一個(gè)節(jié)點(diǎn)上,它們的哈希值必須要一樣。要想哈希值一樣,傳入哈希函數(shù)的字符串必須一樣。那我們只能傳進(jìn)去兩個(gè)一模一樣的字符串了,那不就變成同一個(gè)key了,后面的會(huì)覆蓋前面的數(shù)據(jù)。
這里的問題是我們都是拿整個(gè)key去計(jì)算哈希值,這就導(dǎo)致key和參與計(jì)算哈希值的字符串耦合了,需要將它們解耦才行,就是key和參與計(jì)算哈希值的字符串有關(guān)但是又不一樣。
redis基于這個(gè)原理為我們提供了方案,叫做key哈希標(biāo)簽。先看例子,{user1000}.following,{user1000}.followers,相信你已經(jīng)看出了門道,就是僅使用Key中的位于{和}間的字符串參與計(jì)算哈希值。
這樣可以保證哈希值相同,落到相同的節(jié)點(diǎn)上。但是key又是不同的,不會(huì)互相覆蓋。使用哈希標(biāo)簽把一組相關(guān)的key關(guān)聯(lián)了起來,問題就這樣被輕松愉快地解決了。
相信你已經(jīng)發(fā)現(xiàn)了,要解決問題靠的是巧妙的奇思妙想,而不是非要用牛逼的技術(shù)牛逼的算法。這就是小強(qiáng),小而強(qiáng)大。
最后再來談選擇的哲學(xué)。redis的核心就是以最快的速度進(jìn)行常用數(shù)據(jù)結(jié)構(gòu)的key/value存取,以及圍繞這些數(shù)據(jù)結(jié)構(gòu)的運(yùn)算。對(duì)于與核心無關(guān)的或會(huì)拖累核心的都選擇弱化處理或不處理,這樣做是為了保證核心的簡(jiǎn)單、快速和穩(wěn)定。
其實(shí)就是在廣度和深度面前,redis選擇了深度。所以節(jié)點(diǎn)不去處理自己不擁有的key,集群不去支持多key命令。這樣一方面可以快速地響應(yīng)客戶端,另一方面可以避免在集群內(nèi)部有大量的數(shù)據(jù)傳輸與合并。
單線程模型
redis集群的每個(gè)節(jié)點(diǎn)里只有一個(gè)線程負(fù)責(zé)接受和執(zhí)行所有客戶端發(fā)送的請(qǐng)求。技術(shù)上使用多路復(fù)用I/O,使用Linux的epoll函數(shù),這樣一個(gè)線程就可以管理很多socket連接。
除此之外,選擇單線程還有以下這些原因:
1、redis都是對(duì)內(nèi)存的操作,速度極快(10W+QPS)
2、整體的時(shí)間主要都是消耗在了網(wǎng)絡(luò)的傳輸上
3、如果使用了多線程,則需要多線程同步,這樣實(shí)現(xiàn)起來會(huì)變的復(fù)雜
4、線程的加鎖時(shí)間甚至都超過了對(duì)內(nèi)存操作的時(shí)間
5、多線程上下文頻繁的切換需要消耗更多的CPU時(shí)間
6、還有就是單線程天然支持原子操作,而且單線程的代碼寫起來更簡(jiǎn)單
事務(wù)
事務(wù)大家都知道,就是把多個(gè)操作捆綁在一起,要么都執(zhí)行(成功了),要么一個(gè)也不執(zhí)行(回滾了)。redis也是支持事務(wù)的,但可能和你想要的不太一樣,一起來看看吧。
redis的事務(wù)可以分為兩步,定義事務(wù)和執(zhí)行事務(wù)。使用multi命令開啟一個(gè)事務(wù),然后把要執(zhí)行的所有命令都依次排上去。這就定義好了一個(gè)事務(wù)。此時(shí)使用exec命令來執(zhí)行這個(gè)事務(wù),或使用discard命令來放棄這個(gè)事務(wù)。
你可能希望在你的事務(wù)開始前,你關(guān)心的key不想被別人操作,那么可以使用watch命令來監(jiān)視這些key,如果開始執(zhí)行前這些key被其它命令操作了則會(huì)取消事務(wù)的。也可以使用unwatch命令來取消對(duì)這些key的監(jiān)視。
redis事務(wù)具有以下特點(diǎn):
1、如果開始執(zhí)行事務(wù)前出錯(cuò),則所有命令都不執(zhí)行
2、一旦開始,則保證所有命令一次性按順序執(zhí)行完而不被打斷
3、如果執(zhí)行過程中遇到錯(cuò)誤,會(huì)繼續(xù)執(zhí)行下去,不會(huì)停止的
4、對(duì)于執(zhí)行過程中遇到錯(cuò)誤,是不會(huì)進(jìn)行回滾的
看完這些,真想問一句話,你這能叫事務(wù)嗎?很顯然,這并不是我們通常認(rèn)為的事務(wù),因?yàn)樗B原子性都保證不了。保證不了原子性是因?yàn)閞edis不支持回滾,不過它也給出了不支持的理由。
不支持回滾的理由:
1、redis認(rèn)為,失敗都是由命令使用不當(dāng)造成
2、redis這樣做,是為了保持內(nèi)部實(shí)現(xiàn)簡(jiǎn)單快速
3、redis還認(rèn)為,回滾并不能解決所有問題
哈哈,這就是霸王條款,因此,好像使用redis事務(wù)的不太多
管道
客戶端和集群的交互過程是串行化阻塞式的,即客戶端發(fā)送了一個(gè)命令后必須等到響應(yīng)回來后才能發(fā)第二個(gè)命令,這一來一回就是一個(gè)往返時(shí)間。如果你有很多的命令,都這樣一個(gè)一個(gè)的來進(jìn)行,會(huì)變得很慢。
redis提供了一種管道技術(shù),可以讓客戶端一次發(fā)送多個(gè)命令,期間不需要等待服務(wù)器端的響應(yīng),等所有的命令都發(fā)完了,再依次接收這些命令的全部響應(yīng)。這就極大地節(jié)省了許多時(shí)間,提升了效率。
聰明的你是不是意識(shí)到了另外一個(gè)問題,多個(gè)命令就是多個(gè)key啊,這不就是上面提到的多key操作嘛,那么問題來了,你如何保證這多個(gè)key都是同一個(gè)節(jié)點(diǎn)上的啊,哈哈,redis集群又放棄了對(duì)管道的支持。
不過可以在客戶端模擬實(shí)現(xiàn),就是使用多個(gè)連接往多個(gè)節(jié)點(diǎn)同時(shí)發(fā)送命令,然后等待所有的節(jié)點(diǎn)都返回了響應(yīng),再把它們按照發(fā)送命令的順序整理好,返回給用戶代碼。哎呀,好麻煩呀。
協(xié)議
簡(jiǎn)單了解下redis的協(xié)議,知道redis的數(shù)據(jù)傳輸格式。
發(fā)送請(qǐng)求的協(xié)議:
*參數(shù)個(gè)數(shù)CRLF$參數(shù)1的字節(jié)數(shù)CRLF參數(shù)1的數(shù)據(jù)CRLF...$參數(shù)N的字節(jié)數(shù)CRLF參數(shù)N的數(shù)據(jù)CRLF
例如,SET name lixinjie,實(shí)際發(fā)送的數(shù)據(jù)是:
*3\r\n$3\r\nSET\r\n$4\r\nname\r\n$8\r\nlixinjie\r\n
接受響應(yīng)的協(xié)議:
單行回復(fù),***個(gè)字節(jié)是+
錯(cuò)誤消息,***個(gè)字節(jié)是-
整型數(shù)字,***個(gè)字節(jié)是:
批量回復(fù),***個(gè)字節(jié)是$
多個(gè)批量回復(fù),***個(gè)字節(jié)是*
例如,
+OK\r\n
-ERR Operation against\r\n
:1000\r\n
$6\r\nfoobar\r\n
*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n
可見redis的協(xié)議設(shè)計(jì)的非常簡(jiǎn)單。
看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝您對(duì)億速云的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。