對於Redis這種內存數據庫來講,除了訪問的是內存以外,Redis訪問速度飛快還取決於其餘的一些因素,而這些都跟Redis的高可用性有很大關係。下面是衡量Redis的三個緯度:
1.高性能:線程模型、網絡I/O模型、數據結構,合理的數據編碼2.高可用性:主從複製、哨兵模式、Cluster分片集羣和持久化機制3.高拓展性:負載均衡本篇文章,咱們主要來介紹Redis的高性能特性的幾個相關因素。 根據官方數據,Redis的QPS能夠達到約100000/秒,橫軸表示鏈接數,縱軸表示QPS。 參考資料:https://redis.io/topics/benchmarks
Redis4.0以前是單線程模型,緣由是由於在此以前CPU不是瓶頸,網絡I/O纔是瓶頸,而單線程模型一來避免了多線程模型之間的上下文切換,二來又能夠經過多路複用來實現併發,而且代碼更容易維護。html
不過在Redis4.0以後,redis新增了一些能夠被其餘線程異步處理的刪除操做,例如:UNLINK、FLUSHALL ASYNC 和 FLUSHDB ASYNC。緣由是有一些超大的鍵值對佔用了很大的內容,例如幾十 MB 或者幾百 MB 的數據,這些數據的刪除在幾百毫秒內結束不了,若是是同步的就會阻塞待處理的任務,因此加入了多線程,目的就是爲了異步處理這些大的數據。
redis
參考資料爲:https://zhuanlan.zhihu.com/p/91490643https://stackoverflow.com/questions/10489298/redis-is-single-threaded-then-how-does-it-do-concurrent-i-ohttp://www.odbms.org/2018/03/the-little-known-feature-of-redis-4-0-that-will-speed-up-your-applications/
由於Redis是單線程模型的緣由,實現併發操做的話,是須要採用了多路複用機制的,例如:epoll,關於epoll的介紹能夠參考個人另一篇文章4.同時管理多個socket的高效方法-epoll
算法
參考資料:https://draveness.me/redis-io-multiplexing/https://www.jianshu.com/p/8f2fb61097b8https://baijiahao.baidu.com/s?id=1676709704453688282&wfr=spider&for=pc
首先,Redis整個數據庫就是一個全局哈希表,而哈希表的時間複雜度是 O(1),只須要計算每一個鍵的哈希值,便知道對應的哈希桶位置,定位桶裏面的 entry 找到對應數據,這個也是 Redis 快的緣由之一。數據庫
其次,不一樣的數據類型使用不一樣的數據結構速度才得以提高,而且每種數據類型都有一種或者多種數據結構來支撐。數組
這也是咱們將 SDS 的 buf 屬性稱爲字節數組的緣由 —— Redis 不是用這個數組來保存字符, 而是用它來保存一系列二進制數據。安全
好比說, 使用 SDS 來保存以前提到的特殊數據格式就沒有任何問題, 由於 SDS 使用 len 屬性的值而不是空字符來判斷字符串是否結束, 如圖 2-18 所示。網絡
參考資料:http://redisbook.com/preview/sds/different_between_sds_and_c_string.html
壓縮列表是 List 、hash、 sorted Set 三種數據類型底層實現之一。數據結構
當一個列表只有少許數據的時候,而且每一個列表項要麼就是小整數值,要麼就是長度比較短的字符串,那麼 Redis 就會使用壓縮列表來作列表鍵的底層實現。多線程
Ziplist 是由一系列特殊編碼的內存塊構成的列表, 一個 ziplist 能夠包含多個節點(entry), 每一個節點能夠保存一個長度受限的字符數組(不以 \0 結尾的 char 數組)或者整數。併發
字符數組長度小於等於 63 (26−1)字節的字符數組長度小於等於 16383 (214−1) 字節的字符數組長度小於等於 4294967295 (232−1)字節的字符數組整數4 位長,介於 0 至 12 之間的無符號整數1 字節長,有符號整數3 字節長,有符號整數int16_t 類型整數int32_t 類型整數int64_t 類型整數
ziplist 在表頭有三個字段 zlbytes、zltail 和 zllen,分別表示列表佔用字節數、列表尾的偏移量和列表中的 entry 個數;壓縮列表在表尾還有一個 zlend,表示列表結束。
若是咱們要查找定位第一個元素和最後一個元素,能夠經過表頭三個字段的長度直接定位,複雜度是 O(1)。而查找其餘元素時,就沒有這麼高效了,只能逐個查找,此時的複雜度就是 O(N)。
3)雙端列表
Redis List 數據類型一般被用於隊列、微博關注人時間軸列表等場景。不論是先進先出的隊列,仍是先進後出的棧,雙端列表都很好的支持這些特性。
雙端鏈表仍是 Redis 列表類型的底層實現之一, 當對列表類型的鍵進行操做 —— 好比執行 RPUSH 、 LPOP 或 LLEN 等命令時, 程序在底層操做的可能就是雙端鏈表。
雙端鏈表及其節點的性能特性以下:
1.節點帶有前驅和後繼指針,訪問前驅節點和後繼節點的複雜度爲 O(1)O(1) ,而且對鏈表的迭代能夠在從表頭到表尾和從表尾到表頭兩個方向進行;2.鏈表帶有指向表頭和表尾的指針,所以對錶頭和表尾進行處理的複雜度爲 O(1)O(1) ;3.鏈表帶有記錄節點數量的屬性,因此能夠在 O(1)O(1) 複雜度內返回鏈表的節點數量(長度);
除此以外,Redis爲雙端鏈表還實現了一個迭代器, 這個迭代器能夠從兩個方向對雙端鏈表進行迭代:
1.沿着節點的 next 指針前進,從表頭向表尾迭代;2.沿着節點的 prev 指針前進,從表尾向表頭迭代;
雙端鏈表的實現:
參考資料:http://origin.redisbook.com/internal-datastruct/adlist.html
4)skipList 跳躍表
從圖中能夠看到, 跳躍表主要由如下部分構成: 1.表頭(head):負責維護跳躍表的節點指針。2.跳躍表節點:保存着元素值,以及多個層。3.層:保存着指向其餘元素的指針。高層的指針越過的元素數量大於等於低層的指針,爲了提升查找的效率,程序老是從高層先開始訪問,而後隨着元素值範圍的縮小,慢慢下降層次。4.表尾:所有由 NULL 組成,表示跳躍表的末尾。
參考資料:https://redisbook.readthedocs.io/en/latest/internal-datastruct/skiplist.html
5)整數集合(intset)
Intset 只支持升級,不支持降級。
Intset 是有序的,程序使用二分查找算法來實現查找操做,複雜度爲 O(lgN) 。
Intset 是集合鍵的底層實現之一,若是一個集合是下面的狀況,那麼 Redis 就會使用 intset 來保存集合元素。
只保存着整數元素;
元素的數量很少;
參考資料:https://redisbook.readthedocs.io/en/latest/compress-datastruct/intset.html
因素4:合理的數據編碼
Redis 使用對象(redisObject)來表示數據庫中的鍵值,當咱們在 Redis 中建立一個鍵值對時,至少建立兩個對象,一個對象是用作鍵值對的鍵對象,另外一個是鍵值對的值對象。
typedef struct redisObject{ //類型:包含字符串對象、列表對象、哈希對象、集合對象、有序集合對象。 unsigned type:4; //編碼 unsigned encoding:4; //指向底層數據結構的指針 void *ptr; //... }robj;
編碼介紹:
1)String:存儲數字的話,採用 int 類型的編碼,若是是非數字的話,採用 raw 編碼;
2)List:List 對象的編碼能夠是 ziplist 或 linkedlist,字符串長度 < 64 字節且元素個數 < 512 使用 ziplist 編碼,不然轉化爲 linkedlist 編碼;
備註:這兩個條件是能夠修改的,在 redis.conf 中:list-max-ziplist-entries 512list-max-ziplist-value 64
3)Hash:Hash 對象的編碼能夠是 ziplist 或 hashtable。
當 Hash 對象同時知足如下兩個條件時,Hash 對象採用 ziplist 編碼,不然就是 hashtable 編碼。
1.Hash 對象保存的全部鍵值對的鍵和值的字符串長度均小於 64 字節。2. Hash 對象保存的鍵值對數量小於 512 個。
4)Set:Set 對象的編碼能夠是 intset 或 hashtable,intset 編碼的對象使用整數集合做爲底層實現,把全部元素都保存在一個整數集合裏面。
保存元素爲整數且元素個數小於必定範圍使用 intset 編碼,任意條件不知足,則使用 hashtable 編碼。
5)Zset:Zset 對象的編碼能夠是 ziplist 或 zkiplist,當採用 ziplist 編碼存儲時,每一個集合元素使用兩個緊挨在一塊兒的壓縮列表來存儲。
Ziplist 壓縮列表第一個節點存儲元素的成員,第二個節點存儲元素的分值,而且按分值大小從小到大有序排列。
當 Zset 對象同時知足一下兩個條件時,採用 ziplist 編碼,若是不知足以上條件的任意一個,ziplist 就會轉化爲 zkiplist 編碼。
Zset 保存的元素個數小於 128。Zset 元素的成員長度都小於 64 字節。
備註:這兩個條件是能夠修改的,在 redis.conf 中:zset-max-ziplist-entries 128zset-max-ziplist-value 64