是數(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ū)ο螅藭r(shí)才具有數(shù)據(jù)類型箱叁。
這一點(diǎn)必須要記住墅垮。所以任何東西只要能轉(zhuǎn)化成字節(jié)數(shù)組(byte[])的,都可以存到redis里蝌蹂。管你是字符串噩斟、數(shù)字、對象孤个、圖片剃允、聲音、視頻、還是文件斥废,只要變成byte數(shù)組椒楣。
因此redis里的String指的并不是字符串,它其實(shí)表示的是一種最簡單的數(shù)據(jù)結(jié)構(gòu)牡肉,即一個(gè)key只能對應(yīng)一個(gè)value捧灰。這里的key和value都是byte數(shù)組,只不過key一般是由一個(gè)字符串轉(zhuǎn)換成的byte數(shù)組统锤,value則根據(jù)實(shí)際需要而定毛俏。
在特定情況下,對value也會(huì)有一些要求饲窿,比如要進(jìn)行自增或自減操作煌寇,那value對應(yīng)的byte數(shù)組必須要能被解碼成一個(gè)數(shù)字才行,否則會(huì)報(bào)錯(cuò)逾雄。
那么List這種數(shù)據(jù)結(jié)構(gòu)阀溶,其實(shí)表示一個(gè)key可以對應(yīng)多個(gè)value,且value之間是有先后順序的鸦泳,value值可以重復(fù)银锻。
Set這種數(shù)據(jù)結(jié)構(gòu),表示一個(gè)key可以對應(yīng)多個(gè)value做鹰,且value之間是沒有先后順序的击纬,value值也不可以重復(fù)。
Hash這種數(shù)據(jù)結(jié)構(gòu)誊垢,表示一個(gè)key可以對應(yīng)多個(gè)key-value對掉弛,此時(shí)這些key-value對之間的先后順序一般意義不大,這是一個(gè)按照名稱語義來訪問的數(shù)據(jù)結(jié)構(gòu)喂走,而非位置語義。
Sorted Set這種數(shù)據(jù)結(jié)構(gòu)谋作,表示一個(gè)key可以對應(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)在你對這5種數(shù)據(jù)結(jié)構(gòu)有了更清晰的認(rèn)識(shí),那它們的對應(yīng)命令對你來說就是小case了寂殉。
集群帶來的問題與解決思路
如果想學(xué)習(xí)Java工程化囚巴、高性能及分布式、深入淺出。微服務(wù)彤叉、Spring庶柿,MyBatis,Netty源碼分析的朋友可以加我的Java高級交流:787707172秽浇,群里有阿里大牛直播講解技術(shù)浮庐,以及Java大型互聯(lián)網(wǎng)技術(shù)的視頻免費(fèi)分享給大家。
集群帶來的好處是顯而易見的柬焕,比如容量增加审残、處理能力增強(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)是相對固定和少數(shù)的先鱼,雖然有增加節(jié)點(diǎn)和剔除節(jié)點(diǎn)俭正。但集群里存儲(chǔ)的key,則是完全隨機(jī)焙畔、沒有規(guī)律掸读、不可預(yù)測、數(shù)量龐多宏多,還非扯梗瑣碎。
這就好比一所大學(xué)和它的所有學(xué)生之間的關(guān)系伸但。如果大學(xué)和學(xué)生直接掛鉤的話肾请,一定會(huì)比較混亂。現(xiàn)實(shí)是它們之間又加入了好幾層更胖,首先有院系铛铁,其次有專業(yè)隔显,再者有年級,最后還有班級避归。經(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è)大的收納箱咙冗,把這些書按照書名的長度放入不同的收納箱,然后把這些收納箱放到桌子上漂彤。
這樣就變成了乞娄,桌子上是收納箱,收納箱里是書籍显歧。這樣書籍移動(dòng)很方便,搬起一個(gè)箱子就走了确镊。尋找書籍也很方便士骤,只要數(shù)一數(shù)書名的長度,去對應(yīng)的箱子里找就行了蕾域。
其實(shí)我們也沒做什么拷肌,只是買了幾個(gè)箱子到旦,按照某種規(guī)則把書裝入箱子。就這么簡單的舉動(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)和槽的對應(yīng)關(guān)系仰冠。
接下來就需要對key求哈希值乏冀,然后對16384取余,余數(shù)是幾key就落入對應(yīng)的槽里洋只。slot = CRC16(key) % 16384辆沦。
以槽為單位移動(dòng)數(shù)據(jù),因?yàn)椴鄣臄?shù)目是固定的识虚,處理起來比較容易肢扯,這樣數(shù)據(jù)移動(dòng)問題就解決了。
使用哈希函數(shù)計(jì)算出key的哈希值舷礼,這樣就可以算出它對應(yīng)的槽鹃彻,然后利用集群存儲(chǔ)的槽和節(jié)點(diǎn)的映射關(guān)系查詢出槽所在的節(jié)點(diǎn),于是數(shù)據(jù)和節(jié)點(diǎn)就映射起來了妻献,這樣數(shù)據(jù)分配問題就解決了蛛株。
我想說的是,一般的人只會(huì)去學(xué)習(xí)各種技術(shù)育拨,高手更在乎如何跳出技術(shù)谨履,尋求一種解決方案或思路方向,順著這個(gè)方向走下去熬丧,八九不離十能找到你想要的答案笋粟。
集群對命令操作的取舍
客戶端只要和集群中的一個(gè)節(jié)點(diǎn)建立鏈接后,就可以獲取到整個(gè)集群的所有節(jié)點(diǎn)信息析蝴。此外還會(huì)獲取所有哈希槽和節(jié)點(diǎn)的對應(yīng)關(guān)系信息害捕,這些信息數(shù)據(jù)都會(huì)在客戶端緩存起來,因?yàn)檫@些信息相當(dāng)有用闷畸。
客戶端可以向任何節(jié)點(diǎn)發(fā)送請求尝盼,那么拿到一個(gè)key后到底該向哪個(gè)節(jié)點(diǎn)發(fā)請求呢?其實(shí)就是把集群里的那套key和節(jié)點(diǎn)的映射關(guān)系理論搬到客戶端來就行了佑菩。
所以客戶端需要實(shí)現(xiàn)一個(gè)和集群端一樣的哈希函數(shù)盾沫,先計(jì)算出key的哈希值裁赠,然后再對16384取余,這樣就找到了該key對應(yīng)的哈希槽赴精,利用客戶端緩存的槽和節(jié)點(diǎn)的對應(yīng)關(guān)系信息佩捞,就可以找到該key對應(yīng)的節(jié)點(diǎn)了。
接下來發(fā)送請求就可以了蕾哟。還可以把key和節(jié)點(diǎn)的映射關(guān)系緩存起來一忱,下次再請求該key時(shí),直接就拿到了它對應(yīng)的節(jié)點(diǎn)渐苏,不用再計(jì)算一遍了掀潮。
理論和現(xiàn)實(shí)總是有差距的,集群已經(jīng)發(fā)生了變化琼富,客戶端的緩存還沒來得及更新仪吧。肯定會(huì)出現(xiàn)拿到一個(gè)key向?qū)?yīng)的節(jié)點(diǎn)發(fā)請求鞠眉,其實(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)信息,讓客戶端再去請求一次哗戈,類似于HTTP的302重定向郊艘。
這其實(shí)是個(gè)選擇問題,也是個(gè)哲學(xué)問題唯咬。結(jié)果就是redis集群選擇了后者纱注。因此,節(jié)點(diǎn)只處理自己擁有的key胆胰,對于不擁有的key將返回重定向錯(cuò)誤狞贱,即-MOVED key 127.0.0.1:6381,客戶端重新向這個(gè)新節(jié)點(diǎn)發(fā)送請求蜀涨。
所以說選擇是一種哲學(xué)瞎嬉,也是個(gè)智慧。稍后再談這個(gè)問題厚柳。先來看看另一個(gè)情況氧枣,和這個(gè)問題有些相同點(diǎn)。
redis有一種命令可以一次帶多個(gè)key别垮,如MGET挑胸,我把這些稱為多key命令。這個(gè)多key命令的請求被發(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í)自己測試一下就行了钳吟。
在這個(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)幫我們想好了恨课。
可以來簡單推理下舆乔,要想讓兩個(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)算。對于與核心無關(guān)的或會(huì)拖累核心的都選擇弱化處理或不處理届垫,這樣做是為了保證核心的簡單释液、快速和穩(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ā)送的請求登淘。技術(shù)上使用多路復(fù)用I/O箫老,使用Linux的epoll函數(shù),這樣一個(gè)線程就可以管理很多socket連接黔州。
除此之外耍鬓,選擇單線程還有以下這些原因:
1、redis都是對內(nèi)存的操作流妻,速度極快(10W+QPS)
2牲蜀、整體的時(shí)間主要都是消耗在了網(wǎng)絡(luò)的傳輸上
3、如果使用了多線程绅这,則需要多線程同步涣达,這樣實(shí)現(xiàn)起來會(huì)變的復(fù)雜
4、線程的加鎖時(shí)間甚至都超過了對內(nèi)存操作的時(shí)間
5、多線程上下文頻繁的切換需要消耗更多的CPU時(shí)間
6度苔、還有就是單線程天然支持原子操作匆篓,而且單線程的代碼寫起來更簡單
事務(wù)
如果想學(xué)習(xí)Java工程化、高性能及分布式林螃、深入淺出奕删。微服務(wù)、Spring疗认,MyBatis,Netty源碼分析的朋友可以加我的Java高級交流:787707172伏钠,群里有阿里大牛直播講解技術(shù)横漏,以及Java大型互聯(lián)網(wǎng)技術(shù)的視頻免費(fèi)分享給大家。
事務(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命令來取消對這些key的監(jiān)視炬藤。
redis事務(wù)具有以下特點(diǎn):
1御铃、如果開始執(zhí)行事務(wù)前出錯(cuò),則所有命令都不執(zhí)行
2刻像、一旦開始畅买,則保證所有命令一次性按順序執(zhí)行完而不被打斷
3、如果執(zhí)行過程中遇到錯(cuò)誤细睡,會(huì)繼續(xù)執(zhí)行下去谷羞,不會(huì)停止的
4、對于執(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)簡單快速
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集群又放棄了對管道的支持搀捷。
不過可以在客戶端模擬實(shí)現(xiàn)星掰,就是使用多個(gè)連接往多個(gè)節(jié)點(diǎn)同時(shí)發(fā)送命令多望,然后等待所有的節(jié)點(diǎn)都返回了響應(yīng),再把它們按照發(fā)送命令的順序整理好氢烘,返回給用戶代碼怀偷。哎呀,好麻煩呀播玖。
協(xié)議
簡單了解下redis的協(xié)議椎工,知道redis的數(shù)據(jù)傳輸格式。
發(fā)送請求的協(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ì)的非常簡單吗讶。
如果想學(xué)習(xí)Java工程化燎猛、高性能及分布式、深入淺出照皆。微服務(wù)重绷、Spring,MyBatis膜毁,Netty源碼分析的朋友可以加我的Java高級交流:787707172昭卓,群里有阿里大牛直播講解技術(shù),以及Java大型互聯(lián)網(wǎng)技術(shù)的視頻免費(fèi)分享給大家瘟滨。
---------------------
作者:JavaQQ群854630135
來源:CSDN
原文:https://blog.csdn.net/javaQQ561487941/article/details/89378112
版權(quán)聲明:本文為博主原創(chuàng)文章候醒,轉(zhuǎn)載請附上博文鏈接!