深刻理解redis

本文將主要從Redis適用範圍,與Memcached, Java容器對比,核心功能(Pipelining,html

Pub/Sub,LRU,Transactions, Persistence, Replication),分佈式架構設計,Cluster,node

內部實現及數據結構來深刻了解Redis,適用於已經瞭解並有Redis操做經驗之程序員。程序員

1. Redis介紹 web

REmote DIctionary Server(Redis)是一個開源(BSD協議)的,使用ANSI C語言編寫,redis

基於內存in-memory數據存儲結構,能夠看成NoSQL數據庫,緩存和消息代理來使用。支持算法

多種數據結構,包括string, hashes, lists, set, sorted sets, bitmaps, hyperloglogs等。此外,數據庫

Redis還支持內置replication, LRU (Least Recently Used )算法管理緩存,Pub/Sub,部分緩存

原子性操做,簡單事務支持,基於磁盤的數據持久化以及3.0實現了分佈式Redis支持HA,安全

久違的Redis Cluster。從2013年5月開始,Redis的開發由Pivotal公司主持。服務器

2. Redis之父Antirez

 

飲水思源,Redis之父Salvatore Sanfilippo, 一名來自意大利西西里島(Sicily)出生於

1977年的程序員,網名Antirez, 常居住於Catania。Antirez的IT職業生涯開始於系統管理員,

IT安全,並於Web 2.0的年代創立一家web網絡公司,主要開發社交應用。以後,在一次實時

統計分析產品開發中,爲了節省成本以及高性能擴展性,Antirez意識到須要一種支持多種複雜

數據結構的in-memory數據庫,而且支持快速操做。Redis就此誕生並開源。以後,VM慧眼相

中了Redis, 並僱傭Antirez去全職開發,而以後又衍生出Pivotal公司,Antirez則繼續主持Redis

開發。

3. Redis適用場景 

Redis一般被你們稱爲數據結構服務器,一種輕量級K/V數據存儲,如咱們上文介紹,支持豐富

的數據類型。Redis以其速度而聞名,這使得其稱爲某一特定領域適用的最優選擇。

那請問你們是在什麼場景下選用Redis呢?有小夥伴說大多數狀況是大量數據須要緩存吧。

那又請問這種狀況爲何不用Memcached呢?又或者乾脆選擇Java自帶容器ArrayList, HashMap,

ConcurrentHashMap, HashSet呢?咱們帶着這兩個問題往下看。

 

3.1 互聯網/社交

Redis的用戶多爲互聯網公司如Twitter, Weibo(微博), Uber, StackOverflow, Airbnb, Alibaba等。

對於社交網站Twitter, Weibo,這些網站主要使用Redis進行高效的社交關係管理,緩存用戶好友,

粉絲,關注等,而且能夠經過Redis內置數據模型快速計算共同好友等。

 

咱們先看各大社交網站Twitter, Weibo(微博是國內最大的Redis用戶)。這些網站的每一個用戶都有

好友,粉絲,關注等,而且能夠相互查看共同好友等。Redis能夠用來幫助高效管理這些社交關係, 

如求共同好友:

如上,Redis很方便的使用有序集合的交集功能實現。其中user:100000:follow爲用戶1的粉絲列表,

user:200000:follow爲用戶2的粉絲列表,out:100000:200000爲交集共同好友列表。

又如另外一個經常使用場景,電商各類計數,如商品維度計數(關注度,喜歡數,評論數,瀏覽數等)。

能夠採用Redis的Hash類型,並藉助其原子性操做來維護計數。

用戶維度計數(動態,關注數,粉絲數,發帖數等)

固然除了這些互聯網用途,Redis最經常使用的功能仍是被看成緩存。提到緩存,你們固然會想到memcached。

Redis v.s. Memcached

  • 數據類型與操做:Redis擁有更多豐富的數據結構支持與操做,而Memcached則需客戶端本身處理並進行網絡交互

 

  • 內存使用率:簡單K/V存儲,Memcached內存利用率更高(使用了slab與大小不一樣的chunk來管理內存),而若是採用Redis Hash來存儲則其組合壓縮,內存利用率高於Memcached

  • 性能:整體來講,兩者性能接近;Redis使用了單核(單線程IO複用,封裝了AeEvent事件處理框架,實現了epoll,kqueue,select),Memcached採用了多核,各有利弊;當數據大於100K的時候,Memcached性能高於Redis

  • 數據持久化:Redis支持數據文件持久化,RDB與AOF兩種策略;Memcached則不支持

  • 分佈式:Memcached自己並不支持服務器端分佈式,客戶端只能藉助一致性哈希分佈式算法來實現Memcached分佈式存儲;固然Redis也是從3.0版本開始才支持服務器端cluster的,重要的是如今支持了。

  • 其餘方面:Redis提供其餘一些功能,如Pub/Sub, Queue, 簡單Transacation, Replication等。

 

好了,千言不抵一圖:


Redis v.s. HashMap

做爲單機數據緩存,Java自帶容器也是備選方案之一。咱們進行壓力測試,採用ConcurrentHashMap, Memcached(假設近似於Redis), MySQL進行Benchmark。

具體數據以下:

 

能夠看出,HashMap(上圖顯示爲一根線)總體操做插入/查詢/移除都 25 - 90倍快於Memcached/Redis; 而Memcached/Redis又 5 -12 倍快於MySQL InnoDB,符合預期。究其緣由,Java容器單機內存直接讀取,無網絡開銷,無須序列化等。

而反之之因此要用Redis的緣由與好處則包括:

 

HashMap單機受限於內存容量,而正是Redis分佈式之優點

  • HashMap當數據量超過必定限制後,須要妥善管理堆內存,否則會形成內存溢出或者Memory Leak;Redis則具有了文件持久性,以及Failover達到HA.

  • HashMap只能受限於本機,而Redis天生分佈式,可讓多個App Server訪問,負載均衡。

因此Redis適合所有數據都在內存的場景包括須要臨時持久化,尤爲做爲緩存來使用,並支持對緩存數據進行簡單處理計算;如涉及Redis與RDBMS雙向同步的話,則須要引入一些複雜度。

 

4. Redis核心功能  

 

4.1 Pipelining 

Reids是一個TCP客戶端-服務器模式,使用了應請求/響應協議,客戶端與服務器的交互是阻塞的方式。

以下一個請求/響應事例,客戶端發起4個INCR命令,服務端依次響應:

  • Client:INCR X

  • Server:1

  • Client:INCR X

  • Server:2

  • Client:INCR X

  • Server:3

  • Client:INCR X

  • Server:4

客戶端與服務器經過網絡鏈接,影響性能的關鍵因素往返時間(RTT Round Trip Time)則受限於網絡

速度,如在網速較慢狀況下RTT須要250毫秒,哪怕服務器性能卓越能夠每秒處理1萬個請求,然而總體

則客戶端受網絡制約只能每秒處理4個請求。即使使用了loopback接口節省RTT時間,但若是須要大量的

寫操做,總體性能仍不可觀。

 

Redis引入了批處理,管道(Pipelining),即客戶端能夠一次發送多個命令給服務器,無須等待服務器的

返回,而服務器端則回將請求放入一個有序的管道中,在執行完成後,一次性將返回值發送回客戶端。

上述事例在使用Pipelining時以下,客戶端經過緩衝區,一次性批量發送請求INCR,服務端則處理後統一

返回結果:

  • Client:INCR X

  • Client:INCR X

  • Client:INCR X

  • Client:INCR X

  • Server:1

  • Server:2

  • Server:3

  • Server:4


4.2 Pub/Sub

Reids提供瞭解耦消息的發佈/訂閱(Pub/Sub)通訊模式,Pub/Sub採用事件做爲基本通訊機制,支持時間

(非同時),空間(無須知道具體位置)與同步(可異步模式)等解耦合,而發佈與訂閱則經過通道

(channel)來通訊。

Antirez稱,最初引入Pub/Sub是應用戶需求,而且Redis自己的架構已經支持Pub/Sub, 因此只用了150行

代碼就把Pub/Sub已實現的內部功能暴露稱Pub/Sub API。

上圖爲較早版本中發佈消息的一段代碼片斷,能夠看出消息保存在一個List鏈表數據結構中,消息的類型爲

messagebulk。另外,Redis也支持通配符模式匹配的訂閱方式。

 

Pub/Sub v.s. 消息中間件

Reids的Pub/Sub與消息中間件相比,並不支持持久化,如系統宕機,網絡問題等都會形成消息丟失;

Redis的消息多用於實時性較高的消息推送,並不保證可靠性。Redis的消息也沒法支持水平擴展,

如經過增長consumer或者訂閱者分組之類進行負載均衡。

性能方面,Redis的Pub/Sub與經常使用消息中間件如著名的RabbitMQ比較,則伯仲之間。

 

上圖以處理10萬個消息爲例,在發佈消息方面,RabbitMQ的耗時爲Redis的75%;訂閱消費消息

RabbitMQ耗時爲Redis的86%,可見總體性能Redis已與消息中間件相近。

 

4.3 LRU 

LRU(Least Recently Used) 最近最久未使用算法,是多數緩存系統當內存受限時自動清理舊數據的

經常使用經常使用算法之一。當Redis使用內存達到配置maxmemory時,Redis會根據配置的policy進行數據置

換處理,其中策略包括以下:

  • noenviction(不清除)

  • allkeys-lru(從全部數據集選擇最近最少用)

  • volatile-lru(從設置過時時間的數據集選擇最近最少用)

  • allkeys-random(全部數據集隨機選取淘汰),

  • volatile-random(以設置過時時間數據集中隨機),

  • volatile-ttl(從已設置過時過時時間的數據集中選擇,非LRU)

Redis出於性能及節約內存考量,採用的並不是嚴格意義LRU算法算法,而是近似的LRU算法,即Redis經過採樣

一小部分鍵,而後在樣本池中進行LRU。固然在Redis 3.0中,算法進一步改進爲維護回收候選鍵池,改善了性

能同時更接近於LRU算法行爲。

 

下圖爲官網提供Reids LRU性能測試,分別爲理論LRU算法,10個樣本的Redis 3.0 LRU算法,以及5個樣本的

Redis 2.8與3.0算法的表現,其中淺灰色表示被置換出去的key,灰色爲沒有被置換的key,綠色爲新增的key。

測試算法較簡單,首先默認導入必定數目key,以後從第一個key遍歷至最後一個key(至關於按時間順序使用過了

當前遍歷的key),以後再增長50%的數目觸發清理策略。

能夠看出,理論算法爲最舊的50%被替換;一樣樣本爲5狀況下,3.0表現要優於2.8,3.0則更接近於理論值,

2.8算法則略爲遜色。

 

4.4 Transactions 

 

絕大多數NoSQL選擇不支持事務,而Reids以命令方式(MULTI, EXEC, DISCARD, WA WATCH)提供了簡單的

類/僞事務支持,之因此稱之爲類/僞事務,是由於Redis的只保證了事務必須ACID的C(一致性),I(隔離性),

並不保證A(原子性)與持久性(D),Redis事務甚至不支持回滾操做。

 

實際上,Redis的事務提供了一種將多個命令打包並置入事務隊列,以後批量一次性,有序的按照先進先出

(FIFO)的順序執行機制。事務在執行過程當中不會被中斷,全部命令命令執行以後,事務才結束。

 

Transaction v.s. Pipelining

Redis的Transaction與Pipelining都是批量執行命令,其主要差異爲:

  • Pipelining主要是提供了網絡層面的優化,客戶端經過緩存多個命令並按批次發送給服務端處理以節省網絡RTT(Round Trip TIme)時間開銷,但這些命令並不保證事務性(Redis的單線程保證了單個命令自己是原子的,但多個client能夠發起多個命令串行執行)。

  • Transaction則保證了一個client發起的事務中的命令能夠連續的執行,中間不會插入其它client的命令,而此則受益於Redis的單線程模式,很容易實現。

因此,Transaction與Pipelining不但不衝突,對於批量命令甚至能夠結合起來使用以達到網絡優化及事務性

保證雙重收效。

 

4.5 Persistence

Redis提供了兩種經常使用的不一樣級別的磁盤持久化方式,RDB與AOF(Append-Only-File)。RDB持久化在指定

的時間間隔生成數據集的時間點快照(Snapshot), AOF則相似RDMS的binlog日誌。 

因爲單線程運行,Redis的RDB在備份時爲不影響系統正常使用,藉助Linux的fork命令及copy on write機制,

在fork出的子進程中進行備份寫RDB文件。RDB的備份相對簡單而且文件小,但沒法保證數據的完整性,

如Redis在RDB的間隔時間內宕機,則面臨丟失期間的數據。RDB較適合按期的歸檔備份及方便災難恢復。

而AOF則提供了更好的持久性,Redis會將每一個命令都追加到AOF文件中,並提供了三種持久化策略:

  • no fsync at all:Redis不調用fsync持久化, 操做系統決定同步時機(多數30秒)

  • fsync everysecond:每秒(延遲會兩秒)進行一次fsync將緩衝區數據寫入磁盤

  • fsync always:每一個命令都進行同步,持久化寫入磁盤,安全但較慢

AOF後臺執行的方式與RDB相似,也是藉助fork開啓子進程進行寫入AOF文件。爲了減小AOF文件的大小,

Redis提了了文件壓縮命令,另外支持日誌重寫,後臺重構AOF文件以減小所需命令。

 

4.6 Replication

 

Redis支持Master/Slave主從配置,實現弱一致性,支持負載均衡,提升HA。Redis的Master能夠對應

多個Slave, Slave也能夠級聯多個Slave。一般Master負責讀寫服務,Slave負責讀服務,Master與Slave

間靠Replication按期來進行同步。

Redis的主從結構以下圖所示DAG(有向無環圖)

Slave會按期給Master發送SYNC命令進行同步,若是第一次鏈接則進行全量同步不然增量同步。

Master則啓動後臺快找進程saving來收集最近修改數據集全部命令並將其用db file傳輸給Slave。

 

 

另外,Redis從2.8版本開始支持中斷後(網絡斷開等故障)的斷點續傳功能,無須從新同步。

Master維護一個內存緩衝區,主從服務器都維護一個複製偏移量(offset)和master run id,

當斷開從新鏈接後,Master判斷兩個master run id是否相同,並根據指定的偏移量繼續斷點續傳。

 

 

5. Redis系統架構  

 

Sentinel架構

Reids Sentinel誕生於2012年(Redis 2.4版本),建議在單機Redis或者客戶端模式Cluster的時候

(非 3.0版本Redis Cluster)採用,做爲HA, Failover來使用。

Sentinel主要提供了集羣管理,包括監控,通知,自動故障恢復。如上圖,當其中一個master沒法

正常工做時,Sentinel將把一個Slave提高爲Master, 從而自動恢復故障。而Sentinel自己也作到了

分佈式,能夠部署多個Sentinel實例來監控Redis實例(建議基數,至少3個Sentinel實例來監控一

組Redis Master/Slaves),多個Sentinel進程間經過Gossip協議來肯定Master是否宕機,經過

Agreement協議來決定是否執行故障自動遷移以及從新選主,總體設計相似ZooKeeper

(應該是做者參考了當年的ZK吧?)。

 

Cluster架構

Redis Cluster開始設計於2011年(早於Sentinel),正式誕生於2015年愚人節,Redis 3.0,其中之

苦辣酸甜只有Antirez本身知道。

 

 

如上圖,從3.0開始, Redis從一個單純的NoSQL內存數據庫變成了一個真正分佈式NoSQL數據庫。

歸納來講,所謂分佈式即支持數據分片,而且自動管理故障恢復(failover)與備份(replication)。

 

如上圖Redis Cluster採用了無中心結構,每一個節點都保存,共享數據和集羣狀態,每一個節點與

其它全部節點通訊,使用Gossip協議來傳播及發現新節點,經過分區來提供必定程度可用性,

當某個node的Master宕機時,Cluste會自動選舉一個Slave造成一個新的Master,這裏應該是

借鑑,重用了Sentinel的功能。

另外,Redis Cluster並無使用一般的一致性哈希, 而引入哈希槽的概念,Cluster中固定有

16384個slot, 每一個key經過CRC16校驗後對16384取模來決定其對應slot的位置,而每一個node

負責一部分的slot管理,當node變化時,動態調整slot的分佈,而數據則無須挪動。對於客戶端

來講,client能夠向任意一個實例請求,Cluster會自動定位須要訪問的slot。

上圖查詢路由過程當中,咱們隨機發送到任意一個Redis實例,這個實例會按照上文提到的CRC16

校驗後取模定位,並轉發至正確的Redis實例中。

 

然而,徹底去中心化的架構同時也失去了一些靈活與總控能力,如可經過引入中央控制的自動發

現節點的變化及時Rebalance,分區粒度的備份,故障時分區自動調整,Gossip消息的通訊開銷,

路由表維護等。

值得一提的是,Redis的企業版/商業版Redis Labs Enterprise Cluster(RLEC)則彷佛解決了咱們

上述問題,如引入了中央控制Cluster Manager來管理,監控,分片遷移等工做, 引入高性能Proxy

隱藏幕後的路由實現等。

 

固然,前提是商業化付費版本了,咱們也期待將來的開源Redis逐步能夠引入這些概念。

 

6. Redis 內部數據結構  

 

Redis支持豐富的數據類型,並提供了大量簡單高效的功能。爲了高效使用Redis,開發設計人員須要對Redis的數據結構進行選型,如選擇鏈表仍是集合等。 下面咱們快速瞭解一下Redis內部數據結構及其代碼實現。Redis的底層數據結構總覽圖:

 

 

 

上圖列出了Redis內部底層的一些重要數據結構,包括List, Set, Hash, String等。

來看幾個比較核心的的數據結構。

 

Redis Object 

 

Redis 3.x後的redisObject以下圖所示:

 

redisObject定義中使用了位字段(bit filed)。簡單來講,redisObject定義了類型,編碼方式,

LRU時間,引用計數,*ptr指向實際保存值指針。

  • type: redisObject的類型,字符串,列表,集合,有序集,哈希表等

  • encoding: 底層實現結構,字符串,整數,跳躍表,壓縮列表等

  • ptr:實際指向保存值的數據結構

舉個具體例子,redisObject{type: REDIS_LIST, encoding:REDIS_ENCODING_LINKEDLIST}, 

這個對象是Redis列表,其值保存在一個鏈表中,ptr指針指向這個列表。

用慣了JVM的虛擬機,咱們也來回顧一下C或者Redis是如何管理對象的。整體而言,Redis本身實現對象管理機制,並基於引用計數的垃圾回收。

毫無疑問的直接malloc開闢內存空間,設置type,encoding,引用計數默認1,設置默認LRU。能夠看出refcount,猜想其內部是基於引用計數來管理釋放內存空間的。事實要以代碼爲準。


果不其然,Redis提供了incrRefCount與decrRefCount來管理對象跟蹤對象的引用,  當減小引用時檢測計數器爲是否須要釋放內存對象。

RedisDB    

 

RedisDB內部數據結構,封裝了數據庫層面的信息:

 

從redisDB來看,幾個重要屬性:

  • id:數據庫內部編號,僅供內部操做使用,如AOF等

  • dict:存放整個數據庫的鍵值對,鍵爲字符串,值爲Redis的數據結構,如List, Set, Hash等。

  • expires:鍵的過時時間

具體代碼以下:

 

代碼註釋較爲清晰,其中long long是C99標準新加的,64位長整型。

 

RedisServer    

 

RedisServer代碼在Redis 3.0支持Cluster後變得較複雜,咱們只列出部分代碼。

 

整體來講包含以下幾個大部分:

  • 通用部分:如pid進程id,數據庫指針,命令字典表,Sentinel模式標誌位等

  • 網絡信息:如port TCP監聽端口,Cluster Bus監聽socket, 已使用slot數量,Active的客戶端列表, 當前客戶端,Slaves列表等。

  • 其它信息:如AOF信息,統計信息, 配置信息(如已經配置總db數量dbnum等),日誌信息,Replication配置,Pub/Sub, Cluster信息,Lua腳本信息配置等等。

 

Redis Hash

Redis的哈希表/字典是其核心數據結構之一,值得深刻研究。Redis Hash數據結構, Hash新建立時,

在不影響效率狀況下,Redis默認使用zipmap做爲底層實現以節省空間,只有當size超出必定限制後

(hash-max-zipmap-entries ),Redis纔會自動把zipmap轉換爲下圖Hash Table。


 

上圖字典的底層實現爲哈希表,每一個字典包含2個哈希表,ht[0], ht[1], 1號哈希表是在rehash過程當中才

使用的。而哈希表則由dictEntry構成。

 

代碼層面,每一個字典包含了3個內部數據結構:

  • Dict:字典的根結構,包含了2個dictht,其中2做爲rehashing之用

  • Dictht:包含了linkedlist dictEntry

  • DictEntry:包含了3個數據結構(double/uint64_6/int64t)的鏈表,相似Java HashMap中的Entry結構

 

Hash算法

目前Redis中引入了一些經典哈希算法,而HashTable則主要爲如下兩種:

 

  • MurmurHash2 32bit算法:著名的非加密型哈希函數,能產生32位或64位哈希值,最新版本爲

  • MurmurHash3。該算法針對一個字符串進行哈希,可表現較強離散性。

  • 基於djb算法實現散列算法:該算法較爲簡單,一樣是將字符串轉換爲哈希值。主要利用字符串中

  • 的ASCII碼與一個隨機seed,進行變換獲得哈希值。

評估一個哈希算法的優劣,主要看其哈希值的離散均勻效果以及消除衝突程度。Redis在HashTable中

引入的上述兩種算法不失簡單高效,離散均勻。

 

Rehash

相似Java中的HashMap, 當有新鍵值對添加到Redis字典時,有可能會觸發rehash。Redis中處理哈希

碰撞的方法與Java同樣,都是採用鏈表法,整個哈希表的性能則依賴於它的大小size和它已經保存節點

數量used的比率。

比率在1:1時,哈希表的性能最好,若是節點數量比哈希表大小大不少的話,則整個哈希表就退化成多個

鏈表,其性能優點全無。

 

上圖的哈希表,平均每次失敗查找須要訪問5個節點。爲了保持高效性能,在不修改鍵值對狀況下,

須要進行rehash,目標是將ratio比率維持在1:1左右。

 

Ratio = Used / Size

 

rehash觸發條件:

  • 天然rehash:ratio >= 1, 且變量dict_can_resize爲真

  • 強制rehash:ratio大於dict_force_resize_ratio(v 3.2.1版本爲5)

rehash執行過程:

  • 建立ht[1]並分配至少2倍於ht[0] table的空間

  • 將ht[0] table中的全部鍵值對遷移到ht[1] table

  • 將ht[0]數據清空,並將ht[1]替換爲新的ht[0]

Redis哈希爲了不整個rehash過程當中服務被阻塞,採用了漸進式的rehash,即rehash程序激活後,並非

立刻執行直到完成,而是分屢次,漸進式(incremental)的完成。同時,爲了保證併發安全,在執行rehash

中間執行添加時,新的節點會直接添加到ht[1]而不是ht[0], 這樣保證了數據的完整性與安全性。

另外一方面,哈希的Rehash在還提供了創新的(相對於Java HashMap)收縮(shrink)字典,當可用節點遠遠

大於已用節點的時候,rehash會自動進行收縮,具體過程與上面相似以保證比率始終高效使用。

 

7. 總結

本文從Redis的一些核心功能,適用範圍與本質緣由,分佈式架構設計,Cluster,重要內部數據結構及代碼片斷等層面深刻了解,在Redis使用方面,開發及設計人員一般須要對Redis有必定深度理解,對業務模型進行相應的數據結構選型,還需提早預估內存使用,是否須要持久化,分佈式等。整體來講,Redis很是適合與傳統關係型數據庫結合使用,作高性能數據緩存,輕量級消息隊列及跨機器共享內存。但願本文對Redis開發人員有所幫助,碰到一些實現細節,建議仍是要深刻其源代碼一探究竟。有關其它Redis的命令操做及其它高級功能則可參考Redis官網。

關於redis的幾點舞誤區

https://www.cnblogs.com/renjiaqi/p/8994715.html

相關文章
相關標籤/搜索