摘要:當被監聽的準備好執行連接應答讀取等等操作時,與操作相對應的文件事件就會產生,根據文件事件來為關聯對應的事件處理器,從而實現功能。服務器使用單線程單進程的方式處理命令請求。
前言
只有光頭才能變強
好的,今天我們要上黃金段位了,如果還沒經歷過青銅和白銀階段的,可以先去蹭蹭經驗再回來:
從零單排學Redis【青銅】
從零單排學Redis【白銀】
看過相關Redis基礎的同學可以知道Redis是單線程的,很多面試題也很可能會問到“為什么Redis是單線程的還那么快”。
這篇文章來講講單線程的內部的原理。
文本力求簡單講清每個知識點,希望大家看完能有所收獲
一、基礎鋪墊在講解Redis之前,我們先來一些基礎的鋪墊,有更好的閱讀體驗。
1.1網路編程我們在初學Java的時候肯定會學過網絡編程這一章節的,當時學完寫的應用可能就是“網絡聊天室”。
寫出來的效果可能就是在console噼里啪啦的輸入數據,然后噼里啪啦的返回數據,就完事了..(扎心了)
網絡編程可簡單分為TCP和UPD兩種,一般我們更多關注的是TCP。TCP網絡編程在Java中封裝成Socket和SocketServer,我們來回顧一下最簡單的TCP網絡編程吧:
TCP客戶端
public class ClientDemo { public static void main(String[] args) throws IOException { //創建發送端的Socket對象 Socket s = new Socket("192.168.1.106",8888); //Socket對象可以獲取輸出流 OutputStream os = s.getOutputStream(); os.write("hello,tcp,我來了".getBytes()); s.close(); } }
TCP服務端:
public class ServerDemo { public static void main(String[] args) throws IOException { //創建接收端的Socket對象 ServerSocket ss = new ServerSocket(8888); //監聽客戶端連接,返回一個對應的Socket對象 //偵聽并接受到此套接字的連接,此方法會阻塞 Socket s = ss.accept(); //獲取輸入流,讀取數據 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(); } }
上面的代碼就可以實現:客戶端向服務器發送數據,服務端能夠接收客戶端發送過來的數據。
1.2IO多路復用之前我已經寫過Java NIO的文章了,Java的NIO也是基于IO多路復用模型的,建議先去看一下再回來,文章寫得挺詳細和通俗的了:JDK10都發布了,nio你了解多少?
這里就簡單回顧一下吧:
I/O多路復用的特點是通過一種機制一個進程能同時等待多個文件描述符,而這些文件描述符其中的任意一個進入讀就緒狀態、等等,select()函數就可以返回。
select/epoll的優勢并不是對于單個連接能處理得更快,而是在于能處理更多的連接。
說白了,使用IO多路復用機制的,一般自己會有一套事件機制,使用一個線程或者進程監聽這些事件,如果這些事件被觸發了,則調用對應的函數來處理。
二、Redis事件Redis服務器是一個事件驅動程序,主要處理以下兩類事件:
文件事件:文件事件其實就是對Socket操作的抽象,Redis服務器與Redis客戶端的通信會產生文件事件,服務器通過監聽并處理這些事件來完成一系列的網絡操作
時間事件:時間事件其實就是對定時操作的抽象,前面我們已經講了RDB、AOF、定時刪除鍵這些操作都可以由服務端去定時或者周期去完成,底層就是通過觸發時間事件來實現的!
2.1文件事件Redis開發了自己的網絡事件處理器,這個處理器被稱為文件事件處理器。
文件事件處理器由四部分組成:
文件事件處理器使用I/O多路復用程序來同時監聽多個Socket。當被監聽的Socket準備好執行連接應答(accept)、讀取(read)等等操作時,與操作相對應的文件事件就會產生,根據文件事件來為Socket關聯對應的事件處理器,從而實現功能。
要值得注意的是:Redis中的I/O多路復用程序會將所有產生事件的Socket放到一個隊列里邊,然后通過這個隊列以有序、同步、每次一個Socket的方式向文件事件分派器傳送套接字。也就是說:當上一個Socket處理完畢后,I/O多路復用程序才會向文件事件分派器傳送下一個Socket。
首先,IO多路復用程序首先會監聽著Socket的AE_READABLE事件,該事件對應著連接應答處理器
可以理解簡單成SocketServet.accpet()
此時,一個名字叫做3y的Socket要連接服務器啦。服務器會用連接應答處理器處理。創建出客戶端的Socket,并將客戶端的Socket與命令請求處理器進行關聯,使得客戶端可以向服務器發送命令請求。
相當于Socket s = ss.accept();,創建出客戶端的Socket,然后將該Socket關聯命令請求處理器
此時客戶端就可以向主服務器發送命令請求了
假設現在客戶端發送一個命令請求set Java3y "關注、點贊、評論" ,客戶端Socket將產生AE_READABLE事件,引發命令請求處理器執行。處理器讀取客戶端的命令內容,然后傳給對應的程序去執行。
客戶端發送完命令請求后,服務端總得給客戶端回應的。此時服務端會將客戶端的Scoket的AE_WRITABLE事件與命令回復處理器關聯。
最后客戶端嘗試讀取命令回復時,客戶端Socket產生AE_WRITABLE事件,觸發命令回復處理器執行。當把所有的回復數據寫入到Socket之后,服務器就會解除客戶端Socket的AE_WRITABLE事件與命令回復處理器的關聯。
最后以《Redis設計與實現》的一張圖來概括:
2.2時間事件持續運行的Redis服務器會定期對自身的資源和狀態進行檢查和調整,這些定期的操作由serverCron函數負責執行,它的主要工作包括:
更新服務器的統計信息(時間、內存占用、數據庫占用)
清理數據庫的過期鍵值對
AOF、RDB持久化
如果是主從服務器,對從服務器進行定期同步
如果是集群模式,對進群進行定期同步和連接
...
Redis服務器將時間事件放在一個鏈表中,當時間事件執行器運行時,會遍歷整個鏈表。時間事件包括:
周期性事件(Redis一般只執行serverCron時間事件,serverCron時間事件是周期性的)
定時事件
2.3時間事件和文件事件文件事件和時間事件之間是合作關系,服務器會輪流處理這兩種事件,并且處理事件的過程中不會發生搶占。
時間事件的實際處理事件通常會比設定的到達時間晚一些
三、Redis多線程為什么快?1)純內存操作
2)核心是基于非阻塞的IO多路復用機制
3)單線程避免了多線程的頻繁上下文切換問題
四、客戶端與服務器在《Redis設計與實現》中各用了一章節來寫客戶端與服務器,我看完覺得比較底層的東西,也很難記得住,所以我決定總結一下比較重要的知識。如果以后真的遇到了,再來補坑~
服務器使用clints鏈表連接多個客戶端狀態,新添加的客戶端狀態會被放到鏈表的末尾
一個服務器可以與多個客戶端建立網絡連接,每個客戶端可以向服務器發送命令請求,而服務器則接收并處理客戶端發送的命令請求,并向客戶端返回命令回復。
Redis服務器使用單線程單進程的方式處理命令請求。在數據庫中保存客戶端執行命令所產生的數據,并通過資源管理來維持服務器自身的運轉。
4.1客戶端客戶端章節中主要講解了Redis客戶端的屬性(客戶端狀態、輸入/輸出緩沖區、命令參數、命令函數等等)
typedef struct redisClient{ //客戶端狀態的輸入緩沖區用于保存客戶端發送的命令請求,最大1GB,否則服務器將關閉這個客戶端 sds querybuf; //負責記錄argv數組的長度。 int argc; // 命令的參數 robj **argv; // 客戶端要執行命令的實現函數 struct redisCommand *cmd, *lastcmd; //記錄了客戶端的角色(role),以及客戶端所處的狀態。 (REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI) int flags; //記錄客戶端是否通過了身份驗證 int authenticated; //時間相關的屬性 time_t ctime; /* Client creation time */ time_t lastinteraction; /* time of the last interaction, used for timeout */ time_t obuf_soft_limit_reached_time; //固定大小的緩沖區用于保存那些長度比較小的回復 /* Response buffer */ int bufpos; char buf[REDIS_REPLY_CHUNK_BYTES]; //可變大小的緩沖區用于保存那些長度比較大的回復 list *reply; //可變大小緩沖區由reply 鏈表和一個或多個字符串對象組成 //... }4.2服務端
服務器章節中主要講解了Redis服務器讀取客戶端發送過來的命令是如何解析,以及初始化的過程。
服務器從啟動到能夠處理客戶端的命令請求需要執行以下的步驟:
初始化服務器狀態
載入服務器配置
初始化服務器的數據結構
還原數據庫狀態
執行事件循環
總的來說是這樣子的:
def main(): init_server(); while server_is_not_shutdown(); aeProcessEvents() clean_server();
從客戶端發送命令道完成主要包括的步驟:
客戶端將命令請求發送給服務器
服務器讀取命令請求,分析出命令參數
命令執行器根據參數查找命令的實現函數,執行實現函數并得出命令回復
服務器將命令回復返回給客戶端
五、最后現在臨近雙十一買阿里云服務器就特別省錢!之前我買學生機也要9.8塊錢一個月,現在最低價只需要8.3一個月!
無論是Nginx/Elasticsearch/Redis這些技術都是在Linux下完美運行的,如果還是程序員新手,買一個學習Linux基礎命令,學習搭建環境也是不錯的選擇。
如果有要買服務器的同學可通過我的鏈接直接享受最低價:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.pfn5xpli
本來也想把“復制”(主從)在這邊一起寫的,但寫完可能就很長了,所以留到下一篇吧。
如果大家有更好的理解方式或者文章有錯誤的地方還請大家不吝在評論區留言,大家互相學習交流~~~
參考資料:
《Redis設計與實現》
《Redis實戰》
一個堅持原創的Java技術公眾號:Java3y,歡迎大家關注
3y所有的原創文章:
文章的目錄導航(腦圖+海量視頻資源):https://github.com/ZhongFuCheng3y/3y
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72111.html
摘要:前言只有光頭才能變強好的,今天我們要上鉑金段位了,如果還沒經歷過青銅和白銀和黃金階段的,可以先去蹭蹭經驗再回來從零單排學青銅從零單排學白銀從零單排學黃金這篇文章主要講的是主從復制。 前言 只有光頭才能變強 好的,今天我們要上鉑金段位了,如果還沒經歷過青銅和白銀和黃金階段的,可以先去蹭蹭經驗再回來: 從零單排學Redis【青銅】 從零單排學Redis【白銀】 從零單排學Redis【黃金...
摘要:可以通過以下兩個配置盡量減少數據丟失的可能從零單排學鉑金三,敬請期待參考資料設計與實現實戰如果你覺得我寫得還不錯,了解一下堅持原創的技術公眾號。 前言 只有光頭才能變強 好的,今天我們要上【鉑金二】了,如果還沒有上鉑金的,趕緊先去蹭蹭經驗再回來(不然不帶你上分了): 從零單排學Redis【青銅】 從零單排學Redis【白銀】 從零單排學Redis【黃金】 從零單排學Redis【鉑金一...
閱讀 841·2021-11-15 17:58
閱讀 3641·2021-11-12 10:36
閱讀 3778·2021-09-22 16:06
閱讀 956·2021-09-10 10:50
閱讀 1325·2019-08-30 11:19
閱讀 3309·2019-08-29 16:26
閱讀 928·2019-08-29 10:55
閱讀 3341·2019-08-26 13:48