1. 程式人生 > >Redis的效能幻想與殘酷現實

Redis的效能幻想與殘酷現實

2011 年,當初選擇 Redis 作為主要的記憶體資料儲存,主要吸引我的是它提供多樣的基礎資料結構可以很方便的實現業務需求。另一方面又比較擔心它的效能是否足以支撐,畢竟當時 Redis 還屬於比較新的開源產品。但 Redis 官網宣稱其是提供多資料結構的高效能儲存,我們對其還是抱有幻想的。

幻想

要了解 Redis 的效能,我們先看看官方的基準效能測試資料,心裡有個底。

  測試前提

  Redis version 2.4.2

  Using the TCP loopback

  Payload size = 256 bytes 

  測試結果

  SET: 198412.69/s

  GET: 198019.80/s

這個資料剛一看覺得有點超出預期了,不過看了測試前提是規避了網路開銷的,Client 和 Server 全在本機。而真實的使用場景肯定是需要走網路的,而且使用的客戶端庫也是不同的。不過這個官方參考資料當時讓我們對 Redis 的效能還是抱有很大的期待的。

另外官方文件中也提到,在區域網環境下只要傳輸的包不超過一個 MTU (乙太網下大約 1500 bytes),那麼對於 10、100、1000 bytes 不同包大小的處理吞吐能力實際結果差不多。關於吞吐量與資料大小的關係可見下面官方網站提供的示意圖。


驗證

基於我們真實的使用場景,我們搭建了效能驗證環境,作了一個驗證測試,如下(資料來自同事 @kusix 當年的測試報告,感謝)。

  測試前提

  Redis version 2.4.1

  Jmeter version 2.4

  Network 1000Mb

  Payload size = 100 bytes 

  測試結果

  SET: 32643.4/s

  GET: 32478.8/s

在實驗環境下得到的測試資料給人的感覺和官方差了蠻多,這裡面因為有網路和客戶端庫綜合的影響所以沒有實際的橫向比較意義。這個實驗環境實測資料只對我們真實的生產環境具有指導參考作用。在實驗環境的測試,單 Redis 例項執行穩定,單核 CPU 利用率在 70% ~ 80% 之間波動。除了測試 100 bytes 的包,還測了 1k、10k 和 100k 不同大小的包,如下圖所示:


誠然,1k 基本是 Redis 效能的一個拐點,這一點從上圖看趨勢是和官方圖的一致。

現實

基於實驗室測試資料和實際業務量,現實中採用了 Redis 分片來承擔更大的吞吐量。一個單一 Redis 分片一天的 ops 波動在 20k~30k 之間,單核 CPU 利用率在 40% ~ 80% 之間波動,如下圖。


這與當初實驗室環境的測試結果接近,而目前生產環境使用的 Redis 版本已升級到 2.8 了。如果業務量峰值繼續增高,看起來單個 Redis 分片還有大約 20% 的餘量就到單例項極限了。那麼可行的辦法就是繼續增加分片的數量來分攤單個分片的壓力,前提是能夠很容易的增加分片而不影響業務系統。這才是使用 Redis 面臨的真正殘酷現實考驗。

殘酷

Redis 是個好東西,提供了很多好用的功能,而且大部分實現的都還既可靠又高效(主從複製除外)。所以一開始我們犯了一個天真的用法錯誤:把所有不同型別的資料都放在了一組 Redis 叢集中。

  • 長生命週期的使用者狀態資料

  • 臨時快取資料

  • 後臺統計用的流水資料

導致的問題就是當你想擴分片的時候,客戶端 Hash 對映就變了,這是要遷移資料的。而所有資料放在一組 Redis 裡,要把它們分開就麻煩了,每個 Redis 例項裡面都是千萬級的 key。

而另外一個問題是單個 Redis 的效能上限帶來的瓶頸問題。由於 CPU 的單核頻率都發展到了瓶頸,都在往多核發展,一個 PC Server 一般 24或32 核。但 Redis 的單執行緒設計機制只能利用一個核,導致單核 CPU 的最大處理能力就是 Redis 單例項處理能力的天花板了。

舉個具體的案例,新功能上線又有點不放心,於是做了個開關放在 Redis,所有應用可以很方便的共享。通過讀取 Redis 中的開關 key 來判斷是否啟用某個功能,對每個請求做判斷。這裡的問題是什麼?這個 key 只能放在一個例項上,而所有的流量進入都要去這個 Redis GET 一下,導致該分片例項壓力山大。而它的極限在我們的環境上不過 4 萬 OPS,這個天花板其實並不高。

總結

認識清楚了現實的殘酷性,瞭解了你所在環境 Redis 的真實效能指標,區分清幻想和現實。我們才能真正考慮好如何合理的利用 Redis 的多功能特性,並有效規避的它的弱項,再給出一些 Redis 的使用建議:

  • 根據資料性質把 Redis 叢集分類;我的經驗是分三類:cache、buffer 和 db

    • cache:臨時快取資料,加分片擴容容易,一般無持久化需要。

    • buffer:用作緩衝區,平滑後端資料庫的寫操作,根據資料重要性可能有持久化需求。

    • db:替代資料庫的用法,有持久化需求。

  • 規避在單例項上放熱點 key。

  • 同一系統下的不同子應用或服務使用的 Redis 也要隔離開

另外,有一種觀點認為用作快取 Memcache 更合適,這裡可以獨立分析下其中的優劣取捨吧。Memcache 是設計為多執行緒的,所以在多核機器上單例項對 CPU 的利用更有效,所以它的效能天花板也更高。(見下圖)要達到同樣的效果,對於一個 32 核機器,你可能需要部署 32 個 Redis 例項,對運維也是一種負擔。


除此,Redis 還有個 10k 問題,當快取資料大於 10k(用作靜態頁面的快取,就可能超過這個大小)延遲會明顯增加,這也是單執行緒機制帶來的問題。如果你的應用業務量離 Redis 的效能天花板還比較遠而且也沒有 10k 需求,那麼用 Redis 作快取也是合理的,可以讓應用減少多依賴一種外部技術棧。最後,搞清楚現階段你的應用到底需要什麼,是多樣的資料結構和功能、更好的擴充套件能力還是更敏感的效能需求,然後再來選擇合適的工具吧。別隻看到個基準測試的效能資料,就歡呼雀躍起來了。

額外扯點其他的,Redis 的作者 @antirez 對自己的產品和技術那是相當自信。一有人批評 Redis 的問題,他都是要跳出來在自己的 blog 里加以迴應和說明的。比如有人說 Redis 功能多容易使用但也容易誤用,作者就跑出來解釋我設計是針對每種不同場景的,你用的不對怪我咯,怪我咯。有人說快取場景 Memcache 比 Redis 更合適,作者也專門寫了篇文章來說明,大概就是 Memcache 有的 Redis 都有,它沒有的我還有。當然最後也承認多執行緒是沒有的,但正在思考為 Redis I/O 增加執行緒,每個 Client 一個執行緒獨立處理,就像 Memcache 一樣,已經等不及要去開發和測試了,封住所以批評者的嘴。

Redis 這些年不斷的增加新功能和優化改進,讓它變得更靈活場景適應性更多的同時,也讓我們在使用時需要更細緻的思考,不是它有什麼我就用什麼,而是你需要什麼你就選擇什麼。

這篇先到這,後面還會再寫寫關於 Redis 擴充套件方面的主題。

參考

[1] antirez. [Redis Documentation](http://redis.io/documentation). 

[2] antirez. [Clarifications about Redis and Memcached](http://antirez.com/news/94). 

[3] antirez. [Lazy Redis is better Redis](http://antirez.com/news/93). 

[4] antirez. [On Redis, Memcached, Speed, Benchmarks and The Toilet](http://oldblog.antirez.com/post/redis-memcached-benchmark.html). 

[5] antirez. [An update on the Memcached/Redis benchmark](http://oldblog.antirez.com/post/update-on-memcached-redis-benchmark.html). 

[6] dormando. [Redis VS Memcached (slightly better bench)](http://dormando.livejournal.com/525147.html). 

[7] Mike Perham. [Storing Data with Redis](http://www.mikeperham.com/2015/09/24/storing-data-with-redis/).