您好,登錄后才能下訂單哦!
這篇文章的內容主要圍繞怎么解析Redis6中的單線程和多線程模型進行講述,文章內容清晰易懂,條理清晰,非常適合新手學習,值得大家去閱讀。感興趣的朋友可以跟隨小編一起閱讀吧。希望大家通過這篇文章有所收獲!
如果單純的說redis是單線程或多線程,這個回答肯定不嚴謹,不同版本使用的線程模型是不一樣的。【相關推薦:Redis視頻教程】
版本3.x ,最早版本,也就是大家口口相傳的redis是單線程。
版本4.x,嚴格意義來說也不是單線程,而是負責處理客戶端請求的線程是單線程,但是 開始加了點多線程的東西(異步刪除)
。
最新版本的6.0.x后, 告別了大家印象中的單線程,用一種全新的多線程
來解決問題。
2.1 單線程真實含義
主要是指Redis的網絡IO和鍵值對讀寫是由一個線程來完成的,Redis在處理客戶端的請求時包括獲取 (socket 讀)、解析、執行、內容返回 (socket 寫) 等都由一個順序串行的主線程處理,這就是所謂的“單線程”。這也是Redis對外提供鍵值存儲服務的主要流程。
但Redis的其他功能, 比如持久化、異步刪除、集群數據同步等等,其實是由額外的線程
執行的。
可以這么說,Redis工作線程是單線程的。但是,整個Redis來說,是多線程的
;
2.2 單線程性能快原因
Redis 3.x 單線程時代但是性能很快的主要原因
:
基于內存操作:所有數據都存在內存中,因此所有的運算都是內存級別的
數據結構簡單:Redis的數據結構是專門設計的,而這些簡單的數據結構的查找和操作的時間大部分復雜度都是o(1)
多路復用和費阻塞IO:使用IO多路復用功能監聽多個socket連接客戶端,這樣就可以使用一個線程連接處理多個請求,減少線程切換帶來的開銷,同時避免IO阻塞操作
避免上下文切換:因為是單線程模型,就可以避免不必要的上先文切換和多線程競爭,這樣可以省去多線程切換帶來的時間和性能上的消耗,而且單線程不會導致死鎖問題的發生
2.3 采用單線程原因
Redis 是基于內存操作的, 因此他的瓶頸可能是機器的內存或者網絡帶寬而并非 CPU
,既然 CPU 不是瓶頸,那么自然就采用單線程的解決方案了,況且使用多線程比較麻煩。 但是在 Redis 4.0 中開始支持多線程了,例如后臺刪除等功能
。
簡單來說,Redis 4.0 之前一直采用單線程的主要原因有以下三個:
使用單線程模型是 Redis 的開發和維護更簡單,因為單線程模型方便開發和調試;多線程模型雖然在某些方面表現優異,但是它卻引入了程序執行順序的不確定性,帶來了并發讀寫的一系列問題,增加了系統復雜度、同時可能存在線程切換、甚至加鎖解鎖、死鎖造成的性能損耗。Redis通過AE事件模型以及IO多路復用等技術,處理性能非常高,因此沒有必要使用多線程。單線程機制使得 Redis 內部實現的復雜度大大降低,Hash 的惰性 Rehash、Lpush 等等 “線程不安全” 的命令都可以無鎖進行。
即使使用單線程模型也并發的處理多客戶端的請求,主要使用的是多路復用和非阻塞 IO;
對于 Redis 系統來說, 主要的性能瓶頸是內存或者網絡帶寬而并非 CPU
。
3.1 引入多線程原因
既然單線程那么好,為啥又要引入多線程?
單線程也有自己的煩惱,比如大key刪除問題:
正常情況下使用 del 指令可以很快的刪除數據,而當被刪除的 key 是一個非常大的對象時,例如時包含了成千上萬個元素的 hash 集合時,那么 del 指令就會造成 Redis 主線程卡頓。
因此,在 Redis 4.0 中就新增了多線程的模塊,當然此版本中的多線程主要是為了解決刪除數據效率比較低的問題。可以通過惰性刪除有效避免Redis卡頓問題(大key刪除等問題),步驟如下:
unlink key
: 與DEL一樣刪除key功能的lazy free實現,唯一不同是,UNLINK在刪除集合類型鍵時,如果集合鍵的元素個數大于64個,主線程中只是把待刪除鍵從數據庫字典中摘除,會把真正的內存釋放操作,給單獨的bio來操作。如果元素個數較少(少于64個)或者是String類型,也會在主線程中直接刪除。
flushall/flushdb async
: 對于清空數據庫命令flushall/flushdb,添加了async異步清理選項,使得redis在清空數據庫時都是異步操作。實現邏輯是為數據庫新建一個新的空的字典,把原有舊的數據庫字典給后臺線程來逐一刪除其中的數據,釋放內存。
把刪除工作交給了后臺子進程異步刪除數據
因為Redis是單個主線程處理,redis之父antirez一直強調"Lazy Redis is better Redis". 而lazy free
的本質就是把某些cost(主要時間復制度,占用主線程cpu時間片)較高刪除操作
, 從redis主線程剝離讓bio子線程來處理,極大地減少主線阻塞時間。從而減少刪除導致性能和穩定性問題。
Redis 4.0 就引入了多個線程來實現數據的異步惰性刪除等功能,但是其處理讀寫請求的仍然只有一個線程,所以仍然算是狹義上的單線程。
從上一小結分析:Redis的主要性能瓶頸是內存或網絡帶寬而并非CPU。內存問題比較好解決,因此Redis的瓶頸原因為網絡IO。接下來,引入多線程模型。
3.2 多線程工作原理
I/O 的讀和寫本身是堵塞的,比如當 socket 中有數據時,Redis 會通過調用先將數據從內核態空間拷貝到用戶態空間,再交給 Redis 調用,而這個拷貝的過程就是阻塞的,當數據量越大時拷貝所需要的時間就越多,而這些操作都是基于單線程完成的。
在 Redis 6.0 中新增了多線程的功能來提高 I/O 的讀寫性能,他的主要實現思路是將主線程的 IO 讀寫任務拆分給一組獨立的線程去執行,這樣就可以使多個 socket 的讀寫可以并行化了,采用多路 I/O 復用技術可以讓單個線程高效的處理多個連接請求(盡量減少網絡IO的時間消耗),將最耗時的Socket的讀取、請求解析、寫入單獨外包出去,剩下的命令執行仍然由主線程串行執行并和內存的數據交互。
結合上圖可知,將網絡數據讀寫、請求協議解析通過多個IO線程的來處理,對于真正的命令執行來說,仍然使用主線程操作(線程安全),是個不錯的折中辦法。因此,對于整個Redis來說是多線程的,但是對于工作線程(命令執行)仍舊是單線程
。
核心流程大概如下:
流程簡述如下:
主線程獲取 socket 放入等待列表
將 socket 分配給各個 IO 線程(并不會等列表滿)
主線程阻塞等待 IO 線程(多線程)
讀取 socket 完畢
主線程執行命令 - 單線程
(如果命令沒有接收完畢,會等 IO 下次繼續)
主線程阻塞等待 IO 線程(多線程)
將數據回寫 socket 完畢(一次沒寫完,會等下次再寫)
解除綁定,清空等待隊列
特點如下:
IO 線程要么同時在讀 socket,要么同時在寫,不會同時讀或寫
IO 線程只負責讀寫 socket 解析命令,不負責命令處理(主線程串行執行命令)
IO 線程數可自行配置(目前代碼限制上限為 512,默認為 1(關閉此功能))
經過有心人士的壓測,目前性能能提高 1 倍以上。
疑問1:等待列表不滿 一直阻塞不處理嗎?
回復:阻塞時檢測的是,IO 線程是否還有任務。等處理完了才繼續往下。這些任務是在執行時添加的,如果 任務數< 線程數,那有些線程就拿不到任務,它的待處理任務就是 0 。分配了任務的線程,在處理好 IO 事件后,pending 就會清零,沒拿到任務的線程 pending 本來就是 0,所以不會阻塞。 這塊還是有點疑問,哪位大佬可以解釋下(評論哈)?
3.4 默認開啟多線程嗎?
在Redis6.0中, 多線程機制默認是關閉的
,如果需要使用多線程功能,需要在redis.conf中完成兩個設置。
設置io-thread-do-reads配置項為yes,表示啟動多線程。
設置線程個數。關于線程數的設置,官方的建議是如果為 4 核的 CPU,建議線程數設置為 2 或 3, 如果為 8 核 CPU 建議線程數設置為 6
,線程數一定要小于機器核數,線程數并不是越大越好。
Redis自身出道就是優秀,基于內存操作、數據結構簡單、多路復用和非阻塞 I/O、避免了不必要的線程上下文切換等特性,在單線程的環境下依然很快;
但對于大數據的 key 刪除還是卡頓厲害,因此在 Redis 4.0 引入了多線程unlink key/flushall async 等命令,主要用于 Redis 數據的異步刪除;
而在 Redis 6.0 中引入了 I/O 多線程的讀寫,這樣就可以更加高效的處理更多的任務了, Redis 只是將 I/O 讀寫變成了多線程
,而 命令的執行依舊是由主線程串行執行的
,因此在多線程下操作 Redis 不會出現線程安全的問題
。
Redis 無論是當初的單線程設計,還是如今與當初設計相背的多線程,目的只有一個:讓 Redis 變得越來越快。
感謝你的閱讀,相信你對“怎么解析Redis6中的單線程和多線程模型”這一問題有一定的了解,快去動手實踐吧,如果想了解更多相關知識點,可以關注億速云網站!小編會繼續為大家帶來更好的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。