Redis為什麼這麼快?
導讀
Redis是一個開源的記憶體中的資料結構儲存系統,在實際的開發過程中,Redis已經成為不可或缺的元件之一,基於記憶體實現、合理的資料結構、合理的資料編碼、合理的執行緒模型等特徵不僅僅讓Redis變得如此之快,同時也造就了Redis對更多或者複雜的場景的支援。
導讀
Redis是一個開源的記憶體中的資料結構儲存系統,在實際的開發過程中,Redis已經成為不可或缺的元件之一,基於記憶體實現、合理的資料結構、合理的資料編碼、合理的執行緒模型等特徵不僅僅讓Redis變得如此之快,同時也造就了Redis對更多或者複雜的場景的支援。
2009年由 Salvatore Sanfilippo(Redis之父)釋出初始版本。
2013年5月之前,由VMare贊助。
2013年5月-2015年6月,由Pivotal贊助。
2015年6月起,由Redis Labs贊助。
圖2 Redis在db-engineer.com上的排名
圖3 Redis每年的受歡迎程度
2009年5月釋出Redis初始版本;
2012年釋出Redis 2.6.0;
2013年11月釋出Redis 2.8.0;
2015年4月釋出Redis 3.0.0,在該版本Redis引入叢集;
2017年7月釋出Redis 4.0.0,在該版本Redis引入了模組系統;
2018年10月釋出Redis 5.0.0,在該版本引入了Streams結構;
2020年5月2日釋出 6.0.1(穩定版),在該版本中引入多執行緒、RESP3協議、無盤複製副本;
2022年1月31日釋出 7.0 RC1,在該版本中主要是對效能和記憶體進行最佳化,新的AOF模式。
根據官方的文件,Redis 已經在超過 60000 個連線上進行了基準測試,並且在這些條件下仍然能夠維持 50000 q/s。同樣的請求量如果打到MySQL上,那很可能直接崩掉。
With high-end configurations, the number of client connections is also an important factor. Being based on epoll/kqueue, the Redis event loop is quite scalable. Redis has already been benchmarked at more than 60000 connections, and was still able to sustain 50000 q/s in these conditions. As a rule of thumb, an instance with 30000 connections can only process half the throughput achievable with 100 connections. Here is an example showing the throughput of a Redis instance per number of connections;
那是什麼原因造就了Redis可以具有如此高的效能?主要分為以下幾個方面:
圖5 Redis為什麼這麼快-思維導圖
4.1 基於記憶體實現
Mysql的資料儲存持久化是儲存到磁碟上的,讀取資料是記憶體中如果沒有的話,就會產生磁碟I/O,先把資料讀取到記憶體中,再讀取資料。而Redis則是直接把資料儲存到記憶體中,減少了磁碟I/O造成的消耗。
圖6 Redis與Mysql儲存方式區別
4.2 高效的資料結構
合理的資料結構,就是可以讓應用/程式更快。Mysql索引為了提高效率,選擇了B+樹的資料結構。先看下Redis的資料結構&內部編碼圖:
圖7 Redis底層資料結構
4.2.1 SDS簡單動態字串
Redis沒有采用原生C語言的字串型別而是自己實現了字串結構-簡單動態字串(simple dynamic string)。
圖8 C語言字串型別
圖9 SDS字串型別
SDS與C語言字串的區別:
獲取字串長度:C字串的複雜度為O(N),而SDS的複雜度為O(1)。
杜絕緩衝區溢位(C語言每次需要手動擴容),如果C字串想要擴容,在沒有申請足夠多的記憶體空間下,會出現記憶體溢位的情況,而SDS記錄了字串的長度,如果長度不夠的情況下會進行擴容。
減少修改字串時帶來的記憶體重分配次數。
空間預分配,
規則1:修改後長度< 1MB,預分配同樣大小未使用空間,free=len;
規則2:修改後長度 >= 1MB,預分配1MB未使用空間。
惰性空間釋放,SDS 縮短時,不是回收多餘的記憶體空間,而是free記錄下多餘的空間,後續有變更,直接使用free中記錄的空間,減少分配。
4.2.2 embstr & raw
Redis 的字串有兩種儲存方式,在長度特別短時,使用 emb 形式儲存(embeded),當長度超過 44 時,使用 raw 形式儲存。
圖10 embstr和raw資料結構
為什麼分界線是 44 呢?
在CPU和主記憶體之間還有一個高速資料緩衝區,有L1,L2,L3三級快取,L1級快取時距離CPU最近的,CPU會有限從L1快取中獲取資料,其次是L2,L3。
圖11 CPU三級快取
L1最快但是其儲存空間也是有限的,大概64位元組,拋去物件固定屬性佔用的空間,以及‘\0’,剩餘的空間最多是44個位元組,超過44位元組L1快取就會存不下。
圖12 SDS在L1快取中的儲存方式
4.2.3 字典(DICT)
Redis 作為 K-V 型記憶體資料庫,所有的鍵值就是用字典來儲存。字典就是雜湊表,比如HashMap,透過key就可以直接獲取到對應的value。而雜湊表的特性,在O(1)時間複雜度就可以獲得對應的值。
【Objective-c】//字典結構資料typedef struct dict { dictType *type; //介面實現,為字典提供多型性 void *privdata; //儲存一些額外的資料 dictht ht[2]; //兩個hash表 long rehashidx. //漸進式rehash時記錄當前rehash的位置} dict;
兩個hashtable通常情況下只有一個hashtable是有值的,另外一個是在進行rehash的時候才會用到,在擴容時逐漸的從一個hashtable中遷移至另外一個hashtable中,搬遷結束後舊的hashtable會被清空。
圖13 Redis hashtable
【Objective-c】
//hashtable的結構如下:
typedef struct dictht {
dictEntry **table; //指向第一個陣列
unsigned long size; //陣列的長度
unsigned long sizemask; //用於快速hash定位
unsigned long used; //陣列中元素的個數
} dictht;
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d; //用於zset,儲存score值
} v;
struct dictEntry *next;
} dictEntry;
圖14 Redis hashtable
4.2.4 壓縮列表(ziplist)
redis為了節省記憶體空間,zset和hash物件在資料比較少的時候採用的是ziplist的結構,是一塊連續的記憶體空間,並且支援雙向遍歷。
圖15 ziplist資料結構
4.2.5 跳躍表
跳躍表是Redis特有的資料結構,就是在連結串列的基礎上,增加多級索引提升查詢效率。
跳躍表支援平均 O(logN),最壞 O(N)複雜度的節點查詢,還可以透過順序性操作批次處理節點。
圖16 跳躍表資料結構
4.3 合理的資料編碼
String:如果儲存數字的話,是用int型別的編碼;如果儲存非數字,小於等於39位元組的字串,是embstr;大於39個位元組,則是raw編碼。 List:如果列表的元素個數小於512個,列表每個元素的值都小於64位元組(預設),使用ziplist編碼,否則使用linkedlist編碼。 Hash:雜湊型別元素個數小於512個,所有值小於64位元組的話,使用ziplist編碼,否則使用hashtable編碼。 Set:如果集合中的元素都是整數且元素個數小於512個,使用intset編碼,否則使用hashtable編碼。 Zset:當有序集合的元素個數小於128個,每個元素的值小於64位元組時,使用ziplist編碼,否則使用skiplist(跳躍表)編碼
4.4 合理的執行緒模型
首先是單執行緒模型-避免了上下文切換造成的時間浪費,單執行緒指的是網路請求模組使用了一個執行緒,即一個執行緒處理所有網路請求,其他模組仍然會使用多執行緒;在使用多執行緒的過程中,如果沒有一個良好的設計,很有可能造成線上程數增加的前期吞吐率增加,後期吞吐率反而增長沒有那麼明顯了。
多執行緒的情況下通常會出現共享一部分資源,當多個執行緒同時修改這一部分共享資源時就需要有額外的機制來進行保障,就會造成額外的開銷。
第一種:安排一個老師,按順序逐個檢查。先檢查A,然後是B,之後是C、D...這中間如果有一個學生卡住,全班都會被耽誤。這種模式就好比用迴圈挨個處理socket,根本不具有併發能力。這種方式只需要一個老師,但是耗時時間會比較長。 第二種:安排30個老師,每個老師檢查一個學生的作業。這種類似於為每一個socket建立一個程式或者執行緒處理連線。這種方式需要30個老師(最消耗資源),但是速度最快。 第三種:安排一個老師,站在講臺上,誰解答完誰舉手。這時C、D舉手,表示他們作業做完了,老師下去依次檢查C、D的答案,然後繼續回到講臺上等。此時E、A又舉手,然後去處理E和A。這種方式可以在最小的資源消耗的情況下,最快的處理完任務。
圖18 I/O多路複用
- 純記憶體操作,記憶體的訪問是非常迅速的;
- 多路複用的I/O模型,可以高併發的處理更多的請求;
- 精心設計的高效的資料結構;
- 合理的內部資料編碼,對記憶體空間的高效實用。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024420/viewspace-2938051/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 為什麼要用Redis?Redis為什麼這麼快?(來自知乎)Redis
- 為什麼Redis這麼快?5分鐘成為Redis高手Redis
- 硬核!15張圖解Redis為什麼這麼快圖解Redis
- Redis 為什麼這麼快?這才是最完美的回答Redis
- redis為什麼快Redis
- Redis為什麼那麼快?Redis
- 為什麼redis是單執行緒的以及為什麼這麼快?Redis執行緒
- Redis是單執行緒的,但Redis為什麼這麼快?Redis執行緒
- Nginx 為什麼這麼快?Nginx
- redis是單執行緒的,為什麼這麼快Redis執行緒
- 比Redis快5倍的中介軟體,究竟為什麼這麼快?Redis
- 快速排序為什麼這麼快?排序
- 破玩意 | Redis 為什麼那麼快Redis
- 京東二面,Redis為什麼那麼快?Redis
- Google Analytics為什麼會這麼快Go
- 為什麼要使用Redis做快取Redis快取
- [譯][A crash course in WebAssembly] 為什麼WebAssembly這麼快Web
- StackExchange.Redis跑起來,為什麼這麼溜?Redis
- 碾壓Python!為什麼Julia速度這麼快?Python
- Redis為何這麼快–資料儲存角度Redis
- Redis單執行緒,為什麼速度快Redis執行緒
- redis淘汰+過期雙向保證高可用 | redis 為什麼那麼快?Redis
- 剖析Disruptor:為什麼會這麼快?(二)神奇的快取行填充快取
- Kafka 為什麼快Kafka
- redis字典快速對映+hash釜底抽薪+漸進式rehash | redis為什麼那麼快Redis
- 為什麼要用RedisRedis
- Kafka為什麼效能這麼快?4大核心原因詳解Kafka
- 告訴你MySQL主鍵查詢為什麼這麼快MySql
- Netty是什麼,Netty為什麼速度這麼快,執行緒模型分析Netty執行緒模型
- Kafka為什麼速度那麼快?Kafka
- Redis 為何這麼快?聊聊它的資料結構~Redis資料結構
- 為什麼前端這麼多人前端
- MYSQL索引為什麼這麼快?瞭解索引的神奇之處MySql索引
- 為什麼要使用 Redis?Redis
- 微軟精心打造的 Vista 系統,為什麼死得這麼快?微軟
- 微軟精心打造的Vista系統,為什麼死得這麼快?微軟
- 為什麼Python這麼慢?Python
- 為什麼 Python 這麼慢?Python