Redis數據類型及數據結構

1 Redis數據類型

Redis包含五大基本數據類型和三個特殊類型,下面介紹數據類型的基本使用。

1.1 五大基本數據類型

1.1.1 String類型

String類型可以存數字、存字符串、存序列化的對象等

存數字時,可以執行加減操作

27.0.0.1:2222> set value 10
OK

127.0.0.1:2222> get value
"10"

127.0.0.1:2222> decr value   # 減一操作
(integer) 9

127.0.0.1:2222> incrby value 5   # 執行加5
(integer) 14


存字符串

127.0.0.1:6379[2]> APPEND key1 abcd   #在字符串中追加字符串
(integer) 6

127.0.0.1:6379[2]> strlen key1       #獲取字符串的長度
(integer) 6

127.0.0.1:6379[2]> GETRANGE key1 0 3   #截取範圍字符串(雙閉區間)
"v2ab"

127.0.0.1:6379[2]> mset k1 v1 k2 v2 k3 v3  #同時存入多個值
OK


存對象

存對象時,其實也是將對象序列化成字符串進行存儲。



1.1.2 List類型

list集合類型,在redis中可以頭插入/刪除、尾插入/刪除;可以尾插入尾刪除實現棧,尾插入頭刪除實現隊列,也可以單純的存可重複的集合元素。

lpush/rpush/lpop/rpop   #左右插入 左右刪除
lindex list 1   #根據下標獲取值
lrange list 0 1   lrange list 0 -1  #下標範圍獲取值
llen list    #獲取list的長度
lrem list 1 one   #刪除list中one,若有多個只移除一個
ltrim list 1 2    #通過下標截取list,list將會被改變
lset list 0 item   #將list的0號元素更新爲item
linsert list before one zone  #將one的前面插入zone


1.1.3 Set類型

存儲不重複的集合元素,比如在商品秒殺中存儲某商品已經秒殺的用戶ID,防止重複秒殺。

Redis中還可以返回兩個集合的並集、交集等實現多元功能,比如兩個用戶的共同好友。

sadd myset hello  # 添加值
smembers myset    #查看所有值
sismember myset hello  #是否有hello
scard myset        #獲取set集合中的元素個數
srem myset hello   #刪除元素
srandmember myset  #隨機的獲取其中一個值
spop myset         #隨機的刪除其中一個值
sdiff set1 set2    #返回兩個集合的差集   
sinter set1 set2   #返回兩個集合的交集(共同好友)


1.1.4 Zset類型

在set的基礎上增加一個score用於排序。比如在博客文章閱讀排行榜中,可以返回閱讀量從高到低的排行。

zadd myset 1 one   #1爲score,用於排序
zrangebyscore myset -inf +inf  #按照score升序排列。-inf +inf代表負無窮和正無窮
zrangebyscore myset -inf 100  #按照score升序排列,並輸出score在[-inf, 100]之間的value
zrange myset 0 -1   #默認升序輸出全部


zrevrange myset 0 -1  #降序輸出全部
ZREVRANGEBYSCORE zset1 10 -10  #按score降序排列,並輸出score在[-10, 10]  之間的value

zrem salary zhangsan  #刪除zhangsan
zcard salary  #獲取salary有序集合中元素個數
zcount salary 100 1000  #獲取指定區間的元素個數


1.1.5 Hash類型

存入鍵值對

hset myhash k1 v1   #向myhash添加鍵值對k1-v1
hmset myhash k2 v2 k3 v3    #向myhash添加鍵值對
hget myhash k1
hmget myhash k2 k3

127.0.0.1:6379[2]> hgetall myhash   #獲取所有的鍵值
1) "k1"
2) "v1"
3) "k2"
4) "v2"
5) "k3"
6) "v3"

hkeys myhash   #獲取所有的key
hvals myhash   #獲取所有的value

hexists myhash k1  #判斷字段是否存在

1.2 三種特殊類型

1.2.1 geospatial 地理位置

應用:朋友定位,附近的人,打車距離計算?

geoadd命令:添加地理位置

## 規則:兩極無法添加,一般下載城市數據,直接java程序導入。
## 有效經度 -180~180  有效緯度 -85~85
##  geoadd key value(經度 緯度 名稱)
127.0.0.1:6379[2]> GEOADD china:city 116.40 39.90 beijing 121.44 31.21 shanghai
127.0.0.1:6379[2]> GEOADD china:city 104.07 30.67 chengdu 106.54 29.58 chongqing
127.0.0.1:6379[2]> GEOADD china:city 114.109 22.54 shenzhen 120.16 30.31 hangzhou

geopos命令:獲取地理位置

127.0.0.1:6379[2]> geopos china:city chengdu
1) 1) "104.07000213861465454"
   2) "30.67000055930392222"

geodist命令:返回兩個位置之間的距離

單位: m km

127.0.0.1:6379[2]> geodist china:city chengdu beijing km  查看成都到北京的直線距離
"1516.1652"

georadius 以某個位置爲中心,找出半徑多少內的元素

附近的人功能實現?

127.0.0.1:6379[2]> GEORADIUS china:city 110 30 1000 km   距離110 30 直線距離小於1000km的城市
1) "chongqing"
2) "chengdu"
3) "shenzhen"
4) "hangzhou"

127.0.0.1:6379[2]> GEORADIUS china:city 110 30 1000 km count 2 限制個數爲2,只查詢兩個
1) "chongqing"
2) "chengdu"

georadiusByMember 通過名字來確定位置,找出半徑多少內的元素

127.0.0.1:6379[2]> GEORADIUSbymember china:city chengdu 1000 km count 2
1) "chengdu"
2) "chongqing"

1.2.2 Hyperloglog基數統計

應用:統計網站訪問的人數,不可重複計算。

傳統方法:建立一個set保存用戶id,訪問一個用戶就往set存入,最後返回set大小

Hyperloglog統計:將用戶ID放入Hyperloglog即可

對比:放入Set的方法會佔用較多內存,Hyperloglog利用位圖減少內存開銷,但是有一定誤差

pfadd users a b c d e f g h i a c d a b  #將userid放入
pfcount users   #統計基數數量(排除重複元素)

1.2.3 bitmap位圖

位圖:按位存儲,每位只能存儲0或1,分別代表兩種狀態(多種狀態也可以擴展多個位代表某個狀態)

應用:統計用戶一年每天登錄狀態

127.0.0.1:6379[2]> setbit sign 0 1  #設置第0位爲1
127.0.0.1:6379[2]> setbit sign 1 1  #設置第1位爲1....

127.0.0.1:6379[2]> GETBIT sign 0 #獲取第0位

127.0.0.1:6379[2]> bitcount sign  #獲取爲1的個數
127.0.0.1:6379[2]> bitcount sign 0 5   #獲取在0-5之間爲1的個數



2 Redis數據結構

2.1 String數據結構

String類型有三種儲存方式:int、embstr、raw類型

int儲存

當我們存入數字時,就會使用int編碼儲存,測試如下:

127.0.0.1:2222> set num 123456
OK
127.0.0.1:2222> OBJECT encoding num
"int"

那麼如果數字範圍大於Java的Integer範圍呢?

127.0.0.1:2222> set num 2147483648  # 32位int最大範圍是2147483647
OK
127.0.0.1:2222> OBJECT encoding num  #輸出還是int,猜測是使用64位存儲
"int"

127.0.0.1:2222> set num 9223372036854775807
OK
127.0.0.1:2222> OBJECT encoding num
"int"
127.0.0.1:2222> set num 9223372036854775808  
OK
127.0.0.1:2222> OBJECT encoding num   # 大於64位int最大值後不再使用int儲存。
"embstr"

# 本人使用的是64位機器,不知道32位機中C++中int是多少?

存入浮點數呢?

127.0.0.1:2222> set num 1.9
OK
127.0.0.1:2222> OBJECT encoding num
"embstr"

可見Redis中的數值類型只有一個64位int,沒有浮點數。


embstr儲存

在int測試中,大於64位的數值會用embstr存儲。另外,在字符串或者浮點小於39字節的也會使用embstr存儲。


raw儲存

127.0.0.1:2222> set k aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
OK
127.0.0.1:2222> OBJECT encoding k
"raw"    # 當儲存的字符串過長(大於39字節)就會使用raw類型

2.2 List數據結構

127.0.0.1:2222> LPUSH list 1 2 3 4 5
(integer) 5
127.0.0.1:2222> OBJECT encoding list
"quicklist"

quicklist簡單理解(寫着寫着就把詳解改成簡單理解…

可以看到,List底層是使用一種叫quicklist的數據結構存儲。在redis的安裝目錄的src下打開quicklist.h,看看quicklist的結構定義:

typedef struct quicklist {
    quicklistNode *head;        // 頭結點指針。quicklistNode爲結點的數據結構
    quicklistNode *tail;        // 尾結點
    unsigned long count;        /* total count of all entries in all ziplists */
    unsigned long len;          /* number of quicklistNodes */
    int fill : QL_FILL_BITS;              /* fill factor for individual nodes */
    unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */
    unsigned int bookmark_count: QL_BM_BITS;
    quicklistBookmark bookmarks[];
} quicklist;

看到這隻能理解頭結點和尾結點。爲什麼會有count和len兩個計數?註釋的意思是count代表所有ziplist的entries數量,而len代表quicklistNodes的數量。

**問題一:**ziplist是什麼結構,爲什麼存在?不是像普通雙向鏈表一樣在quicklistNodes裏面存值麼?

繼續看quicklistNode的結構定義:

typedef struct quicklistNode {
    struct quicklistNode *prev;
    struct quicklistNode *next;
    unsigned char *zl;           // 這個是存的什麼? zl=ziplist?
    unsigned int sz;             /* ziplist size in bytes */
    unsigned int count : 16;     /* count of items in ziplist */
    unsigned int encoding : 2;   /* RAW==1 or LZF==2 */
    unsigned int container : 2;  /* NONE==1 or ZIPLIST==2 */
    unsigned int recompress : 1; /* was this node previous compressed? */
    unsigned int attempted_compress : 1; /* node can't compress; too small */
    unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;

放眼整個quicklistNode結構,能夠存儲數據的好像只有zl這個指針了。看來繞不過ziplist了…於是查看ziplist.h,並沒有結構定義,而是在ziplist.c中定義宏,redis沒有提供一個結構體來保存壓縮列表的信息,而是提供了一組宏來定位每個成員的地址。(以下內容來源:https://blog.csdn.net/men_wen/article/details/70176753)

/* Utility macros */
//  ziplist的成員宏定義
//  (*((uint32_t*)(zl))) 先對char *類型的zl進行強制類型轉換成uint32_t *類型,
//  然後在用*運算符進行取內容運算,此時zl能訪問的內存大小爲4個字節。

#define ZIPLIST_BYTES(zl)       (*((uint32_t*)(zl)))
//將zl定位到前4個字節的bytes成員,記錄這整個壓縮列表的內存字節數

#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))
//將zl定位到4字節到8字節的tail_offset成員,記錄着壓縮列表尾節點距離列表的起始地址的偏移字節量

#define ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))
//將zl定位到8字節到10字節的length成員,記錄着壓縮列表的節點數量

#define ZIPLIST_HEADER_SIZE     (sizeof(uint32_t)*2+sizeof(uint16_t))
//壓縮列表表頭(以上三個屬性)的大小10個字節

#define ZIPLIST_ENTRY_HEAD(zl)  ((zl)+ZIPLIST_HEADER_SIZE)
//返回壓縮列表首節點的地址

#define ZIPLIST_ENTRY_TAIL(zl)  ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))
//返回壓縮列表尾節點的地址

#define ZIPLIST_ENTRY_END(zl)   ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)
//返回end成員的地址,一個字節。

ziplist是由*一系列特殊編碼的連續內存塊組成的順序存儲結構*,類似於數組,ziplist在內存中是連續存儲的,但是不同於數組,爲了節省內存 ziplist的每個元素所佔的內存大小可以不同,每個節點可以用來存儲一個整數或者一個字符串。

空間中的結構組成如下圖所示:

这里写图片描述

  • zlbytes:佔4個字節,記錄整個壓縮列表佔用的內存字節數。
  • zltail_offset:佔4個字節,記錄壓縮列表尾節點entryN距離壓縮列表的起始地址的字節數。
  • zllength:佔2個字節,記錄了壓縮列表的節點數量。
  • entry[1-N]:長度不定,保存數據。
  • zlend:佔1個字節,保存一個常數255(0xFF),標記壓縮列表的末端。

entry結點的定義如下:

这里写图片描述

  • prev_entry_len:記錄前驅節點的長度。
  • encoding:記錄當前節點的value成員的數據類型以及長度。
  • value:根據encoding來保存字節數組或整數

雲裏霧裏的理解了一下ziplist(沒辦法,C++知識不過關),回到quicklist,quicklist的quicklistNode中存儲的數據是一個ziplist,所以問題一解決。

最後放一張抄過來的圖,很清晰的表達了quicklist的結構,原文博客:https://blog.csdn.net/harleylau/article/details/80534159

quicklist结构

2.3 Set數據結構

127.0.0.1:2222> SADD set 1 2 3 4
(integer) 4
127.0.0.1:2222> OBJECT encoding set
"intset"

127.0.0.1:2222> SADD set aa bb cc dd
(integer) 4
127.0.0.1:2222> OBJECT encoding set
"hashtable"

Set有兩種實現:intset和hashtable。顯然intset用於存儲int類型的set,如果有非intset則用hashtable。

2.3.1 intset

typedef struct intset {
    uint32_t encoding;
    uint32_t length;
    int8_t contents[];
} intset;

數據結構非常簡單,用數組存儲int集合。數組查找效率低,所以猜測肯定是有序數組,看一下添加元素的函數:

intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
    uint8_t valenc = _intsetValueEncoding(value);
    uint32_t pos;
    if (success) *success = 1;

    /* Upgrade encoding if necessary. If we need to upgrade, we know that
     * this value should be either appended (if > 0) or prepended (if < 0),
     * because it lies outside the range of existing values. */
    if (valenc > intrev32ifbe(is->encoding)) {
        /* This always succeeds, so we don't need to curry *success. */
        return intsetUpgradeAndAdd(is,value);
    } else {
        /* Abort if the value is already present in the set.
         * This call will populate "pos" with the right position to insert
         * the value when it cannot be found. */
        if (intsetSearch(is,value,&pos)) {  //搜索元素是否存在
            if (success) *success = 0;
            return is;
        }

        is = intsetResize(is,intrev32ifbe(is->length)+1); //擴容,數組大小加一
        if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);
    }

    _intsetSet(is,pos,value); //賦值
    is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
    return is;
}

顯然,使用數組來存儲,在添加或刪除元素時效率低下,中間涉及到數組擴容和元素移動。

2.3.2 Hashtable

(待續)

2.4 Zset

127.0.0.1:2222> ZADD zs 10 aaaa 22 fbbb 9 ffccc 7 abh 6 odd
(integer) 5

127.0.0.1:2222> OBJECT encoding zs
"ziplist"

可見Zset可以使用ziplist儲存。

在配置文件中,有下面兩個配置,分別代表zset使用ziplist存儲的時候,最大限制存儲entries的個數和每個節點最大存儲字節數,只有破壞一個條件就會轉成skiplist。

# Similarly to hashes and lists, sorted sets are also specially encoded in
# order to save a lot of space. This encoding is only used when the length and
# elements of a sorted set are below the following limits:
zset-max-ziplist-entries 128
zset-max-ziplist-value 64

修改zset-max-ziplist-entries爲5,重啓redis,測試一下

127.0.0.1:2222> OBJECT encoding zs
"skiplist"    # 變成skiplist

2.4.1 skiplist數據結構

skiplist可以理解成多層有序鏈表。數據結構如圖

圖片來源:https://blog.csdn.net/qianshangding0708/article/details/104935313?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-5.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-5.nonecase)

img

img

跳錶相對於紅黑樹的優勢:

  • 範圍查找簡單,紅黑樹需要找到最小值後中序遍歷
  • 插入和刪除元素更簡單,而紅黑樹可能有子樹的調整
  • 實現更加簡單,更少的指針。

參考鏈接:https://blog.csdn.net/men_wen/article/details/70176753

https://blog.csdn.net/harleylau/article/details/80534159

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章