39 _ Redis 6.0的新特性:多線程、客戶端緩存與安全


你好,我是蔣德鈞。

Redis官方在今年5月份正式推出了6.0版本,這個版本中有很多的新特性。所以,6.0剛剛推出,就受到了業界的廣泛關注。

所以,在課程的最后,我特意安排了這節課,想來和你聊聊Redis 6.0中的幾個關鍵新特性,分別是面向網絡處理的多IO線程、客戶端緩存、細粒度的權限控制,以及RESP 3協議的使用。

其中,面向網絡處理的多IO線程可以提高網絡請求處理的速度,而客戶端緩存可以讓應用直接在客戶端本地讀取數據,這兩個特性可以提升Redis的性能。除此之外,細粒度權限控制讓Redis可以按照命令粒度控制不同用戶的訪問權限,加強了Redis的安全保護。RESP 3協議則增強客戶端的功能,可以讓應用更加方便地使用Redis的不同數據類型。

只有詳細掌握了這些特性的原理,你才能更好地判斷是否使用6.0版本。如果你已經在使用6.0了,也可以看看怎么才能用得更好,少踩坑。

首先,我們來了解下6.0版本中新出的多線程特性。

從單線程處理網絡請求到多線程處理

在Redis 6.0中,非常受關注的第一個新特性就是多線程。這是因為,Redis一直被大家熟知的就是它的單線程架構,雖然有些命令操作可以用后台線程或子進程執行(比如數據刪除、快照生成、AOF重寫),但是,從網絡IO處理到實際的讀寫命令處理,都是由單個線程完成的。

隨着網絡硬件的性能提升,Redis的性能瓶頸有時會出現在網絡IO的處理上,也就是說,單個主線程處理網絡請求的速度跟不上底層網絡硬件的速度

為了應對這個問題,一般有兩種方法。

第一種方法是,用用戶態網絡協議棧(例如DPDK)取代內核網絡協議棧,讓網絡請求的處理不用在內核里執行,直接在用戶態完成處理就行。

對於高性能的Redis來說,避免頻繁讓內核進行網絡請求處理,可以很好地提升請求處理效率。但是,這個方法要求在Redis的整體架構中,添加對用戶態網絡協議棧的支持,需要修改Redis源碼中和網絡相關的部分(例如修改所有的網絡收發請求函數),這會帶來很多開發工作量。而且新增代碼還可能引入新Bug,導致系統不穩定。所以,Redis 6.0中並沒有采用這個方法。

第二種方法就是采用多個IO線程來處理網絡請求,提高網絡請求處理的並行度。Redis 6.0就是采用的這種方法。

但是,Redis的多IO線程只是用來處理網絡請求的,對於讀寫命令,Redis仍然使用單線程來處理。這是因為,Redis處理請求時,網絡處理經常是瓶頸,通過多個IO線程並行處理網絡操作,可以提升實例的整體處理性能。而繼續使用單線程執行命令操作,就不用為了保證Lua腳本、事務的原子性,額外開發多線程互斥機制了。這樣一來,Redis線程模型實現就簡單了。

我們來看下,在Redis 6.0中,主線程和IO線程具體是怎么協作完成請求處理的。掌握了具體原理,你才能真正地會用多線程。為了方便你理解,我們可以把主線程和多IO線程的協作分成四個階段。

階段一:服務端和客戶端建立Socket連接,並分配處理線程

首先,主線程負責接收建立連接請求。當有客戶端請求和實例建立Socket連接時,主線程會創建和客戶端的連接,並把 Socket 放入全局等待隊列中。緊接着,主線程通過輪詢方法把Socket連接分配給IO線程。

階段二:IO線程讀取並解析請求

主線程一旦把Socket分配給IO線程,就會進入阻塞狀態,等待IO線程完成客戶端請求讀取和解析。因為有多個IO線程在並行處理,所以,這個過程很快就可以完成。

階段三:主線程執行請求操作

等到IO線程解析完請求,主線程還是會以單線程的方式執行這些命令操作。下面這張圖顯示了剛才介紹的這三個階段,你可以看下,加深理解。

階段四:IO線程回寫Socket和主線程清空全局隊列

當主線程執行完請求操作后,會把需要返回的結果寫入緩沖區,然后,主線程會阻塞等待IO線程把這些結果回寫到Socket中,並返回給客戶端。

和IO線程讀取和解析請求一樣,IO線程回寫Socket時,也是有多個線程在並發執行,所以回寫Socket的速度也很快。等到IO線程回寫Socket完畢,主線程會清空全局隊列,等待客戶端的后續請求。

我也畫了一張圖,展示了這個階段主線程和IO線程的操作,你可以看下。

了解了Redis主線程和多線程的協作方式,我們該怎么啟用多線程呢?在Redis 6.0中,多線程機制默認是關閉的,如果需要使用多線程功能,需要在redis.conf中完成兩個設置。

1.設置io-thread-do-reads配置項為yes,表示啟用多線程。

io-threads-do-reads yes

2.設置線程個數。一般來說,線程個數要小於Redis實例所在機器的CPU核個數,例如,對於一個8核的機器來說,Redis官方建議配置6個IO線程。

io-threads  6

如果你在實際應用中,發現Redis實例的CPU開銷不大,吞吐量卻沒有提升,可以考慮使用Redis 6.0的多線程機制,加速網絡處理,進而提升實例的吞吐量。

實現服務端協助的客戶端緩存

和之前的版本相比,Redis 6.0新增了一個重要的特性,就是實現了服務端協助的客戶端緩存功能,也稱為跟蹤(Tracking)功能。有了這個功能,業務應用中的Redis客戶端就可以把讀取的數據緩存在業務應用本地了,應用就可以直接在本地快速讀取數據了。

不過,當把數據緩存在客戶端本地時,我們會面臨一個問題:如果數據被修改了或是失效了,如何通知客戶端對緩存的數據做失效處理?

6.0實現的Tracking功能實現了兩種模式,來解決這個問題。

第一種模式是普通模式。在這個模式下,實例會在服務端記錄客戶端讀取過的key,並監測key是否有修改。一旦key的值發生變化,服務端會給客戶端發送invalidate消息,通知客戶端緩存失效了。

在使用普通模式時,有一點你需要注意一下,服務端對於記錄的key只會報告一次invalidate消息,也就是說,服務端在給客戶端發送過一次invalidate消息后,如果key再被修改,此時,服務端就不會再次給客戶端發送invalidate消息。

只有當客戶端再次執行讀命令時,服務端才會再次監測被讀取的key,並在key修改時發送invalidate消息。這樣設計的考慮是節省有限的內存空間。畢竟,如果客戶端不再訪問這個key了,而服務端仍然記錄key的修改情況,就會浪費內存資源。

我們可以通過執行下面的命令,打開或關閉普通模式下的Tracking功能。

CLIENT TRACKING ON|OFF

第二種模式是廣播模式。在這個模式下,服務端會給客戶端廣播所有key的失效情況,不過,這樣做了之后,如果key 被頻繁修改,服務端會發送大量的失效廣播消息,這就會消耗大量的網絡帶寬資源。

所以,在實際應用時,我們會讓客戶端注冊希望跟蹤的key的前綴,當帶有注冊前綴的key被修改時,服務端會把失效消息廣播給所有注冊的客戶端。和普通模式不同,在廣播模式下,即使客戶端還沒有讀取過key,但只要它注冊了要跟蹤的key,服務端都會把key失效消息通知給這個客戶端

我給你舉個例子,帶你看一下客戶端如何使用廣播模式接收key失效消息。當我們在客戶端執行下面的命令后,如果服務端更新了user:id:1003這個key,那么,客戶端就會收到invalidate消息。

CLIENT TRACKING ON BCAST PREFIX user

這種監測帶有前綴的key的廣播模式,和我們對key的命名規范非常匹配。我們在實際應用時,會給同一業務下的key設置相同的業務名前綴,所以,我們就可以非常方便地使用廣播模式。

不過,剛才介紹的普通模式和廣播模式,需要客戶端使用RESP 3協議,RESP 3協議是6.0新啟用的通信協議,一會兒我會給你具體介紹。

對於使用RESP 2協議的客戶端來說,就需要使用另一種模式,也就是重定向模式(redirect)。在重定向模式下,想要獲得失效消息通知的客戶端,就需要執行訂閱命令SUBSCRIBE,專門訂閱用於發送失效消息的頻道_redis_:invalidate。同時,再使用另外一個客戶端,執行CLIENT TRACKING命令,設置服務端將失效消息轉發給使用RESP 2協議的客戶端。

我再給你舉個例子,帶你了解下如何讓使用RESP 2協議的客戶端也能接受失效消息。假設客戶端B想要獲取失效消息,但是客戶端B只支持RESP 2協議,客戶端A支持RESP 3協議。我們可以分別在客戶端B和A上執行SUBSCRIBE和CLIENT TRACKING,如下所示:

//客戶端B執行,客戶端B的ID號是303
SUBSCRIBE _redis_:invalidate

//客戶端A執行
CLIENT TRACKING ON BCAST REDIRECT 303

這樣設置以后,如果有鍵值對被修改了,客戶端B就可以通過_redis_:invalidate頻道,獲得失效消息了。

好了,了解了6.0 版本中的客戶端緩存特性后,我們再來了解下第三個關鍵特性,也就是實例的訪問權限控制列表功能(Access Control List,ACL),這個特性可以有效地提升Redis的使用安全性。

從簡單的基於密碼訪問到細粒度的權限控制

在Redis 6.0 版本之前,要想實現實例的安全訪問,只能通過設置密碼來控制,例如,客戶端連接實例前需要輸入密碼。

此外,對於一些高風險的命令(例如KEYS、FLUSHDB、FLUSHALL等),在Redis 6.0 之前,我們也只能通過rename-command來重新命名這些命令,避免客戶端直接調用。

Redis 6.0 提供了更加細粒度的訪問權限控制,這主要有兩方面的體現。

首先,6.0版本支持創建不同用戶來使用Redis。在6.0版本前,所有客戶端可以使用同一個密碼進行登錄使用,但是沒有用戶的概念,而在6.0中,我們可以使用ACL SETUSER命令創建用戶。例如,我們可以執行下面的命令,創建並啟用一個用戶normaluser,把它的密碼設置為“abc”:

ACL SETUSER normaluser on > abc

另外,6.0版本還支持以用戶為粒度設置命令操作的訪問權限。我把具體操作列在了下表中,你可以看下,其中,加號(+)和減號(-)就分別表示給用戶賦予或撤銷命令的調用權限。

為了便於你理解,我給你舉個例子。假設我們要設置用戶normaluser只能調用Hash類型的命令操作,而不能調用String類型的命令操作,我們可以執行如下命令:

ACL SETUSER normaluser +@hash -@string

除了設置某個命令或某類命令的訪問控制權限,6.0版本還支持以key為粒度設置訪問權限。

具體的做法是使用波浪號“~”和key的前綴來表示控制訪問的key。例如,我們執行下面命令,就可以設置用戶normaluser只能對以“user:”為前綴的key進行命令操作:

ACL SETUSER normaluser ~user:* +@all

好了,到這里,你了解了,Redis 6.0可以設置不同用戶來訪問實例,而且可以基於用戶和key的粒度,設置某個用戶對某些key允許或禁止執行的命令操作。

這樣一來,我們在有多用戶的Redis應用場景下,就可以非常方便和靈活地為不同用戶設置不同級別的命令操作權限了,這對於提供安全的Redis訪問非常有幫助。

啟用RESP 3協議

Redis 6.0實現了RESP 3通信協議,而之前都是使用的RESP 2。在RESP 2中,客戶端和服務器端的通信內容都是以字節數組形式進行編碼的,客戶端需要根據操作的命令或是數據類型自行對傳輸的數據進行解碼,增加了客戶端開發復雜度。

而RESP 3直接支持多種數據類型的區分編碼,包括空值、浮點數、布爾值、有序的字典集合、無序的集合等。

所謂區分編碼,就是指直接通過不同的開頭字符,區分不同的數據類型,這樣一來,客戶端就可以直接通過判斷傳遞消息的開頭字符,來實現數據轉換操作了,提升了客戶端的效率。除此之外,RESP 3協議還可以支持客戶端以普通模式和廣播模式實現客戶端緩存。

小結

這節課,我向你介紹了Redis 6.0的新特性,我把這些新特性總結在了一張表里,你可以再回顧鞏固下。

最后,我也再給你一個小建議:因為Redis 6.0是剛剛推出的,新的功能特性還需要在實際應用中進行部署和驗證,所以,如果你想試用Redis 6.0,可以嘗試先在非核心業務上使用Redis 6.0,一方面可以驗證新特性帶來的性能或功能優勢,另一方面,也可以避免因為新特性不穩定而導致核心業務受到影響。

每課一問

你覺得,Redis 6.0的哪個或哪些新特性會對你有幫助呢?

歡迎在留言區寫下你的思考和答案,我們一起交流討論。如果你覺得今天的內容對你有所幫助,也歡迎你分享給你的朋友或同事。我們下節課見。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM