日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

性能突出的 Redis 是咋使用 epoll 的?

發布時間:2024/8/23 数据库 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 性能突出的 Redis 是咋使用 epoll 的? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者 | 閃客

來源 | 低并發編程

我是個 redis 服務,我馬上就要啟動了

因為我的主人正在控制臺輸入:

./redis-server

宏觀上看下我的流程

突然,主人按下了回車鍵,不得了了。

shell 程序把我的程序加載到了內存,開始執行我的 main 方法,一切就從這里開始了。

int?main(int?argc,?char?**argv)?{...initServer();...aeCreateFileEvent(fd, acceptHandler, ...);...aeMain();... }

不要覺得我這里很復雜,其實主要就三大步。

第一步,我通過 listenToPort() 方法創建了一個 TCP 連接。

我的這個方法真是見名知意,而且如果展開看就更會發現沒什么神秘的,就是 socket bind listen 標準三步走,建立了一個 TCP 監聽,返回了一個文件描述符 fd。

第二步,我通過 aeCreateFileEvent() 方法,將上面那個創建了 TCP 連接返回的文件描述符 fd,加入到一個叫 aeFileEvent的鏈表中。

同時將這個文件描述符綁定一個函數 acceptHandler,這樣當有客戶端連接進來時,便會執行這個函數。

第三步,我通過 aeMain() 方法,將上面的 aeFileEvent 鏈表中的文件描述符,統統作為 select 的入參,這是 IO 多路復用模式。

好了,其實就是開啟了一個 TCP 監聽,然后如果有客戶端進來的話,讓他執行 acceptHandler 函數而已。

之后我就一直死等著客戶端連接了。

void?aeMain(aeEventLoop?*eventLoop) {eventLoop->stop?=?0;while?(!eventLoop->stop)aeProcessEvents(eventLoop,?AE_ALL_EVENTS); }

展開體驗下我的具體工作

此時,另外一個人啟動了一個 redis-client,連接到了我。

redis-cli?-h?host?-p?port

那么我頭上的 fd 就會感知有數據讀入,并執行 acceptHandler 方法。

static?void?acceptHandler(...)?{...cfd?=?anetAccept(...);...c?=?createClient(cfd))... }

可以看到,當有新客戶端連接進來時,便會調用 createClient 創建一個專屬的 client 為其服務。

static?redisClient?*createClient(int?fd)?{...aeCreateFileEvent(c->fd,?readQueryFromClient, ...);... }

這里又可以看到,所謂的專屬服務,其實仍然是這個 aeCreateFileEvent 函數。

這個上面說了,這個函數的功能就是把文件描述符掛在鏈表上,然后分配一個處理函數。

當然,這回的處理函數不再是處理新客戶端連接的 acceptHandler,而是處理具體客戶端傳來的 redis 命令的函數 readQueryFromClient

不難想象,如果再來一個客戶端,又來一個客戶端... 那么不斷將新客戶端的文件描述符掛上去即可,而監聽新客戶端連接的,始終是最上面那個文件描述符。

好了,服務端開啟了監聽,客戶端也連上了服務端,此時我仍然在死等狀態,只不過等的不只是新客戶端連接到達,還在等待已經連接上的客戶端發來命令。

請注意,這里的死等,只有一個線程,循環調用 aeProcessEvents 函數,用 select 的方式監聽多個文件描述符。放上剛剛 main 方法的第三步,幫大家回憶一下。

void?aeMain(aeEventLoop?*eventLoop) {eventLoop->stop?=?0;while?(!eventLoop->stop)aeProcessEvents(eventLoop,?AE_ALL_EVENTS); }

當有新客戶端建立連接時,會觸發 acceptHandler 函數執行,多出一個等待數據的描述符。

當有客戶端數據傳來時,會觸發 readQueryFromClient 函數執行,完成這個命令的操作。

注意,由于只有一個線程在監聽這些描述符,并做處理。所以即使客戶端并發地發送命令,后面仍然是依次取出命令,順序執行

這也就是我們常常說的,redis 是單線程的,命令與命令之間是順序執行,無需考慮線程安全的問題。

為了方便大家吹牛,我來拔高一下

大家發現沒,我的啟動過程,其實就分成兩個大的部分。

一個是監聽客戶端的請求,就是用 IO 多路復用的方式,監聽多個文件描述符,就剛剛那個 aeMain() 方法干的事嘛。

一個是執行相應的函數去處理這個請求,具體執行什么函數就是出現多次的 aeCreateFileEvent() 方法去綁定的,這個相應的函數說得高大上一點,叫做事件處理器

這里所謂的連接應答處理器,就是剛剛監聽連接的文件描述符所綁定的函數 acceptHandler。

所謂的命令請求處理器,就是監聽客戶端命令(讀事件)的文件描述符綁定的函數 readQueryFromClient。

所謂的命令回復處理器,就是后面要提到的,監聽客戶端響應(寫事件)的文件描述符綁定的函數 sendReplyToClient。

這種一個負責響應 IO 事件,一個負責交給相應的事件處理器去處理,就叫做 Reactor 模式

Redis 正是基于 Reactor 模式開發了自己的文件事件處理器,實現了高性能的網絡通信模型,并且保持了 Redis 內部單線程設計的簡單性

有點擔心這句話吹牛的逼格不夠,其實我是參考了《Redis 設計與實現》,截圖給大家。



具體怎么執行一個 Redis 命令

現在,我們通過一個已建立好連接的客戶端,發一個 redis 命令。

<client 6379> set?dibingfa?niubi

此時 readQueryFromClient 函數將被執行。

這個函數會去一張表中尋找命令所對應的函數,這部分用的編碼技巧叫命令模式。

static?struct?redisCommand?cmdTable[]?=?{{"get",getCommand,2,REDIS_CMD_INLINE},{"set",setCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},{"setnx",setnxCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},{"del",delCommand,-2,REDIS_CMD_INLINE},{"exists",existsCommand,2,REDIS_CMD_INLINE},... }

找到了 set 命令對應的函數就是 setCommand

這個函數,最終就會一步步地將 key 和 value 分別存儲起來。

處理完命令后,就要發送響應給客戶端了。

static?void?setCommand(redisClient?*c)?{...addReply(c,?nx???shared.cone?:?shared.ok); }

這個響應,并不是直接同步寫回去,當然也不是開啟一個線程去異步寫回去。

它仍然是調用那個萬惡的 aeCreateFileEvent 函數,將 sendReplyToClient 函數掛在需要響應的客戶端連接的文件描述符上。

static?void?addReply(redisClient?*c,?robj?*obj)?{...aeCreateFileEvent(server.el,?c->fd,?AE_WRITABLE,sendReplyToClient,?c,?NULL)?==?AE_ERR); }

好了,這回上一小節挖的坑,終于補上了。

以上這些個破玩意,就是我的啟動過程啦,我是不是很可愛。

后記

整篇文章我好像沒講 Redis 為啥那么快,因為我感覺這個問題問得不好。

你可以從接收網絡請求的 IO 多路復用角度說起,也可以從事件處理器驅動的 Reactor 模式說起,還可以從具體處理命令時的數據結構說起,比如單單是字符串背后的 sds 其實就做了很多的巧妙設計。

如果我是面試官,我會具體讓面試者聊聊 Redis 的啟動流程,或者 Redis 處理命令的整個流程。

這里面可挖的點挺多的,如果能談笑風生,那自然是技術水平還不錯。

另外,你會發現本文出現的很多唬人的術語,比如 Reactor 模式,事件處理器等,看一遍 Redis 源碼后你會發現真的非常簡單。

毫不客氣地說,一切絲毫不談具體實現,和你堆砌一大堆唬人名詞的文章或者人,都是在耍流氓。

本文我參考的是 Redis3.0.0 源碼,但成文時用的講解代碼是 Redis1.0.0,整個網絡模塊的設計是完全一樣的。

往期推薦

Redis 緩存擊穿(失效)、緩存穿透、緩存雪崩怎么解決?

如果被問到分布式鎖,應該怎樣回答?

三分鐘教你用 Scarlet 寫一個 WebSocket App

Java 底層知識:什么是?“橋接方法”??

點分享

點收藏

點點贊

點在看

總結

以上是生活随笔為你收集整理的性能突出的 Redis 是咋使用 epoll 的?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。