深入理解Redis線程模型的原理及使用
1 redis是單線程還是多線程
總的來說:redis是客戶端多線程,服務(wù)端要分版本,redis4.X以前:單線程,之后的版本核心線程是單線程,其他也使用的多線程。
客戶端
Redis為了實現(xiàn)服務(wù)端與更多的客戶端進行連接,使用多線程來維護與客戶端的socket連接。在redis.conf中的參數(shù)“maxclients” 就是設(shè)置最大連接客戶端連接數(shù)的

服務(wù)端
在服務(wù)端,redis響應(yīng)網(wǎng)絡(luò)I/O和鍵值對讀寫的請求,是由一個單獨的主線程來完成,Redis基于epoll實現(xiàn)了IO多路復(fù)用,用來滿足一個主線程可以同時響應(yīng)多個客戶端的socket連接的請求。

Redis將客戶端多個并發(fā)的請求轉(zhuǎn)成了串行的執(zhí)行方式,這種串行化的線程模型,不僅規(guī)避了MySQL的臟讀、幻讀、不可重復(fù)讀之類的并發(fā)問題, 而且加上Redis基于內(nèi)存工作的極高性能,也讓Redis成為很多并發(fā)問題的解決工具。
Redis4.X以前的版本,都是采用的純單線程。之后的版本,加入了多線程,做了優(yōu)化,核心的任務(wù)還是單線程執(zhí)行,對于一些費時的,比如持久化RDB,AOF文件、unlink異步刪除、集群數(shù)據(jù)同步等,都是由額外的線程執(zhí)行的。對于 FLUSHALL操作,也提供了異步的方式。

問:為什么CPU早就多核了,Redis的核心線程模型卻用單線呢?
答:Redis一直保持核心線程的單線程模型,其實是因為對于現(xiàn)代的Redis來說,CPU通常不會成為Redis的性能瓶頸。影響Redis的性能瓶頸大部分是內(nèi)存和網(wǎng)絡(luò)。因此,核心線程改為多線程的要求并不急切。另外,Redis的這種單線程為主的工作機制還可以減少線程上下文切換的性能消耗。而且,如果Redis將核心線程改為多線程并發(fā)執(zhí)行,那么就必然帶來資源競爭,反而會極大增加Redis的業(yè)務(wù)復(fù)雜性,影響Redis的業(yè)務(wù)執(zhí)行效率。
2 Redis如何保證指令原子性
問題:
Redis是支持同時連接多個客戶端,如果多個客戶端同時進行讀寫請求,由于核心的讀寫鍵值的操作,Redis是單線程處理的,那多個請求同時過來就會排隊,排隊的順序就可能會亂,針對單個客戶端,Redis并沒有類似MySQL的事務(wù)那樣保證同一個客戶端的操作原子性。那redis是如何保證指令原子性的呢?
2.1 Redis指令原子性的實現(xiàn)機制
2.1.1 復(fù)合指令
Redis內(nèi)部提供了很多復(fù)合指令,一個指令可以執(zhí)行多個操作, 比如 MSET(HMSET)、GETSET、SETNX、SETEX。這些復(fù)合指令都能很好的保持原子性。
2.1.2 Redis事務(wù)
Redis事務(wù)通過MULTI、EXEC、DISCARD和WATCH命令實現(xiàn),允許將多個操作打包為一個原子單元執(zhí)行。事務(wù)中的所有命令會按順序執(zhí)行,且不會被其他客戶端命令打斷。
# 丟棄事務(wù) DISCARD (null) # 執(zhí)行事務(wù)中的所有命令。 EXEC (null) # 開啟事務(wù) MULTI (null) # 去掉監(jiān)聽 UNWATCH (null) # 監(jiān)聽某一個key的變化。key有變化后,就執(zhí)行當前事務(wù) WATCH key [key ...]
事務(wù)的執(zhí)行流程
- 開啟事務(wù):使用
MULTI命令標記事務(wù)開始,后續(xù)命令會進入隊列而非立即執(zhí)行。 - 命令入隊:輸入操作命令(如
SET、GET、INCR等),這些命令會按順序存入隊列。 - 執(zhí)行或放棄:
EXEC:執(zhí)行隊列中的所有命令,返回各命令的結(jié)果。DISCARD:取消事務(wù),清空命令隊列。
事務(wù)的原子性特點
- 非嚴格原子性:Redis事務(wù)僅保證命令順序執(zhí)行,但單條命令失敗不會回滾(與關(guān)系型數(shù)據(jù)庫不同)。例如語法錯誤會導(dǎo)致整個事務(wù)失敗,而運行時錯誤(如對字符串執(zhí)行
INCR)僅影響當前命令。 - 無隔離級別:事務(wù)執(zhí)行期間,其他客戶端命令不會插入,但事務(wù)內(nèi)操作的結(jié)果在
EXEC前不可見。
WATCH命令與樂觀鎖WATCH用于監(jiān)控一個或多個鍵,若這些鍵在EXEC前被其他客戶端修改,則事務(wù)會失?。ǚ祷?code>nil)。適用于需要檢測數(shù)據(jù)變化的場景。
示例代碼:
WATCH balance MULTI DECRBY balance 50 EXEC # 若balance被其他客戶端修改,此處返回nil
2.1.3 Pipeline
Redis Pipeline 是一種客戶端技術(shù),用于將多個命令一次性發(fā)送到服務(wù)器并批量接收響應(yīng),減少網(wǎng)絡(luò)往返時間(RTT),顯著提升批量操作的性能。適用于需要執(zhí)行大量命令的場景(如批量寫入、讀?。?。
RTT: 當客戶端執(zhí)行一個指令,數(shù)據(jù)包需要通過網(wǎng)絡(luò)從Client傳到Server,然后再從Server返回到Client。這個中間的時間消耗,就稱為RTT(Round Trip Time)。
Pipeline 的核心原理
- 傳統(tǒng)模式:客戶端發(fā)送一個命令后需等待服務(wù)器響應(yīng),再發(fā)送下一個命令,每次命令消耗一個 RTT。
- Pipeline 模式:客戶端將多個命令打包一次性發(fā)送,服務(wù)器按順序執(zhí)行后批量返回結(jié)果,僅消耗一次 RTT。
非原子性:Pipeline 中的命令會被分批發(fā)送到服務(wù)器,但服務(wù)器可能在其他客戶端命令間插入執(zhí)行(需用
MULTI/EXEC實現(xiàn)原子性)。
錯誤處理:某條命令失敗不會影響后續(xù)命令執(zhí)行,需檢查返回結(jié)果中的錯誤信息。
合理批量大小:避免單次 Pipeline 數(shù)據(jù)量過大導(dǎo)致網(wǎng)絡(luò)阻塞或超時(建議分批發(fā)送)。
與事務(wù)(Transaction)的區(qū)別
- Pipeline:批量發(fā)送命令,無原子性保證。
- 事務(wù)(MULTI/EXEC):命令按順序原子性執(zhí)行,但會消耗更多 RTT(需配合 Pipeline 優(yōu)化)。
適用場景
- 批量寫入數(shù)據(jù)(如日志入庫)。
- 批量讀取多個鍵值(減少網(wǎng)絡(luò)延遲)。
- 高頻命令需低延遲(如計數(shù)器遞增)。
2.1.4 lua腳本
Lua是一種輕量級、高效的腳本語言,比如參數(shù)類型、作用域、函數(shù)等,設(shè)計初衷為嵌入其他應(yīng)用程序中擴展功能。
Redis中Lua腳本的作用
Redis從2.6版本開始支持Lua腳本,主要解決以下問題:
- 原子性操作:腳本內(nèi)的多條Redis命令作為一個整體執(zhí)行,避免競態(tài)條件。
- 減少網(wǎng)絡(luò)開銷:合并多個操作為單個腳本,降低客戶端與Redis的通信次數(shù)。
- 復(fù)雜邏輯封裝:實現(xiàn)如分布式鎖、限流等需多命令組合的功能。
Lua腳本在Redis中的基本用法
- 腳本執(zhí)行命令
通過EVAL直接執(zhí)行腳本,或使用SCRIPT LOAD預(yù)加載后以EVALSHA調(diào)用:
-- 直接執(zhí)行示例
EVAL "return redis.call('GET', KEYS[1])" 1 mykey
-- 預(yù)加載腳本
SCRIPT LOAD "return redis.call('SET', KEYS[1], ARGV[1])"
EVALSHA "sha1哈希值" 1 key value
參數(shù)傳遞規(guī)則
- KEYS:鍵名列表,用于標識Redis中操作的數(shù)據(jù)。
- ARGV:額外參數(shù)列表,傳遞非鍵名數(shù)據(jù)。
- 必須明確指定KEYS的數(shù)量(如
EVAL script 2 key1 key2 arg1)。
腳本特性
- 原子性:腳本執(zhí)行期間其他命令會被阻塞。
- 緩存機制:Redis緩存腳本的SHA1哈希,避免重復(fù)傳輸。
- 調(diào)試支持:通過
redis-cli --ldb可進行腳本調(diào)試。
應(yīng)用場景
分布式鎖 ,以下腳本實現(xiàn)鎖的獲取與釋放:
-- 獲取鎖(SET if Not eXists)
local ok = redis.call('SETNX', KEYS[1], ARGV[1])
if ok == 1 then
redis.call('EXPIRE', KEYS[1], ARGV[2])
end
return ok
-- 釋放鎖(驗證值匹配)
if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('DEL', KEYS[1])
else
return 0
end
到此這篇關(guān)于深入理解Redis線程模型的原理及使用的文章就介紹到這了,更多相關(guān)Redis線程模型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis?存儲對象信息用?Hash?和String的區(qū)別
這篇文章主要介紹了Redis存儲對象信息用Hash和String的區(qū)別,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-09-09
redis的list數(shù)據(jù)類型相關(guān)命令介紹及使用
本文主要介紹了redis的list數(shù)據(jù)類型相關(guān)命令介紹及使用,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01
python腳本實現(xiàn)Redis未授權(quán)批量提權(quán)
這篇文章主要給大家介紹了關(guān)于利用python腳本實現(xiàn)redis未授權(quán)批量提權(quán)的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起學(xué)習學(xué)習吧。2017-09-09
Redisson實現(xiàn)Redis分布式鎖的幾種方式
本文在講解如何使用Redisson實現(xiàn)Redis普通分布式鎖,以及Redlock算法分布式鎖的幾種方式的同時,也附帶解答這些同學(xué)的一些疑問,感興趣的可以了解一下2021-08-08

