搜尋
首頁資料庫RedisRedis的事件驅動模型是什麼

為什麼 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刪除
REDIS:了解其架構和目的REDIS:了解其架構和目的Apr 26, 2025 am 12:11 AM

Redis是一种内存数据结构存储系统,主要用作数据库、缓存和消息代理。它的核心特点包括单线程模型、I/O多路复用、持久化机制、复制与集群功能。Redis在实际应用中常用于缓存、会话存储和消息队列,通过选择合适的数据结构、使用管道和事务、以及进行监控和调优,可以显著提升其性能。

REDIS與SQL數據庫:關鍵差異REDIS與SQL數據庫:關鍵差異Apr 25, 2025 am 12:02 AM

Redis和SQL數據庫的主要區別在於:Redis是內存數據庫,適用於高性能和靈活性需求;SQL數據庫是關係型數據庫,適用於復雜查詢和數據一致性需求。具體來說,1)Redis提供高速數據訪問和緩存服務,支持多種數據類型,適用於緩存和實時數據處理;2)SQL數據庫通過表格結構管理數據,支持複雜查詢和事務處理,適用於電商和金融系統等需要數據一致性的場景。

REDIS:它如何充當數據存儲和服務REDIS:它如何充當數據存儲和服務Apr 24, 2025 am 12:08 AM

REDISACTSASBOTHADATASTOREANDASERVICE.1)ASADATASTORE,ITUSESIN-MEMORYSTOOGATOFORFOFFASTESITION,支持VariousDatharptructuresLikeKey-valuepairsandsortedsetsetsetsetsetsetsets.2)asaservice,ItprovidespunctionslikeItionitionslikepunikeLikePublikePublikePlikePlikePlikeAndluikeAndluAascriptingiationsmpleplepleclexplectiations

REDIS與其他數據庫:比較分析REDIS與其他數據庫:比較分析Apr 23, 2025 am 12:16 AM

Redis與其他數據庫相比,具有以下獨特優勢:1)速度極快,讀寫操作通常在微秒級別;2)支持豐富的數據結構和操作;3)靈活的使用場景,如緩存、計數器和發布訂閱。選擇Redis還是其他數據庫需根據具體需求和場景,Redis在高性能、低延遲應用中表現出色。

REDIS的角色:探索數據存儲和管理功能REDIS的角色:探索數據存儲和管理功能Apr 22, 2025 am 12:10 AM

Redis在數據存儲和管理中扮演著關鍵角色,通過其多種數據結構和持久化機製成為現代應用的核心。 1)Redis支持字符串、列表、集合、有序集合和哈希表等數據結構,適用於緩存和復雜業務邏輯。 2)通過RDB和AOF兩種持久化方式,Redis確保數據的可靠存儲和快速恢復。

REDIS:了解NOSQL概念REDIS:了解NOSQL概念Apr 21, 2025 am 12:04 AM

Redis是一種NoSQL數據庫,適用於大規模數據的高效存儲和訪問。 1.Redis是開源的內存數據結構存儲系統,支持多種數據結構。 2.它提供極快的讀寫速度,適合緩存、會話管理等。 3.Redis支持持久化,通過RDB和AOF方式確保數據安全。 4.使用示例包括基本的鍵值對操作和高級的集合去重功能。 5.常見錯誤包括連接問題、數據類型不匹配和內存溢出,需注意調試。 6.性能優化建議包括選擇合適的數據結構和設置內存淘汰策略。

REDIS:現實世界的用例和示例REDIS:現實世界的用例和示例Apr 20, 2025 am 12:06 AM

Redis在現實世界中的應用包括:1.作為緩存系統加速數據庫查詢,2.存儲Web應用的會話數據,3.實現實時排行榜,4.作為消息隊列簡化消息傳遞。 Redis的多功能性和高性能使其在這些場景中大放異彩。

REDIS:探索其功能和功能REDIS:探索其功能和功能Apr 19, 2025 am 12:04 AM

Redis脫穎而出是因為其高速、多功能性和豐富的數據結構。 1)Redis支持字符串、列表、集合、散列和有序集合等數據結構。 2)它通過內存存儲數據,支持RDB和AOF持久化。 3)從Redis6.0開始引入多線程處理I/O操作,提升了高並發場景下的性能。

See all articles

熱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

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

熱工具

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。