Redis原理1-基本數(shù)據(jù)類型與存儲原理

String 字符串

存儲類型

可以用來存儲字符串兰英、整數(shù)、浮點數(shù)供鸠。

操作命令

設(shè)置多個值(批量操作畦贸,原子性)

mset weishao 1 lbj 2

設(shè)置值,如果 key 存在楞捂,則不成功

setnx weishao

基于此可實現(xiàn)分布式鎖薄坏。用 del key 釋放鎖。 但如果釋放鎖的操作失敗了寨闹,導致其他節(jié)點永遠獲取不到鎖胶坠,怎么辦? 加過期時間繁堡。單獨用 expire 加過期沈善,也失敗了,無法保證原子性椭蹄,怎么辦闻牡?多參數(shù)

set key value [expiration EX seconds|PX milliseconds][NX|XX]

使用參數(shù)的方式

set lock1 1 EX 10 NX

(整數(shù))值遞增

incr weishao
incrby weishao 100

(整數(shù))值遞減

decr weishao
decrby weishao 100

浮點數(shù)增量

set f 2.6 
incrbyfloat f 7.3

獲取多個值

mget weishao lbj

獲取值長度

strlen weishao

字符串追加內(nèi)容

append weishao 123

獲取指定范圍的字符

getrange weishao 0 5

存儲(實現(xiàn))原理

數(shù)據(jù)模型

set hello word 為例,因為Redis 是KV 的數(shù)據(jù)庫绳矩,它是通過hashtable 實現(xiàn)的(我們把這個叫做外層的哈希)罩润。所以每個鍵值對都會有一個dictEntry(源碼位置:dict.h),里面指向了key 和value 的指針翼馆。next 指向下一個dictEntry割以。

typedef struct dictEntry {
    void *key; /* key 關(guān)鍵字定義*/

    union {
        void *val; uint64_t u64; /* value 定義*/
        int64_t s64; double d;
    } v;

    struct dictEntry *next; /* 指向下一個鍵值對節(jié)點*/
} dictEntry;
image.png

key 是字符串,但是Redis 沒有直接使用C 的字符數(shù)組写妥,而是存儲在自定義的SDS中拳球。

value 既不是直接作為字符串存儲,也不是直接存儲在SDS 中珍特,而是存儲在redisObject 中祝峻。實際上五種常用的數(shù)據(jù)類型的任何一種,都是通過redisObject 來存儲的。

redisObject

redisObject 定義在src/server.h 文件中莱找。

typedef struct redisObject {
    unsigned type:4; /* 對象的類型酬姆,包括:OBJ_STRING、OBJ_LIST奥溺、OBJ_HASH辞色、OBJ_SET、OBJ_ZSET */
    unsigned encoding:4; /* 具體的數(shù)據(jù)結(jié)構(gòu)*/
    unsigned lru:LRU_BITS; /* 24 位浮定,對象最后一次被命令程序訪問的時間相满,與內(nèi)存回收有關(guān)*/
    int refcount; /* 引用計數(shù)。當refcount 為0 的時候桦卒,表示該對象已經(jīng)不被任何對象引用立美,則可以進行垃圾回收了*/
    void *ptr; /* 指向?qū)ο髮嶋H的數(shù)據(jù)結(jié)構(gòu)*/
} robj;

可以使用type 命令來查看對外的類型。

127.0.0.1:6379> type weishao
string

內(nèi)部編碼

image.png
127.0.0.1:6379> set number 1
OK
127.0.0.1:6379> set weishao "hello world hello world hello world hello world hello world hello world "
OK
127.0.0.1:6379> set lbj mvp
OK
127.0.0.1:6379> object encoding number
"int"
127.0.0.1:6379> object encoding lbj
"embstr"
127.0.0.1:6379> object encoding weishao 
"raw"

字符串類型的內(nèi)部編碼有三種:
1方灾、int建蹄,存儲8 個字節(jié)的長整型(long,2^63-1)裕偿。
2洞慎、embstr, 代表embstr 格式的SDS(Simple Dynamic String 簡單動態(tài)字符串),存儲小于44 個字節(jié)的字符串嘿棘。
3劲腿、raw,存儲大于44 個字節(jié)的字符串(3.2 版本之前是39 字節(jié))鸟妙。

/* object.c */
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44

幾個疑問

問題1谆棱、什么是SDS?
Redis 中字符串的實現(xiàn)圆仔。
在3.2 以后的版本中,SDS 又有多種結(jié)構(gòu)(sds.h):sdshdr5蔫劣、sdshdr8坪郭、sdshdr16、sdshdr32脉幢、sdshdr64歪沃,用于存儲不同的長度的字符串,分別代表
2^5=32byte嫌松,
2^8=256byte沪曙,
2^16=65536byte=64KB,
2^32byte=4GB萎羔。

/* sds.h */
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* 當前字符數(shù)組的長度*/
    uint8_t alloc; /*當前字符數(shù)組總共分配的內(nèi)存大小*/
    unsigned char flags; /* 當前字符數(shù)組的屬性液走、用來標識到底是sdshdr8 還是sdshdr16 等*/
    char buf[]; /* 字符串真正的值*/
};

問題2、為什么Redis 要用SDS 實現(xiàn)字符串?
C 語言本身沒有字符串類型(只能用字符數(shù)組char[]實現(xiàn))缘眶。
1嘱根、使用字符數(shù)組必須先給目標變量分配足夠的空間,否則可能會溢出巷懈。
2该抒、如果要獲取字符長度,必須遍歷字符數(shù)組顶燕,時間復(fù)雜度是O(n)凑保。
3、C 字符串長度的變更會對字符數(shù)組做內(nèi)存重分配涌攻。
4欧引、通過從字符串開始到結(jié)尾碰到的第一個'\0'來標記字符串的結(jié)束,因此不能保存圖片癣漆、音頻维咸、視頻、壓縮文件等二進制(bytes)保存的內(nèi)容惠爽,二進制不安全癌蓖。

SDS 的特點:
1、不用擔心內(nèi)存溢出問題婚肆,如果需要會對SDS 進行擴容租副。
2、獲取字符串長度時間復(fù)雜度為O(1)较性,因為定義了len 屬性用僧。
3、通過“空間預(yù)分配”( sdsMakeRoomFor)和“惰性空間釋放”赞咙,防止多次重分配內(nèi)存责循。
4、判斷是否結(jié)束的標志是len 屬性(它同樣以'\0'結(jié)尾是因為這樣就可以使用C語言中函數(shù)庫操作字符串的函數(shù)了)攀操,可以包含'\0'院仿。

image.png

問題3、embstr 和raw 的區(qū)別速和?
embstr 的使用只分配一次內(nèi)存空間(因為RedisObject 和SDS 是連續(xù)的)歹垫,而raw需要分配兩次內(nèi)存空間(分別為RedisObject 和SDS 分配空間)。
因此與raw 相比颠放,embstr 的好處在于創(chuàng)建時少分配一次空間排惨,刪除時少釋放一次空間,以及對象的所有數(shù)據(jù)連在一起碰凶,尋找方便暮芭。
而embstr 的壞處也很明顯鹿驼,如果字符串的長度增加需要重新分配內(nèi)存時,整個RedisObject 和SDS 都需要重新分配空間谴麦,因此Redis 中的embstr 實現(xiàn)為只讀蠢沿。

問題4:int 和embstr 什么時候轉(zhuǎn)化為raw?
當int 數(shù)據(jù)不再是整數(shù)匾效, 或大小超過了long 的范圍(2^63-1=9223372036854775807)時舷蟀,自動轉(zhuǎn)化為embstr。

問題5:明明沒有超過閾值面哼,為什么變成raw 了野宜?

127.0.0.1:6379> set k2 a
OK
127.0.0.1:6379> object encoding k2
"embstr"
127.0.0.1:6379> append k2 b
(integer) 2
127.0.0.1:6379> object encoding k2
"raw"

對于embstr,由于其實現(xiàn)是只讀的魔策,因此在對embstr 對象進行修改時匈子,都會先轉(zhuǎn)化為raw 再進行修改。
因此闯袒,只要是修改embstr 對象虎敦,修改后的對象一定是raw 的,無論是否達到了44個字節(jié)政敢。

問題6:當長度小于閾值時其徙,會還原嗎?
關(guān)于Redis 內(nèi)部編碼的轉(zhuǎn)換喷户,都符合以下規(guī)律:編碼轉(zhuǎn)換在Redis 寫入數(shù)據(jù)時完成唾那,且轉(zhuǎn)換過程不可逆,只能從小內(nèi)存編碼向大內(nèi)存編碼轉(zhuǎn)換(但是不包括重新set)

問題7:為什么要對底層的數(shù)據(jù)結(jié)構(gòu)進行一層包裝呢褪尝?
通過封裝闹获,可以根據(jù)對象的類型動態(tài)地選擇存儲結(jié)構(gòu)和可以使用的命令,實現(xiàn)節(jié)省空間和優(yōu)化查詢速度河哑。

應(yīng)用場景

緩存

String 類型
例如:熱點數(shù)據(jù)緩存(例如報表避诽,明星出軌),對象緩存璃谨,全頁緩存茎用。
可以提升熱點數(shù)據(jù)的訪問速度。

數(shù)據(jù)共享分布式

STRING 類型睬罗,因為Redis 是分布式的獨立服務(wù),可以在多個應(yīng)用之間共享
例如:分布式Session

分布式鎖

STRING 類型setnx 方法旭斥,只有不存在時才能添加成功容达,返回true。

全局ID

INT 類型垂券,INCRBY花盐,利用原子性

計數(shù)器

INT 類型羡滑,INCR 方法
例如:文章的閱讀量,微博點贊數(shù)算芯,允許一定的延遲柒昏,先寫入Redis 再定時同步到數(shù)據(jù)庫。

限流

INT 類型熙揍,INCR 方法
以訪問者的IP 和其他信息作為key职祷,訪問一次增加一次計數(shù),超過次數(shù)則返回false届囚。

位統(tǒng)計

String 類型的BITCOUNT(1.6.6 的bitmap 數(shù)據(jù)結(jié)構(gòu)介紹)有梆。
字符是以8 位二進制存儲的。

set k1 a
setbit k1 6 1
setbit k1 7 0
get k1

a 對應(yīng)的ASCII 碼是97意系,轉(zhuǎn)換為二進制數(shù)據(jù)是01100001
b 對應(yīng)的ASCII 碼是98泥耀,轉(zhuǎn)換為二進制數(shù)據(jù)是01100010
因為bit 非常節(jié)省空間(1 MB=8388608 bit),可以用來做大數(shù)據(jù)量的統(tǒng)計蛔添。
例如:在線用戶統(tǒng)計痰催,留存用戶統(tǒng)計

setbit onlineusers 0 1
setbit onlineusers 1 1
setbit onlineusers 2 0

支持按位與、按位或等等操作迎瞧。

BITOP AND destkey key [key ...] 夸溶,對一個或多個key 求邏輯并,并將結(jié)果保存到destkey 夹攒。
BITOP OR destkey key [key ...] 蜘醋,對一個或多個key 求邏輯或,并將結(jié)果保存到destkey 咏尝。
BITOP XOR destkey key [key ...] 压语,對一個或多個key 求邏輯異或,并將結(jié)果保存到destkey 编检。
BITOP NOT destkey key 胎食,對給定key 求邏輯非,并將結(jié)果保存到destkey 允懂。

計算出7 天都在線的用戶

BITOP "AND" "7_days_both_online_users" "day_1_online_users" "day_2_online_users" ... "day_7_online_users"

Hash 哈希

存儲類型

包含鍵值對的無序散列表厕怜。value 只能是字符串,不能嵌套其他類型蕾总。

同樣是存儲字符串粥航,Hash 與String 的主要區(qū)別?
1生百、把所有相關(guān)的值聚集到一個key 中递雀,節(jié)省內(nèi)存空間
2、只使用一個key蚀浆,減少key 沖突
3缀程、當需要批量獲取值的時候搜吧,只需要使用一個命令,減少內(nèi)存/IO/CPU 的消耗

Hash 不適合的場景:
1杨凑、Field 不能單獨設(shè)置過期時間
2滤奈、沒有bit 操作
3、需要考慮數(shù)據(jù)量分布的問題(value 值非常大的時候撩满,無法分布到多個節(jié)點)

操作命令

hset h1 f 6
hset h1 e 5
hmset h1 a 1 b 2 c 3 d 4
hget h1 a
hmget h1 a b c d
hkeys h1
hvals h1
hgetall h1

key 操作

hget exists h1
hdel h1
hlen h1

存儲(實現(xiàn))原理

Redis 的Hash 本身也是一個KV 的結(jié)構(gòu)蜒程,類似于Java 中的HashMap。
外層的哈希(Redis KV 的實現(xiàn))只用到了hashtable鹦牛。當存儲hash 數(shù)據(jù)類型時搞糕,叫做內(nèi)層的哈希。內(nèi)層的哈希底層可以使用兩種數(shù)據(jù)結(jié)構(gòu)實現(xiàn):

ziplist:OBJ_ENCODING_ZIPLIST(壓縮列表)
hashtable:OBJ_ENCODING_HT(哈希表)

127.0.0.1:6379> hset h2 f aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
(integer) 1
127.0.0.1:6379> hset h3 f aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
(integer) 1
127.0.0.1:6379> object encoding h2
"ziplist"
127.0.0.1:6379> object encoding h3
"hashtable"

ziplist 壓縮列表

ziplist 壓縮列表是什么曼追?

/* ziplist.c 源碼頭部注釋*/
The ziplist is a specially encoded dually linked list that is designed to be very memory efficient. It stores both strings and
integer values, where integers are encoded as actual integers instead of a series of characters. It allows push and pop
operations on either side of the list in O(1) time. However, because every operation requires a reallocation of the memory
used by the ziplist, the actual complexity is related to the amount of memory used by the ziplist.

ziplist 是一個經(jīng)過特殊編碼的雙向鏈表窍仰,它不存儲指向上一個鏈表節(jié)點和指向下一個鏈表節(jié)點的指針,而是存儲上一個節(jié)點長度和當前節(jié)點長度礼殊,通過犧牲部分讀寫性能驹吮,來換取高效的內(nèi)存空間利用率,是一種時間換空間的思想晶伦。只用在字段個數(shù)少碟狞,字段值小的場景里面。

ziplist 的內(nèi)部結(jié)構(gòu)
ziplist.c 源碼第16 行的注釋:

* <zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>
image.png
typedef struct zlentry {
    unsigned int prevrawlensize; /* 上一個鏈表節(jié)點占用的長度*/
    unsigned int prevrawlen; /* 存儲上一個鏈表節(jié)點的長度數(shù)值所需要的字節(jié)數(shù)*/
    unsigned int lensize; /* 存儲當前鏈表節(jié)點長度數(shù)值所需要的字節(jié)數(shù)*/
    unsigned int len; /* 當前鏈表節(jié)點占用的長度*/
    unsigned int headersize; /* 當前鏈表節(jié)點的頭部大谢榕恪(prevrawlensize + lensize)族沃,即非數(shù)據(jù)域的大小*/
    unsigned char encoding; /* 編碼方式*/
    unsigned char *p; /* 壓縮鏈表以字符串的形式保存,該指針指向當前節(jié)點起始位置*/
} zlentry;

編碼encoding(ziplist.c 源碼第204 行)

#define ZIP_STR_06B (0 << 6) //長度小于等于63 字節(jié)
#define ZIP_STR_14B (1 << 6) //長度小于等于16383 字節(jié)
#define ZIP_STR_32B (2 << 6) //長度小于等于4294967295 字節(jié)
image.png

問題:什么時候使用ziplist 存儲泌参?
當hash 對象同時滿足以下兩個條件的時候脆淹,使用ziplist 編碼:
1)所有的鍵值對的健和值的字符串長度都小于等于64byte(一個英文字母一個字節(jié));
2)哈希對象保存的鍵值對數(shù)量小于512 個沽一。

/* src/redis.conf 配置*/
hash-max-ziplist-value 64 // ziplist 中最大能存放的值長度
hash-max-ziplist-entries 512 // ziplist 中最多能存放的entry 節(jié)點數(shù)量
/* 源碼位置:t_hash.c 盖溺,當達字段個數(shù)超過閾值,使用HT 作為編碼*/
if (hashTypeLength(o) > server.hash_max_ziplist_entries)
    hashTypeConvert(o, OBJ_ENCODING_HT);

/*源碼位置: t_hash.c铣缠,當字段值長度過大烘嘱,轉(zhuǎn)為HT */
for (i = start; i <= end; i++) {
    if (sdsEncodedObject(argv[i]) && sdslen(argv[i]->ptr) > server.hash_max_ziplist_value){
        hashTypeConvert(o, OBJ_ENCODING_HT);
        break;
    }
}

一個哈希對象超過配置的閾值(鍵和值的長度有>64byte,鍵值對個數(shù)>512 個)時蝗蛙,會轉(zhuǎn)換成哈希表(hashtable)

hashtable(dict)

在Redis 中蝇庭,hashtable 被稱為字典(dictionary),它是一個數(shù)組+鏈表的結(jié)構(gòu)捡硅。
源碼位置:dict.h
前面我們知道了遗契,Redis 的KV 結(jié)構(gòu)是通過一個dictEntry 來實現(xiàn)的。
Redis 又對dictEntry 進行了多層的封裝病曾。

typedef struct dictEntry {
    void *key; /* key 關(guān)鍵字定義*/
    union {
        void *val; uint64_t u64; /* value 定義*/
        int64_t s64; double d;
    } v;

    struct dictEntry *next; /* 指向下一個鍵值對節(jié)點*/
} dictEntry;

dictEntry 放到了dictht(hashtable 里面):

/* This is our hash table structure. Every dictionary has two of this as we
* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
    dictEntry **table; /* 哈希表數(shù)組*/
    unsigned long size; /* 哈希表大小*/
    unsigned long sizemask; /* 掩碼大小牍蜂,用于計算索引值√┩浚總是等于size-1 */
    unsigned long used; /* 已有節(jié)點數(shù)*/
} dictht;

ht 放到了dict 里面:

typedef struct dict {
    dictType *type; /* 字典類型*/
    void *privdata; /* 私有數(shù)據(jù)*/
    dictht ht[2]; /* 一個字典有兩個哈希表*/
    long rehashidx; /* rehash 索引*/
    unsigned long iterators; /* 當前正在使用的迭代器數(shù)量*/
} dict;

從最底層到最高層dictEntry——dictht——dict——OBJ_ENCODING_HT
總結(jié):哈希的存儲結(jié)構(gòu)


image.png

注意:dictht 后面是NULL 說明第二個ht 還沒用到鲫竞。dictEntry*后面是NULL 說明沒有hash 到這個地址。dictEntry 后面是NULL 說明沒有發(fā)生哈希沖突逼蒙。

問題:為什么要定義兩個哈希表呢从绘?ht[2]
redis 的hash 默認使用的是ht[0],ht[1]不會初始化和分配空間是牢。
哈希表dictht 是用鏈地址法來解決碰撞問題的僵井。在這種情況下,哈希表的性能取決于它的大胁道狻(size 屬性)和它所保存的節(jié)點的數(shù)量(used 屬性)之間的比率:

  • 比率在1:1 時(一個哈希表ht 只存儲一個節(jié)點entry)批什,哈希表的性能最好;
  • 如果節(jié)點數(shù)量比哈希表的大小要大很多的話(這個比例用ratio 表示社搅,5 表示平均一個ht 存儲5 個entry)驻债,那么哈希表就會退化成多個鏈表,哈希表本身的性能優(yōu)勢就不再存在形葬。
    在這種情況下需要擴容合呐。Redis 里面的這種操作叫做rehash。
    rehash 的步驟:
    1笙以、為字符ht[1]哈希表分配空間淌实,這個哈希表的空間大小取決于要執(zhí)行的操作,以及ht[0]當前包含的鍵值對的數(shù)量猖腕。
    擴展:ht[1]的大小為第一個大于等于ht[0].used*2拆祈。
    2、將所有的ht[0]上的節(jié)點rehash 到ht[1]上谈息,重新計算hash 值和索引缘屹,然后放入指定的位置。
    3侠仇、當ht[0]全部遷移到了ht[1]之后轻姿,釋放ht[0]的空間,將ht[1]設(shè)置為ht[0]表逻炊,并創(chuàng)建新的ht[1]互亮,為下次rehash 做準備。

問題:什么時候觸發(fā)擴容余素?
負載因子(源碼位置:dict.c):

static int dict_can_resize = 1;
static unsigned int dict_force_resize_ratio = 5;

ratio = used / size豹休,已使用節(jié)點與字典大小的比例
dict_can_resize 為1 并且dict_force_resize_ratio 已使用節(jié)點數(shù)和字典大小之間的比率超過1:5,觸發(fā)擴容

應(yīng)用場景

String 可以做的事情桨吊,Hash 都可以做威根。

存儲對象類型的數(shù)據(jù)

比如對象或者一張表的數(shù)據(jù)凤巨,比String 節(jié)省了更多key 的空間,也更加便于集中管理洛搀。

購物車

key:用戶id敢茁;field:商品id;value:商品數(shù)量留美。
+1:hincr彰檬。-1:hdecr。刪除:hdel谎砾。全選:hgetall逢倍。商品數(shù):hlen。

List 列表

操作命令

元素增減:

lpush queue a
lpush queue b c
rpush queue d e
lpop queue
rpop queue
blpop queue
brpop queue

取值

lindex queue 0
lrange queue 0 -1
image.png

存儲(實現(xiàn))原理

在早期的版本中景图,數(shù)據(jù)量較小時用ziplist 存儲较雕,達到臨界值時轉(zhuǎn)換為linkedlist 進行存儲,分別對應(yīng)OBJ_ENCODING_ZIPLIST 和OBJ_ENCODING_LINKEDLIST 症歇。
3.2 版本之后郎笆,統(tǒng)一用quicklist 來存儲。quicklist 存儲了一個雙向鏈表忘晤,每個節(jié)點都是一個ziplist宛蚓。

127.0.0.1:6379> object encoding queue
"quicklist"

quicklist

quicklist(快速列表)是ziplist 和linkedlist 的結(jié)合體。
quicklist.h设塔,head 和tail 指向雙向鏈表的表頭和表尾

typedef struct quicklist {
    quicklistNode *head; /* 指向雙向鏈表的表頭*/
    quicklistNode *tail; /* 指向雙向鏈表的表尾*/
    unsigned long count; /* 所有的ziplist 中一共存了多少個元素*/
    unsigned long len; /* 雙向鏈表的長度凄吏,node 的數(shù)量*/
    int fill : 16; /* fill factor for individual nodes */
    unsigned int compress : 16; /* 壓縮深度,0:不壓縮闰蛔; */
} quicklist;

quicklistNode 中的*zl 指向一個ziplist痕钢,一個ziplist 可以存放多個元素。

typedef struct quicklistNode {
struct quicklistNode *prev; /* 前一個節(jié)點*/
struct quicklistNode *next; /* 后一個節(jié)點*/
unsigned char *zl; /* 指向?qū)嶋H的ziplist */
unsigned int sz; /* 當前ziplist 占用多少字節(jié)*/
unsigned int count : 16; /* 當前ziplist 中存儲了多少個元素序六,占16bit(下同)任连,最大65536 個*/
unsigned int encoding : 2; /* 是否采用了LZF 壓縮算法壓縮節(jié)點,1:RAW 2:LZF */
unsigned int container : 2; /* 2:ziplist例诀,未來可能支持其他結(jié)構(gòu)存儲*/
unsigned int recompress : 1; /* 當前ziplist 是不是已經(jīng)被解壓出來作臨時使用*/
unsigned int attempted_compress : 1; /* 測試用*/
unsigned int extra : 10; /* 預(yù)留給未來使用*/
} quicklistNode;
image.png

ziplist 的結(jié)構(gòu)前面已經(jīng)說過了随抠,不再重復(fù)。

應(yīng)用場景

用戶消息時間線timeline

因為List 是有序的繁涂,可以用來做用戶時間線

消息隊列

List 提供了兩個阻塞的彈出操作:BLPOP/BRPOP拱她,可以設(shè)置超時時間。
BLPOP:BLPOP key1 timeout 移出并獲取列表的第一個元素扔罪, 如果列表沒有元素會阻塞列表直到等待超時或發(fā)現(xiàn)可彈出元素為止秉沼。
BRPOP:BRPOP key1 timeout 移出并獲取列表的最后一個元素, 如果列表沒有元素會阻塞列表直到等待超時或發(fā)現(xiàn)可彈出元素為止。

隊列:先進先出:rpush blpop唬复,左頭右尾矗积,右邊進入隊列,左邊出隊列敞咧。
棧:先進后出:rpush brpop

Set 集合

存儲類型

String 類型的無序集合漠魏,最大存儲數(shù)量2^32-1(40 億左右)。

操作命令

添加一個或者多個元素

sadd myset a b c d e f g

獲取所有元素

smembers myset

統(tǒng)計元素個數(shù)

scard myset

隨機獲取一個元素

srandmember key

隨機彈出一個元素

spop myset

移除一個或者多個元素

srem myset d e f

查看元素是否存在

sismember myset a

存儲(實現(xiàn))原理

Redis 用intset 或hashtable 存儲set妄均。如果元素都是整數(shù)類型,就用inset 存儲哪自。
如果不是整數(shù)類型丰包,就用hashtable(數(shù)組+鏈表的存來儲結(jié)構(gòu))。
問題:KV 怎么存儲set 的元素壤巷?key 就是元素的值邑彪,value 為null。
如果元素個數(shù)超過512 個胧华,也會用hashtable 存儲寄症。

配置文件redis.conf
set-max-intset-entries 512
127.0.0.1:6379> sadd iset 1 2 3 4 5 6
(integer) 6
127.0.0.1:6379> object encoding iset
"intset"
127.0.0.1:6379> sadd myset a b c d e f
(integer) 6
127.0.0.1:6379> object encoding myset
"hashtable"

應(yīng)用場景

抽獎

隨機獲取元素
spop myset

點贊、簽到矩动、打卡

這條微博的ID 是t1001有巧,用戶ID 是u3001。
用like:t1001 來維護t1001 這條微博的所有點贊用戶悲没。
點贊了這條微博:sadd like:t1001 u3001
取消點贊:srem like:t1001 u3001
是否點贊:sismember like:t1001 u3001
點贊的所有用戶:smembers like:t1001
點贊數(shù):scard like:t1001
比關(guān)系型數(shù)據(jù)庫簡單許多篮迎。

商品標簽

sadd tags:i5001 畫面清晰細膩
sadd tags:i5001 真彩清晰顯示屏
sadd tags:i5001 流暢至極

商品篩選

獲取差集
獲取交集
獲取并集

ZSet 有序集合

存儲類型

image.png

sorted set,有序的set示姿,每個元素有個score甜橱。
score 相同時,按照key 的ASCII 碼排序栈戳。
數(shù)據(jù)結(jié)構(gòu)對比:


image.png

操作命令

添加元素

zadd myzset 10 java 20 php 30 ruby 40 cpp 50 python

獲取全部元素

zrange myzset 0 -1 withscores
zrevrange myzset 0 -1 withscores

根據(jù)分值區(qū)間獲取元素

zrangebyscore myzset 20 30

移除元素
也可以根據(jù)score rank 刪除

zrem myzset php cpp

統(tǒng)計元素個數(shù)

zcard myzset

分值遞增

zincrby myzset 5 python

根據(jù)分值統(tǒng)計個數(shù)

zcount myzset 20 60

獲取元素rank

zrank myzset java

獲取元素score

zsocre myzset java

也有倒序的rev 操作(reverse)

存儲(實現(xiàn))原理

同時滿足以下條件時使用ziplist 編碼:

  • 元素數(shù)量小于128 個
  • 所有member 的長度都小于64 字節(jié)

在ziplist 的內(nèi)部岂傲,按照score 排序遞增來存儲。插入的時候要移動之后的數(shù)據(jù)子檀。

對應(yīng)redis.conf 參數(shù):
zset-max-ziplist-entries 128
zset-max-ziplist-value 64

超過閾值之后镊掖,使用skiplist+dict 存儲。

問題:什么是skiplist命锄?
我們先來看一下有序鏈表:

image.png

在這樣一個鏈表中堰乔,如果我們要查找某個數(shù)據(jù),那么需要從頭開始逐個進行比較脐恩,直到找到包含數(shù)據(jù)的那個節(jié)點镐侯,或者找到第一個比給定數(shù)據(jù)大的節(jié)點為止(沒找到)。
也就是說,時間復(fù)雜度為O(n)苟翻。同樣韵卤,當我們要插入新數(shù)據(jù)的時候,也要經(jīng)歷同樣的查找過程崇猫,從而確定插入位置沈条。
而二分查找法只適用于有序數(shù)組,不適用于鏈表诅炉。
假如我們每相鄰兩個節(jié)點增加一個指針(或者理解為有三個元素進入了第二層)蜡歹,讓指針指向下下個節(jié)點。
image.png

這樣所有新增加的指針連成了一個新的鏈表涕烧,但它包含的節(jié)點個數(shù)只有原來的一半(上圖中是7, 19, 26)月而。在插入一個數(shù)據(jù)的時候,決定要放到那一層议纯,取決于一個算法(在redis 中t_zset.c 有一個zslRandomLevel 這個方法)驼卖。

現(xiàn)在當我們想查找數(shù)據(jù)的時候步责,可以先沿著這個新鏈表進行查找络凿。當碰到比待查數(shù)據(jù)大的節(jié)點時觉既,再回到原來的鏈表中的下一層進行查找。比如阀参,我們想查找23肝集,查找的路徑是沿著下圖中標紅的指針所指向的方向進行的:


image.png
  1. 23 首先和7 比較,再和19 比較结笨,比它們都大包晰,繼續(xù)向后比較。
  2. 但23 和26 比較的時候炕吸,比26 要小伐憾,因此回到下面的鏈表(原鏈表),與22比較赫模。
  3. 23 比22 要大树肃,沿下面的指針繼續(xù)向后和26 比較。23 比26 小瀑罗,說明待查數(shù)據(jù)23 在原鏈表中不存在

在這個查找過程中胸嘴,由于新增加的指針,我們不再需要與鏈表中每個節(jié)點逐個進行比較了斩祭。需要比較的節(jié)點數(shù)大概只有原來的一半劣像。這就是跳躍表。
為什么不用AVL 樹或者紅黑樹摧玫?因為skiplist 更加簡潔耳奕。

應(yīng)用場景

排行榜
id 為6001 的新聞點擊數(shù)加1:zincrby hotNews:20190926 1 n6001
獲取今天點擊最多的15 條:zrevrange hotNews:20190926 0 15 withscores

——學自咕泡學院

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子屋群,更是在濱河造成了極大的恐慌闸婴,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芍躏,死亡現(xiàn)場離奇詭異邪乍,居然都是意外死亡,警方通過查閱死者的電腦和手機对竣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門庇楞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人否纬,你說我怎么就攤上這事姐刁。” “怎么了烦味?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長壁拉。 經(jīng)常有香客問我谬俄,道長,這世上最難降的妖魔是什么弃理? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任溃论,我火速辦了婚禮,結(jié)果婚禮上痘昌,老公的妹妹穿的比我還像新娘钥勋。我一直安慰自己,他們只是感情好辆苔,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布算灸。 她就那樣靜靜地躺著,像睡著了一般驻啤。 火紅的嫁衣襯著肌膚如雪菲驴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天骑冗,我揣著相機與錄音赊瞬,去河邊找鬼。 笑死贼涩,一個胖子當著我的面吹牛巧涧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播遥倦,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼谤绳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起闷供,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤烟央,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后歪脏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疑俭,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年婿失,在試婚紗的時候發(fā)現(xiàn)自己被綠了钞艇。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡豪硅,死狀恐怖哩照,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情懒浮,我是刑警寧澤飘弧,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站砚著,受9級特大地震影響次伶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜稽穆,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一冠王、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧舌镶,春花似錦柱彻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至否灾,卻和暖如春吓蘑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坟冲。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工磨镶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人健提。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓琳猫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親私痹。 傳聞我的和親對象是個殘疾皇子脐嫂,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載:可能是目前最詳細的Redis內(nèi)存模型及應(yīng)用解讀 Redis是目前最火爆的內(nèi)存數(shù)據(jù)庫之一统刮,通過在內(nèi)存中讀寫數(shù)據(jù)...
    jwnba24閱讀 624評論 0 4
  • Redis主要支持的數(shù)據(jù)類型有5種:String ,Hash 账千,List 侥蒙,Set ,和 Sorted Set匀奏。 ...
    愛情小傻蛋閱讀 1,416評論 0 0
  • 前言 Redis是目前最火爆的內(nèi)存數(shù)據(jù)庫之一鞭衩,通過在內(nèi)存中讀寫數(shù)據(jù),大大提高了讀寫速度娃善,可以說Redis是實現(xiàn)網(wǎng)站...
    小陳阿飛閱讀 805評論 0 1
  • 同去同歸同天地论衍,同水同情同夢想。共建共美地球村聚磺,共同一念圖圖騰坯台。
    愛成云留閱讀 253評論 3 3
  • 如果可以 我想把面包分一半與你 再陪你走一段遙遠的路 踏著冬日的積雪 路的盡頭是映著圓月的海灣 此刻 風和樹干都已...
    夏木之森閱讀 682評論 8 6