Redis--單執行緒

心恩惠动發表於2024-04-23

redis是單執行緒的嗎?

不是,redis的單執行緒指的是命令的執行是單執行緒的,如接收客戶端請求->解析請求 ->進行資料讀寫等操作->傳送資料給客戶端這個過程是由一個執行緒(主執行緒)完成的。

redis程式並不是單執行緒,redis 在啟動的時候,會啟動後臺執行緒(BIO):

  • Redis 在 2.6 版本,會啟動 2 個後臺執行緒,分別處理關閉檔案、AOF 刷盤這兩個任務
  • Redis 在 4.0 版本之後,新增了一個新的後臺執行緒,用來非同步釋放 Redis 記憶體,也就是 lazyfree 執行緒。例如執行 unlink key / flushdb async / flushall async 等命令,會把這些刪除操作交給後臺執行緒來執行,好處是不會導致 Redis 主執行緒卡頓。因此,當我們要刪除一個大 key 的時候,不要使用 del 命令刪除,因為 del 是在主執行緒處理的,這樣會導致 Redis 主執行緒卡頓,因此我們應該使用 unlink 命令來非同步刪除大key。
  • Redis 6.0 版本之後,也採用了多個 I/O 執行緒來處理網路請求,這是因為隨著網路硬體的效能提升,Redis 的效能瓶頸有時會出現在網路 I/O 的處理上。

關閉檔案、AOF 刷盤、釋放記憶體這三個任務都有各自的任務佇列:

  • BIO_CLOSE_FILE,關閉檔案任務佇列:當佇列有任務後,後臺執行緒會呼叫 close(fd) ,將檔案關閉
  • BIO_AOF_FSYNC,AOF刷盤任務佇列:當 AOF 日誌配置成 everysec 選項後,主執行緒會把 AOF 寫日誌操作封裝成一個任務,也放到佇列中。當發現佇列有任務後,後臺執行緒會呼叫 fsync(fd),將 AOF 檔案刷盤
  • BIO_LAZY_FREE,lazy free 任務佇列:當佇列有任務後,後臺執行緒會 free(obj) 釋放物件 / free(dict) 刪除資料庫所有物件 / free(skiplist) 釋放跳錶物件

redis單執行緒模型

Redis 6.0 版本之前的單線模式如下圖:

圖中的藍色部分是一個事件迴圈,是由主執行緒負責的,可以看到網路 I/O 和命令處理都是單執行緒。 Redis 初始化的時候,會做下面這幾件事情:

  • 首先,呼叫 epoll_create() 建立一個 epoll 物件和呼叫 socket() 建立一個服務端 socket
  • 然後,呼叫 bind() 繫結埠和呼叫 listen() 監聽該 socket;
  • 然後,將呼叫 epoll_ctl() 將 listen socket 加入到 epoll,同時註冊「連線事件」處理函式。

初始化完後,主執行緒就進入到一個事件迴圈函式,主要會做以下事情:

  • 首先,先呼叫處理傳送佇列函式,看是傳送佇列裡是否有任務,如果有傳送任務,則透過 write 函式將客戶端傳送快取區裡的資料傳送出去,如果這一輪資料沒有傳送完,就會註冊寫事件處理函式,等待 epoll_wait 發現可寫後再處理 。
  • 接著,呼叫 epoll_wait 函式等待事件的到來:
    • 如果是連線事件到來,則會呼叫連線事件處理函式,該函式會做這些事情:呼叫 accpet 獲取已連線的 socket -> 呼叫 epoll_ctl 將已連線的 socket 加入到 epoll -> 註冊「讀事件」處理函式;
    • 如果是讀事件到來,則會呼叫讀事件處理函式,該函式會做這些事情:呼叫 read 獲取客戶端傳送的資料 -> 解析命令 -> 處理命令 -> 將客戶端物件新增到傳送佇列 -> 將執行結果寫到傳送快取區等待傳送;
    • 如果是寫事件到來,則會呼叫寫事件處理函式,該函式會做這些事情:透過 write 函式將客戶端傳送快取區裡的資料傳送出去,如果這一輪資料沒有傳送完,就會繼續註冊寫事件處理函式,等待 epoll_wait 發現可寫後再處理 。

redis命令執行使用單執行緒為什麼還那麼快?

  1. 記憶體儲存:Redis 將資料儲存在記憶體中,這使得資料的讀取和寫入操作非常快速。相比於磁碟儲存的資料庫系統,Redis 能夠透過直接從記憶體中讀取資料來提供更高的效能。

  2. 非阻塞 I/O:Redis 採用了非阻塞 I/O 操作,透過非同步地處理網路請求和資料讀寫操作,實現了高效的 I/O 操作。當 Redis 執行一些可能會導致阻塞的操作時,如持久化或網路請求,它會將這些操作委託給作業系統處理,同時繼續處理其他請求,從而最大限度地減少了等待時間。

  3. 簡單的資料結構:Redis 提供了簡單而高效的資料結構,如字串、雜湊表、列表、集合和有序集合等。這些資料結構的實現經過了精心最佳化,能夠在單執行緒模型下快速執行各種操作,如插入、刪除、查詢和排序等。

  4. 高效的網路通訊:Redis 使用基於 TCP 的客戶端/伺服器模型,透過高效的網路通訊協議與客戶端進行通訊。Redis 的協議非常簡單且高效,減少了網路傳輸的開銷,並且支援批次操作和管道(pipeline)操作,從而減少了網路往返的次數,提高了整體效能。

  5. 非常低的延遲:Redis 的單執行緒模型消除了多執行緒環境下的鎖競爭和上下文切換開銷,使得請求處理的延遲非常低。這對於需要快速響應的應用場景非常有利,如實時計數、會話儲存和快取等。

redis 6.0引用I/O多執行緒

雖然 Redis 的主要工作(網路 I/O 和執行命令)一直是單執行緒模型,但是在 Redis 6.0 版本之後,也採用了多個 I/O 執行緒來處理網路請求,這是因為隨著網路硬體的效能提升,Redis 的效能瓶頸有時會出現在網路 I/O 的處理上。

所以為了提高網路 I/O 的並行度,Redis 6.0 對於網路 I/O 採用多執行緒來處理。但是對於命令的執行,Redis 仍然使用單執行緒來處理,所以大家不要誤解 Redis 有多執行緒同時執行命令。

Redis 官方表示,Redis 6.0 版本引入的多執行緒 I/O 特性對效能提升至少是一倍以上。

Redis 6.0 版本支援的 I/O 多執行緒特性,預設情況下 I/O 多執行緒只針對傳送響應資料(write client socket),並不會以多執行緒的方式處理讀請求(read client socket)。要想開啟多執行緒處理客戶端讀請求,就需要把 Redis.conf 配置檔案中的 io-threads-do-reads 配置項設為 yes。

//讀請求也使用io多執行緒
io-threads-do-reads yes

同時, Redis.conf 配置檔案中提供了 IO 多執行緒個數的配置項。

// io-threads N,表示啟用 N-1 個 I/O 多執行緒(主執行緒也算一個 I/O 執行緒)
io-threads 4

關於執行緒數的設定,官方的建議是如果為 4 核的 CPU,建議執行緒數設定為 2 或 3,如果為 8 核 CPU 建議執行緒數設定為 6,執行緒數一定要小於機器核數,執行緒數並不是越大越好。

因此, Redis 6.0 版本之後,Redis 在啟動的時候,預設情況下會額外建立 6 個執行緒(這裡的執行緒數不包括主執行緒):

  • Redis-server : Redis的主執行緒,主要負責執行命令;
  • bio_close_file、bio_aof_fsync、bio_lazy_free:三個後臺執行緒,分別非同步處理關閉檔案任務、AOF刷盤任務、釋放記憶體任務;
  • io_thd_1、io_thd_2、io_thd_3:三個 I/O 執行緒,io-threads 預設是 4 ,所以會啟動 3(4-1)個 I/O 多執行緒,用來分擔 Redis 網路 I/O 的壓力。

本文轉載自:https://xiaolincoding.com/redis/base/redis_interview.html#redis-%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B

相關文章