目錄
為什麼 Redis 不使用基本的 Socket 程式設計模型?
select 和 poll 機制實作 IO 多路復用
select機制
如何使用 select 机制来实现网络通信
poll机制
epoll機制
Reactor 模型的工作機制
首頁 資料庫 Redis Redis的事件驅動模型是什麼

Redis的事件驅動模型是什麼

Jun 04, 2023 am 10:20 AM
redis

為什麼 Redis 不使用基本的 Socket 程式設計模型?

使用Socket 模型實現網路通訊時,需要經過建立Socket、監聽埠、處理連線和讀寫請求等多個步驟,現在我們就來具體了解下這些步驟中的關鍵操作,以此協助我們分析Socket 模型中的不足。

首先,當我們需要讓伺服器端和客戶端進行通訊時,可以在伺服器端透過以下三步,來建立監聽客戶端連線的監聽套接字(Listening Socket):

  • 呼叫socket 函數,建立一個套接字。一般情況下,我們將該套接字稱為主動套接字

  • 呼叫bind 函數,將主動套接字和目前伺服器的IP 和監聽埠進行綁定;

  • 呼叫listen 函數,將主動套接字轉換為監聽套接字,開始監聽客戶端的連線。

在完成上述三步驟之後,伺服器端就可以接收客戶端的連線請求了。為了能及時收到客戶端的連線請求,我們可以執行一個循環流程,在該流程中呼叫 accept 函數,用於接收客戶端連線請求。

這裡你需要注意的是,accept 函數是阻塞函數,也就是說,如果此時一直沒有客戶端連線請求,那麼,伺服器端的執行流程會一直阻塞在 accept 函數。一旦有客戶端連接請求到達,accept 將不再阻塞,而是處理連接請求,和客戶端建立連接,並傳回已連接套接字(Connected Socket)。

最後,伺服器端可以透過呼叫 recv 或 send 函數,在剛才傳回的已連接套接字上,接收並處理讀寫請求,或將資料傳送給客戶端。

程式碼:

listenSocket = socket(); //调用socket系统调用创建一个主动套接字
bind(listenSocket); //绑定地址和端口
listen(listenSocket); //将默认的主动套接字转换为服务器使用的被动套接字,也就是监听套接字
while(1) { //循环监听是否有客户端连接请求到来
connSocket = accept(listenSocket);//接受客户端连接
recv(connSocket);//从客户端读取数据,只能同时处理一个客户端
send(connSocket);//给客户端返回数据,只能同时处理一个客户端
}
登入後複製

不過,從上述程式碼中,你可能會發現,雖然它能夠實現伺服器端和客戶端之間的通信,但是程式每調用一次accept 函數,只能處理一個客戶端連線。因此,如果想要處理多個並發客戶端的請求,我們就需要使用多線程,來處理透過 accept 函數建立的多個客戶端連接上的請求。

使用這種方法後,我們需要在accept 函數返回已連接套接字後,創建一個線程,並將已連接套接字傳遞給創建的線程,由該線程負責這個連接套接字上後續的數據讀寫。同時,伺服器端的執行流程會再次呼叫 accept 函數,等待下一個客戶端連線。

多執行緒:

listenSocket = socket(); //调用socket系统调用创建一个主动套接字
bind(listenSocket); //绑定地址和端口
listen(listenSocket); //将默认的主动套接字转换为服务器使用的被动套接字,也就是监听套接字
while(1) { //循环监听是否有客户端连接请求到来
connSocket = accept(listenSocket);//接受客户端连接
pthread_create(processData, connSocket);//创建新线程对已连接套接字进行处理
}

processData(connSocket){
recv(connSocket);//从客户端读取数据,只能同时处理一个客户端
send(connSocket);//给客户端返回数据,只能同时处理一个客户端
}
登入後複製

雖然這個方法能提升伺服器端的同時處理能力,但是,Redis 的主執行流程是由一個執行緒在執行,無法使用多執行緒的方式來提升並發處理能力。所以,該方法對redis並不起作用。

還有沒有其他方法,能幫助 Redis 提升並發客戶端的處理能力呢?這就要用到作業系統提供的IO多工功能。在基本的 Socket 程式設計模型中,accept 函數只能在一個監聽套接字上監聽客戶端的連接,recv 函數也只能在一個已連接套接字上,等待客戶端發送的請求。

因為 Linux 作業系統在實際應用上比較廣泛,所以這堂課,我們主要來學習 Linux 上的 IO 多路復用機制。 select、poll以及epoll是Linux所提供的IO多路復用機制的三種主要形式。下面,我們就分別來學習下這三種機制的實作想法和使用方法。接下來,我們再探討為什麼 Redis 常常選擇使用 epoll 機制來實作網路通訊。

select 和 poll 機制實作 IO 多路復用

首先,我們來了解下 select 機制的程式設計模型。

不過在具體學習之前,我們需要知道,對於一種 IO 多路復用機制來說,我們需要掌握哪些要點,這樣可以幫助我們快速抓住不同機制的聯繫與區別。其實,當我們學習 IO 多工機制時,我們需要能回答以下問題:第一,多工機制會監聽套接字上的哪些事件?第二,多工機制可以監聽多少個套接字?第三,當有套接字就緒時,多路復用機制如何找到就緒的套接字?

select機制

select 機制中的一個重要函數就是 select 函數。對於 select 函數來說,它的參數包括監聽的檔案描述符數量__nfds、、被監聽描述符的三個集合readfds、writefds、exceptfds,以及監聽時阻塞等待的逾時時長timeout。 select函數原型:

int select(int __nfds, fd_set *__readfds, fd_set *__writefds, fd_set *__exceptfds, struct timeval *__timeout)
登入後複製

這裡你需要注意的是,Linux 針對每一個套接字都會有一個檔案描述符,也就是一個非負整數,用來唯一標識該套接字。在多路復用機制的函數中,通常使用檔案描述符作為參數,這是 Linux 的常見做法。函數透過檔案描述子找到對應的套接字,從而實現監聽、讀寫等操作。

select函數的三個參數指定了需要監視的檔案描述子集合,實際上代表了需要監視的套接字集合。那麼,為什麼會有三個集合呢?

关于刚才提到的第一个问题,即多路复用机制监听的套接字事件有哪些。select 函数使用三个集合,表示监听的三类事件,分别是读数据事件,写数据事件,异常事件。

我们进一步可以看到,参数 readfds、writefds 和 exceptfds 的类型是 fd_set 结构体,它主要定义部分如下所示。其中,fd_mask类型是 long int 类型的别名,__FD_SETSIZE 和 __NFDBITS 这两个宏定义的大小默认为 1024 和 32。

所以,fd_set 结构体的定义,其实就是一个 long int 类型的数组,该数组中一共有 32 个元素(1024/32=32),每个元素是 32 位(long int 类型的大小),而每一位可以用来表示一个文件描述符的状态。了解了 fd_set 结构体的定义,我们就可以回答刚才提出的第二个问题了。每个描述符集合都可以被 select 函数监听 1024 个描述符。

如何使用 select 机制来实现网络通信

首先,我们在调用 select 函数前,可以先创建好传递给 select 函数的描述符集合,然后再创建监听套接字。而为了让创建的监听套接字能被 select 函数监控,我们需要把这个套接字的描述符加入到创建好的描述符集合中。

接下来,我们可以使用 select 函数并传入已创建的描述符集合作为参数。程序在调用 select 函数后,会发生阻塞。一旦 select 函数检测到有就绪的描述符,会立即终止阻塞并返回已就绪的文件描述符数。

那么此时,我们就可以在描述符集合中查找哪些描述符就绪了。然后,我们对已就绪描述符对应的套接字进行处理。比如,如果是 readfds 集合中有描述符就绪,这就表明这些就绪描述符对应的套接字上,有读事件发生,此时,我们就在该套接字上读取数据。

而因为 select 函数一次可以监听 1024 个文件描述符的状态,所以 select 函数在返回时,也可能会一次返回多个就绪的文件描述符。我们可以使用循环处理流程,对每个就绪描述符对应的套接字依次进行读写或异常处理操作。

select函数有两个不足

  • 首先,select 函数对单个进程能监听的文件描述符数量是有限制的,它能监听的文件描述符个数由 __FD_SETSIZE 决定,默认值是 1024。

  • 其次,当 select 函数返回后,我们需要遍历描述符集合,才能找到具体是哪些描述符就绪了。这个遍历过程会产生一定开销,从而降低程序的性能。

poll机制

poll 机制的主要函数是 poll 函数,我们先来看下它的原型定义,如下所示:

int poll(struct pollfd *__fds, nfds_t __nfds, int __timeout)
登入後複製

其中,参数 *__fds 是 pollfd 结构体数组,参数 __nfds 表示的是 *__fds 数组的元素个数,而 __timeout 表示 poll 函数阻塞的超时时间。

pollfd 结构体里包含了要监听的描述符,以及该描述符上要监听的事件类型。从 pollfd 结构体的定义中,我们可以看出来这一点,具体如下所示。pollfd 结构体中包含了三个成员变量 fd、events 和 revents,分别表示要监听的文件描述符、要监听的事件类型和实际发生的事件类型。

pollfd 结构体中要监听和实际发生的事件类型,是通过以下三个宏定义来表示的,分别是 POLLRDNORM、POLLWRNORM 和 POLLERR,它们分别表示可读、可写和错误事件。

了解了 poll 函数的参数后,我们来看下如何使用 poll 函数完成网络通信。这个流程主要可以分成三步:

  • 第一步,创建 pollfd 数组和监听套接字,并进行绑定;

  • 第二步,将监听套接字加入 pollfd 数组,并设置其监听读事件,也就是客户端的连接请求;

  • 第三步,循环调用 poll 函数,检测 pollfd 数组中是否有就绪的文件描述符。

而在第三步的循环过程中,其处理逻辑又分成了两种情况:

  • 如果是连接套接字就绪,这表明是有客户端连接,我们可以调用 accept 接受连接,并创建已连接套接字,并将其加入 pollfd 数组,并监听读事件;

  • 如果是已连接套接字就绪,这表明客户端有读写请求,我们可以调用 recv/send 函数处理读写请求。

其实,和 select 函数相比,poll 函数的改进之处主要就在于,它允许一次监听超过 1024 个文件描述符。但是当调用了 poll 函数后,我们仍然需要遍历每个文件描述符,检测该描述符是否就绪,然后再进行处理。

epoll機制

首先,epoll 機制是使用 epoll_event 結構體,來記錄待監聽的文件描述符及其監聽的事件類型的,這和 poll 機制中使用 pollfd 結構體比較類似。

那麼,對於 epoll_event 結構體來說,其中包含了 epoll_data_t 聯合體變量,以及整數類型的 events 變數。 epoll_data_t 聯合體中有記錄檔案描述子的成員變數fd,而events 變數會取值使用不同的巨集定義值,來表示epoll_data_t 變數中的檔案描述子所關注的事件類型,例如一些常見的事件類型包括以下這幾種。

  • EPOLLIN:讀取事件,表示檔案描述子對應套接字有資料可讀。

  • EPOLLOUT:寫事件,表示檔案描述子對應套接字有資料要寫。

  • EPOLLERR:錯誤事件,表示檔案描述子對於套接字出錯。

在使用 select 或 poll 函數的時候,建立好檔案描述子集合或 pollfd 陣列後,就可以往數組中加入我們需要監聽的檔案描述子。

但對於 epoll 機制來說,我們需要先呼叫 epoll_create 函數,建立一個 epoll 實例。這個 epoll 實例內部維護了兩個結構,分別是記錄要監聽的檔案描述符和已經就緒的檔案描述符,,而對於已經就緒的檔案描述符來說,它們會被傳回給使用者程式處理。

所以,我們在使用 epoll 機制時,就不用像使用 select 和 poll 一樣,遍歷查詢哪些檔案描述子已經就緒了。因此,epoll 的效率比 select 和 poll 更高。

在建立了 epoll 實例後,我們需要再使用 epoll_ctl 函數,為被監聽的檔案描述子新增監聽事件類型,以及使用 epoll_wait 函數取得就緒的檔案描述子。

了解了 epoll 函數的使用方法了。實際上,也正是因為epoll 能自訂監聽的描述符數量,以及可以直接返回就緒的描述符,Redis 在設計和實現網路通訊框架時,就基於epoll 機制中的epoll_create、epoll_ctl 和epoll_wait 等函數和讀寫事件,進行了封裝開發,實現了用於網路通訊的事件驅動框架,從而使得Redis 雖然是單執行緒運行,但是仍然能高效應對高並發的客戶端存取。

Reactor 模型的工作機制

Reactor 模型就是網路伺服器端用來處理高並發網路IO 請求的一種程式設計模型,模型特徵:

  • 三類處理事件,即連接事件、寫事件、讀取事件;

  • 三個關鍵角色,即reactor、acceptor、handler。

Reactor 模型處理的是客戶端和伺服器端的互動過程,而這三類事件正好對應了客戶端和伺服器端互動過程中,不同類別請求在伺服器端引發的待處理事件:

  • 當一個客戶端要和伺服器端進行交互時,客戶端會向伺服器端發送連接請求,以建立連接,這就對應了伺服器端的一個鏈接事件

  • 一旦連線建立後,客戶端會給伺服器端發送讀取請求,以便讀取資料。伺服器端在處理讀取請求時,需要向客戶端寫回數據,這對應了伺服器端的寫事件

  • 無論客戶端給伺服器端發送讀取或寫入請求,伺服器端都需要從客戶端讀取請求內容,所以在這裡,讀取或寫入請求的讀取就對應了伺服器端的讀取事件

三個關鍵角色:

  • #首先,連線事件由acceptor 來處理,負責接收連線;acceptor 在接收連線後,會建立handler,用於網路連線上對後續讀寫事件的處理;

  • 其次,讀寫事件由handler 處理;

  • 最後,在高並發場景中,連結事件、讀寫事件會同時發生,所以,我們需要有一個角色專門監聽和分配事件,這就是reactor 角色。當有連線請求時,reactor 將產生的連線事件交由 acceptor 處理;當有讀寫請求時,reactor 將讀寫事件交由 handler 處理。

那麼,現在我們已經知道,這三個角色是圍繞著事件的監聽、轉發和處理來進行互動的,那麼在程式設計時,我們又該如何實現這三者的交互呢?這就離不開事件驅動。

實作 Reactor 模型時,需要編寫的總體程式碼控制邏輯,稱為事件驅動框架。事件驅動框架由兩個部分組成:事件初始化和事件捕獲、分流及處理的主循環。簡而言之。

事件初始化是在伺服器程式啟動時就執行的,它的作用主要是建立需要監聽的事件類型,以及該類別事件對應的 handler。而一旦伺服器完成初始化後,事件初始化也就相應完成了,伺服器程式就需要進入事件擷取、分發和處理的主循環。

用while迴圈來當作這個主迴圈。然後在這個主循環中,我們需要捕捉發生的事件、判斷事件類型,並根據事件類型,呼叫在初始化時建立好的事件 handler 來實際處理事件。

比如說,當有連線事件發生時,伺服器程式需要呼叫 acceptor 處理函數,建立和客戶端的連線。而當有讀事件發生時,就表示有讀或寫請求發送到了伺服器端,伺服器程式就要調用具體的請求處理函數,從客戶端連線中讀取請求內容,進而完成了讀取事件的處理。

Reactor 模型的基本工作機制:客戶端的不同類別請求會在伺服器端觸發連線、讀取、寫三類事件,這三類事件的監聽、分發和處理又是由reactor、acceptor、handler三類角色來完成的,然後這三類角色會透過事件驅動框架來實現互動和事件處理。

以上是Redis的事件驅動模型是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

redis集群模式怎麼搭建 redis集群模式怎麼搭建 Apr 10, 2025 pm 10:15 PM

Redis集群模式通過分片將Redis實例部署到多個服務器,提高可擴展性和可用性。搭建步驟如下:創建奇數個Redis實例,端口不同;創建3個sentinel實例,監控Redis實例並進行故障轉移;配置sentinel配置文件,添加監控Redis實例信息和故障轉移設置;配置Redis實例配置文件,啟用集群模式並指定集群信息文件路徑;創建nodes.conf文件,包含各Redis實例的信息;啟動集群,執行create命令創建集群並指定副本數量;登錄集群執行CLUSTER INFO命令驗證集群狀態;使

redis數據怎麼清空 redis數據怎麼清空 Apr 10, 2025 pm 10:06 PM

如何清空 Redis 數據:使用 FLUSHALL 命令清除所有鍵值。使用 FLUSHDB 命令清除當前選定數據庫的鍵值。使用 SELECT 切換數據庫,再使用 FLUSHDB 清除多個數據庫。使用 DEL 命令刪除特定鍵。使用 redis-cli 工具清空數據。

redis怎麼讀取隊列 redis怎麼讀取隊列 Apr 10, 2025 pm 10:12 PM

要從 Redis 讀取隊列,需要獲取隊列名稱、使用 LPOP 命令讀取元素,並處理空隊列。具體步驟如下:獲取隊列名稱:以 "queue:" 前綴命名,如 "queue:my-queue"。使用 LPOP 命令:從隊列頭部彈出元素並返回其值,如 LPOP queue:my-queue。處理空隊列:如果隊列為空,LPOP 返回 nil,可先檢查隊列是否存在再讀取元素。

centos redis如何配置Lua腳本執行時間 centos redis如何配置Lua腳本執行時間 Apr 14, 2025 pm 02:12 PM

在CentOS系統上,您可以通過修改Redis配置文件或使用Redis命令來限制Lua腳本的執行時間,從而防止惡意腳本佔用過多資源。方法一:修改Redis配置文件定位Redis配置文件:Redis配置文件通常位於/etc/redis/redis.conf。編輯配置文件:使用文本編輯器(例如vi或nano)打開配置文件:sudovi/etc/redis/redis.conf設置Lua腳本執行時間限制:在配置文件中添加或修改以下行,設置Lua腳本的最大執行時間(單位:毫秒)

redis指令怎麼用 redis指令怎麼用 Apr 10, 2025 pm 08:45 PM

使用 Redis 指令需要以下步驟:打開 Redis 客戶端。輸入指令(動詞 鍵 值)。提供所需參數(因指令而異)。按 Enter 執行指令。 Redis 返迴響應,指示操作結果(通常為 OK 或 -ERR)。

redis怎麼使用鎖 redis怎麼使用鎖 Apr 10, 2025 pm 08:39 PM

使用Redis進行鎖操作需要通過SETNX命令獲取鎖,然後使用EXPIRE命令設置過期時間。具體步驟為:(1) 使用SETNX命令嘗試設置一個鍵值對;(2) 使用EXPIRE命令為鎖設置過期時間;(3) 當不再需要鎖時,使用DEL命令刪除該鎖。

redis命令行怎麼用 redis命令行怎麼用 Apr 10, 2025 pm 10:18 PM

使用 Redis 命令行工具 (redis-cli) 可通過以下步驟管理和操作 Redis:連接到服務器,指定地址和端口。使用命令名稱和參數向服務器發送命令。使用 HELP 命令查看特定命令的幫助信息。使用 QUIT 命令退出命令行工具。

redis過期策略怎麼設置 redis過期策略怎麼設置 Apr 10, 2025 pm 10:03 PM

Redis數據過期策略有兩種:定期刪除:定期掃描刪除過期鍵,可通過 expired-time-cap-remove-count、expired-time-cap-remove-delay 參數設置。惰性刪除:僅在讀取或寫入鍵時檢查刪除過期鍵,可通過 lazyfree-lazy-eviction、lazyfree-lazy-expire、lazyfree-lazy-user-del 參數設置。

See all articles