單執行緒Redis效能為何如此之高?

逆月翎發表於2019-09-25

文章原創於公眾號:程式猿周先森。本平臺不定時更新,喜歡我的文章,歡迎關注我的微信公眾號。file

實際專案開發中現在無法逃避的一個問題就是快取問題,而快取問題也是面試必問知識點之一,如果面試官好一點可能會簡單的問你二八定律或者熱資料和冷資料,但是如果問的深入一點可能就會問到快取更新、降級、預熱、雪崩、穿透等問題,而這些問題可能會攔下大部分平時不怎麼關注快取的朋友,這些問題實際上都和快取伺服器息息相關,我們日常中經常使用的快取伺服器一般有兩種:Redis和Memcached。本篇開始正式進入Redis系列文章,本篇主要講講Redis使用單執行緒為何速度還能如此之快?

既然談到快取伺服器有兩種,那我們為何要選擇Redis呢?Redis與Memcached兩者之間有何區別呢?

Redis 和 Memcached 的區別

  • Redis支援常見資料型別:Redis 不僅僅支援簡單的 key/value 型別的資料,同時還提供string(字串)、list(連結串列)、set(集合)、zset(有序集合)和hash(雜湊型別)等資料結構的儲存。而Memcache 只支援簡單的資料型別 String。
  • Redis 支援資料的持久化,可以將記憶體中的資料保持在磁碟中,重啟的時候可以再次載入進行使用,而 Memecache 把資料全部存在記憶體之中。
  • 叢集模式:Memcached 沒有原生的叢集模式,需要依靠客戶端來實現往叢集中分片寫入資料;但是 Redis 目前是原生支援 Cluster 模式的。
  • Memcached 是多執行緒,非阻塞 IO 複用的網路模型;Redis 使用單執行緒的多路 IO 複用模型。
    file
      
Redis是一個key-value儲存系統。它支援儲存的value型別相對更多,包括string(字串)、list(連結串列)、set(集合)、zset(有序集合)和hash(雜湊型別)。這些資料型別都支援push/pop、add/remove及取交集並集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎上,redis支援各種不同方式的排序。為了保證效率,資料都是快取在記憶體中。區別的是redis會週期性的把更新的資料寫入磁碟或者把修改操作寫入追加的記錄檔案,並且在此基礎上實現了主從同步。簡單來說 Redis 就是一個資料庫,不過與傳統資料庫不同的是 Redis 的資料是存在記憶體中的,所以存寫速度非常快,因此 Redis 被廣泛應用於快取方向。Redis 也經常用來做分散式鎖。Redis 提供了多種資料型別來支援不同的業務場景。除此之外,Redis 支援事務 、持久化、LUA 指令碼、LRU 驅動事件、多種叢集方案。Redis中常用的資料型別實際上只有5種:String、Hash、List、Set、ZSet,我們可以先看下這五種基本資料型別的用法:複製程式碼

String

  • 常用命令:set、get、decr、incr、mget 等。

String 資料結構是簡單的 Key-Value 型別,Value 可以是string或者數字。常規 Key-Value 快取應用;常規計數:部落格數,閱讀數等。

Hash

  • 常用命令:hget、hset、hgetall 等。

Hash 特別適合用於儲存物件。

List

  • 常用命令:lpush、rpush、lpop、rpop、lrange 等。

連結串列是 Redis 最重要的資料結構之一,Redis List 為一個雙向連結串列,支援反向查詢和遍歷,更方便操作,不過帶來了額外的記憶體開銷。

Set

  • 常用命令:sadd、spop、smembers、sunion 等。

Set 其實和List都是列表的選項,Set 是可以自動去重的。當需要儲存一個不出現重複資料的列表資料,Set 是一個最好的選擇。你可以基於 Set 輕易實現交集、並集、差集的操作。

Sorted Set

  • 常用命令:zadd、zrange、zrem、zcard 等。

Sorted Set 相比Set增加了一個權重引數 Score,使得集合中的元素能夠按 Score 進行有序排列。

資料庫工作模式如果按照儲存方式進行劃分可以分成兩種:硬碟資料庫和記憶體資料庫。Redis讀寫資料之所以如此之快實際上就是由於Redis將資料儲存在記憶體中,所以在讀寫資料時不會受到硬碟I/O速度限制,所以讀寫速度自然很快。而硬碟資料庫則是在記憶體中儲存一個索引,然後根據索引去硬碟中查詢對應的值,所以效率肯定會相對更慢。file

Redis基於記憶體採用單執行緒單程式模型的Key-Value資料庫,經過官方測試每秒查詢次數可以高達100000+,那為什麼Redis如此快呢?最關鍵的一點其實剛才已經提到過,因為Redis完全基於記憶體,Redis接收到的大部分請求都是直接操作記憶體就可以完成的,所以處理請求非常迅速,而且Redis中使用單執行緒,避免了不必要的上下文切換和競爭鎖機制,也不會出現頻繁切換執行緒導致CPU消耗,不會存在多執行緒的死鎖等一系列問題。在Redis中使用多路複用I/O模型,而不是非阻塞I/O,非阻塞I/O之前在Nginx提到過,所以我們不重複介紹,我們重點看看多路I/O複用模型。

多路I/O複用模型實際上是使用select、poll、epoll同時監聽多個流的I/O事件,在無I/O事件時也就是空閒狀態下會將執行緒阻塞,當有I/O事件需要處理時,執行緒就是從阻塞狀態下喚醒,然後使用epoll輪詢一遍所有發生I/O事件的流。多路複用實際上還就是說多個網路連線複用同一個執行緒,採用多路I/O複用技術可以讓單個程式高效的處理多個連線請求,且Redis在記憶體中對資料進行操作,所以資料操作速度非常快,所以速度不會受到瓶頸,所以Redis才可以具有很高的吞吐量及效能。Redis的瓶頸主要來源於機器記憶體或網路頻寬,CPU不是Redis的瓶頸所在,再加上單執行緒更易於實現,所以順理成章Redis採用單執行緒的方式,但是使用單執行緒的方式是無法發揮多核CPU的優勢的,比如在進行比較耗時的操作時會使得Redis併發量下降,因為單執行緒所以某一時刻只能處理一個操作,所以執行耗時操作會導致併發量的下降,有一個簡單的解決方案就是在多核CPU下可以單機開多個Redis例項來解決這個問題。

歡迎關注公眾號:程式猿周先森。file

相關文章