用Java手寫Redis服務(wù)端阳似,從設(shè)計者的角度聊一聊Redis本身

零骚勘,起因

我為什么要造redis這個輪子?

1撮奏,破除對redis神秘感俏讹。
2,“基礎(chǔ)服務(wù)中臺”的同事們在開會討論redis云畜吊,以及redis代理泽疆。
3,開一個redis資源并不是容易事玲献,為什么不可以不可以寫成java直接推送到未來云上殉疼,簡單方便梯浪。

以這個思路我開始使用業(yè)余時間研究了redis的tcp通訊原理與redis命令,出發(fā)點(diǎn)是寫一個redis云代理之類的云管理軟件瓢娜,但是還是忍不住寫成了java版的redis挂洛,本文章主要分享redis的編寫心路歷程。

一眠砾,redis通訊與Netty

1虏劲,tcp

連到Redis服務(wù)器的客戶端建立了一個到6379端口的TCP連接。

雖然RESP在技術(shù)上不特定于TCP褒颈,但是在Redis的上下文中柒巫,該協(xié)議僅用于TCP連接(或類似的面向流的連接,如unix套接字)哈肖。

使用netty作為通訊框架吻育。

2,協(xié)議

Redis客戶端和服務(wù)器端通信使用名為 RESP (REdis Serialization Protocol) 的協(xié)議淤井。雖然這個協(xié)議是專門為Redis設(shè)計的布疼,它也可以用在其它 client-server 通信模式的軟件上。 RESP 協(xié)議在Redis1.2被引入币狠,直到Redis2.0才成為和Redis服務(wù)器通信的標(biāo)準(zhǔn)游两。這個協(xié)議需要在你的Redis客戶端實(shí)現(xiàn)。

RESP 是一個支持多種數(shù)據(jù)類型的序列化協(xié)議:簡單字符串(Simple Strings),錯誤( Errors),整型( Integers), 大容量字符串(Bulk Strings)和數(shù)組(Arrays)漩绵。

RESP在Redis中作為一個請求-響應(yīng)協(xié)議以如下方式使用:

客戶端以大容量字符串RESP數(shù)組的方式發(fā)送命令給服務(wù)器端贱案。 服務(wù)器端根據(jù)命令的具體實(shí)現(xiàn)返回某一種RESP數(shù)據(jù)類型。 在 RESP 中止吐,數(shù)據(jù)的類型依賴于首字節(jié):

單行字符串(Simple Strings): 響應(yīng)的首字節(jié)是 "+" 錯誤(Errors): 響應(yīng)的首字節(jié)是 "-" 整型(Integers): 響應(yīng)的首字節(jié)是 ":" 多行字符串(Bulk Strings): 響應(yīng)的首字節(jié)是"$" 數(shù)組(Arrays): 響應(yīng)的首字節(jié)是 "*" 另外宝踪,RESP可以使用大容量字符串或者數(shù)組類型的特殊變量表示空值,下面會具體解釋碍扔。RESP協(xié)議的不同部分總是以 "\r\n" (CRLF) 結(jié)束瘩燥。 字符串 "foobar" 編碼如下:

"$6\r\nfoobar\r\n"

實(shí)際redis命令是什么樣的,比如 SET lhjljh lhjkjhkh

*3\r\n$3\r\nSET\r\n$6\r\nlhjljh\r\n$8\r\nlhjkjhkh

RESP協(xié)議中文詳情文檔

3不同,編解碼

由于RESP天然是面向處理命令的厉膀,所以沒辦法直接把redis消息像grpc或者dubbo那樣直接序列化和反序列化消息。并且每個內(nèi)容限定了長度二拐,很適合做成及時序列化服鹅、零拷貝,直接針對輸入流做反序列化和序列化百新,這一點(diǎn)與Protostuff序列化協(xié)議的設(shè)計很類似企软。 所以序列化直接將服務(wù)端接收的流量直接轉(zhuǎn)成值。

編解碼的實(shí)體類直接加入redis server 的處理某一個長連接tcp客戶端的管道上饭望。

如果有興趣研究可以看c語言原版的源碼分析視頻:網(wǎng)站redis源碼分析視頻

4仗哨,命令處理

將消息解碼成RESP聚蝶,還需要將RESP轉(zhuǎn)為Command對象,這里因?yàn)槭莏ava語言藻治,方法與類綁定碘勉,編寫上和理解上會更加容易。但是會增加一些開銷桩卵。

二验靡,redis 的數(shù)據(jù)結(jié)構(gòu)

1,底層主結(jié)構(gòu)

底層主樹使用跳表ConcurrentSkipListMap實(shí)現(xiàn)雏节,沒用hash類map的原因是服務(wù)端是集群后胜嗓,客戶端可能使用hash路由,會導(dǎo)致服務(wù)端嚴(yán)重的hash沖突钩乍,性能大打折扣

key為封裝的“String”辞州,重寫了equals方法避免相同的key但是在jvm中指針不同

value是一個接口,實(shí)現(xiàn)類是redis的五大基本類型寥粹,所有數(shù)據(jù)類型都包含超時時間

2变过,key

用封裝的值做value的原因是方便統(tǒng)一管理

3,list

底層使用LinkedList的原因是LinkedList實(shí)現(xiàn)了多種接口涝涤,實(shí)現(xiàn)各種命令直接調(diào)用其現(xiàn)成實(shí)現(xiàn)的方法即可

4媚狰,set

底層使用HashSet,redis里的set沒有多特殊

5阔拳,hash

底層使用HashMap崭孤,這里和開頭說的HashMap不沖突。為什么不用跳表糊肠?壓縮列表很巧妙辨宠,大抵的意思就是將通信收到的數(shù)組直接填充到list中,將list直接按照次序直接當(dāng)map使用货裹,主要是0拷貝的思想嗤形,無需創(chuàng)建新資源,性能極高泪酱,但注意壓縮列表與壓縮無關(guān)派殷。


6还最,zset

首先需要封裝一個帶有值和分值的對象

再用TreeMap重寫compare方法即可墓阀,使用TreeMap原因是他天然有良好的排序功能,很多hash一致路由的算法都用的TreeMap二開拓轻。

三斯撮,redis AOF 持久化

1,aof線程與tcp線程解耦扶叉,即寫緩沖

再解析redis命令時勿锅,將redis寫命令添加到寫aof日志的隊列中

這里自己封裝了一個堵塞隊列帕膜,單線程吞吐量可以達(dá)到3000W /s是LinkedBlockingQueue的6到10倍,完全可以勝任此場景

RingBlockingQueue吞吐量非常高的原因是使用了內(nèi)存連續(xù)頁的機(jī)制溢十。

2垮刹,aof持久化協(xié)議

aof協(xié)議一句話概括就是將寫命令,追加到日志中张弛,開始時將命令讀取荒典,當(dāng)作收到網(wǎng)絡(luò)的命令執(zhí)行即可。由于協(xié)議過于簡單吞鸭,這里就不貼鏈接了寺董。 aof之日格式如下圖:

3,aof的加載與存儲實(shí)現(xiàn)

這里讀寫內(nèi)存都是用的內(nèi)存文件映射刻剥,好處是讀寫性能好遮咖,壞處是可能會出現(xiàn)內(nèi)存泄漏,調(diào)試期間比較麻煩造虏。

4御吞,內(nèi)存文件映射與面向?qū)ο?/strong>

這里存儲和加載aof文件的代碼都是面向過程的,看起來非常復(fù)雜漓藕。實(shí)際上之前是按照面向?qū)ο髮懙钠桥海庋b成了行對象,調(diào)用落盤符和拾起方法就可以寫入和讀取aof中的命令撵术,但是TPS僅為10w/s背率,后來權(quán)衡后改為面向過程,吞吐量提升到了100W的TPS以上嫩与。

四寝姿,redis 的集群特性

1,主從

這里很容易聯(lián)想到mysql的主從划滋,很多場景下會使用基于mysql主從的讀寫分離饵筑,或者zk的主從。 但實(shí)際上redis的主從是不保證一致性的处坪,個人認(rèn)為redist的主從主要考慮的是cap的分布式容錯性根资。 因?yàn)閞edis主從不保證一致性,所以使用redis讀寫分離同窘,可能造成一些不一致的問題玄帕,寫寫是一致的,但是讀是不一致的想邦,可以根據(jù)項(xiàng)目需要做取舍裤纹。

2,主從復(fù)制

redis的主從復(fù)制這里作者沒看懂(可能也是一致性上有坑沒動力去看)丧没,所以沒寫出來鹰椒。

3锡移,分片集群

redis集群主要分為幾個維度: 主從、分區(qū)集群漆际、代理淆珊。 一般在redis客戶端的視角下,主要是分區(qū)集群奸汇,根據(jù)發(fā)送給redis的key做hash套蒂、md5等操作,取一個所有客戶端的共識值茫蛹,將key和value發(fā)送操刀,也就是客戶端路由 分布式軟件的集群實(shí)現(xiàn)方式京東的redis集群設(shè)計到redis具體一個分片。

五婴洼,redis 的壓測與調(diào)優(yōu)

1骨坑,aof內(nèi)存泄漏

開啟aof壓測發(fā)現(xiàn)出現(xiàn)了內(nèi)存泄漏,后來發(fā)現(xiàn)是頻繁新建內(nèi)存池而造成的柬采,所以將內(nèi)存池池化欢唾,即aof對象中僅存在一個bytebuff內(nèi)存池。

2粉捻,內(nèi)存復(fù)用提升性能

這里編解碼沒有單獨(dú)開辟byte數(shù)據(jù)接收bytebuff的數(shù)據(jù)進(jìn)行編解碼礁遣,編解碼直接讀取bytebuff進(jìn)行編解碼,沒有出現(xiàn)內(nèi)存拷貝肩刃,唯獨(dú)新建了BytesWrapper對象祟霍,但存儲的數(shù)據(jù)都是使用BytesWrapper對象,對內(nèi)存新建/銷毀的開銷很少盈包。

3沸呐,0.05%消息延遲超200ms排查

下圖為c語言版的redis壓測數(shù)據(jù):

下圖為java語言版的redis壓測數(shù)據(jù):

4,性能表現(xiàn)

redis原版的性能大概是E5系列CPU 4-5w左右呢燥,上圖中是使用amd芯片測試的數(shù)據(jù)崭添。 使用redis自帶的壓測工具,維持100個客戶端連接叛氨,java版性能是c語言原版性能的75-90%左右呼渣,性能依然強(qiáng)悍。

作者:傷心的菜狗開發(fā)
鏈接:
https://juejin.cn/post/7045544580309057572

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末寞埠,一起剝皮案震驚了整個濱河市屁置,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌畸裳,老刑警劉巖缰犁,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件淳地,死亡現(xiàn)場離奇詭異怖糊,居然都是意外死亡帅容,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門伍伤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來并徘,“玉大人,你說我怎么就攤上這事扰魂÷笃颍” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵劝评,是天一觀的道長姐直。 經(jīng)常有香客問我,道長蒋畜,這世上最難降的妖魔是什么声畏? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮姻成,結(jié)果婚禮上插龄,老公的妹妹穿的比我還像新娘。我一直安慰自己科展,他們只是感情好均牢,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著才睹,像睡著了一般徘跪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上琅攘,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天真椿,我揣著相機(jī)與錄音,去河邊找鬼乎澄。 笑死突硝,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的置济。 我是一名探鬼主播解恰,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼浙于!你這毒婦竟也來了护盈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤羞酗,失蹤者是張志新(化名)和其女友劉穎腐宋,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡胸竞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年欺嗤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卫枝。...
    茶點(diǎn)故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡煎饼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出校赤,到底是詐尸還是另有隱情吆玖,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布马篮,位于F島的核電站沾乘,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏浑测。R本人自食惡果不足惜意鲸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望尽爆。 院中可真熱鬧怎顾,春花似錦、人聲如沸漱贱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至擎值,卻和暖如春鸠儿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背进每。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工田晚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留国葬,地道東北人芹壕。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓踢涌,卻偏偏與公主長得像斯嚎,于是被迫代替她去往敵國和親挨厚。 傳聞我的和親對象是個殘疾皇子糠惫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 前言 文本已收錄至我的GitHub倉庫巢价,歡迎Star:https://github.com/bin39232820...
    六脈神劍的程序人生閱讀 275評論 0 1
  • 一固阁、簡述 Redis 全稱Remote Dictionary Server。是一個開源的 BSD[https://...
    Djbfifjd閱讀 1,007評論 0 1
  • Catalog 1 Redis適用場景碉克?Redis底層數(shù)據(jù)結(jié)構(gòu)使用場景漏麦? 2 Redis到底是單線程還是多線程撕贞?R...
    allen鍋閱讀 308評論 0 0
  • 金九銀十即將到來测垛,整理了20道經(jīng)典Redis面試題,希望對大家有幫助脊奋。 1. 什么是Redis诚隙?它主要用來什么的?...
    學(xué)編程的小屁孩閱讀 1,524評論 0 1
  • 進(jìn)程久又、線程、協(xié)程 進(jìn)程進(jìn)程是系統(tǒng)分配資源的最小單位炉峰,一個應(yīng)用程序就是一個進(jìn)程疼阔,每個進(jìn)程都是相互獨(dú)立的線程線程是程序...
    ES_KYW閱讀 635評論 0 0