Redis如今基本也算是後臺開發的基礎服務,基本像Mysql同樣廣泛在應用中使用了。我第一次接觸的Nosql是memcache用來解決誇服務session共享問題。後來由於memcache沒法持久化問題改成使用Redis。此次主要針對Redis作一個整理。c++
類型 | 特色說明 |
---|---|
String | 類型是 Redis 最基本的數據類型,string 類型的值最大能存儲 512MB |
Hash | Redis hash 是一個 string 類型的 field 和 value 的映射表,hash 特別適合用於存儲對象。 |
List | Redis 列表是簡單的字符串列表,按照插入順序排序。你能夠添加一個元素到列表的頭部(左邊)或者尾部(右邊) |
Set | Redis 的 Set 是 string 類型的無序集合。集合是經過哈希表實現的,因此添加,刪除,查找的複雜度都是 O(1) |
ZSet | 與Set不一樣的是每一個元素都會關聯一個double類型的分數。redis正是經過分數來爲集合中的成員進行從小到大的排序 |
HyperLogLog | 在 2.8.9 版本添加是用來作基數統計的算法,HyperLogLog 的優勢是,在輸入元素的數量或者體積很是很是大時,計算基數所需的空間老是固定 的、而且是很小的 |
Bitmaps | 可作爲布隆過濾器使用 |
GeoHash | Redis 3.2 版本地理空間位置(緯度、經度、名稱) |
以set k1 hello爲例,由於Redis是KV的數據庫,它是經過hashtable實現的(咱們把這個叫作外層的哈希)。因此每一個鍵值對都會有一個dictEntry(源碼位置:dict.h),裏面指向了key和value的指針。next指向下一個dictEntry。redis
typedef struct dictEntry { void *key; /* Key關鍵字定義 */ union { void *val; /* value定義 */ uint64_t u64; int64_t s64; double d; } v; struct dictEntry *next;/*下一個節點*/ } dictEntry;
key是字符串,可是Redis沒有直接使用C的字符數組,而是存儲在自定義的SDS中。算法
value既不是直接做爲字符串存儲,也不是直接存儲在SDS中,而是存儲在redisObject中。實際上五種經常使用的數據類型的任何一種,都是經過redisObject來存儲的。sql
redisObject定義在src/server.h文件中數據庫
typedef struct redisObject { unsigned type:4; /*對象類型,包括 OBJ_STRING、OBJ_LIST、OBJ_HASH、OBJ_SET、OBJ_ZSET*/ unsigned encoding:4;/* 具體數據結構*/ unsigned lru:LRU_BITS; /*24位,對象最後一次被命令程序訪問的時間,與內存回收有關*/ /* LRU time (relative to global lru_clock) or * LFU data (least significant 8 bits frequency * and most significant 16 bits access time). */ int refcount;/*引用計數。當refcount爲0的時候,表示該對象已經不被任何對象引用,則能夠進行垃圾回收了*/ void *ptr;/*指向對象實際的數據結構*/ } robj;
127.0.0.1:6379>set num1 1 OK 127.0.0.1:6379>set str1 "aaaadddddddddddddddddddddddddddddddccccccccccccccccccc" OK 127.0.0.1:6379>set str2 beijing OK 127.0.0.1:6379>object encoding num1 "int" 127.0.0.1:6379>object encoding str1 "embstr" 127.0.0.1:6379>object encoding str2 "raw
字符串類型的內部編碼有三種:數組
一、int,存儲8個字節的長整型(long,2^63-1)。安全
二、embstr,表明embstr格式的SDS(SimpleDynamicString簡單動態字符串),存儲小於44個字節的字符串。session
三、raw,存儲大於44個字節的字符串(3.2版本以前是39字節)。數據結構
/* <object.c> * Create a string object with EMBSTR encoding if it is smaller than * OBJ_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is * used. * * The current limit of 44 is chosen so that the biggest string object * we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */ #define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
Redis中字符串的實現。在3.2之後的版本中,SDS又有多種結構(sds.h):sdshdr五、sdshdr八、sdshdr1六、sdshdr3二、sdshdr64,用於存儲不一樣的長度的字符串,分別表明:
2^5=32byte
2^8=256byte
2^16=65536byte=64KB
2^32byte=4GB。app
C語言自己沒有字符串類型(只能用字符數組char[]實現)。
一、使用字符數組必須先給目標變量分配足夠的空間,不然可能會溢出。
二、若是要獲取字符長度,必須遍歷字符數組,時間複雜度是O(n)。
三、C字符串長度的變動會對字符數組作內存重分配。
四、經過從字符串開始到結尾碰到的第一個'\0'來標記字符串的結束,所以不能保存圖片、音頻、視頻、壓縮文件等二進制(bytes)保存的內容,二進制不安全。
一、不用擔憂內存溢出問題,若是須要會對SDS進行擴容。
二、獲取字符串長度時間複雜度爲O(1),由於定義了len屬性。
三、經過「空間預分配」(sdsMakeRoomFor)和「惰性空間釋放」,防止屢次重分配內存。
四、判斷是否結束的標誌是len屬性(它一樣以'\0'結尾是由於這樣就可使用C語言中函數庫操做字符串的函數了),能夠包含'\0'。
c字符串 | SDS |
---|---|
embstr | 只讀 |
獲取字符串長度的複雜度爲O(N) | 獲取字符串長度的複雜度爲O(1) |
API是不安全的,可能會形成緩衝區溢出 | API是安全的,不會早晨個緩衝區溢出 |
修改字符串長度N次必然須要執行N次內存重分配 | 修改字符串長度N次最多須要執行N次內存重分配 |
只能保存文本數據 | 能夠保存文本或者二進制數據 |
可使用全部<string.h>庫中的函數 | 可使用一部分<string.h>庫中的函數 |
embstr的使用只分配一次內存空間(由於RedisObject和SDS是連續的),而raw須要分配兩次內存空間(分別爲RedisObject和SDS分配空間)。
所以與raw相比,embstr的好處在於建立時少分配一次空間,刪除時少釋放一次空間,以及對象的全部數據連在一塊兒,尋找方便。
而embstr的壞處也很明顯,若是字符串的長度增長鬚要從新分配內存時,整個RedisObject和SDS都須要從新分配空間,所以Redis中的embstr實現爲只讀。
當int數據再也不是整數,或大小超過了long的範圍(2^631=9223372036854775807)時,自動轉化爲embstr。
127.0.0.1:6379>set s1 1 OK 127.0.0.1:6379>append s1 a (integer)2 127.0.0.1:6379>object encoding s1 "raw"
127.0.0.1:6379>set s2 a OK 127.0.0.1:6379>object encoding s2 "embstr" 127.0.0.1:6379>append s2 b (integer)2 127.0.0.1:6379>object encoding s2 "raw"
對於embstr,因爲其實現是隻讀的,所以在對embstr對象進行修改時,都會先轉化爲raw再進行修改。
所以,只要是修改embstr對象,修改後的對象必定是raw的,不管是否達到了44個字節。
關於Redis內部編碼的轉換,都符合如下規律:編碼轉換在Redis寫入數據時完成,且轉換過程不可逆,只能從小內存編碼向大內存編碼轉換(可是不包括從新set)
經過封裝,能夠根據對象的類型動態地選擇存儲結構和可使用的命令,實現節省空間和優化查詢速度。