只有光頭才能變強java
好的,今天咱們要上黃金段位了,若是還沒經歷過青銅和白銀階段的,能夠先去蹭蹭經驗再回來:git
看過相關Redis基礎的同窗能夠知道Redis是單線程的,不少面試題也極可能會問到「爲何Redis是單線程的還那麼快」。程序員
這篇文章來說講單線程的內部的原理。github
文本力求簡單講清每一個知識點,但願你們看完能有所收穫面試
在講解Redis以前,咱們先來一些基礎的鋪墊,有更好的閱讀體驗。redis
咱們在初學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(); } }
上面的代碼就能夠實現:客戶端向服務器發送數據,服務端可以接收客戶端發送過來的數據。
以前我已經寫過Java NIO的文章了,Java的NIO也是基於IO多路複用模型的,建議先去看一下再回來,文章寫得挺詳細和通俗的了:JDK10都發布了,nio你瞭解多少?
這裏就簡單回顧一下吧:
select()
函數就能夠返回。說白了,使用IO多路複用機制的,通常本身會有一套事件機制,使用一個線程或者進程監聽這些事件,若是這些事件被觸發了,則調用對應的函數來處理。
Redis服務器是一個事件驅動程序,主要處理如下兩類事件:
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設計與實現》的一張圖來歸納:
持續運行的Redis服務器會按期對自身的資源和狀態進行檢查和調整,這些按期的操做由serverCron函數負責執行,它的主要工做包括:
Redis服務器將時間事件放在一個鏈表中,當時間事件執行器運行時,會遍歷整個鏈表。時間事件包括:
在《Redis設計與實現》中各用了一章節來寫客戶端與服務器,我看完以爲比較底層的東西,也很難記得住,因此我決定總結一下比較重要的知識。若是之後真的遇到了,再來補坑~
服務器使用clints鏈表鏈接多個客戶端狀態,新添加的客戶端狀態會被放到鏈表的末尾
客戶端章節中主要講解了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 鏈表和一個或多個字符串對象組成 //... }
服務器章節中主要講解了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
原本也想把「複製」(主從)在這邊一塊兒寫的,但寫完可能就很長了,因此留到下一篇吧。
若是你們有更好的理解方式或者文章有錯誤的地方還請你們不吝在評論區留言,你們互相學習交流~~~
參考資料:
一個堅持原創的Java技術公衆號:Java3y,歡迎你們關注
3y全部的原創文章: