從零單排學(xué)Redis【黃金】

前言

只有光頭才能變強(qiáng)

好的柠硕,今天我們要上黃金段位了溅呢,如果還沒經(jīng)歷過青銅和白銀階段的骡湖,可以先去蹭蹭經(jīng)驗(yàn)再回來:

看過相關(guān)Redis基礎(chǔ)的同學(xué)可以知道Redis是單線程的幢竹,很多面試題也很可能會問到“為什么Redis是單線程的還那么快”榛臼。

這篇文章來講講單線程的內(nèi)部的原理伊佃。

文本力求簡單講清每個知識點(diǎn),希望大家看完能有所收獲

一沛善、基礎(chǔ)鋪墊

在講解Redis之前航揉,我們先來一些基礎(chǔ)的鋪墊,有更好的閱讀體驗(yàn)金刁。

1.1網(wǎng)路編程

我們在初學(xué)Java的時候肯定會學(xué)過網(wǎng)絡(luò)編程這一章節(jié)的帅涂,當(dāng)時學(xué)完寫的應(yīng)用可能就是“網(wǎng)絡(luò)聊天室”。

寫出來的效果可能就是在console噼里啪啦的輸入數(shù)據(jù)尤蛮,然后噼里啪啦的返回?cái)?shù)據(jù)漠秋,就完事了..(扎心了)

網(wǎng)絡(luò)編程可簡單分為TCP和UPD兩種,一般我們更多關(guān)注的是TCP抵屿。TCP網(wǎng)絡(luò)編程在Java中封裝成Socket和SocketServer庆锦,我們來回顧一下最簡單的TCP網(wǎng)絡(luò)編程吧:

TCP客戶端


public class ClientDemo {
    public static void main(String[] args) throws IOException {
        //創(chuàng)建發(fā)送端的Socket對象
        Socket s = new Socket("192.168.1.106",8888);
        
        //Socket對象可以獲取輸出流
        OutputStream os = s.getOutputStream();
        os.write("hello,tcp,我來了".getBytes());

        s.close();
    }
}

TCP服務(wù)端:


public class ServerDemo {
    public static void main(String[] args) throws IOException {
        //創(chuàng)建接收端的Socket對象
        ServerSocket ss = new ServerSocket(8888);

        //監(jiān)聽客戶端連接,返回一個對應(yīng)的Socket對象
        //偵聽并接受到此套接字的連接轧葛,此方法會阻塞
        Socket s = ss.accept();

        //獲取輸入流搂抒,讀取數(shù)據(jù)
        InputStream is = s.getInputStream();

        byte[] bys = new byte[1024];
        int len = is.read(bys);
        String str = new String (bys,0,len);

        String ip = s.getInetAddress().getHostAddress();
        System.out.println(ip + "    ---" +str);

        //釋放資源
        s.close();
        //ss.close();  

    }
}

上面的代碼就可以實(shí)現(xiàn):客戶端向服務(wù)器發(fā)送數(shù)據(jù),服務(wù)端能夠接收客戶端發(fā)送過來的數(shù)據(jù)尿扯。

1.2IO多路復(fù)用

之前我已經(jīng)寫過Java NIO的文章了求晶,Java的NIO也是基于IO多路復(fù)用模型的,建議先去看一下再回來衷笋,文章寫得挺詳細(xì)和通俗的了:JDK10都發(fā)布了芳杏,nio你了解多少?

這里就簡單回顧一下吧:

  • I/O多路復(fù)用的特點(diǎn)是通過一種機(jī)制一個進(jìn)程能同時等待多個文件描述符,而這些文件描述符其中的任意一個進(jìn)入讀就緒狀態(tài)爵赵、等等吝秕,select()函數(shù)就可以返回。
  • select/epoll的優(yōu)勢并不是對于單個連接能處理得更快空幻,而是在于能處理更多的連接烁峭。

說白了,使用IO多路復(fù)用機(jī)制的秕铛,一般自己會有一套事件機(jī)制约郁,使用一個線程或者進(jìn)程監(jiān)聽這些事件,如果這些事件被觸發(fā)了但两,則調(diào)用對應(yīng)的函數(shù)來處理鬓梅。

二、Redis事件

Redis服務(wù)器是一個事件驅(qū)動程序谨湘,主要處理以下兩類事件:

  • 文件事件:文件事件其實(shí)就是對Socket操作的抽象己肮,Redis服務(wù)器與Redis客戶端的通信會產(chǎn)生文件事件,服務(wù)器通過監(jiān)聽并處理這些事件來完成一系列的網(wǎng)絡(luò)操作
  • 時間事件:時間事件其實(shí)就是對定時操作的抽象悲关,前面我們已經(jīng)講了RDB谎僻、AOF、定時刪除鍵這些操作都可以由服務(wù)端去定時或者周期去完成寓辱,底層就是通過觸發(fā)時間事件來實(shí)現(xiàn)的艘绍!

2.1文件事件

Redis開發(fā)了自己的網(wǎng)絡(luò)事件處理器,這個處理器被稱為文件事件處理器秫筏。

文件事件處理器由四部分組成:

文件事件處理器組成

文件事件處理器使用I/O多路復(fù)用程序來同時監(jiān)聽多個Socket诱鞠。當(dāng)被監(jiān)聽的Socket準(zhǔn)備好執(zhí)行連接應(yīng)答(accept)、讀取(read)等等操作時这敬,與操作相對應(yīng)的文件事件就會產(chǎn)生航夺,根據(jù)文件事件來為Socket關(guān)聯(lián)對應(yīng)的事件處理器,從而實(shí)現(xiàn)功能崔涂。

要值得注意的是:Redis中的I/O多路復(fù)用程序會將所有產(chǎn)生事件的Socket放到一個隊(duì)列里邊阳掐,然后通過這個隊(duì)列以有序、同步冷蚂、每次一個Socket的方式向文件事件分派器傳送套接字缭保。也就是說:當(dāng)上一個Socket處理完畢后,I/O多路復(fù)用程序才會向文件事件分派器傳送下一個Socket蝙茶。

首先艺骂,IO多路復(fù)用程序首先會監(jiān)聽著Socket的AE_READABLE事件,該事件對應(yīng)著連接應(yīng)答處理器

  • 可以理解簡單成SocketServet.accpet()
監(jiān)聽著Socket的AE_READABLE事件

此時隆夯,一個名字叫做3y的Socket要連接服務(wù)器啦钳恕。服務(wù)器會用連接應(yīng)答處理器處理别伏。創(chuàng)建出客戶端的Socket,并將客戶端的Socket與命令請求處理器進(jìn)行關(guān)聯(lián)忧额,使得客戶端可以向服務(wù)器發(fā)送命令請求厘肮。

  • 相當(dāng)于Socket s = ss.accept();,創(chuàng)建出客戶端的Socket宙址,然后將該Socket關(guān)聯(lián)命令請求處理器
  • 此時客戶端就可以向主服務(wù)器發(fā)送命令請求了
客戶端請求連接轴脐,服務(wù)器創(chuàng)建出客戶端Scoket调卑,關(guān)聯(lián)命令請求處理器

假設(shè)現(xiàn)在客戶端發(fā)送一個命令請求set Java3y "關(guān)注抡砂、點(diǎn)贊、評論"恬涧,客戶端Socket將產(chǎn)生AE_READABLE事件注益,引發(fā)命令請求處理器執(zhí)行。處理器讀取客戶端的命令內(nèi)容溯捆,然后傳給對應(yīng)的程序去執(zhí)行丑搔。

客戶端發(fā)送完命令請求后,服務(wù)端總得給客戶端回應(yīng)的提揍。此時服務(wù)端會將客戶端的Scoket的AE_WRITABLE事件與命令回復(fù)處理器關(guān)聯(lián)啤月。

客戶端的Scoket的AE_WRITABLE事件與命令回復(fù)處理器關(guān)聯(lián)

最后客戶端嘗試讀取命令回復(fù)時,客戶端Socket產(chǎn)生AE_WRITABLE事件劳跃,觸發(fā)命令回復(fù)處理器執(zhí)行谎仲。當(dāng)把所有的回復(fù)數(shù)據(jù)寫入到Socket之后,服務(wù)器就會解除客戶端Socket的AE_WRITABLE事件與命令回復(fù)處理器的關(guān)聯(lián)刨仑。

最后以《Redis設(shè)計(jì)與實(shí)現(xiàn)》的一張圖來概括:

Redis事件交互過程

2.2時間事件

持續(xù)運(yùn)行的Redis服務(wù)器會定期對自身的資源和狀態(tài)進(jìn)行檢查和調(diào)整郑诺,這些定期的操作由serverCron函數(shù)負(fù)責(zé)執(zhí)行,它的主要工作包括:

  • 更新服務(wù)器的統(tǒng)計(jì)信息(時間杉武、內(nèi)存占用辙诞、數(shù)據(jù)庫占用)
  • 清理數(shù)據(jù)庫的過期鍵值對
  • AOF、RDB持久化
  • 如果是主從服務(wù)器轻抱,對從服務(wù)器進(jìn)行定期同步
  • 如果是集群模式飞涂,對進(jìn)群進(jìn)行定期同步和連接
  • ...

Redis服務(wù)器將時間事件放在一個鏈表中,當(dāng)時間事件執(zhí)行器運(yùn)行時祈搜,會遍歷整個鏈表封拧。時間事件包括:

  • 周期性事件(Redis一般只執(zhí)行serverCron時間事件,serverCron時間事件是周期性的)
  • 定時事件

2.3時間事件和文件事件

  • 文件事件和時間事件之間是合作關(guān)系夭问,服務(wù)器會輪流處理這兩種事件泽西,并且處理事件的過程中不會發(fā)生搶占。
  • 時間事件的實(shí)際處理事件通常會比設(shè)定的到達(dá)時間一些

三缰趋、Redis多線程為什么快捧杉?

  • 1)純內(nèi)存操作
  • 2)核心是基于非阻塞的IO多路復(fù)用機(jī)制
  • 3)單線程避免了多線程的頻繁上下文切換問題

四陕见、客戶端與服務(wù)器

在《Redis設(shè)計(jì)與實(shí)現(xiàn)》中各用了一章節(jié)來寫客戶端與服務(wù)器,我看完覺得比較底層的東西味抖,也很難記得住评甜,所以我決定總結(jié)一下比較重要的知識。如果以后真的遇到了仔涩,再來補(bǔ)坑~

服務(wù)器使用clints鏈表連接多個客戶端狀態(tài)忍坷,新添加的客戶端狀態(tài)會被放到鏈表的末尾

客戶端--鏈表
  • 一個服務(wù)器可以與多個客戶端建立網(wǎng)絡(luò)連接,每個客戶端可以向服務(wù)器發(fā)送命令請求熔脂,而服務(wù)器則接收并處理客戶端發(fā)送的命令請求佩研,并向客戶端返回命令回復(fù)。
  • Redis服務(wù)器使用單線程單進(jìn)程的方式處理命令請求霞揉。在數(shù)據(jù)庫中保存客戶端執(zhí)行命令所產(chǎn)生的數(shù)據(jù)旬薯,并通過資源管理來維持服務(wù)器自身的運(yùn)轉(zhuǎn)。

4.1客戶端

客戶端章節(jié)中主要講解了Redis客戶端的屬性(客戶端狀態(tài)适秩、輸入/輸出緩沖區(qū)绊序、命令參數(shù)、命令函數(shù)等等)


typedef struct redisClient{
    
    //客戶端狀態(tài)的輸入緩沖區(qū)用于保存客戶端發(fā)送的命令請求,最大1GB,否則服務(wù)器將關(guān)閉這個客戶端
    sds querybuf;  
    
    
    //負(fù)責(zé)記錄argv數(shù)組的長度秽荞。
    int argc;   
    
    // 命令的參數(shù)
    robj **argv;  
    
    // 客戶端要執(zhí)行命令的實(shí)現(xiàn)函數(shù)
    struct redisCommand *cmd, *lastcmd;  


    //記錄了客戶端的角色(role),以及客戶端所處的狀態(tài)骤公。 (REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI) 
    int flags;             
    
    //記錄客戶端是否通過了身份驗(yàn)證
    int authenticated;     
    
    //時間相關(guān)的屬性
    time_t ctime;           /* Client creation time */       
    time_t lastinteraction; /* time of the last interaction, used for timeout */
    time_t obuf_soft_limit_reached_time;
    
    
    //固定大小的緩沖區(qū)用于保存那些長度比較小的回復(fù)
    /* Response buffer */
    int bufpos;
    char buf[REDIS_REPLY_CHUNK_BYTES];
    
    //可變大小的緩沖區(qū)用于保存那些長度比較大的回復(fù)
    list *reply; //可變大小緩沖區(qū)由reply 鏈表和一個或多個字符串對象組成
    //...
}

4.2服務(wù)端

服務(wù)器章節(jié)中主要講解了Redis服務(wù)器讀取客戶端發(fā)送過來的命令是如何解析,以及初始化的過程扬跋。

服務(wù)器從啟動到能夠處理客戶端的命令請求需要執(zhí)行以下的步驟:

  • 初始化服務(wù)器狀態(tài)
  • 載入服務(wù)器配置
  • 初始化服務(wù)器的數(shù)據(jù)結(jié)構(gòu)
  • 還原數(shù)據(jù)庫狀態(tài)
  • 執(zhí)行事件循環(huán)

總的來說是這樣子的:


def main():

    init_server();

    while server_is_not_shutdown();
        aeProcessEvents()

    clean_server();
    

從客戶端發(fā)送命令道完成主要包括的步驟:

  • 客戶端將命令請求發(fā)送給服務(wù)器
  • 服務(wù)器讀取命令請求阶捆,分析出命令參數(shù)
  • 命令執(zhí)行器根據(jù)參數(shù)查找命令的實(shí)現(xiàn)函數(shù),執(zhí)行實(shí)現(xiàn)函數(shù)并得出命令回復(fù)
  • 服務(wù)器將命令回復(fù)返回給客戶端

五胁住、最后

現(xiàn)在臨近雙十一買阿里云服務(wù)器就特別省錢趁猴!之前我買學(xué)生機(jī)也要9.8塊錢一個月,現(xiàn)在最低價只需要8.3一個月彪见!

無論是Nginx/Elasticsearch/Redis這些技術(shù)都是在Linux下完美運(yùn)行的儡司,如果還是程序員新手,買一個學(xué)習(xí)Linux基礎(chǔ)命令余指,學(xué)習(xí)搭建環(huán)境也是不錯的選擇捕犬。

如果有要買服務(wù)器的同學(xué)可通過我的鏈接直接享受最低價https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.pfn5xpli


本來也想把“復(fù)制”(主從)在這邊一起寫的,但寫完可能就很長了酵镜,所以留到下一篇吧碉碉。

如果大家有更好的理解方式或者文章有錯誤的地方還請大家不吝在評論區(qū)留言,大家互相學(xué)習(xí)交流~~~

參考資料:

  • 《Redis設(shè)計(jì)與實(shí)現(xiàn)》
  • 《Redis實(shí)戰(zhàn)》

一個堅(jiān)持原創(chuàng)的Java技術(shù)公眾號:Java3y淮韭,歡迎大家關(guān)注

3y所有的原創(chuàng)文章:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末垢粮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子靠粪,更是在濱河造成了極大的恐慌蜡吧,老刑警劉巖毫蚓,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異昔善,居然都是意外死亡元潘,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進(jìn)店門君仆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來翩概,“玉大人,你說我怎么就攤上這事返咱≡勘樱” “怎么了?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵洛姑,是天一觀的道長上沐。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么泣崩? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任贬蛙,我火速辦了婚禮,結(jié)果婚禮上乓旗,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好两入,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著敲才,像睡著了一般裹纳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上紧武,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天剃氧,我揣著相機(jī)與錄音,去河邊找鬼阻星。 笑死朋鞍,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的妥箕。 我是一名探鬼主播滥酥,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼畦幢!你這毒婦竟也來了坎吻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤宇葱,失蹤者是張志新(化名)和其女友劉穎瘦真,沒想到半個月后返奉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吗氏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年芽偏,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弦讽。...
    茶點(diǎn)故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡污尉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出往产,到底是詐尸還是另有隱情被碗,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布仿村,位于F島的核電站锐朴,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蔼囊。R本人自食惡果不足惜焚志,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望畏鼓。 院中可真熱鬧酱酬,春花似錦、人聲如沸云矫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽让禀。三九已至挑社,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巡揍,已是汗流浹背痛阻。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吼肥,地道東北人录平。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像缀皱,于是被迫代替她去往敵國和親斗这。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評論 2 355

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理啤斗,服務(wù)發(fā)現(xiàn)表箭,斷路器,智...
    卡卡羅2017閱讀 134,657評論 18 139
  • 時光荏苒 麻木荒蕪 匆匆中不知飄向何處 那就做一株暖陽 給人以溫暖明媚 儲存能量 共度指尖蔥蘢的時光 是一樹花開 ...
    藍(lán)小愚閱讀 440評論 3 11
  • 我2017的愿望們 1.去7個城市旅行 【當(dāng)前進(jìn)度1/7】 因?yàn)榍迕髦驣BU的國際件培訓(xùn)彼水,我陰差陽錯的去了一趟深...
    大王老師的日記閱讀 420評論 0 2
  • 賈怪人閱讀 292評論 1 0
  • 近期一直在出差,晝夜顛倒的日子讓我精神有些恍惚极舔》锔玻回家后看見床頭放的那本八月份的《新聞周刊》還沒讀完,才發(fā)現(xiàn)原...
    Victoria喵閱讀 236評論 0 0