文章目錄

  • 一、Redis 是否為單線程?
  • Redis 的線程組成
  • 二、為什麼採用單線程處理命令
  • 2.1 單線程的侷限
  • 2.2 任務類型與優化策略
  • 2.2.1 IO 密集型任務
  • 2.2.2 CPU 密集型任務
  • 2.3 為什麼不直接用多線程來處理命令?
  • 三、Redis 的 IO 多線程工作原理
  • 3.1 Reactor 模型流程
  • 3.2 為什麼需 IO 多線程
  • 3.3 多線程下的執行方式
  • 3.4 多線程啓用

否為單線程?就是一、Redis

Redis 通常被描述為“單線程”的數據庫,但這種説法並不完全準確。Redis 的命令處理(核心業務邏輯)是單線程的,但它在運行時還會配合多個後台線程輔助線程:就是執行一些耗時或異步任務。因此,更準確的説法Redis 的核心命令處理是單線程的,但整體運行機制是多線程協作的。

Redis 的線程組成

Redis 的 redis-server 線程是核心業務線程,核心負責兩項關鍵工作:一是命令處理,即接收、解析並執行客户端發送的命令;二是網絡事件監聽,實時監測客户端與 Redis 之間的網絡連接狀態及數據傳輸情況。

Redis 系列(七):Redis 線程模型原理_客户端

  • 主線程(redis-server):負責命令解析、執行以及網絡事件監聽我們説的“單線程命令處理”。就是,也就
  • 後台異步線程(BIO)
  • bio_close_file:負責異步關閉文件(例如 RDB 或 AOF 文件),避免主線程等待 IO。
  • bio_aof_fsync:負責異步執行 AOF 刷盤操作。
  • bio_lazy_free:異步釋放大塊內存,防止阻塞主線程。
  • IO 多線程(io_thd_xxx):用於在高併發下並行處理網絡信息的讀寫,減輕主線程的 IO 壓力。
  • jemalloc 背景線程jemalloc_bg_thd 是 Redis 默認使用的內存分配器的後台清理線程,用於優化內存碎片。

二、為什麼採用單線程處理命令

2.1 單線程的侷限

單線程的核心問題是無法承載耗時操作,一旦執行耗時操作,會直接阻塞整個 Redis 服務,嚴重降低其響應性能。對 Redis 而言,需規避的耗時操控主要有兩類:

  • 耗時的CPU 運算:例如複雜的數學計算、對大規模數據的遍歷操作等,這類操作會長時間佔用 CPU 資源,導致核心線程無法處理其他命令;
  • 阻塞的IO 操作:例如同步的磁盤寫入(數據直接寫入磁盤且需等待寫入做完)、同步的網絡讀寫(等待網絡數據傳輸完成後再執行後續操作),這類操作會讓核心線程處於等待狀態,無法高效處理命令。

2.2 任務類型與優化策略

Redis 的任務負載既含有大量IO 操作(磁盤 IO、網絡 IO),也涉及少量CPU 運算。Redis 針對這兩類任務採用了不同的優化方式。

2.2.1 IO 密集型任務

異步化 + 多路複用 + 多線程輔助

磁盤 IO 的優化

:就是Redis 需要將內存數據持久化到磁盤(RDB 快照或 AOF 日誌),但這些操作耗時很長。 Redis 的策略

  • RDB 持久化:通過 fork 創建子進程,讓子進程執行 RDB 快照生成工作,即把內存中的數據完整備份到磁盤,而主進程繼續處理命令;
  • AOF 刷盤異步化:對於 AOF 持久化(記錄每一條寫命令到檔案),Redis 採用異步刷盤方式,由bio_aof_fsync後台線程負責將 AOF 緩衝區的命令寫入磁盤,核心線程只需將命令寫入緩衝區即可,無需等待磁盤寫入結束。

網絡 IO 的優化

Redis 是典型的網絡服務器,需同時處理大量客户端的命令請求,當數據請求或返回數據量較大時,網絡 IO 壓力會顯著增加。它採用了高效的Reactor 模型(IO 多路複用)

  • 通過 epollselect 等系統調用,Redis 能在單線程中監聽多個客户端的 IO 狀態。不應該為每個客户端分配一個獨立線程或進程,極大減少了系統開銷。
  • 一旦某個 IO 就緒,就通過回調機制立即處理信息;
  • 在極端高併發場景下,Redis 會開啓 IO 多線程(io_thd_xxx)專門處理這些耗時的 IO 操作,讓多個線程並行處理讀寫請求,減少核心線程的阻塞。

2.2.2 CPU 密集型任務

Redis 雖存在 CPU 運算場景,但通過 “算法優化、數據結構適配”,讓單線程也能高效處理 CPU 密集任務,無需依賴多線程:

(1)分治方式優化

對於艱難的 CPU 運算任務,Redis 採用 “分而治之” 的思路,將大問題拆分為多個獨立的小問題,單線程逐個解決小問題,避免因一次性處理大難題導致的 CPU 長時間佔用。

(2)數據結構動態切換

Redis 的五大根本數據結構(string、list、hash、set、zset)均提供多種實現方案,會根據數據量大小、資料存儲類型自動選擇最高效的結構,減少 CPU 運算耗時。例如 list 結構,當數據量較小時採用壓縮列表(ziplist),數據量較大時自動切換為雙向鏈表(linkedlist),兩種結構分別適配不同數據規模,確保 CPU 處理效率。

(3)漸進式數據遷移

對於大的耗時任務(如大 key 遷移、數據結構重構),Redis 將其拆分為多個耗時短的小任務,單線程分階段、分批次執行,每次僅處理一個小任務,處理達成後先返回處理其他命令,後續再繼續執行下一個小任務,避免單次管理耗時過久阻塞核心線程。

2.3 為什麼不直接用多線程來處理命令?

如果用多線程來處理命令,會帶來一系列問題,反而可能抵消多線程的性能優勢:

1. 加鎖複雜度高

  • Redis 的核心數據結構並非獨立隔離的對象,而是高度共享的全局狀態;
  • 如果多個線程同時運行這些結構,必須加鎖保護;
  • 鎖的粒度很難控制,容易引發死鎖、性能抖動或鎖競爭;
  • 反而破壞了 Redis 的輕量特性。

2. 上下文切換開銷大

  • 多線程需要頻繁保存和恢復執行狀態(上下文切換),這本身會消耗 CPU;
  • 若是管理粒度較小,此種切換成本甚至高於並行帶來的收益。

三、Redis 的 IO 多線程工作原理

Redis 的事件處理基於Reactor 模型,通過 epoll 等 IO 多路複用機制,讓少量線程同時監聽多個 socket 的 IO 狀態;當某個 socket 可讀 / 可寫時,就把事件分發給對應邏輯處理,不需要為每個連接單獨分配線程。Redis 基於該模型,引入了 IO 多線程機制,在保持“命令執行邏輯仍為單線程”的同時,讓read / write / encode / decode這些耗時的 IO 運行能夠被多個線程並行執行,從而提升整體吞吐性能。

3.1 Reactor 模型流程

Redis 系列(七):Redis 線程模型原理_Redis_02

Redis 的網絡 IO 處理流程如下:

  1. Acceptor 階段
    當客户端發起連接時,acceptor 負責建立連接,並將新連接交給 Reactor 管理。
  2. Reactor 主循環
    Reactor(主線程)依據 IO 多路複用同時監聽多個客户端的 socket:
  • 如果檢測到可讀事件,説明客户端發送了數據;
  • 如果檢測到可寫事件通過,説明能夠往客户端發送響應。
  1. Dispatch 分發階段
    Reactor 將這些已就緒的事件派發(dispatch)給合適的處理流程。
    對於 Redis 而言,就是按照順序執行:
  • read:讀取客户端請求數據;
  • decode:解析 Redis 協議(RESP);
  • compute:執行命令(由主線程完成);
  • encode:對執行結果進行協議封裝;
  • send:將結果發送給客户端。

每個客户端請求經過統一的 Reactor 調度後,依次結束讀、解碼、計算、編碼和發送。

3.2 為什麼應該 IO 多線程

在傳統單線程模型中,Redis會對客户端請求進行完全串行處理:先讀取第一個客户端的請求,接着依次執行協議解析、命令執行、結果編碼,最後發送響應,之後才會處理下一個客户端。當多個客户端幾乎同時發起請求時,read(讀取請求數據)、send(發送響應)等IO操作只能排隊串行執行,很容易因IO耗時成為性能瓶頸,限制整體處理效率。

尤其是在以下兩種情況下,單線程 IO 的效率非常低:

  1. 客户端發送大量數據(寫入大數據)
  • Redis 得從 socket 中拷貝大量數據到用户態;
  • 協議解析(decode)需要 CPU;
  • 整個讀過程是耗時且阻塞的。
  1. 客户端讀取大量數據(大 key 讀取)
  • Redis 需要將大量資料從用户態拷貝到內核;
  • 編碼(encode)和發送(send)過程同樣是 CPU 密集 + IO 密集;
  • 單線程逐一發送時,其他客户端會被阻塞。

因此,Redis 在 6.0 之後引入 IO 多線程機制,讓多個線程並行執行這些read / encode / send階段,極大地提升了帶寬利用率和併發性能。

3.3 多線程下的執行方式

Redis 系列(七):Redis 線程模型原理_客户端_03

假設現在有三個客户端同時向 Redis 發送命令請求。

在默認情況下,Redis 是單線程執行的。 主線程依次處理每個客户端的請求,流程大致是:read → decode → compute → encode → send。也就是説,Redis 先從第一個客户端讀取命令並解析,然後執行命令邏輯、生成結果並返回響應; 接着再處理第二個客户端、第三個客户端。這意味着在任意時刻,只有一個請求在被完整處理,其餘請求都在等待。

開啓 IO 多線程後,Redis 把read、decode、encode部分並行化,而命令執行compute仍由主線程串行完成。

具體過程是這樣的:

  1. 主線程收集請求
    發現三個客户端的 socket 可讀後,將它們放入全局隊列。
  2. 分發任務
    主線程把這三個請求分配給不同的 IO 線程隊列,比如:3 個客户端的請求分別被放入 io_threads_list[0]、io_threads_list[1]、io_threads_list[2]。
  3. 多線程並行讀取
    各個 IO 線程(包括主線程自身)並行執行 read + decode,從網絡中讀取數據並解析命令。解析搞定後,每個線程將命令對象交回主線程等待執行。
  4. 主線程執行命令
    所有請求解析完後,主線程統一串行執行三條命令邏輯,保證命令執行順序和一致性。
  5. 多線程並行發送響應
    命令執行完後,主線程再將結果分發給 IO 線程,IO 線程並行執行 encode + send,將響應寫回客户端。
  6. 主線程等待同步
    等所有線程發送完結果後,主線程進入下一輪事件循環。

Redis 系列(七):Redis 線程模型原理_Redis_04


圖中結構説明:

  • 上方的 server.clients_pending_*表示主線程收集到的所有“待處理客户端請求”,是一個全局任務隊列
  • 下方的多個 io_threads_list[i]表示多個 I/O 線程,每個線程都有自己獨立的任務隊列。
  • 左邊的 io_threads_list[0]特殊:對應主線程。

每個線程(包括主線程)都擁有一個獨立的任務隊列,主線程負責分發任務同步等待

3.4 多線程啓用

Redis 默認不開啓 IO 多線程。只有在滿足以下條件時才有意義:

  • 多個客户端同時活躍連接;
  • 網絡 IO 成為性能瓶頸;
  • redis.conf 配置文件中顯式啓用:
io-threads-do-reads yes
io-threads 4

由於 IO 階段僅涉及材料的讀取與發送(不修改共享數據結構),因此不必須額外的加鎖邏輯。 主線程在執行命令時,所有 IO 線程都處於等待狀態,避免了競爭條件。