Redis 6.0多線程探秘之一
本文轉載自微信公眾號「后端技術指南針」,作者大白 。轉載本文請聯系后端技術指南針公眾號。
1.老牌八股面試題
想必我們經常聽到一個爛大街面試題:
Redis為什么選擇單線程?
這種問法其實并不嚴謹,為啥這么說呢:
- Redis的版本很多3.x、4.x、6.x,版本不同架構也是不同的,不限定版本問這種問題,是不是有點耍流氓。
- 限定版本之后 比如4.x,嚴格意義來說Redis也不是單線程,而是負責處理客戶端請求的線程是單線程。
- 最新版本的6.0版本,告別了大家印象中的單線程,用一種全新的多線程來解決問題。
你要是這么一回答,面試官估計都會想:
啊呀,碰到行家了,反正這個問題我也不太清楚,好好聽下他咋解決吧!
Redis的版本迭代和里程碑
Redis從發布至今,已經有十余年的時光了,一直遵循著自己的命名規則:
- 版本號第二位如果是奇數,則為非穩定版本 如2.7、2.9、3.1
- 版本號第二位如果是偶數,則為穩定版本 如2.6、2.8、3.0、3.2
- 當前奇數版本就是下一個穩定版本的開發版本,如2.9版本是3.0版本的開發版本
我們在生產環境一般都會選擇穩定版本來部署,在每個大版本之間還會有若干個小版本,目前最新的版本是6.2.2。
我們可以通過redis.io官網來下載自己感興趣的版本進行源碼閱讀:
歷史發布版本的源碼:https://download.redis.io/releases/
其中有幾個里程碑式的版本,需要我們了解下:
5.0版本是直接升級到6.0版本,對于這個激進的升級,antirez表現得很有信心和興奮,所以第一時間發文來闡述6.0的一些重大功能"Redis 6.0.0 GA is out!":
http://antirez.com/news/132
注:GA是Generally Available的縮寫,意思是開發團隊認為該版本是穩定版。
扯了這么多,就是希望大家清楚一點,Redis是與時俱進的,千萬不要以為Redis就是一直是那個單線程。
2.不一樣的Redis之父
我們常說字如其人,對于我們程序員來說,碼如其人,也是十分貼切。
從多個歷史版本中我們隱約可以感覺到Redis之父Antirez是個很特別的人。
大白你這說的不是廢話嘛,畢竟是頂流扛把子程序員,怎么會輕易隨波逐流。
還是舉個實際的例子感受一樣,什么叫總能搞點不一樣的。
集群方案
Redis單機版出來之后,官方集群版遲遲沒有發布,這個時候業界就出了一些集群方案,比較有代表性的是codis和Tweproxy,這兩種方案都是中心化的方案以及中間層的思想。
由于市場的需求旺盛,這兩種方案很快被很多公司應用到生產環境,然而官方集群方案卻遲遲沒有發布,這一等就是4年,直到2015年4月1號才發布。
同樣地Antirez還是激動地發了一篇文章"Redis cluster, no longer vaporware."
http://antirez.com/news/79
備注:標題可以譯為Redis集群不再是幻想。
在官方集群方案中采用了P2P模式去中心化的思想、借助slot來實現一致性哈希、以及gossip協議來實現集群通信,整體架構更加簡潔。
3.Redis 6.0多線程的神秘面紗
Redis作為內存型NoSQL可以說是高性能的代名詞,生產環境中數萬QPS都是家常便飯。
試想一下,Redis如何進一步來提高性能呢?這恐怕也是Redis之父苦苦思索的問題。
擒賊先擒王,要提高性能,就要看看是什么卡脖子了。
Redis的瓶頸是什么
通常來說多線程對于提高CPU利用率有重要作用,但是Redis對于提高CPU利用率并不感冒,在Redis看來如果要提高CPU利用率,那在一臺機器部署多個實例就好了。
其實想想Redis之所以那么青睞單線程,肯定是嘗到了單線程的甜頭:
- 業務模型簡單,并發讀寫沒問題
- 單線程完全無鎖化 不死鎖無線程切換損耗,性能賊好
- 處理底層復雜的數據結構時有線程安全做保證,十分放心
- ......
其實在Redis 4.0就引入了多個線程來實現數據的異步刪除等功能,但是其處理讀寫請求的仍然只有一個線程,所以仍然算是狹義上的單線程。
拋開CPU之后,影響Redis性能的地方主要就剩下:內存和網絡IO。
內存更多屬于硬件范疇的東西,比如我們用容量更大、吞吐率更高的內存介質來進行優化,因此對于Redis來說可以優化的空間有限。
最后Redis的瓶頸可以初步定為:網絡IO。
Redis的基本架構
在優化網絡IO之前,我們有必要回顧下Redis單線程整體架構:
Redis采用Reactor模式的網絡模型,對于一個客戶端請求,主線程負責一個完整的處理過程:
從socket中讀取數據和往socket寫數據都是比較耗時的網絡IO操作,解析請求和內存交互耗時可能遠小于IO操作。
對于這種問題,我們常見的解決方法是標準的多線程化:
該方案中工作線程的功能是一樣的,MemCached就是采用這種方案,具體的流程:
Memcached采用 master-woker 模式進行工作,主線程采用 Libevent 監聽和處理事件,主線程將連接請求封裝為任務放到隊列中,根據算法選擇空閑的工作線程,相應的工作線程處理完成后通過soeket與客戶端進行數據交互。
但是Redis 6.0的多線程并沒有這么做。
Redis自己的多線程
單線程給Redis帶來的好處,或許更大。
另外一點如果做成標準化的多線程,對于Redis來說可能更不好處理,因為多線程帶來的線程安全問題和底層復雜的數據結構操作都十分棘手。
Redis 6.0將處理過程中最耗時的Socket的讀取、請求解析、寫入單獨外包出去,剩下的命令執行仍然由單線程來完成和內存的數據交互。
這樣一來,網絡IO操作就變成多線程化了,其他核心部分仍然是線程安全的,確實是個不錯的折中辦法。
畫外音:Redis 6.0 將網絡數據讀寫、請求協議解析通過多個IO線程的來處理 ,對于真正的命令執行來說,仍然使用主線程操作,真是個很特別的多線程啊!
4.小結
本文最多算個入門篇,關于Redis多線程的更大細節,我們下期再搞。