這篇 redis 學習筆記主要介紹 redis 的數據結構和數據類型,並討論數據結構的選擇以及應用場景的優化。html
Redis是一種面向「鍵/值」對類型數據的分佈式NoSQL數據庫系統,特色是高性能,持久存儲,適應高併發的應用場景。redis
Sds (Simple Dynamic String,簡單動態字符串)是 Redis 底層所使用的字符串表示,它被用 在幾乎全部的 Redis 模塊中sql
Redis 是一個鍵值對數據庫(key-value DB),數據庫的值能夠是字符串、集合、列表等多種類 型的對象,而數據庫的鍵則老是字符串對象shell
在 Redis 中, 一個字符串對象除了能夠保存字符串值以外,還能夠保存 long 類型的值當字符串對象保存的是字符串時,它包含的纔是 sds 值,不然的話,它就 是一個 long 類型的值數據庫
雙端鏈表仍是 Redis 列表類型的底層實現之一,當對列表類型的鍵進行操做——好比執行 RPUSH 、LPOP 或 LLEN 等命令時,程序在底層操做的可能就是雙端鏈表數組
字典(dictionary),又名映射(map)或關聯數組(associative array), 它是一種抽象數據結 構,由一集鍵值對(key-value pairs)組成,各個鍵值對的鍵各不相同,程序能夠將新的鍵值對 添加到字典中,或者基於鍵進行查找、更新或刪除等操做緩存
Redis 是一個鍵值對數據庫,數據庫中的鍵值對就由字典保存:每一個數據庫都有一個與之相對應的字典,這個字典被稱之爲鍵空間(key space)。bash
Redis 的 Hash 類型鍵使用字典和壓縮列表兩種數據結構做爲底層實現數據結構
跳躍表(skiplist)是一種隨機化的數據,由 William Pugh 在論文《Skip lists: a probabilistic alternative to balanced trees》中提出,這種數據結構以有序的方式在層次化的鏈表中保存元素,它的效率能夠和平衡樹媲美——查找、刪除、添加等操做均可以在對數指望時間下完成, 而且比起平衡樹來講,跳躍表的實現要簡單直觀得多併發
和字典、鏈表或者字符串這幾種在 Redis 中大量使用的數據結構不一樣,跳躍表在 Redis 的惟一做用,就是實現有序集數據類型
跳躍表將指向有序集的 score 值和 member 域的指針做爲元素,並以 score 值爲索引,對有序集元素進行排序。
整數集合(intset)用於有序、無重複地保存多個整數值,它會根據元素的值,自動選擇該用什麼長度的整數類型來保存元素
Intset 是集合鍵的底層實現之一,若是一個集合:
Ziplist 是由一系列特殊編碼的內存塊構成的列表,一個 ziplist 能夠包含多個節點(entry),每一個節點能夠保存一個長度受限的字符數組(不以 0 結尾的 char 數組)或者整數
redisObject 是 Redis 類型系統的核心,數據庫中的每一個鍵、值,以及 Redis 自己處理的參數,都表示爲這種數據類型
redisObject 的定義位於 redis.h :
/* * Redis 對象 */ typedef struct redisObject { // 類型 unsigned type:4; // 對齊位 unsigned notused:2; // 編碼方式 unsigned encoding:4; // LRU 時間(相對於 server.lruclock) unsigned lru:22; // 引用計數 int refcount; // 指向對象的值 void *ptr; } robj;
type 、encoding 和 ptr 是最重要的三個屬性。
type 記錄了對象所保存的值的類型,它的值多是如下常量的其中一個
/* * 對象類型 */ #define REDIS_STRING 0 // 字符串 #define REDIS_LIST 1 // 列表 #define REDIS_SET 2 // 集合 #define REDIS_ZSET 3 // 有序集 #define REDIS_HASH 4 // 哈希表
encoding 記錄了對象所保存的值的編碼,它的值多是如下常量的其中一個
/* * 對象編碼 */ #define REDIS_ENCODING_RAW 0 // 編碼爲字符串 #define REDIS_ENCODING_INT 1 // 編碼爲整數 #define REDIS_ENCODING_HT 2 // 編碼爲哈希表 #define REDIS_ENCODING_ZIPMAP 3 // 編碼爲 zipmap(2.6 後再也不使用) #define REDIS_ENCODING_LINKEDLIST 4 // 編碼爲雙端鏈表 #define REDIS_ENCODING_ZIPLIST 5 // 編碼爲壓縮列表 #define REDIS_ENCODING_INTSET 6 // 編碼爲整數集合 #define REDIS_ENCODING_SKIPLIST 7 // 編碼爲跳躍表
ptr 是一個指針,指向實際保存值的數據結構,這個數據結構由 type 屬性和 encoding 屬性決定。
當執行一個處理數據類型的命令時,Redis 執行如下步驟:
REDIS_STRING (字符串)是 Redis 使用得最爲普遍的數據類型,它除了是 SET 、GET 等命令 的操做對象以外,數據庫中的全部鍵,以及執行命令時提供給 Redis 的參數,都是用這種類型 保存的。
字符串類型分別使用 REDIS_ENCODING_INT 和 REDIS_ENCODING_RAW 兩種編碼
只有能表示爲 long 類型的值,纔會以整數的形式保存,其餘類型 的整數、小數和字符串,都是用 sdshdr 結構來保存
REDIS_HASH (哈希表)是HSET 、HLEN 等命令的操做對象
它使用 REDIS_ENCODING_ZIPLIST和REDIS_ENCODING_HT 兩種編碼方式
Redis 中每一個hash能夠存儲232-1鍵值對(40多億)
REDIS_LIST(列表)是LPUSH 、LRANGE等命令的操做對象
它使用 REDIS_ENCODING_ZIPLIST和REDIS_ENCODING_LINKEDLIST 這兩種方式編碼
一個列表最多能夠包含232-1 個元素(4294967295, 每一個列表超過40億個元素)。
REDIS_SET (集合) 是 SADD 、 SRANDMEMBER 等命令的操做對象
它使用 REDIS_ENCODING_INTSET 和 REDIS_ENCODING_HT 兩種方式編碼
Redis 中集合是經過哈希表實現的,因此添加,刪除,查找的複雜度都是O(1)。
集合中最大的成員數爲 232 - 1 (4294967295, 每一個集合可存儲40多億個成員)
REDIS_ZSET (有序集)是ZADD 、ZCOUNT 等命令的操做對象
它使用 REDIS_ENCODING_ZIPLIST和REDIS_ENCODING_SKIPLIST 兩種方式編碼
不一樣的是每一個元素都會關聯一個double類型的分數。redis正是經過分數來爲集合中的成員進行從小到大的排序。
有序集合的成員是惟一的,但分數(score)卻能夠重複。
集合是經過哈希表實現的,因此添加,刪除,查找的複雜度都是O(1)。 集合中最大的成員數爲 232 - 1 (4294967295, 每一個集合可存儲40多億個成員)
在數據庫中,全部鍵的過時時間都被保存在 redisDb 結構的 expires 字典裏:
typedef struct redisDb { // ... dict *expires; // ... } redisDb;
expires 字典的鍵是一個指向 dict 字典(鍵空間)裏某個鍵的指針,而字典的值則是鍵所指 向的數據庫鍵的到期時間,這個值以 long long 類型表示
Redis 有四個命令能夠設置鍵的生存時間(能夠存活多久)和過時時間(何時到期):
雖然有那麼多種不一樣單位和不一樣形式的設置方式,可是 expires 字典的值只保存「以毫秒爲單位的過時 UNIX 時間戳」 ,這就是說,經過進行轉換,全部命令的效果最後都和 PEXPIREAT 命令的效果同樣。
若是一個鍵是過時的,那它何時會被刪除?
下邊是參考答案
Redis 使用的過時鍵刪除策略是惰性刪除加上按期刪除
好比你能夠把上面說到的sorted set的score值設置成過時時間的時間戳,那麼就能夠簡單地經過過時時間排序,定時清除過時數據了,不只是清除Redis中的過時數據,你徹底能夠把Redis裏這個過時時間當成是對數據庫中數據的索引,用Redis來找出哪些數據須要過時刪除,而後再精準地從數據庫中刪除相應的記錄
這個需求與上面需求的不一樣之處在於,前面操做以時間爲權重,這個是以某個條件爲權重,好比按頂的次數排序,這時候就須要咱們的sorted set出馬了,將你要排序的值設置成sorted set的score,將具體的數據設置成相應的value,每次只須要執行一條ZADD命令便可
使用 incr 命令 定時使用 getset 命令 讀取數據 並設置新的值 0
例如假設咱們的話題D 1000被加了三個標籤tag 1,2,5和77,就能夠設置下面兩個集合:
$ redis-cli sadd topics:1000:tags 1 (integer) 1 $ redis-cli sadd topics:1000:tags 2 (integer) 1 $ redis-cli sadd topics:1000:tags 5 (integer) 1 $ redis-cli sadd topics:1000:tags 77 (integer) 1 $ redis-cli sadd tag:1:objects 1000 (integer) 1 $ redis-cli sadd tag:2:objects 1000 (integer) 1 $ redis-cli sadd tag:5:objects 1000 (integer) 1 $ redis-cli sadd tag:77:objects 1000 (integer) 1
要獲取一個對象的全部標籤:
$ redis-cli smembers topics:1000:tags 1. 5 2. 1 3. 77 4. 2
得到一份同時擁有標籤1, 2,10和27的對象列表。
這能夠用SINTER命令來作,他能夠在不一樣集合之間取出交集
問題
: Instagram的照片數量已經達到3億,而在Instagram裏,咱們須要知道每一張照片的做者是誰,下面就是Instagram團隊如何使用Redis來解決這個問題並進行內存優化的。
具體方法,參考下邊這篇文章:節約內存:Instagram的Redis實踐。
最後,感謝女友支持。
歡迎關注(April_Louisa) | 請我喝芬達 |
---|---|