我是 Redis,今年 11 歲了~redis
曾幾什麼時候我是辣麼的單純,辣麼的可愛,而現在我竟背叛了當初「誓言」,決心在多線程這條路上義無反顧的一路狂奔,沒錯我就是大家口中那個既可愛又迷人的 Redis,你能夠叫我小 R...R 😊。安全
一波騷操做結束,咱們開始今天的正文。bash
咱們知道在 Redis 4.0 以後就陸陸續續添加了一些多線程的功能,難道單線程不香了嗎?網絡
Redis 的單線程曾幾什麼時候仍是咱們炫耀的資本,優雅又不失高效的設計,讓無數的追求者爲之着迷。數據結構
你要問我排第幾?Nginx 是我大哥,NodeJS 是我小弟,我在家中排名老二。多線程
咱們兄弟仨可謂單線程的傑出表明,不只演示了咱們的優雅更加展示了咱們的高效。併發
🙋♂️有人可能會問:爲何單線程的我,居然如此囂張?app
家中有礦唄,Redis 單線程但性能依舊很快的主要緣由有如下幾點:異步
來看一下個人父親大大是如何評價個人,Redis 的 FAQ(Frequently Asked Questions,常見問題)回答了單線程的這個問題,具體內容以下:socket
Redis is single threaded. How can I exploit multiple CPU / cores?
It's not very frequent that CPU becomes your bottleneck with Redis, as usually Redis is either memory or network bound. For instance, using pipelining Redis running on an average Linux system can deliver even 1 million requests per second, so if your application mainly uses O(N) or O(log(N)) commands, it is hardly going to use too much CPU.
However, to maximize CPU usage you can start multiple instances of Redis in the same box and treat them as different servers. At some point a single box may not be enough anyway, so if you want to use multiple CPUs you can start thinking of some way to shard earlier.
You can find more information about using multiple Redis instances in the Partitioning page.
However with Redis 4.0 we started to make Redis more threaded. For now this is limited to deleting objects in the background, and to blocking commands implemented via Redis modules. For future releases, the plan is to make Redis more and more threaded.
他的大致意思是說 Redis 是基於內存操做的,所以他的瓶頸多是機器的內存或者網絡帶寬而並不是 CPU,既然 CPU 不是瓶頸,那麼天然就採用單線程的解決方案了,何況使用多線程比較麻煩。可是在 Redis 4.0 中開始支持多線程了,例如後臺刪除等功能。
簡單來講,Redis 4.0 以前一直採用單線程的主要緣由有如下三個:
可是單線程也有單線程的苦惱,好比當我(Redis)須要刪除一個很大的數據時,由於是單線程同步操做,這就會致使 Redis 服務卡頓,因而在 Redis 4.0 中就新增了多線程的模塊,固然此版本中的多線程主要是爲了解決刪除數據效率比較低的問題的,他的相關指令有如下三個:
執行示例以下所示:
> unlink key # 後臺刪除某個 key
> OK # 執行成功
> flushall async # 清空全部數據
> OK # 執行成功
複製代碼
這樣我就能夠把這些壞人「瞬間」拉黑(刪除)了。
所謂的「瞬間」刪除其實有些誇張,只是從返回的結果來看是刪除成功了,可是這只是把刪除工做交給了後臺的小弟(子線程)異步來刪除數據了。
小貼士:正常狀況下使用 del 指令能夠很快的刪除數據,而當被刪除的 key 是一個很是大的對象時,例如時包含了成千上萬個元素的 hash 集合時,那麼 del 指令就會形成 Redis 主線程卡頓,所以使用惰性刪除能夠有效的避免 Redis 卡頓的問題。
以前在 Redis 4.0 中你說刪除比較慢,騙我開大(多線程)來處理也就罷了,爲毛 Redis 6.0 還要多線程嘞?
實際上是這樣的在 Redis 4.0 版本中雖然引入了多線程,但此版本中的多線程只能用於大數據量的異步刪除,然而對於非刪除操做的意義並非很大。
但若是咱們使用咱們在非刪除的環境下使用多線程的話就能夠分攤 Redis 同步讀寫 I/O 的壓力,以及充分的利用多核 CPU 的資源了,這樣就能夠有效的提高 Redis 的 QPS(Query Per Second,每秒查詢率)了。
在 Redis 中雖然使用了 I/O 多路複用,而且是基於非阻塞 I/O 進行操做的,但 I/O 的讀和寫自己是堵塞的,好比當 socket 中有數據時,Redis 會經過調用先將數據從內核態空間拷貝到用戶態空間,再交給 Redis 調用,而這個拷貝的過程就是阻塞的,當數據量越大時拷貝所須要的時間就越多,而這些操做都是基於單線程完成的。
I/O 多路複用,簡單來講就是經過監測文件的讀寫事件,再通知線程執行相關操做,保證 Redis 的非阻塞 I/O 可以順利執行完成的機制。
所以在 Redis 6.0 中新增了多線程的功能來提升 I/O 的讀寫性能,他的主要實現思路是將主線程的 IO 讀寫任務拆分給一組獨立的線程去執行,這樣就可使多個 socket 的讀寫能夠並行化了,但 Redis 的命令依舊是由主線程串行執行的。
須要注意的是 Redis 6.0 默認是禁用多線程的,能夠經過修改 Redis 的配置文件 redis.conf 中的 io-threads-do-reads
等於 true
來開啓多線程,完整配置爲 io-threads-do-reads true
,除此以外咱們還須要設置線程的數量才能正確的開啓多線程的功能,一樣是修改 Redis 的配置,例如設置 io-threads 4
表示開啓 4 個線程。
小貼士:關於線程數的設置,官方的建議是若是爲 4 核的 CPU,建議線程數設置爲 2 或 3,若是爲 8 核 CPU 建議線程數設置爲 6,線程數必定要小於機器核數,線程數並非越大越好。
關於 Redis 的性能,個人父王 antirez(Redis 做者)在 RedisConf 2019 分享時曾提到,Redis 6 引入的多線程 I/O 特性對性能提高至少是一倍以上。國內也有人在阿里雲使用 4 個線程的 Redis 版本和單線程的 Redis 進行比較測試,發現測試的結果和 antirez 給出的結論基本吻合,性能基本能夠提升一倍。
Redis 雖然依靠本身的:基於內存操做、數據結構簡單、多路複用和非阻塞 I/O、避免了沒必要要的線程上下文切換等特性,在單線程的環境下依然很快;但對於大數據的 key 刪除仍是卡的飛起,所以在 Redis 4.0 引入了多線程:unlink key/flushall async 等命令,主要用於 Redis 數據的刪除,而在 Redis 6.0 中引入了 I/O 多線程的讀寫,這樣就能夠更加高效的處理更多的任務****了,Redis 只是將 I/O 讀寫變成了多線程,而命令的執行依舊是由主線程串行執行的,所以在多線程下操做 Redis 不會出現線程安全的問題。
Redis 不管是當初的單線程設計,仍是現在與當初設計相背的多線程,目的只有一個:讓 Redis 變得愈來愈快。
因此 Redis 依舊沒變,他仍是那個曾經的追風少年~
最後的話
原創不易,若是以爲本文對你有用,請隨手點擊一個「贊」,這是對做者最大的支持與鼓勵,謝謝你。
關注公衆號「Java中文社羣」回覆「乾貨」,獲取 50 篇原創乾貨 Top 榜。